Functions, exceptions, return types and when to log your errors

Ioannis Papikas
4 min readDec 30, 2016

--

Using libraries out of the box can be easy and pretty much straight forward, but when it’s time you developed your own project in scale, then you get all the real challenges.

I want to believe that the code that I write is clean, efficient and follows all the S.O.L.I.D. principles, plus some extra ones and I try to implement any design patterns that can facilitate my work (attention here, using design patterns everywhere can be an anti-pattern itself). While I can fully understand the most of the concepts, Exception handling continues to trouble me even after 10 years of coding experience. Should I throw? Should I catch? Should leave it and pray?

Throwing an exception

I’ve read many books and I got mixed signals regarding exceptions, when and what to throw. After working on several projects, felt the real pain of tracking failures, bugs and broken code in production, I reached to a simple conclusion:

Functions’ purpose is to take your code from A (input) to B (return value, output). If something breaks between A and B, then an exception should be thrown.

What breaks a function? Usually we define a break when there is an error inside the function that comes from another component or another service. If the function doesn’t know how to handle it and recover, it breaks. Simple as that.

The above rule is quite easy to follow. I personally have some guidelines to think when I write a function, always in relation to the vertical layer of the object or function. In addition, there is an interesting principle to have in mind (Postel’s law) about function input and output:

Be conservative in what you do, be liberal in what you accept from others (often reworded as “Be conservative in what you send, be liberal in what you accept”).

1. Input — Check arguments

This is an optional check and it is affected strictly by your design and the vertical layers in your code. I personally in most of the cases check explicitly for all the function input, either manually or using some validation component. If something is wrong, throw an Exception. It’s simple as that.

Usually the checks that a function has to perform before proceeding with execution come from business requirements and they tell you whether you should make a check at all or where to add the check.

2. Handling Errors — Logging

Functions have an amount of responsibility. When an error occurs, one function can catch an error and recover or just re-throw the error and die.

If the function doesn’t know how to handle an internal error, it is function’s responsibility to wrap the error (if not wrapped already) and re-throw it.

Logging the error is a different story. The error can come from another component of the project or from a 3rd party library. In the first case, we can assume that the other component logged the error and the current function doesn’t have to do anything, just re-throw. If, however, the error comes from an external library (which I guess doesn’t have the responsibility or privileges to log or anything similar, I call this primary error), then the function should catch, log (full logging with backtrace and stuff) and re-throw the error.

Logging is fully debatable and there are two different approaches, log in the bottom layer (where the error occurs for the first time) or log in the top layer by the generic application error handler (good to have if you don’t have already). Both approaches have advantages and disadvantages, you can choose the one that suits to your products better.

3. Output — Pay attention

As part of the Postel’s law, arguments can be wrong and you can choose to check for them or not. Output however is more strict and your code should always return what is meant to return.

If the function is supposed to return an array of data but no data were found, simply return an empty array, don’t return null. If the function is supposed to return a specific object populated with data, if the data were not found, return null instead of an empty object*. And so on. If an error occurs in between, throw the exception and notify the caller about what failed and why.

*Empty objects are also debatable. There are different opinions where each custom object can have a special flag indicating that the object is empty or not found. This approach can be problematic in cases where the object already have a similar property.

The above simple rules can structure your code in a beautiful way and facilitate your contracts between different classes and components.

Handling Exceptions

Exceptions are part of your project’s reporting mechanism for errors. Spend some time building it properly, it will save you a lot of time in the future.

Usually, different projects create custom exception objects in order to be able to separate project exceptions from normal code exceptions and errors. This makes the life of a developer a lot easier in handling these exceptions.

The goal of the error reporting/handling mechanism is to always display custom made exceptions to the user. So, catch primary errors and wrap them into custom made exceptions. Catch custom errors and just re-throw them:

Conclusions

Unless you are building a very small pet project for your university class or to experiment, do yourself a favour and add a small task to define errors, exceptions, handling mechanisms and a ‘when-to-log’ principle. It will save you time, space (in logs that is, if you over-log at least), debugging effort and it you will put another quality brick to your project.

Thank you.

If you believe this is a good way to organize your code and your error mechanisms, hit the recommendation button and spread the word! Please feel free to comment and discuss :-)

--

--