In a recent tech talk, I watched Miško Hevery propose an interesting challenge to his audience: start a toy project and try to write the code with no if-else or switch blocks at all. None at all? Before thinking about how to do this, why would you want to do it in the first place? On the surface, it may seem to the unassuming a bit counter-intuitive. Comparison-based branching is at the heart of programming, and the concept of an if-else or switch block is almost universal, existing in most every programming language ever devised. What's Hevery's agenda with this exercise – and what can possibly be gained from trying to write a program without branching entirely?

Branching Considered Harmful?

In 1968, E.W. Diskjstra, famed programmer and author of the shortest-path algorithm, wrote an article to be published in the Communications of the ACM entitled "A Case Against the GOTO Statement" that was later renamed "Goto Statement Considered Harmful" by the editor. The phrase has grown into a meme of its own in the CS community, denoting a practice that has fallen from grace and become widely discouraged.

I bring this up not to point out its cultural signficance, but the core of his argument:

The go to statement as it stands is just too primitive[;] it is too much an invitation to make a mess of one's program.

Essentially, Hevery is making this very point for the if-else statement, 40 years later, but for very similar reasons. If-else and switch statements easily account for some of the ugliest code that I have seen (and written). Overuse of if-else blocks can lead to the arrow antipattern, a scenario where conditional branches are so deeply nested they become unmaintainable. Too often developers fall into this trap, described aptly by another Dijsktra quote:

The prisoner falls in love with his chains…

Hevery's point is that code without if (or switch) statements is:

  • Easier to read and understand
  • Easier to test
  • Easier to maintain, extend, etc.

But how do we write code without branching constructs? If we are talking about fixing the arrow antipattern, you may know a time when you avoided this antipattern by using separation of concerns, for example, the MVC pattern instead of a monolithic servlet.

Hevery's Suggestions

Hevery goes on to explain how good OO design can eliminate the need to explicitly code the most egregious uses of branching, by instead favoring dependency injection and polymorphism. But this technique is only really useful where we can create methods and objects that are cohesive and loosely coupled.

With polymorphism, you dispatch into a method whose binding is not known at compile-time but determined at runtime.

This is the weapon of choice when we are working on code that is highly procedural and there is a bad OO-design smell. But it is far from a one-size-fits-all solution. If we were to transform every branch in our program by instead using polymorphism, it would be a tangle of incohesive objects that would likely be harder to maintain than the original procedural code. Luckily, there are other techniques that are more appropriate in certain circumstances.

Hevery continues: certainly, primitive comparisons are going to be the most difficult to eliminate, but shouldn't happen very often. So we reluctantly allow these to stick around. Another class of if statements exist to check against nulls. Hevery explains how null pointer dereference checks quickly become pandemic, occurring every time a member of an object is referenced. This should largely be a non-issue, and is only a problem because code is allowing nulls to be returned. The semantics/method contract of most methods can easily be changed to prohibit the return of nulls. (This is only really difficult when recursion is used.) Developers, as it turns out, don't have to return null values. This is particularly true with collections, and warranted a section in Joshua Bloch's Effective Java. Instead return an empty collection can be returned, or you can implement the null object pattern (e.g., a no-op logger). Hevery finally rallies for ditching using return codes in favor of throwing relevant exceptions.

Note: I encourage you to watch the talk, as he continues talking about how specifically you can use inheritance and polymorphism to remove branching. The rest of this article will talk about other strategies to remove unnecessary branches.

Remove Copy-Pasted Loops And Conditionals: Introduce Enumerable Operations

Many languages contain an enumerable construct that includes "higher order" looping operations that can eliminate the need for many specialized cases that are usually solved by a foreach loop with an embedded conditional check of some sort. This type of construct can be traced back to Smalltalk, and exists in most modern dynamic languages such as Ruby, Groovy, Python, and provided by a number of Javascript libraries. Getting this to work in Java is a bit of a challenge because there is no mechanism for closures (yet) but some partial solutions do exist (they lack the full power of true closures, but overusing true closures can break down OO-style constructs). For further information, take a look at lambdaj, Adrian Kuhn's Pimp My Foreach and Google Collections' Iterators.

Here is an example of the types of things this style of programming can fix.

ListInteger> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
ListInteger> oddNumbers = new ArrayListInteger>();
 
for (Integer check : numbers) {
 if (check % 2 != 0) {
 oddNumbers.add(check);
 }
}

In the case of the Google Collection, they call their enumberable construct a "Iterable" and the closure construct a "Predicates," we'll be able to replace the above code with this:

PredicateInteger> oddIntegerPredicate = new PredicateInteger>() {
 public boolean apply(Integer input) {
 return input % 2 != 0;
 }
};
 
IterableInteger> oddNumbers = Iterables.filter(numbers, oddIntegerPredicate);

LambdaJ, and Kuhn's solution work in a similar fashion. While syntactically not as pretty as the dynamic languages, these constructs are functionally equivalent and arguably no more painful than the original code. In many cases of deeply nested for-if-for-ifs, it significantly reduces the cyclomatic complexity). This style of programming, once everyone on the team has become familiar with it, can reduce often copy-and-pasted looping code to one liners, removing some really ugly duplication and the potential for those hard-to-spot copy and paste errors.

Lookout for Train Wrecks: Obey the Law (of Demeter – and other SOLID Principles).

Steve Freeman and Nat Pryce describe "train wreck" code in their new book Growing Object Oriented Software Guided By Tests as chained "getter" method calls, linked together like train cars. The authors call them "wrecks" because the code is essentially an egregious violation of the Law of Demeter. One of Nat's examples:

employee.getJobDescription().getResponsibilities().add(new ManagementResponsibility(employee.getDepartment()));

The problem is that this style of programming destroys all encapsulation and information hiding concepts in code. Train wrecks can become pandemic as well because instead of using an object-oriented style of programming that passes messages between objects to perform work, we essentially allow objects to know too much about their collaborators and details. Another way this has been said is "talk only to your friends" or "ask, don't tell." A client object asks other objects what their state is, and uses an if statement to perform an action, instead of telling the collaborating object what it wants done, and letting that object figure it out. Instead of the train wreck, we should extract the behavior to a method that allows us to tell our collaborator what we want done:

employee.promoteToManager();

Complex conditional "train wrecks" can often be extracted in the same way. Quite often these types of objects are really just procedural code masquerading as a class, and will be in violation of other SOLID-principles such as the Single Responsibility principle. Making this code more "SOLID" will make it easier to test and maintain.

The Bigger Picture: Well Designed, Testable Code

Remember, this exercise in design (by removing ugly branching) is not just for aesthetics. There is a very legit reason to try these techniques: writing testable code. Hevery's talk (and his other talks) are essentially about how to write code that is testable. Freeman and Pryce's book is about the very close relationship between testability and OO-design.

If you like these ideas, you might be interested to try the TDD As If You Meant It exercise as well. This is an exercise that will show you how to listen to test code to drive adding features that are driven out by tests into your production codebase by implementing them first in the test class. This is the ultimate way to ensure that your production code contains only what it needs and nothing more, and that code is adequately covered by tests! Remember, the more code you write into production code, the more you're going to need to maintain it, and the more skeptical you should be about how readable and well designed it is. These techniques and exercises may seem silly on the surface, but if you explore them I am confident they will help you write better code!