ybiC has asked for the wisdom of the Perl Monks concerning the following question:
I recently postulated in the CB that some perl scripts might be trivial enough for one not to be concerned about presence of global variables. After being soundly pelted with a barrage of rotton fruit, I took the advice of the wise (sist|breth)ren to reduce the number of global vars from 23 to 2 in the following scriptlet.
So my question is: are my efforts below at reducing globals via blocks, subroutines, and return values properly idiomatic, or profoundly idiotic?
cheers,
Don
striving toward Perl Adept
(it's pronounced "why-bick")
#! /usr/bin/perl -w
## dhlogs.pl
## pod at tail
use strict;
use Date::Calc qw(Today Add_Delta_Days);
use LWP::UserAgent;
print "\n Fetching log...\n\n";
my $yesterday = Yesterday();
my @msg = Main(
'joe@modem.com', # remote domain
'yosef', # remote id
'secret', # remote password
"access.log.$yesterday", # remote logfile input
'c:/logs', # local dir to write to
'access.log', # local logfile output
);
print "$_\n" for(@msg);
sub Main {
my ($dom, $id, $pw, $logweb, $logdir, $loglocal) = @_;
die $! unless (-d $logdir && -w _);
my $proxy = 'http://host.dom:port';
my $proxyuse = 1;
my $url = "ftp://$id:$pw\@$dom/logs/";
my $ua = new LWP::UserAgent;
$ua->proxy(['http', 'ftp'] => $proxy) if ($proxyuse == 1);
$url = new URI::URL("$url/$logweb");
my $req = new HTTP::Request "GET" => ("$url");
my $res = $ua->request($req);
if ($res->is_success) {
my $rescont = $res->content;
open OUT, "> $logdir/$loglocal" or die $!;
print OUT $rescont or die $!;
close OUT or die $!;
my @return = (
" Program run successful\n",
" Website: $dom",
" Web log: $logweb",
" Local dir: $logdir",
" Local log: $loglocal\n",
);
} else {
my $resmsg = $res->message;
}
}
sub Yesterday {
my $Dd = -1;
my($ty, $tm, $td) = Today();
my($yy, $ym, $yd) = Add_Delta_Days($ty, $tm, $td, $Dd);
my $yesterday = sprintf "%04d-%02d-%02d", $yy, $ym, $yd;
}
END {
print
" Date::Calc $Date::Calc::VERSION\n",
" LWP::UserAgent $LWP::UserAgent::VERSION\n",
" Perl $]\n",
" OS $^O\n",
"\n";
my $stayopen = 0; # 0 is no, 1 is yes
my $closedelay = 5; # seconds to wait before closing window
if ($stayopen == 1) {
print "\n Hit <enter> to exit\n\n";
<STDIN>;
} else {
print "\n This window will close in $closedelay seconds...\n";
sleep $closedelay;
}
}
=head1 NAME
dhlogs.pl
=head1 DESCRIPTION
Automate fetch of yesterday's DreamHost web logfile
=head1 SYNOPSIS
dhlogs.pl<enter>
No arguments or options - all params set in code
=head1 TESTED
ActivePerl 5.6.1-631
Windows 5.0.2195 sp2 (Win2k pro)
Date::Calc 5.0
LWP::UserAgent 1.77
=head1 UPDATE
2002-03-18 20:05 CS
Check for write perms in logdir
sub Main{ ... }
Test with ActivePerl launching from icon and from command-line
'print' instead of 'warn' in END{...}
2002-03-16 21:50 CST
Test on Win2k against Inet-accessed FTP+Apache access log
Test on Win2k against local Debian FTP+Apache access log
Debug LWP::UserAgent FTP w/proxy
Leading-zero pad Yesterday() return value via sprintf
Reduce number of global vars
{...} blocks
subamify find yesterday's date, return @yesterday
END{...} instead of &e2e()
Variableize dom, id, pw
Date::Calc snippet for yesterday's date
Pause after (success|failure)
2002-03-13
Initial working code
=head1 TODO
Runlog along with print to console
Test on Cygwin against Inet-accessed FTP+Apache access log
Test on Debian against Inet-accessed FTP+Apache access log
Test on big brudder's Win98 box
=head1 AUTHOR
ybiC
=cut
(jeffa) Re: Is this a good approach for reducing number of global variables?
by jeffa (Bishop) on Mar 19, 2002 at 04:24 UTC
|
I will agree with the assessment that some Perl scripts are
indeed trivial enough to not be concerned with globals -
but not 23 globals! ;)
I think that the stucture of this script is excellent!
Adding command line argument support should be a breeze
now (hint! hint!). However, i would change the name of the
subroutine Main to something more descriptive like
fetch_message or just fetch. (and lower case is nicer!)
Off topic, i advise against re-using variables like you
do with this snippet:
my $url = "ftp://$id:$pw\@$dom/logs/";
$url = new URI::URL("$url/$logweb");
Taken out of context like that, you hopefully see that
this really is not good. It was a string, now it's an
object - this is the kind of practice that results in hard
to track bugs. Better to just be safe and decouple:
my $site = "ftp://$id:$pw\@$dom/logs";
my $url = new URI::URL("$site/$logweb");
Also (and i say this because i have been corrected on this
as well), if a subroutine returns a value, return the
value:
sub Yesterday {
# sidetrack: following golf just a thought ...
my($yy, $ym, $yd) = Add_Delta_Days(Today(), -1);
return sprintf "%04d-%02d-%02d", $yy, $ym, $yd;
}
And this doesn't just go for this subroutine, adding returns to your Main function is highly recommended.
Sure Perl evaluates the last expression and returns it,
but saying that with an explicit return says a lot more
to others trying to understand the script (especially
if they are Perl newbies coming from another language).
Other than those nit-picks, i say nice work ybiC...
UPDATE
...but also heed chromatic's wisdom!
jeffa
L-LL-L--L-LL-L--L-LL-L--
-R--R-RR-R--R-RR-R--R-RR
B--B--B--B--B--B--B--B--
H---H---H---H---H---H---
(the triplet paradiddle with high-hat)
| [reply] [d/l] [select] |
Re: Is this a good approach for reducing number of global variables?
by chromatic (Archbishop) on Mar 19, 2002 at 04:32 UTC
|
For something this small, I wouldn't worry. Besides that, you're only using lexical variables anyway.
The reason to avoid globals is to prevent action at a distance. You have two control branches and a handful of error checks. There's no reason to have subroutines, either. Let glue code be glue code.
I'm a big believer in putting off unnecessary complexity. Your code as it is is simpler without these hoops. If you decide to turn it into a module someday, you won't have too much work to do to generalize it. (It looks awfully specific for that, though.) | [reply] |
Re: Is this a good approach for reducing number of global variables?
by Zaxo (Archbishop) on Mar 19, 2002 at 05:40 UTC
|
Very nice, you've got them all scoped as lexicals. None of the following is criticism, you've chosen a style which is very good for maintainance.
There are a few things you could do to limit scopes further. The simplest and least obtrusive would be a bare block around my $yesterday' and my @msg; like this:
use LWP::UserAgent;
{
print "\n Fetching log...\n\n";
my $yesterday = Yesterday();
my @msg = Main(
'joe@modem.com', # remote domain
'yosef', # remote id
'secret', # remote password
"access.log.$yesterday", # remote logfile input
'c:/logs', # local dir to write to
'access.log', # local logfile output
);
print "$_\n" for(@msg);
}
I included the initial print statement in the block, just in case you want to localize $\ and kin.
My own inclination is to do away with named variables as much as is consistent with maintainability and readability, so I might have written that as: {
local $\="\n";
print "\n Fetching log...\n";
print for Main(
'joe@modem.com', # remote domain
'yosef', # remote id
'secret', # remote password
"access.log.".Yesterday(), # remote logfile input
'c:/logs', # local dir to write to
'access.log', # local logfile output
);
}
but that is less useful for debugging. Its readability is a matter of what one's used to. Similarly, I might write Yesterday() as:sub Yesterday {
sprintf "%04d-%02d-%02d", Add_Delta_Days(Today(),-1);
}
If you want to really gild the lily, you could define a private namespace for your sub names: package myscript; sub Main{} sub Yesterday {} package main;. That would be really fussy though ;-)
After Compline, Zaxo
| [reply] [d/l] [select] |
Re: Is this a good approach for reducing number of global variables?
by Desdinova (Friar) on Mar 19, 2002 at 06:21 UTC
|
Just on the idea that some scripts are too trivial to worry about globals.. I have had several 'trivial' scripts grow well beyond thier orignal intent. In those cases having the proper structure has been very helpful as I add more and more features to it.
Just my 1/50th of a dollar | [reply] |
|
"I've had several 'trivial' scripts grow well beyond thier original intent."
I continually find myself fending off the implicit patina of triviality assigned to Perl by users, evoked by the word 'script' (further bolstered by the impression, however accurate, that Perl is to UNIX what Visual Basic (et al) is to Windows).
Nearly all the so-called 'one-off's I've ever written have somehow wound up either running in Production, or at least used on a regular basis ('soft' production, as it were). It has reached the point where I no longer use the word 'script' to refer to them when speaking to users, instead using the more substantive, 'program.'
Toward that end I try ALWAYS to write my scripts/programs, however trivial, as though they are full-fledged applications (or soon will be). Which is to say I eschew globals, at the expense of passing values to subroutines more than perhaps efficiency would dictate.
dmm
If you GIVE a man a fish you feed him for a day
But, TEACH him to fish and you feed him for a lifetime
| [reply] |
|
There are very few reasons to not do things the right way. If it's no skin off your back, then don't cut corners.
"Shortcuts make for long journeys."
------ We are the carpenters and bricklayers of the Information Age. Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.
| [reply] |
Re: Is this a good approach for reducing number of global variables?
by erikharrison (Deacon) on Mar 19, 2002 at 16:49 UTC
|
I think that the use of globals increases as the program increases in size and complexity - it becomes simpler and sometimes clearer to pass half a dozen variables througha global hash than directly to the subroutine.
Perl gives us alot of power for quick hacks, simple tools, fast fixes. For example, returning the last thing evaluated, providing a default variable ( $_ ) for just about everything means I can construct, say, a script which changes every url in a couple of HTML files, in about five minutes. But most of those features are inappropriate for a program of any size where your spending days working on it anyway, the twenty minute time gain of using them isn't worth in terms of a lost of readability and reuse.
Cheers,
Erik
| [reply] |
|
I think that the use of globals increases as the
program increases in size and complexity - it becomes
simpler and sometimes clearer to pass half a dozen
variables through a global hash than directly to the
subroutine.
GYAH! No, don't do that. It'll be clearer for the
first week or so, then you'll need extra comments to
figure out where that hash is coming from. Then you'll
make a few changes, and forget to update the comments.
(Trust me, you will forget.) Then you'll want
to process many widgets instead of just one, so your
hash will become an array of hashrefs, and you're totally
screwed.
Globals are good for things like program options (i.e.
don't pass around the args hash you got back from Getopt).
Globals are good for collecting the data you're operating
on: if you're modelling TCP connections, then a global
array of connection objects is perfectly reasonable.
Globals are sometimes reasonable for the outermost scope of
your problem space. If you're writing a state
machine, for instance, and you know somehow that you'll
never want to model more than one state machine, then it's
sometimes okay to put your state in the global namespace
rather than packaging it up into a class (or even a hash)
and accessing it opaquely.
Most of the time, though, the little time you save by
making stuff global rather than packaging it up just a
little bit is completely overwhelmed by the time it takes
you to refactor your code when your problem expands, even
if you're just prototyping something. Slinging around
globals is false laziness.
(Anyone else want the soapbox? :-)
--
:wq
| [reply] |
|
|