Extract Variable in Emacs
Tags: perl, emacs, refactoring
Published: 2018-11-01 14:02:56

---
title: Extract Variable in Emacs
published: true
description: A simple Emacs refactoring tool to Extract Variable in Perl
tags: perl,emacs,refactoring
---

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](https://martinfowler.com/books/refactoring.html).

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](https://martinfowler.com/bliki/RefactoringMalapropism.html).


## 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. 

```perl
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:

```perl
$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.

```perl
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:

```perl
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.

```perl
my $books = $schema->resultset("Book");
my @books = $books->search({ ... })->all;
```

Let's rename `$books` --> `$book_rs`. *Extract Variable* on `$books` turns this into:

```perl
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:

```perl
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:

```perl
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](https://metacpan.org/pod/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](https://metacpan.org/pod/Devel::PerlySense#INSTALLATION) 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](https://melpa.org/#/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! :)