Monday, October 3, 2011

Time To Refactor

Refactoring existing code makes better software.  Martin Fowler has championed this concept and as a result, developers look at refactoring code as an essential part of the process — not something to do on slow days.  Of course, idealizing over code design is one thing — making it a reality is another.  Unfortunately, refactoring doesn't happen for one reason or another — usually there isn't enough time.

But what about the programmers that have to stare this code in the face — day in and day out, watching unruliness assume control?  They're the ones who see the problematic consequences of letting ugly code grow and metastasize into other subsystems.  Programmers probably don't decide there is no time to for refactoring.

If there is no time to refactor code, when will it get done, if ever?  Understandably, features and enhancements are expected.  Expected yesterday.  Maybe there is a way for software developers to convince stakeholders that refactoring is the only option left.  Maybe we can treat, somehow, refactored code as a feature?

Code gains weight fast
Code gains weight at an alarming rate.  You might start off your hacking session with a small set of classes, maybe a couple utility functions.  It takes only a couple hours for this lightweight setup to transform itself into a bloated plate of spaghetti code.  Not because you don't know how to write elegant code, but because others are depending on you — it just needs to work.  If we wanted beautiful code the first time around, we're in the wrong business.  Refactoring is about improving existing code, not code that has yet to be written.


As programmers, we idealize solutions — before sitting down and typing it into the editor, we've at least, if only briefly, conceptualized a few alternative approaches to tackling the problem.  This isn't considered big, upfront design — it's more of a personal game plan to help spring us into action.  But, since this is only a quick mental note — not using real code and a real editor and not running real tests — chances are the some aspect of our plan is wrong.

The trouble is, we don't know that some aspect of our ideal implementation is invalid until it's concretely implemented and executed.  Sometimes our code doesn't even reach the point of execution before we've realized it doesn't fit.  Sometimes all it takes is a glance at the interfaces you're working with or the documentation of another component dependency to realize that there is a sudden change in plan.

This sudden realization means that the nice, natural flow that we had envisioned, the one that motivated us to get going in the first place, has been scrapped, ups the urgency of the situation.  Maybe the entire metal workflow isn't thrown out, but the calm confidence we had slowly dissipates.  We're probably not following a waterfall approach — so we need to adapt quickly — pound out something that'll work.  Otherwise, the forward motion of the entire project is jeopardized.

Fixes to sudden disruptions aren't difficult to implement, but they're not the most elegant or efficient solutions.  And so the code gains weight.  It works, but it's become heavier — in terms of raw size and in logical baggage — all within a few hours.

Features are released on time
Software projects have a brigade of developers, all with the attitude of making sure things get shipped when expected.  Even if that means not setting aside some refactoring time.  This process, this seemingly endless cycle of pounding out lines of code, is very rewarding.  Getting stuff done on time is a recipe for success in any software project as far as I'm concerned.

This is the habitual part of it for the programmers — who wouldn't want repeated success by continuously pleasing customers and other stakeholders by shipping early?  Don't fix what isn't broken, right?

And so life goes one — each cycle of the development process yielding a new and enhanced version of the software that exceeds customer expectations.  It's hard to change a good thing while it's good — making decisions to refactor code can seem unjustified.  Is there another way to squeeze in some code improvements that don't necessarily please the client directly?

Well, small incremental improvements can typically be tucked-into sections of the software system during the development process.  These are low-risk, low-effort improvements that don't have an immediate impact on code comprehensibility but instead have a concerted long-term impact.  The trick is — making these small incremental refactorings — is really only beneficial if they're a habitual task of each and every programmer working on the project — cumulative change for the better.  But this can be overridden, mentally, by that other little habit — satisfying the customer by shipping on time.

Forcing the issue
Two competing ideologies vetted against one another — the ship on time versus the refactor messy code — lead to one of two places.  Perhaps the worst thing that can happen is programmers get caught up in cleaning up code and as a result, deadlines for requested features tend to slip.  What we have in this scenario is some nice looking code and a frustrated group of stakeholders.  The product is better off now that the mess has been tidied — paving the way for future ingenuity — but how do you explain that to non-programmers?

The second outcome, and perhaps a good selling point for investing time in refactoring code, is a garbled mess that can't be updated with new features or enhancements to existing features.  There is an illusion that this will never happen because it takes a while to happen.  A misconception of those involved in software projects who don't write any code is that code is an immutable thing — once it's written, it's there and it should just work.  Ask any software developer and they'll tell you that any body of code is a living organism — one that must be fed and cared for.

So, forcing the need to refactor.  The best way to explain it to those involved in the project but do not understand the concept of refactoring code is to present it as a feature.  Or, sometimes even more effective, present it as a blocker for new, potentially interesting features.  This second approach takes some inventiveness, but is ultimately the most effective way to communicate the state of the code base to non programmers.  Think features they'd like to see and what would stop them from happening in terms of the current state of the code.

The reason this approach — let's call it proactive refactoring for features — works so well as a communication tool is that you're taking a deficiency in the fundamental code structure and presenting it in business objective.  Rather than trying to introduce the philosophy of refactoring code — because I can tell you right now they're not interested — you're taking a valid business concern and presenting it in a cohesive way.

One final suggestion on proactive refactoring for features — back your claims up with evidence.  Not a written words, but a quick little prototype that'll make very obvious the problem at hand.  Nothing says urgency like proof that a potential money making feature might not work in our product down the road.