As programmers we tend to love and embrace complexity. Towering class trees and data-driven algorithms fool us into thinking we’re doing our jobs. That is, if you think programming is about writing code. I prefer to think of it as creating features. Not all features are the same, however.
Let’s take this method:
public String printCompanyName()
{
System.out.println("Welcome to Global Dynamics!");
}
and compare it to this method:
public String printCompanyName(String name)
{
System.out.println("Welcome to " + name);
}
It’s natural to view the second method as better. It has the added feature that the company name can vary. But this feature adds complexity and all complexity costs something. To make a decision about which method to prefer, we need to know how valuable the feature we’re adding is. Unused features are worthless. In this example, if the company name never varies throughout the program, we should use the simpler method.
This is the principle of YAGNI – You Ain’t Gonna Need It. Complexity always introduces cost so we should struggle to resist our desires to “improve” the system until we have a clear understanding of what the system should do. And even then, we should only do the minimum required to implement those features. YAGNI evolved out of the agile community as one technique for eliminating feature creep in programming. By only implementing the features needed today, programmers defer the cost of introducing complexity as long as possible. This same principle is stated in a different way in Test-Driven Development — programmers should do the simplest thing possible to get a test to pass. Complexity, when necessary, should evolve out of simplifying code, rather than complexity driving the creation of code.
We do this because complex code has several associated costs. Firstly, it’s a lot harder to change complex code than simple code. The cruft from complex code must be maneuvered around and creates confusion over where to add new code. This directly increases the cost of adding additional features. Secondly, complex code is harder to understand, which increases the cost of training new developers and also the risk of introducing bugs. And simply writing complex code in the first place is error prone.
There are some guidelines. If we only ever print the company name once, having a method to do so is probably wrong. Likewise, for classes, those that have only one child or that only have one method are likely introducing unnecessary complexity. Obviously these should be evaluated on a case-by-case basis, but these are pretty good rules of thumb. On the other hand creating methods and classes can reduce the overall complexity in a program by centralizing, encapsulating, and abstracting. The salient point is that these techniques should be used to reduce the overall complexity of the program. Refactoring should always be an attempt to reduce complexity by introducing structure.
YAGNI applies even when you’re looking at old code. There’s a comparable principle of You Didn’t Need It. When we speak of refactoring we usually mean the process of extracting generalities and creating structure, but it’s just as valuable to deconstruct over-complex implementations. It’s natural to think of the effort invested in creating a rich structure as a sunk cost but it is not. These unnecessary complexes silently intrude on one’s conceptualization of the program and make it harder to create new code. Often it’s more efficient to defactor code before extending it.
Defactoring is a term that has no clear definition associated with it. By defactoring I mean taking code and reversing the process by which it was refactored in order to return the code into less differentiated state. Defactoring is how I suggest solving the invisible design antipattern. Most instances of defactoring are followed by refactoring along different lines, but YAGNI dictates that step is only taken if there’s some clear need for the extra complexity. You might also think of it as attempting to reduce complexity by reducing structure.
While it is true that I view complexity as a necessary evil, I don’t view it as a villain to be fought at every turn. Rather, I view it much like mechanical engineers view friction. If we tried to build a program with no complexity, that program wouldn’t do anything interesting. But we must always be mindful of complexity as a force to be addressed and managed in its own right, on par with performance or defects.
Until next time,
Practice Makes.