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.
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.
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 HackUse 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 HackValidation 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]. |