An_Idea_Within has asked for the wisdom of the Perl Monks concerning the following question:

Hello wise people!

I've been learning Perl "on the go" for a while, and I'm trying to clean up my coding style now. In some cases, I just don't know what the accepted (or a generally acceptable) "clean style" is, however. In particular, I'm trying to find a good way to work with what I consider "parameter variables", that is verbosity levels, debug settings, scripts that are to be called. Consider the following code:

#!/usr/bin/perl -w use warnings; use strict; sub processDir { my $dir = shift @_; my $script = shift @_; my $verbose = shift @_; my $ret = qx($script $dir); chomp($ret); print qq(Output:"$ret"\n) if $verbose; } # Normally via environment variable my $script = "echo -e"; # Normally via glob and processing in this script my @dirs = qw(dir1 dir2 dir3); # Normally via Getopt::Long my $verbose = 1; for my $dir (@dirs) { # Do some stuff, then: processDir($dir, $script, $verbose); }

This how I'd do it at the moment to avoid the generally much-scorned global vars. However, what is the general opinion on this? If I need the $verbose variable in nearly all functions, should I not rather call it via $main::verbose within the functions (and declare this via our in the main)? Or should I even go the use var route?

Also, the way I deal with the $script value just "feels" wrong. If I need it only in that one function, should I rather set it there? However, this doesn't feel perfectly right either, because I'd like to have all the script paramters set and viewable in one central place (the beginning of "main").

I realize there's probably no one true answer to this, but I'd appreciate thoughts and opinions of people who've coded more than just relatively simple, self-contained scripts. My aimis to myke the scripts I produce as readable and maintainable as possible for others who might come in touch with them after me. Many thanks in advance!

Replies are listed 'Best First'.
Re: Best Practice: How do I pass option parameters to functions?
by LanX (Saint) on Oct 29, 2013 at 14:58 UTC
    TIMTOWT encapsulate globals... it depends on your future plans.

    1. If you have many subs which are supposed to react on "global" config settings you can always pass a hashref like already shown.

    2. Another approach is to have a dedicated package My_Config (which is effectively just a global hash, but doesn't pollute 'main::' anymore.

      Just set and read $My_Config::verbose

    3. The best (most radical) possibility to restrict the scope of variables are closures:

      { my $verbose; my $script; sub processDir { my $dir = shift @_; my $ret = qx($script $dir); chomp($ret); print qq(Output:"$ret"\n) if $verbose; } }

      nothing outside the outer block's scope can access them anymore.

      When needed you can also put getter() and setters() subs within this scope to change them dynamically.

    4. Saying this you realize that closures are just another way to think OOP, you are free to create a class Process and to realize the config settings as class attributes.

      But be aware that class attributes are only protected by convention, to make them really private one needs again closures... ;-) (update see also this reply)

    HTH! =)

    Cheers Rolf

    ( addicted to the Perl Programming Language)

Re: Best Practice: How do I pass option parameters to functions?
by SuicideJunkie (Vicar) on Oct 29, 2013 at 14:27 UTC

    I would probably put most of that into a config hashref to make it simpler to pass around and allow it to expand without breaking all your functions later.

    processDirs($config, @dirs) ... sub processDirs { my ($config, @dirs) = @_; print "Processing Dirs...\n" if $config->{verbose} > 0; system($config->{script}, $_) for @dirs; }
Re: Best Practice: How do I pass option parameters to functions?
by davido (Cardinal) on Oct 29, 2013 at 14:51 UTC

    If you have a set of functions that require a common configuration, it's possible (but not inherent) that you have something that would translate well into an object paradigm where the configuration is passed in to the constructor, and made available to a group of related methods.


    Dave

Re: Best Practice: How do I pass option parameters to functions?
by marinersk (Priest) on Oct 29, 2013 at 15:52 UTC
    I'm a big fan of simplifying the writing (and thus to some degree the maintenance) of code. For me, that translates to a module specifically designed to handle the configuration and decision aspects of debugging tack-ons. No point, in my mind, of cluttering the calling sequence with extra parameters not related directly to the function performed by the method/subroutine in question.

    The same logic went for a generic output module to handle variable and uncertain output environments (writing to console, writing to a web page as part of CGI response, writing to a printer, etc.). A special module handles all the configuration and execution details; no special parameters to invoke the work routines of my programs, but when I call the output function it translates, encapsulates, and otherwise transmogrifies the data and puts it out the correct doorway.

    As noted by others, it's still a global-thinking approach, but it doesn't pollute main::namespace, and the modification of its attributes requires a bit more intentional effort than accidentally setting the wrong variable. These are the evils of global variables, and they are avoided by shoving the global function off into its own module.

Re: Best Practice: How do I pass option parameters to functions?
by boftx (Deacon) on Oct 30, 2013 at 03:31 UTC

    On a more general note, it is my preference that any time a function (or method) accepts more than one parameter then it is time to use "named parameters" to prevent future headaches.

    For example:

    #!/usr/bin/perl -w use warnings; use strict; sub processDir { my $args = shift; # note, default for shift here is @_; my $ret = qx($args->{script} $args->{dir}); chomp($ret); print qq(Output:"$ret"\n) if $args->{verbose}; } # Normally via environment variable my $script = "echo -e"; # Normally via glob and processing in this script my @dirs = qw(dir1 dir2 dir3); # Normally via Getopt::Long my $verbose = 1; for my $dir (@dirs) { # Do some stuff, then: processDir( { dir => $dir, script => $script, verbose => $verbose +} ); # which would be exactly the same as: processDir( { script = > $script, dir => $dir, verbose => $verbose + } ); # or this: processDir( { verbose => $verbose, script => $script, dir => $dir +} ); # or any other combination you can think of. }

    There is much, much more that can be said about this, but knowing about the idea should give you the start for some very interesting research.

    Note: Yes, I know I have left out a LOT of potential gotchas in this very crude example. But I feel the essence is clear and a minimal amount of research should reveal a wealth of information on the concept that will stand OP in good stead for a long time to come.

    Update: Another advantage of named parameters is that with a little thought you can deal with the situation where you might start out with a single parameter passed in the normal way, but then allow for named parameters in the future. This example is taken from (my module) DateTimeX::Fiscal::Fiscal5253 (Please read the docs on CPAN to understand just exactly what this allows):

    sub contains { my $self = shift; my %args = @_ == 1 ? ( date => shift ) : @_; $args{date} ||= 'today'; $args{style} ||= $self->{_style}; croak 'Unknown parameter present' if scalar(keys(%args)) > 2; ... }

    You will find several examples of how I use this technique in the source.

    The answer to the question "Can we do this?" is always an emphatic "Yes!" Just give me enough time and money.
Re: Best Practice: How do I pass option parameters to functions?
by tobyink (Canon) on Oct 30, 2013 at 08:18 UTC

    Generally speaking I'm in favour of splitting as much logic as possible out into object-oriented modules.

    #!/usr/bin/env perl use strict; use warnings; # This could go into a separate ".pm" file. { package DirectoryProcessor; use Class::Tiny { script => sub { $ENV{DIR_PROCESSOR_SCRIPT} || "echo -e" }, verbose => sub { 0 }, }; use Getopt::Long qw( GetOptionsFromArray ); sub process { my $self = shift; my ($dir) = @_; my $cmd = sprintf('%s %s', $self->script, $dir); chomp( my $ret = `$cmd` ); print qq(Output: "$ret"\n) if $self->verbose; } sub process_all { my $self = shift; $self->process($_) for @_; } sub handle_argv { my $class = shift; my ($argv, $opt) = @_; $argv ||= \@ARGV; $opt ||= {}; GetOptionsFromArray($argv, $opt, 'script=s', 'verbose!'); my $self = $class->new($opt); $self->process_all(@$argv); } } # Here's the body of the script. DirectoryProcessor->handle_argv;
    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name
Re: Best Practice: How do I pass option parameters to functions?
by builat (Monk) on Oct 29, 2013 at 16:54 UTC
    Hi, may be it is not realy important but as I know... perl -w == use warnings; so it's looks like duplication of commands.
      There is a subtle difference, from warnings:
      The warnings pragma is a replacement for the command line flag -w , but the pragma is limited to the enclosing block, while the flag is global.
        Thanks useful info for me.

        Just the header I've copy/pasted into my scripts ever since I started using perl. I'd thought about changing it, but, quite honestly, I'd completely forgotten about it when posting the question. Thanks for the clarification of the differences, though!