Section 6.22. Loop Labels


6.22. Loop Labels

Label every loop that is exited explicitly, and use the label with every next, last, or redo.

The next, last, and redo statements make it much easier to specify sophisticated flow of control in a readable manner. And that readability is further enhanced if the reader doesn't have to puzzle out which particular loop a given next, last, or redo is controlling.

The easiest way to accomplish that is to label every loop in which a next, last, or redo is used. Then use the same label on each next, last, and redo in that loop. The reader can then match up the name on the keyword against the labels on the surrounding loops to determine which loop's flow of control is being altered.

So you should write:

      INPUT:     for my $try (1..$MAX_TRIES) {         print 'Enter an integer: ';         $int = <>;         last INPUT if not defined $int;         redo INPUT if $int eq "\n";         chomp $int;         last INPUT if $int =~ $INTEGER;     }

instead of:

     for my $try (1..$MAX_TRIES) {         print 'Enter an integer: ';         $int = <>;         last if not defined $int;         redo if $int eq "\n";         chomp $int;         last if $int =~ $INTEGER;     }

Another, less obvious benefit of following this guideline is that the presence of the label at the start of any loop alerts the reader to the fact that the loop has embedded flow control.

Place the label on the line preceding the loop keyword, at the same level of indentation, and with an empty line (or a paragraph comment) above it. That way, the label helps the loop stand out, but leaves the actual loop keyword on the left margin, where it's easy to see.

When you're labeling a loop, choose a label that helps to document the purpose of the loop, and of the flow control statements. In particular, don't name loops LOOP:

     LOOP:     for my $try (1..$MAX_TRIES) {         print 'Enter an integer: ';         $int = <>;         last LOOP if not defined $int;         redo LOOP if $int eq "\n";         chomp $int;         last LOOP if $int =~ $INTEGER;     }

That's as bad as naming a variable $var or calling a subroutine func( ).

Labelling loops is especially important for maintainability. A typical mistake is to initially write code that correctly exits from a loop like so:

     while (my $client_ref = get_client(  )) {         # Retrieve phone number...         my $phone = $client_ref->{phone};         # Skip client if "do not call" was requested...         next if $phone =~ m/do \s+ not \s+ call/ixms;         # Profit!         send_sms_to($phone, $advert);     }

Later, a change of internal data structure may make it necessary to add an inner loop, at which point the flow of control can easily go awry:

     while (my $client_ref = get_client(  )) {         my $preferred_phone;         # Retrieve phone number (clients can now have more than one)...         for my $phone ( @{ $client_ref->{phones} } ) {             # Skip client if "do not call" was requested...             next if $phone =~ m/do \s+ not \s+ call/ixms;             # Select phone number...             $preferred_phone = $phone;             last;         }         # Profit!         send_sms_to($preferred_phone, $advert);     }

The problem here is that the intention was to give up trying to contact clients if any of their numbers is marked "Do Not Call". But moving the next if... inside a nested for loop means that the next no longer moves the loop on to the next client, just on to the next phone number of the current client. Apart from causing you to annoy clients who have specifically asked you not to, this error also introduces the possibility that $preferred_phone will be undefined when it's finally passed to send_sms_to( ).

In contrast, if your policy is to always label every loop that has a flow control statement inside it:

      CLIENT:     while (my $client_ref = get_client(  )) {         
# Retrieve phone number...
my $phone = $client_ref->{phone};
# Skip client if "do not call" was requested...
next CLIENT if $phone =~ m/do \s+ not \s+ call/ixms;
# Profit!
send_sms_to($phone, $advert); }

then either the updated code will be automatically correct:

      CLIENT:     while (my $client_ref = get_client(  )) {         my $preferred_phone;         
# Retrieve phone number (clients can now have more than one)...
PHONE_NUMBER: for my $phone ( @{ $client_ref->{phones} } ) {
# Skip client if "do not call" was requested...
next CLIENT if $phone =~ m/do \s+ not \s+ call/ixms;
# Select phone number...
$preferred_phone = $phone; last PHONE_NUMBER; }
# Profit!
send_sms_to($preferred_phone, $advert); }

or the error will be obvious, as the comment and the target of the next will then be manifestly inconsistent:

      
     # Skip client if "do not call" was requested...
next PHONE_NUMBER if $phone =~ m/do \s+ not \s+ call/ixms;



Perl Best Practices
Perl Best Practices
ISBN: 0596001738
EAN: 2147483647
Year: 2004
Pages: 350
Authors: Damian Conway

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net