Hack 85. Replace Soft References with Real Ones


Combine the benefits of name-based references, lexicals, and typo-checking.

One of the first milestones in becoming an effective programmer is the sudden flash of Zen when you first ask the question "How can I use a variable as a variable name?"[2] The second half of that zap of enlightenment is when you realize why you usually don't need to do that.

[2] See perlfaq7 if you've really never asked this.

Sometimes it's the easiest solution to a problem, thoughespecially when you're refactoring a large, complex piece of code written by someone who just didn't get it yet.

Don't despair; you can have all of the benefits with almost none of the drawbacks.

Suppose you have a sales reporting application such as one the author had to maintain many lives ago. There are multiple types of items for sale, each with its own separate total in the report. You have a parser that reads external data, giving you a key-value pair with the name of the sale category and the value of the item.

Unfortunately, the program uses several lexical-but-file-global variables and you don't have time to change the whole thing to use an obvious %totals hash.

If that's not scary enough, imagine that the actual system really did use symbolic references here (yes, that implies globals, not lexicals!) without error checking and that the sales totals came from the Internet through unencrypted means. Suddenly being a writer seems appealing.


The code, minus the section you need to change and with a fake data-reading section for the purpose of the example, might look something like:

use strict; use warnings; my ($books_total, $movies_total, $candy_total, $certificates_total, $total); create_report( ); print_report( ); exit( ); sub print_report {     print <<END_REPORT; SALES     Books:             $books_total     Movies:            $movies_total     Candy;             $candy_total     Gift Certificates: $certificates_total TOTAL:                 $total END_REPORT } sub create_report {     # your code here } sub get_row {     return unless defined( my $line = <DATA> );     chomp( $line );     return split( ':', $line ); } __DATA__ books:10.00 movies:15.00 candy:7.50 certificates:8.00

The Hack

Use a hash, as the FAQ suggests, stuffed full of references to the variables you need to update. You can build this very concisely, with only a little bit of duplication, with a sadly underused piece of Perl syntaxthe list reference constructor. When given a list of scalars, the reference constructor (\\) returns a list of references to those scalars. That's perfect for the list of values to a hash slice!

sub create_report {     my %totals;     @totals{ qw( books movies candy certificates total )} =     \\( $books_total, $movies_total, $candy_total,        $certificates_total, $total      );     while (my ($category, $value)  = get_row( ))     {         ${ $totals{ $category } } += $value;         ${ $totals{total}       } += $value;     } }

That's better. When your data feed changes next week and gives you a list of product names, not categories, change the list slice assignment to %totals to store multiple references to the same category total scalars under different keys. You still have an ugly mapping of strings to lexical variables, but until you can refactor out the yuck of the rest of the application, you've at least localized the problem in only one spot you need to touch.

Besides, as far as the author knows, the original application is likely still running.

Hacking the Hack

Validation is still a problem here; how do you prevent a typo in the data from an external source from causing a run-time error? With the hash, you can check for a valid key with exists (though storing total in the hash as well is a potential bug waiting to happen). This may be an appropriate place to use a locked hash [Hack #87].



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

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