in reply to when to use subroutine

For me, there are just two major points:

When you find yourself copying two or more adjacent lines of code, and changing just a few items for each copy that you make, then those lines should be a subroutine, and the things that change from one copy to the next should be parameters to the subroutine.

When you find yourself going into a fairly deep flow-control structure of nested loops and/or conditionals, and a lot of the nested blocks involve lots of complicated code (making it hard to see what the flow-control is actually doing), then at least some of those nested blocks might be better off as subroutines, to make the "main" (outer-most) portion of the flow control more compact so that it's easier to read and comprehend.

Both of those should be used in moderation, but the first is more of a general rule that I would apply in most (if not all) cases where it's possible -- try to avoid writing the same thing more than once in a given script -- whereas the second needs to be balanced against a tendency to have too many layers of subroutines calling other subroutines that call other subroutines that ..., which can make it just as hard to keep track of what's really going on.

In any case, it helps to start with pseudo-code, laying out the algorithms and flow-control in a concise and coherent manner, and taking the time to note where a given functionality is needed at different points (i.e. should be a subroutine). And of course, when subroutine and variable names are well chosen, this helps a great deal -- the code starts to become "self documenting".

Replies are listed 'Best First'.
Re^2: when to use subroutine
by perl-diddler (Chaplain) on Oct 28, 2007 at 23:08 UTC
    I really like that first "point". Only thing I'd add is that if the single line of code is complex enough -- maybe it deserves a subroutine of its own.

    As many have said, the purpose of "subs" is to make things more clear. But why does adding subs make things more clear? Obviously you can't (normally)just make every line a sub and have that make it clear.

    I'd think of subroutines as ways of mentally helping you (and others if they read your code) to break your code up into small, functional chunks. Each subroutine can be a concept. Once you have the subroutine 'built', you should be able to use it anywhere (assuming its well designed) like a "custom addition" to perl for your specific program.

    For instance, suppose you want to do something similar to the "index()" function, which can find a single character embedded in a string. However, instead of "single characters", suppose you have "symbolic names of characters" (ex.: "<Up>", "<Down>", "<Left>", "<Right>"). Now you want to find your "character"'s position in the array of characters. Suppose you are trying to find the character named "<Up>" (equivalent to the "up arrow" key that is not on the numeric pad). You need to search for it in an array with the symbolic names from above (<Up>, <Down>...).

    Your normal index function looks like:
    "index $mykey, $string"
    So now you create a function that does the same for your symbolic names, called "my_string_index":

    sub my_string_index($\@) { my ($symkey, $symkeys)=@_; my $index=-1; for ($index=0;$index<=$#$symkeys; ++$index) { if ($symkeys->[$index] eq $symkey) { return $index; } } return -1; }

    Now you can use your new subroutine to perform the same function for your symbolic keys as the regular index does for single-letter keys:
    "my_string_index $key_to_find, @symbolic_keynames;"

    You have "added" your own "string_index" function that finds your symbolic-keys in an array of symbolic keynames. Ex:

    @cursor_keys = qw(<Up> <Down> <Left> <Right>); $keyname_to_find='<Down>'; my_string_index $keyname_to_find, @cursor_keys;
    You know longer have to think about the implementation -- it's just there, in your program. Later, if you don't like the performance of "my_string_index" using the 'for' loop, you can substitute other perl code to speed up the routine. If you've made your function 'stand-alone' (doesn't change anything outside the function), then you won't have to change the code in multiple places. You just have to change your 1 function, "my_string_index", and then everyplace that uses that function will gain in performance.

    Another place to use functions -- as "place holders". If I want to write a file update program and I want to focus on the update code, first, I can start with a dummy skeleton:

    # skeleton file read & write functions... sub readfile { my ($array_p)=@_; @$array_p=<>;} sub save_file { my ($array_p)=@_; foreach (@$array_p) { print $_; } } # (above dummy routines will be changed later) ### main program ### # first open file readfile(\@cur_file); #will put file into specified array #update code #delete blank comment lines and blank lines @mynewfile=grep ( !/^#?\s*$/, @cur_file); #save results back in file save_file(\@mynewfile); #will save array back to file
    Using the above type of skeleton, you can start with your filter simply reading and writing to the terminal for testing. Later you can add "real" code to read and write the file you want (instead of standard input and output), but for the now, you can play & develop just the filter code.

    Hope this gives you some ideas....

    Linda