Refactoring with Emacs
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
.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.
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
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
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
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
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
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
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
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,
wdired-change-to-wdired-mode (bound to
C-x C-q makes the view
writable. Files can be renamed and then “committed” using
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.