If you're using Emacs to wrangle Perl, here's a useful little refactoring feature you can use:
Extract Variable - C-o e e v
-- Do the refactoring Extract Variable of the active region.
Installation instructions below.
What is refactoring again?
Martin Fowler introduced the concept of refactoring in his 1999 book Refactoring: Improving the Design of Existing Code.
Refactoring is a way to restructure code to make it easier to work with, while preserving behaviour. Refactoring happens in small predictable steps, with tests passing after each step.
"your system should not be broken
for more than a few minutes
at a time"
-- Martin Fowler
Since then, the original meaning of the term has shifted to mean... really, any old rewrite of existing code, regardless of how large-scale it is, or whether it's actually changing behaviour by fixing a bug or changing requirements.
These are all legitimate things you can do to a code base, but it's not the activity that Fowler set out to describe.
Extract Variable
Extract Variable is one of the easiest and smallest refactorings to implement while still being quite useful in day-to-day programming. Anything that helps making code clearer on a line or method level is a welcome help.
It's also easy to work with, because it really only has one step.
Walk-through
Here's an example with a piece of code accessing the database config for the application user and the db admin user. The are mostly the same.
my $app = $self->app->config->{services}->{db}->{product}->{app};
my $dbadmin = $self->app->config->{services}->{db}->{product}->{dbadmin};
Select a piece of code (on either of the lines) that is duplicated a lot within the current subroutine and hit C-o e e v
(mnemonic: Edit - Extract Variable).
In this case this seems to be the common part:
$self->app->config->{services}->{db}->{product}
You will be asked for a variable name to put this in. The default is the last word in the selected code ($product
).
All occurrences of the selection will now be replaced with $product
, and the new variable $product
will be declared just before the earliest usage.
my $product = $self->app->config->{services}->{db}->{product};
my $app = $product->{app};
my $dbadmin = $product->{dbadmin};
Before the edit, the mark
was pushed at the location where you started, so you can hit C-u C-SPC
to jump back.
After the edit, the point is left at the new variable declaration so you can ensure that it is in a reasonable location. It's not unusual to need to move it to an outer scope in order for all the usages to be covered by the declaration.
Now you need to ensure this edit makes sense. Both replacements and the declaration are highlighted, so it's easy to see what was changed.
If it's all wrong, just M-x undo
to revert the change.
Once you've eye-balled the edits, hit C-o e h
to remove the Highlights.
Note that the replacement is syntax unaware, so you'll have to ensure it's syntactically correct yourself, and placed in a nice spot in the source code (althugh most of the time it works just fine).
In this particular example, had there been no arrows between the hash keys, the final code would have looked like this:
my $product = $self->app->config->{services}{db}{product};
my $app = $product{app};
my $dbadmin = $product{dbadmin};
and that clearly isn't equivalent Perl code, the $product
hashref being treated as a hash. This is probably the most common failure mode though, and shouldn't happen that often. Now you know.
By default, only the current subroutine is changed. Invoke with the prefix arg C-u
to change the entire buffer: C-u C-o e e v
.
Neat uses for Extract Variable
Duplication
Remove duplicated code (duh), beause small-scale duplication is just shoddy, and this opens up an opportunity to introduce well named variables that describes what things are.
Long lines
Break a long unreadable line into two by extracting an (again, well named) temporary variable. This is particularly useful when there's an if-statement with an unreadably long condition.
Rename
Rename variable - Extract Variable on a variable name, then just delete the new variable declaration.
my $books = $schema->resultset("Book");
my @books = $books->search({ ... })->all;
Let's rename $books
--> $book_rs
. Extract Variable on $books
turns this into:
my $book_rs = $books; # <-- delete this line
my $book_rs = $schema->resultset("Book");
my @books = $book_rs->search({ ... })->all;
Point is now on the first declaration, so just delete that line and we're done.
Strings
Make method call in string:
print "So, you want to make a $object->method_call inside a string\n";
But that doesn't work obviously. There are various ways around that, but one thing you can do is to mark $object->method_call
to extract it, and end up with this:
my $method_call = $object->method_call;
print "So, you want to make a $method_call inside a string\n";
Nice!
Recap
- Select the repeated code you want to extract. It should be syntactically ready to be assigned to a variable
-
C-o e e v
to Extract Variable - Confirm the variable name or enter a better one
- Confirm that the extract makes sense, or fix up the code
-
C-o e h
to end the refactoring by removing the Highlights
Getting the software
Extract Variable is available in Devel::PerlySense, which is a Perl IDE for Emacs. Most of the walkthrough example is actually from the POD of Devel::PerlySense. The key bindings described above is what you get out of the box with PerlySense.
Install with your regular CPAN client and check out the configuration section for details on how to set it up. This also installs all the elisp code.
Extract Variable is all implemented in Elisp, so if you don't want to install PerlySense, you can instead install the Melpa package lang-refactor-perl which contains this one function. If you do this, you'll have to create your own key bindings though.
Cool, now go clean up some code! :)
Top comments (0)