Legacy Code To Testable Code #1: Renaming
This post is part of the “Legacy Code to Testable Code” series. In the series we’ll talk about making refactoring steps before writing tests for legacy code, and how they make our life easier. Other posts include:
The Legacy Code To Testable Code Series
|General patterns||Accessibility||Dealing with dependencies||Advanced patterns|
|Introduction||Add setters||Extract method||Static constructors (initializers)|
|Renaming||More accessors||Extract class||More static constructors|
|Add overload||Introduce parameter||Instance constructors|
|Testable object||Conditionals to guard blocks|
Renaming is easy and is usually safe refactoring. Most IDEs have the refactoring functionality, and most languages (I’m not talking about you, C++) lend themselves to safe renaming.
Why start at renaming? It helps make the code more understandable. When we can understand the legacy code better, our unit tests we write will be more effective. In other words: Don’t write unit tests for code you don’t understand.
Renaming is an easy win on the way there.
Naming things is maybe the hardest thing in programming. Lots of code grows complex (becoming legacy code) because sometimes we slap “Manager” as a class name suffix. It’s like giving ourselves permission to make room for code we don’t know where to put (which would be our legacy). It doesn’t end with one class, though. Once there’s an
AccountManager class, soon
CustomerManager will appear.
The same goes for a
getValidCustomer method that should really be a void method, but instead returning a success code. When we’re sloppy, we allow for generalized, confusing names to flourish in the code. When names are vague, all kinds of implementation pours in. That’s where legacy code comes from.
It doesn’t matter if I wrote the code, or somebody who doesn’t work here anymore did. Legacy code can always do with improvement.
One of our goals in writing unit tests is to improve code. But improving code safely almost without effort is a bonus. Risk is key here. If the IDE can do the renaming refactoring safely, we are more likely to do it. If, on the other hand, we need to rely on manual changes, chances are we won’t risk it.
Mostly, when we’re renaming before writing unit tests, we’ll concentrate more on method names, and maybe variables in the code. These are usually small enough for picking good names (and if not enough, can be extracted). Renaming classes is usually harder, because they generally cover more ground (remember how that happened, kids?).
Renaming is a part of our familiarization with the code, before writing unit tests for it. Even if I don’t know what to test, making the legacy code readable helps me not only understand what it does, but also how to unit test it.
We usually name by scope, if at all. Making the distinction helps in making sense. If there’s already a convention in the code (like “m_” prefix for fields), make sure that the convention is followed in the code you’re observing. If there isn’t a convention, start one.
Compare the type of the variable to the method and to their type. If it can be improved, rename it.
Acct a = BankManager.getAccount();
We can rename a to account, and then we wouldn’t need to remember what a is in the next 500 lines of our method. If the method returning the value seems confusing, its type can help you rename it.
Don’t skimp on vowels! It starts as a clever way to save screen space, but after the vowels go, we think about other options, and soon we’re left with: acct. Not only less readable, but also annoying. Make the code readable.
Apart from renaming, if you can tidy up the code, put the declarations into one area, the beginning of class or methods. If you find declarations spread around, clean it up.
Method are harder to rename – they tend to do more, because of the aforementioned sloppiness. We usually start with a simple name, and then find the method the best place to do a couple things more . We soon have a big method, with a name reminding us of what the method was back then.
This is confusing to the reader, and of course makes it harder to unit test. But we’re not there yet. For now, make the renaming simple: If the method returns something, prefix it with get. If it does something, make sure it start with a verb like set or make or call.
Check out the last lines of the method, or the exit points. Usually (not always), from the structure you’ll see what is the purpose of the function. Try to make the name fit the purpose. It may not be always the case though, so be careful.
Don’t skimp on vowels! And don’t worry about the screen space. Make method names convey their purpose, and use the entire alphabet for it.
These are the hard ones to rename, and I usually recommend not to (at least not until you cut them down to size). We only see the types at declaration or creation time, so renaming them won’t bring us much benefit in terms of understanding.
It might be beneficial to identify a base or derived class in its name. Mostly it won’t, and lends itself to get nasty later, when adding a third hierarchy layer, for example. I still like an I prefix to an interface, although it may get me killed in some communities. And always remember kids:
Don’t skimp on vowels! Applies to classes too.
Now that we’re done with renaming, it’s extraction time. Up next.