How to inject beauty in a coyote ugly code base.
In Part I, I mentioned one of the dilemmas voiced by developers who want to inject some beautiful code in their day-to-day activities:
“I am maintaining a steaming pile of legacy code that I have inherited. I can barely keep the code base working – sort of. I would love to write some beautiful code, but have no time to think about beauty as long as I am saddled with this ugly code base.”
This is a very common situation. Most developers spend most of their time maintaining/enhancing code that they did not write and, more often than not, that code base is not just ugly, but coyote ugly .
So, what’s a developer to do when stuck with code so bad that it should come with an “abandon all hope ye who enter here” warning? There are three basic options (besides the extreme the coyote option – which I don’t want to offer for legal liability reasons):
Option 1: Run for the hills (i.e., ask, beg, bribe for another assignment – or quit).
Option 2: Accept the job with resignation. Abandon hope and prepare yourself for a coding life of quiet desperation highlighted by moments of sheer panic. Help to prove that Thomas Jefferson was spot-on when he said: "Mankind are more disposed to suffer, while evils are sufferable, than to right themselves by abolishing the forms to which they are accustomed.”
Option 3: Accept the job as a challenge. Accept that the code base is ugly; it’s OK – therapeutic even – to make fun of it and of the careless, incompetent, so-called-programmers who developed it. Accept the fact that you cannot single-handedly turn a huge steaming pile of legacy code into a thing of beauty. But be determined and committed to reject and eliminate ugliness in the bits of code that you do touch. In other words: scorn globally, act locally.
The first option is probably the smartest one but, with so much crappy legacy code out there, you might have a hard time finding a suitable job. The second one is the easiest, but is not something that I would ever consider or recommend. The third option is the hardest to embrace, at least at the beginning, but the noblest and the one I want to talk about.
Since it’s unlikely that you will have the option to make sweeping design or architectural changes, the key to success in working with a pile of ugly code is to focus on small improvements. When you run into an ugly bit of code that you can or must change, make a conscious decision to make it more beautiful.
This, of course, raises the issue of what “more beautiful” means. If you have read Beautiful Code, you have probably realized that each author had a personal definition, or bias, for what constitutes beauty. The good news is that since you are the one working on the code, you can apply your own criteria for beauty.
Here are just a few of the many localized actions that you can practice – assuming you agree with my criteria for beauty:
Write tests for a particular piece of code before you change it. A test suite is a beautiful thing to have and it will give you a safety net as you make those changes. If you are working with legacy code, you should become an expert at writing characterization tests*.
Refactor to reduce complexity and improve testability. Method extraction is my favorite refactoring; it’s fast, safe (especially if you have tests), and easy to revert if needed. In my book, any method with a cyclomatic complexity of 8 or more is a candidate for refactoring.
Rename cryptic or ambiguous identifiers. This is a pet peeve of mine. In this age of cheap computing resources there’s no excuse for using a variable name like “ac” instead of “areaCode”.
Delete. If you can identify dead, useless, duplicate, or obsolete code, get rid of it.
Sounds like easy and obvious advice, doesn’t it? Well, it might be easy and obvious but, unfortunately, it’s rarely practiced. Otherwise I would not have bothered to write this. The reality is that, in most cases, ugly code becomes uglier as it’s maintained or enhanced. As a matter of fact, a lot of code starts out pretty nice and clean (both in design and implementation) and degrades as time goes on. It happens gradually. It’s done innocently. In the short term, adding yet another if statement inside a nested for loop is faster and easier than taking some time to think of a way to refactor the code to reduce complexity; it does not look like a really bad thing. But over time, these actions add up.
Taking time to write a little test, perform a simple refactoring, or rename one variable, does not sound like much; but it represents a huge – HUGE – change in attitude and a rare and courageous stance toward legacy code. It means that you don’t accept the inevitable slide from ugly code to uglier code.
Reducing ugliness in legacy code one method at a time, may not be as glamorous as creating a few lines of beautiful code suitable for framing, but I would argue that it might be just as important and a far nobler thing to do – and you’ll never run short of opportunities to practice it.
 Coyote ugly: code so ugly that some developers might prefer to chew their fingers off rather than work on it – much in the same way that a coyote captured in a jaw trap will chew off its own leg to escape certain death. Sorry if this is a bit too graphic.
 Characterization tests: a term coined by Michael Feathers in his excellent book Working Effectively With Legacy Code to describe tests that capture not the specified, but the actual, behavior of a piece of code. See: Characterization Test and Working Effectively With Characterization Tests
The steaming pile is more "normal" than is beauty (20:1 maybe?) from what I've seen. So the key to beautiful code lies in doing a Swan on these ugly code-bases. A major part, as you say, is to roll up the sleeves, write a bunch of tests and start refactoring. But at the same time, you need to look at the big picture and define some architectural layers. The bigger the picture the more of the development team that is going to be affected - plus this gives some high-level guidance to refactoring efforts.
In principle, I agree with you that, especially if the code is meant to stay in production and evolve for a long time, the big picture is very important.
What I have found in practice, however, is that many maintenance teams are rarely given the time to even begin to address the big picture. The short term thinking and direction that was responsible for the steaming pile continues - and often gets worse - during maintenance. The urgent trumps the important on a regular basis. It sucks, but it's the reality for many developers.
You will not get any argument from me on the benefits combining big-picture/longer-term thinking (i.e. think globally) with smaller more localized actions (i.e. act locally) - and I will post more about that later.
My intention with this particular blog was to make sure that developers don't postpone, or forgo altogether, small pro-active improvement they can make TODAY because the situation seems hopeless or because they are waiting/planning for some some future big architectural restructuring sometime in the future.
I'm not thinking of Big Bang restructuring, though maybe thinking of ongoing developoment rather than "just" maintenance.
I think it is feasible to address both the micro and macro structure at the same time.
For example, if some basic architectural layers are defined, and if any new violations of these are communicated to the developers as they change code, then just by avoiding making the "architecture" worse, they are encouraged to make it better as they refactor and add new features. In this way the steam-level can be reduced (maybe even the odd flash of beauty apearing!) across the whole code-base.
> What I have found in practice, however, is that many > maintenance teams are rarely given the time to even begin > to address the big picture.
There is a fundimental difference between code development and code maintenance that needs to be recognised. There are no bugs in code that is being developed, only unfinished code. Maintenance, on the other hand, is about fixing software that is broken in some way and causing someone somewhere pain right now.
It is because of this difference that the priorities of software development are so different from those of software maintenance. The "big picture" should not be a concern for maintenance, their job is to fix bugs. The modern day mantra of "the simplest thing that works" applies equally in maintenance but subtly reworded as "the simplest fix that works".
Trying to develop a code base under the guise of bug fixing leads to a conflict of priorities that is rarely helpful. The two roles need to be kept separate as far as possible.
"The screenshots on the box are taken from a version of the software that you haven't bought."
> There is a fundimental difference between code development > and code maintenance that needs to be recognised.
Hi Vincent, I agree with this. My case for considering the macro-level while refactoring is really appropriate for projects on the ongoing development end of the spectrum. (But if the maintenence level is very high... ;-).
> There is a fundimental difference between code development > and code maintenance that needs to be recognised. There > are no bugs in code that is being developed, only > unfinished code. Maintenance, on the other hand, is about > fixing software that is broken in some way and causing > someone somewhere pain right now.
I think the term 'maintenance' means different things to different people. 'Maintenance', in my mind, not only includes fixing bugs but implementing new features into existing code a.k.a 'enhancements'.
My personal experience is that the time working on enhancements is much less than the time spent fixing bugs.
When fixing bugs becomes the dominant activity in maintenance, it's likely that the code is so badly written that every fix is creating more bugs and refactoring (or a complete rewrite) is definitely going to save time in the long run. I've worked with code that cannot be fixed in a simple manner because it is fundamentally flawed.
Unfortunately, the cost of repeated bandaging is often poorly understood by people who are setting priorities. Eventually the cost of maintaining the code absorbs all the teams time and they run in place.
> I've never been on a team where we've had the luxury of > "pure maintenance" - as in just refactoring and bug fixes. > Management wants some value added. > > As far as coyote ugly code - that's simple. If it's a > known entity that works, just wrap interfaces around it > and don't touch.
The hard code to deal with is ugly, doesn't work properly and has no documented requirements.
Test-driven fixing is very important. A lot of legacy/crappy codes don't have complete or good test cases along with it. I found in some occassions that developers just wrote some test codes in main functions and that is it.
> > I've never been on a team where we've had the luxury of > > "pure maintenance" - as in just refactoring and bug > fixes. > > Management wants some value added. > > > > As far as coyote ugly code - that's simple. If it's a > > known entity that works, just wrap interfaces around it > > and don't touch. > > The hard code to deal with is ugly, doesn't work properly > and has no documented requirements.
Yep, and then you need to seriously consider a rewrite if it's that broken.
> > The hard code to deal with is ugly, doesn't work > properly > > and has no documented requirements. > > Yep, and then you need to seriously consider a rewrite if > it's that broken.
The problem is that it's often hard to get requirements because people who were around when the code was created don't want you going around telling the users it's broken and that no one knows what it should do. If it's not that, management doesn't want to spend the money to redevelop something that was supposedly complete long ago.
It seems to me that a lot of what prevents developers from fixing things is political or otherwise non-technical. If I had my druthers, I'd fix/rewrite every piece of crap that I came across.
Flat View: This topic has 21 replies
on 2 pages