Refactoring with Emacs

March 24, 2017

We’ve been busy refactoring the Manuals Publisher application that’s used by the UK government to publish things like The Highway Code. It’s quite an old Rails application who’s responsibilities have changed a lot since it was first written. To make it easier for people to work on we are pulling out some of the old code, reducing indirection, and renaming classes to better map to the terminology used in the domain.

I use Emacs for most of my day-to-day development, and thought I’d share a few tips I’ve picked up in the last few weeks. Most of these are quite general but a few are specific to working with Ruby and Rails applications.

Ack in project

I use projectile so that Emacs knows that the files under a checked out directory all belong to the same project. Projectile searches up the directory tree until it finds a .git or .projectile file and then considers all code under that directory to be part of the same project.

I can then run M-x projectile-ag to search the project for a string. This is very useful for finding all the references to a class before renaming, for example. I have projectile-ag bound to C-c p a.

Tag files

Emacs has great support for TAGS files. A TAGS file contains references to class and method definitions spread throughout the code which are derived by statically analysing the source. I generate a TAGS file by running

ripper-tags -Re -f TAGS

which uses the ripper-tags gem. ripper-tags uses Ripper the ruby source code parser from the Ruby standard library, so it understands the current syntax of the language.

With a TAGS file in place at the root of a project M-. jumps to the definition of whatever is under my cursor.

Global search and replace in a project

Performing global renaming refactors in a dynamic language like Ruby can be quite difficult. I’ve picked up a couple of tricks that can help. The first uses M-x projectile-replace-regexp which allows search and replace across the whole project, with support for Emacs regular expressions.

Finding references to code by running tests

As Ruby is a very dynamic language, sometimes a simple search and replace will miss references to something that has been renamed - for example when the call site generates the method name at run time. With a fairly comprehensive test suite, I can catch some of these references by making a rename, running the tests using rspec-mode and jumping directly to the source code where the method lookup fails.

Keeping commits small with Magit

When several developers are all refactoring an app at the same time, merging changes can be difficult. As James keeps reminding me, this is made much easier if we keep our commits as small and atomic as possible.

Magit, the git UI for Emacs, has a couple of features that help. The first is an interface on top of git commit -p which makes it easy to stage individual lines of a change.

The second is magit-instant-fixup which commits a change and then instantly performs a rebase to fixup the commit into a previous one. I really like this feature - choosing the commit to fixup into from the log is much easier than copying the SHA on the command line.

Diff ranges in Magit

Magit also provides a convenient way to diff an arbitrary range of commits directly from the log. Select the range and hit d r.

Git time machine

Sometimes in order to understand how to make a refactoring it is useful to see how the code got to the place it is in. git-timemachine lets you quickly flick back through the history of a file.

Renaming files in Dired

Rails has an autoloading mechanism which makes sure files are added to the LOAD_PATH and required. This relies on a concordance between the name of the class and the filename. Dired makes renaming multiple files easy. In a Dired view of a directory, M-x wdired-change-to-wdired-mode (bound to C-x C-q makes the view writable. Files can be renamed and then “committed” using C-c C-c.

ruby-refactor

I should also mention ruby-refactor a minor mode that makes certain simple refactorings easier. Selecting a block of code and calling M-x ruby-refactor-extract-to-method is used to create new methods, for example.

I did add (setq ruby-refactor-add-parens t) to my Emacs configuration to ensure that parentheses were always added to method signatures. I have only just started playing with this mode, and haven’t trained my fingers to remember to use it yet.

Published