Why does software rot?

In our first Q.E.D. Hour, we covered the first section of Robert C. Martin's Design Principles and Design Patterns. This first section talks about the symptoms of rotting design.

When software is created, it is a pure rendition of the designer's intent. But over time, as the software changes, it begins to rot. The changes come from users of the system, but the rot comes from within. Robert C. Martin has identified these four symptoms of rotting design:

  • Rigidity - The tendency for changes to ripple through the system, resulting in a resistance to change.
  • Fragility - The tendency for seemingly unrelated components to break when you make a change.
  • Immobility - The difficulty of isolating a component for reuse in a different system.
  • Viscosity - The friction involved in making a change.

Unlike physical goods, software doesn't rot while it's sitting idle. Software rot occurs only as the system changes. These symptoms only manifest when we try to change the software. So we can eliminate rot in one of two ways: prevent the software from changing, or control its root cause.

DependenciesManage your dependencies
The root cause of rot is dependencies. For example, we use a service oriented architecture at work. Many of our services depend upon our Catalog service. That makes Catalog rigid, because we can't change it without affecting the others. Our Shopping service depends upon many other services, which makes it fragile. Changing anything in the system runs the risk of breaking Shopping. This also makes Shopping immobile, because we cannot reuse it without all of its dependencies.

Robert Martin's paper goes on to describe how to invert dependencies in order to manage them. But in our Q.E.D. discussion, we left that for another day. First, we need to learn how to measure our dependencies. Proof is only possible based on the things you can see.

Right now, we are just interested in static dependencies. These are the dependencies between different pieces of code. Later, we'll talk about dynamic dependencies. Dynamic dependencies occur between different pieces of data. Static dependencies occur at compile-time, while dynamic dependencies occur at run-time.

There are six static dependencies visible from the public interface of a class. These are the types of:

  • The base class
  • Implemented interfaces
  • Constructor parameters
  • Method parameters
  • Method returns
  • Properties

If your class accepts returns or inherits a type, then it depends upon that type. These dependencies are visible dependencies, since consumers of your class can see them.

There are several dependencies hidden from the public interface of a class. Four most common are:

  • Internally created objects
  • Static methods of other classes
  • Extension methods
  • Singletons

If your class takes on any of these dependencies, consumers of your class cannot tell. You run the risk of making your code rigid, fragile, and immobile by having hidden dependencies.

Here's my solution
Make dependencies more visible to reduce rot. Turn hidden dependencies into visible dependencies.

Turn internal object creation into a factory. If you need to create a new object, don't just new one up. Delegate that function to a factory. No "new"s is good news.

Turn static method calls into strategies. Calling static methods directly leads to procedural dependency. You are telling the compiler not just what to do, but also exactly which implementation should be used to do it. Instead, use a strategy. Then you can inject the right implementation for each situation without breaking the caller.

Extension methods are a special case of static methods. It is extremely difficult to track dependencies on extension methods, especially since the name of the class declaring the extension method appears nowhere within the code. You have to study the "using" declarations to find them. Extension methods are best used to add behavior to an interface without requiring implementers to add code. Extension methods are worst used as stupid compiler tricks. And don't even get me started on extension methods that check for null.

Probably the worst hidden dependency is the singleton. The problem isn't that there is only one instance. The problem is that anybody can depend upon it. If your class uses a singleton, there is no way of seeing this dependency from the outside. Singletons tend to be very rigid, since so many other classes depend upon them. Avoid the singleton pattern. Instead, rely on an IoC container to inject a single implementation of an interface into all components that require it. This turns a hidden dependency into a visible one (a constructor parameter).

Making all of your dependencies visible is the first step to managing them. Only then can you hope to fight software rot.

Leave a Reply

You must be logged in to post a comment.