A Powerful Code Transformation Tool
Imagine finding a hidden feature in your IDE that magically transforms code. This is how it would work: you load a piece of source code into your window, and as you read through it, line by line, the code becomes cleaner, clearer, and well-factored. That would be amazing.
I’m here to tell you that such a feature already exists in any modern, full-featured IDE. It’s going to take some effort on your part, but you can achieve this kind of code transformation without magic.
Read by Refactoring is a technique discussed and taught by Arlo Belshee (@arlobelshee) at Industrial Logic. Using this technique, reading code – to figure out what it does, to estimate the effort to modify it, etc. – turns into active refactoring of the code. You read the code by modifying it, nudging it into a clearer, more readable state. The key is you are leveraging your IDE’s built in refactoring functions and mastering the keyboard shortcuts in such a way that your insights of how the code can be improved translate immediately into modification of the code. I’m typing this sentence with no conscious decision of what key to hit or even any thought to commanding my fingers where to go on the keyboard, but the words I think nevertheless appear on the screen. You want to get your mastery of your refactoring tools to the same unconscious, effortless state.
Two things make this powerful:
- The refactorings are “mechanical”. That is, they are not introducing a change of functionality. They are only changes in the code structure: renames; method creation and elimination; inlining.
- Your IDE is handling the actual modifications for you. No risk of fat-fingering and creating a bug. The actual modifications in your code can be considered safe.
The Core Six
In his post, Arlo lays out the 6 refactorings you must learn:
- Rename
- Inline
- Extract Method
- Introduce Local Variable
- Introduce Parameter
- Introduce Field
These are the core six because naming things clearly and organizing code to be concise and logical are the most important things you can do to improve readability – and by doing that, you improve the code and leave less room for bugs.
Any IDE worth using will have a refactoring menu with actions for performing these 6 refactorings, as well as keyboard shortcuts so you won’t need to access the menu at all. If your IDE doesn’t support these core six, or worse, you are just using a text editor, stop reading this immediately and go upgrade your tools.
Mastering the Keyboard
Using the keyboard shortcuts for refactoring is a necessity. You don’t want to waste time reaching for the mouse and clicking through menus – it breaks your flow and slows you down. As you read through code, you must be able to do all the navigation and modification without removing your hands from the keyboard. Menus are for amateurs – experts know how to take full advantage of their tools and become super efficient.
How do you get to this level of expertise with your IDE? Simple – stop using the menus. Always use the keyboard shortcuts. If the shortcut you need doesn’t come immediately to your fingers, try hard to recall it before looking it up. This will help the memorization process. The first thing to memorize is your IDE’s shortcut for listing shortcuts.
If you are a menu user like I was, learning the shortcuts and living completely from the keyboard will seem slow and frustrating at first, but it is important to go through this (short) learning curve. It really doesn’t take that long to get the most often used shortcuts memorized. It all depends on how much code you are writing every day.
Once muscle memory takes hold, the mere thought of making a particular refactoring causes the fingers to reach out and hit the right key combinations to implement it. As you get better at this and get the refactorings burned into your brain, the code will seem to refactor itself at the will of your thoughts.
Incremental Change
The key to read by refactoring is the additive benefits of many small changes. Legacy code or unfamiliar code is too complex to allow discovery of big insights into how to make it better. Instead, small changes, made safely, step by step, transform code into a better state.
Again from Arlo – the refactor loop he proposes is:
- Look at something
- Have an insight
- Write it down (in code)
- Check it in
This is a tight, quick, loop – limited only by the speed at which you have insights and refactor the code. Keep in mind, each loop is a small step. Maybe the first insight is to rename a method so it better describes what it does. That’s a check-in. Next, you inline a variable to increase readability. That’s a check-in. You’ll find yourself doing dozens of check-ins per hour. Each of them small, and each of them safe. Each of them easily rolled back if something goes wrong or you need change direction.
At Jama, my team labels each commit with a risk level. ‘Safe’ for purely mechanical changes that can not change the functionality of the code. ‘Tame’ for mostly mechanical changes, and for situations where mechanical changes might have side effects (see below). ‘Risk’ for full-on non-mechanical coding.
When a bunch of check-ins are committed and sent for code review and QA, the commit log shows what changes need the most attention. A string of 30 commits all labeled safe mean a fast code review and no QA. A bunch of risky and tame commits get more scrutiny and the usual QA attention.
Side Effects
Consider this (contrived) code example:
public boolean changeUserName(User user, String newName) { if ((UserUtil.isRootUser(user) || !UserUtil.canEditUser(user)) && !editName(user, newName)) { Log.info("Can not edit name for user: "+user); return false; } return true; }
An insight you have is to extract the method calls in the if statement into their own variables and give them good names so the logic is clearer. So, your fingers hit alt-cmd-v (extract variable in intelliJ) and you instantly have some booleans.
public boolean changeUserName(User user, String newName) { final boolean noPermission = UserUtil.isRootUser(user) || !UserUtil.canEditUser(user); final boolean editUnsuccessful = !editName(user, newName); if (noPermission || editUnsuccessful) { Log.info("Can not change name for user: " + user); return false; } return true; }
The code is now clearer. It’s obvious you can’t change your name if you are the root user (the root user’s name is always ‘root’) or if you don’t have permissions to edit the user. The if statement is attempting to log a failure to update the user name.
But, see the potential problem? The extracted method calls were part of a short-circuit conditional. In the original code, no attempt would be made to change the user name if the first part of the conditional failed. In the refactored code, editName gets called no mater what. Not what you want!
You must be aware of corner-cases like this and always run your suite of fast-running unit tests even when making ‘safe’ refactors. (You do have a suite of fast unit tests, right?) Speaking of unit tests, they may be the source of another side effect. Good unit tests know little or nothing about what’s going on inside the code under test. They apply inputs and check that the results are as expected, but sometimes, because there is no result to test, or because the code under test has so many dependencies, a test may have too much knowledge about how the code is structured. It may be using mocks and spies and have expectations that specific methods inside the code under test get called in a certain order, with specific parameters, and a certain number of times. Even your most benign refactors (e.g. renaming a private method) may break snoopy tests like these.
So, always run your unit tests, and make sure your code has test coverage before refactoring it. And consider rewriting brittle tests that are too tightly coupled to the implementation of your code.
Benefits
To wrap up, read by refactoring is a technique that can lead you and your team to higher efficiency, better code, and fewer bugs. The benefits include:
- Rapid Development – Safe changes can be merged directly to master without code reviews and QA (mostly).
- Every time you read code, you improve it. Examining code is no longer passive – you gain understanding of how it works, and you leave it better than you found it.
- It trains you to use your tools to your full advantage, making every day more productive.
- Refactoring becomes ingrained in your culture – a part of your daily development flow and no longer a separate task you hope to get to some day.
- This new-found ease with refactoring leads to lower costs for refactoring and thus lower costs for design mistakes/changes. This allows for greater agility.
Enjoy your powerful new refactoring skill!
- Test Driven Development – It’s Not About the Tests - December 8, 2016
- Read by Refactoring - November 23, 2016
- Improve Your Code with Code Forensics - May 11, 2016