Over the past several issues we've explored craft techniques for
writing code that responds to change. Despite all our valiant efforts, code rot
can sneak up on us—and quickly. After just a few days of undisciplined coding in
a last-ditch effort to make a looming deadline, the code debt can become
unmanageable. Like trying to pay off a credit card bill with interest payments
alone, we're no longer able to maintain a productive pace without taking out a
loan of time for the sole purpose of cleaning up the code.
But in today’s market you feel you can't possibly afford to take
time out to clean up code. So instead of you managing the code debt, the debt
begins to manage you. Making changes takes increasingly more time, you get fewer
new features per unit time, and debugging takes up a significant portion of your
schedule. Welcome to the death spiral.
It doesn't have to end this way. The smart money says to pay off
the code debt incrementally so you don't end up in a lose-lose situation. And to
head off code bankruptcy, you need to continually monitor and measure your code
base for the first signs of trouble. Let’s look at several ways to keep your
code in check.
Share and Share Alike
Just as artists and
craftspeople share their work with each other to improve their skills, we too
should frequently share our in-progress code with our peers. This sounds scary
because we risk hurting our pride. But the reality is that the code is destined
to be checked into version control (you are using version control, right?) where
it will be subjected to the harsh light of the entire team. Better to get gentle
feedback early.
One comfortable way to start sharing your code is to ask a
person who has earned your respect to review a manageable amount of your code.
(She'll likely be honored that you think enough of her to ask.) Bear in mind,
her comments on your code may not be what you want to hear, but it’s valuable
feedback nonetheless. If one person finds the code difficult to read or
understand, it’s likely others will as well. Don't take review comments as
attacks on your programming skills but rather as opportunities to improve the
code. Sharing code is a two-way street. Not only will you learn through feedback
you receive from others about your code, you'll also learn by critiquing code
written by others. (And you should feel honored that they would ask!) Being a
reviewer of code puts you in a unique position to learn an array of programming
styles and techniques. You'll be a better programmer for it.
Get More from Code Reviews
Code reviews need not be marathon meetings that ultimately end
with everyone storming out of the room in protest of a curly brace being put on
the wrong line. Rather, by focusing on the craft techniques we've been
practicing, code reviews can be short and effective. Start by asking the
following questions of the code:
-
Are class, method, and variable names meaningful and
expressive?
-
Are the comments acting as deodorant for bad code, or do they
add a sweet smell by making the code more maintainable?
-
If you had to make a change, would you have to remember to
alter code in multiple (duplicated) sections?
-
What additional refactorings would make the code easier to
read and understand?
-
Does the code have automated tests that give you confidence to
make changes predictably?
Each of these questions is intended to help you answer the
underlying question: How comfortable would I be making a change to this code?
After all, it’s the ability to respond to change economically that separates
good code from bad.
But wait, you say, shouldn't a code review also focus on whether
the code actually
works? Yes,
and that’s why you bring your automated tests to the code review. Without tests
to review, the only way to determine if the code works is to painstakingly trace
through the logic using mental assertions. By including automated tests and
their results in the code review, reviewers can objectively validate whether the
code works as expected. And as an added bonus, you'll get a valuable review of
the tests themselves. Reviewers may identify corner cases you may have missed,
for example, and in turn your tests become more solid. You'll be a better tester
for it.
Use Automation to Your Advantage
Early on I said that metrics
from static analysis are largely irrelevant. That doesn't mean we should ignore
them altogether, especially if the metrics can be generated cheaply. Tools that
automatically detect code that can be improved and collect a manageable number
of useful metrics are well worth the one-time setup cost. Put them on a
recurring schedule and they'll continually keep watch over your entire codebase
throughout the development cycle. Then during your code reviews you can focus on
things that a computer can't check for you, such as good naming and
documentation.
Choose code-checking tools that you can tailor for your project.
If a tool’s inspection rules don't jibe with your project’s style, the results
are meaningless to you. If the volume of output is too overwhelming, nobody will
pay attention. It’s a delicate balance, so be sure to start with a minimal set
of valuable metrics and only add new metrics if you're sure somebody will care.
I've had success using the following free tools on Java projects:
-
PMD: A static Java code analyzer that includes a
boatload of builtin rules and supports the ability to write custom rules. CPD
(the Copy/Paste Detector) is an add-on to PMD that uses a clever set of
algorithms to find duplicated code. (http://pmd.sourceforge.net)
-
Checkstyle: A highly configurable coding standard
checker with a default set of standard and optional checks. (http://checkstyle.sourceforge.net)
-
Cobertura: A code coverage analyzer that identifies
areas of code that aren't covered by tests. Unfortunately, code coverage
metrics are often used as instruments for programmer (and tester) abuse. When
used properly as constructive feedback, these metrics can help improve your
testing skills by identifying aspects of code that often go untested. (http://cobertura.sourceforge.net)
-
JDepend: A static code analyzer that generates
design-quality metrics based on Java package dependencies, including
identifying circular package references. Disclaimer: Your humble author wrote
this tool. (http://www.clarkware.com/software/JDepend.html)
When you run these tools, it’s important to bear in mind that
all code and design metrics are imperfect. Software is a human activity, and as
such we should trust the judgment of our teammates over the cold output of a
machine. Use metrics as a guide—not as a big stick with which to pummel your
fellow programmers.
Give Neglected Code a Good Home
So you've tenderly molded
your code into good shape using all the craft techniques we've discussed, and
then one day you get saddled with maintaining code that didn't get as much love.
How do you go about crafting it into something you'd call your own? The answer:
One line at a time.
In the same way you incrementally improved code you already
owned, you can improve new code that comes your way. That process starts by
writing tests that give you confidence to change the code. And it turns out
there’s no better way to learn how unfamiliar code works than to write a
learning test—a test that pokes and prods existing code to tease out how it
works. You learn what the code does—and doesn't do—by actually using it in a
safe context. Each new thing you learn about the code is recorded in an
automated test, until finally you end up with a suite of regression tests that
represent your knowledge base about the code.
Then, in the safety of a suite of good tests, refactor and
reformat the code as you read through it. For example, when you understand what
a chunk of code does, refactor it into a well-named method. Little by little, as
you codify your understanding through refactorings, the code itself improves.
Making Good Code
I hope you've enjoyed this series as we've covered several craft
techniques for improving the internal quality of code so that it responds to
change quickly. Rather than attempting to condense all that into a summary, I
thought I'd leave you with a general recipe that has helped me improve my coding
craft:
-
Write lots of code—because becoming good at any craft takes a
lot of practice.
-
Share your code with your peers to get a broad spectrum of
feedback.
-
Constructively critique code written by others to learn new
ways of doing things.
And above all else, have fun! If you're passionate about writing
code, then it should always be enjoyable. This is your craft.