Clean code — Error Handling

Santi Moreno
4 min readSep 6, 2021

Right now I’m reading for the second time, the great book Clean Code by Robert C. Martin aka Uncle Bob and I think it’s a good idea to write down the most important ideas that it tries to convey to us. Today, I will focus on the seventh chapter.

One of the most interesting things about exceptions is that they define a scope within your program. When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch.

In a way, try blocks are like transactions. Your catch has to leave your program in a consistent state, no matter what happens in the try. For this reason, it is good practice to start with a try-catch-finally statement when you are writing code that could throw exceptions.

Use Unchecked Exceptions

When checked exceptions were introduced in the first version of java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. Moreover, these exceptions were part of the type of the method. Your code literally wouldn't compile if the signature didn't match what your code could do.

The price of checked expectations is an Open/closed Principle violation (I’ll talk about this in future stories).

If you throw a checked exception from a method in your code and the catch is three levels above, you must declare that exception in the signature of each method between you and the catch. This means that a change at a low level of the software can force signature changes on many higher levels.

Consider the calling hierarchy of a large system. Functions at the top call functions below them, which call more functions below them, ad infinitum. Now let's say one of the lowest level functions is modified in such a way that it must throw an exception. If that exception is checked, then the function signature must add a throws clause. But this means that every function that calls our modified function must also be modified either to catch the new exception or to append the appropriate throws clause to its signature. Ad infinitum. The net result is a cascade of changes that work their way from the lowest levels of the software to the highest! Encapsulation is broken because all functions in the path of a throw must know about the details of that low-level exception. Given that the purpose of exceptions is to allow you to handle errors at a distance, it is a shame that checked exceptions break encapsulation in this way.

Checked exceptions can sometimes be useful if you are writing a critical library: You must catch them. But in general application development, the dependency cost outweighs the benefits.

Don’t Return Null

I think that any discussion about error handling should include a mention of the things we do that invite errors. The first on the list is returning null.

public void  registerItem(Item item){
if(item != null){
ItemRegistry registry = persistenseStore.getItemRegistry();
if(regustry != null){
Item existing = registry.getItem(item.getId());
if(existing.getBillingPeriod().hasRetailOwner()){
existing.register(item);
}
}
}
}

If you work in a code base with code like this, it might not look all that bad to you, but it is bad! When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control.

Did you notice the act that there wasn't a null check in the second line of that nested if statement? What would have happened at runtime if persistentStore were null?

It’s easy to say that the problem with the code above is that it is missing a null check, but in actuality, the problem is that it has too many. If you are tempted to return null from a method, consider throwing an exception. If you are calling a null returning method from a third-party API, consider wrapping that method with a method that either throws an exception.

List<Employee> employees = getEmployees();
if(employees != null){
for(Employee e: employees){
totalPay += e.getPay();
}
}

Right now, getEmployees can return null, but does it have to? if we change getEmployee so that it returns an empty list, we can clean up the code:

List<Employee> employees = getEmployees();
for(Employee e: employees){
totalPay += e.getPay();
}

If you code this way, you will minimize the change of NullPointerEceptions and your code will be cleaner.

Conclusion

Clean code is readable but it must also be robust. These are not conflicting goals. We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic. To the degree that we are able to do that, we can reason about it independently and we can make great strides in the maintinability of our code.

--

--

Santi Moreno

Dev and Crypto Lover #Arg #cripto #Bitcoin #IA #machine #learning