When good code goes bad

In this post, I'll be discussing a dynamic that has a corrosive effect on software code. This dynamic is often responsible for code deterioration over time and makes good code go bad.

Good code
You know the kind. It is written with a full understanding of the problem. It is naive enough to be implemented simply yet sophisticated enough to do all it's supposed to do and just a little bit more.

Bad code
You know it when you smell it. The design is convoluted, the API seems unnatural and unintuitive, and worst of all, you're scared to touch it for fear of breaking something.

Good people write code that goes bad despite their best intentions. Bad code may have started off as good before tuning bad. How does this happen?

Code structure scalability
All code has an intrinsic degree of scalability in regards to its structure. I call this the degree of code structure scalability. I use the term scalability not in its performance context, but rather to describe the code's ability to adapt to change. Change is the great corroder of software. It manifests itself in various incarnations: changing business rules, changing usage of code, or even change in applicability. In any of these cases, code structure has to endure the stress of change over time. Can code structure resist change and should it be designed with this in mind? I'd like to focus this post on the simple acknowledgment that this dynamic exists in software and often works against code designers.

How good code goes bad
I use the term code structure to encompass everything from class design, methods, APIs and even internal algorithms. With this in mind, let's say we're designing an interface to handle requests for a simple web service using SOAP based messaging. This interface (SoapService) represents the entry point for back-end processing for this such server. Let's assume that a SOAP message has been parsed and converted into a RequestHandler that can serve the request. Since we are planning on expanding the services we offer, our interface will be designed like this:

Design #1
public interface SoapService
{
public void executeRequest(RequestHandler handler);
}
The request handler is another interface that looks like this:
public interface RequestHandler
{
public void doExecute();
}
The entry point is generic enough that the code related to handling a specific request is embedded in the command object represented by RequestHandler (this is the good old command pattern from GoF). It allows you to scale up the code structure by having as many different implementations of RequestHandler but only one of SoapService. This works great - for a while.

Now suppose we change the business rules (e.g. corrode the code structure) so that a client may send two or more requests at a time. Simple - you change the method signature and use an array of RequestHandlers instead.

Design #2
public void doExecute(RequestHandler [] handlers);
The code structure has managed to scale up and adapt to the new rules. Again, this works great - for a while. Let's stress the code structure a little further (e.g. change the rules): the chain of execution must be short-circuited should an error occur in one of the RequestHandlers. To achieve this, we add RequestHandlerException to the doExecute() method. This allows a RequestHandler to report an error and to indicate that the chain of execution must be aborted.

Design #3
public interface RequestHandler
{
public void doExecute() throws RequestHandlerException;
}
Still works well - for a while. Finally, if a RequestHandler fails, it must notify all previous RequestHandler so that they may opt to undo the command. In any event, all handlers must know if the original request failed or succeeded so that they can perform their own internal bookkeeping.

Blowing past scalability limits
You may actually come up with a creative solution and keep the current code structure. However, I guarantee that your code will have crossed the line and gone from good to bad. In other words, your initial code structure would not have scaled beyond the first three business requirement changes. A better idea would be to redesign the code structure into something more sophisticated.

This brings up an interesting point. If you design a new code structure that manages to serve the new complex business requirements well, and can scale down to serve the initial simpler requirements, isn't it more scalable than the first design? Probably not. This newer code structure would undoubtedly be more complex. If we only need simple chained execution, the second design would not scale down well to meet simpler requirements effectively.

Conclusion
In the end, each code structures serve one type of business requirements well. Each can scale and adapt to changes within certain soft boundaries. Neither code structure scales well up or down across the requirements complexity continuum. However, knowing that this dynamic exists is the first step in choosing the right design.

0 comments: