The alternative to throwing errors is by returning this instead
It could be so simple. Throw an error to inform anybody that something went wrong. But Errors are exceptions, and you need to handle them like raw eggs.
The alternative to raw eggs is a Result-Object. You can get more granular control of handling them. Whereas throwing an exception makes up a new path in your code that can only be caught with a
You have to consider 3 general paths any program can take:
- Happy Path
- Alternative Path
- Exceptional Path
And let’s be honest here, the third one is left behind far more often than it should. Always and ever. You and I are always happy when we finally make the code work and finish the feature. Who cares about the third path?!
Unless you are developing after TDD (Test Driven Development), here comes the plot twist. Make it “disappear”! 🚬
The costs for exceptions might not be quite familiar, but they are heavy.
There is a lot of computation going on in the background. When you throw an exception, the call stack of the current program execution is generated every time. That is no doubt an expensive operation. Now, if the call stack wasn’t important to you or the flow of the program, why even throw a heavy computational error?
Furthermore, are exceptions truly the only way to handle errors? It takes a little practice to figure out when Exceptions are appropriate and when Result objects are suitable for “error handling.”
Just because there is theoretically a programmatic error does not necessarily mean it is a dramatic one. Often you just want to point out that a user has made an incorrect input. And especially this input could be unhandled and crash your code.
And here you have it: Result Objects should be considered by any dev that throws errors as a pitcher does with balls.
Instead of throwing errors that crash the program, if not caught, you’ll end up returning an
Result<T, E> object. Similar to Rust.
Try to comprehend the code below before you read the explanation.
The result of the method
AddMusicAlbum is encapsulated by a
Result class. The result is the actual return argument in the Happy Path or an error description in the Exceptional Path.
No matter what happens (success or error), you will always get the same object back, but with different properties written.
There are several ways to model a Result object, and this is a basic structure.
All variants have in common that they have properties that can be used to query the
Boolean status of the operation (
wasSuccesful) and further properties to get the concrete result (
Value) or the errors that occurred (
I mentioned the word basic structure because the
errors are just simple
strings. It depends entirely on your context, but you are also free to use special error classes. One example is making bitwise combined error masks like so:
00000010 could mean an error in writing the album to the repository has occurred.
Now that I convinced you about Result-Objects. Let’s take a look back and see if you did everything right.
As a usual business in software development, neither the use of exceptions nor the use of result objects is an ultimate solution. Both concepts have their advantages and disadvantages. In some cases, traditional error throwing is best. In others use result objects.
- once thrown, they move up the call stack through several call levels
- methods in-between don’t need to have any code dealing with error handling
- exceptions are fundamentally better suited for unexpected errors
- handling unexpected exceptions, a perfect place is inside middleware
- get to the root of the problem by implementing as much detail as needed
- the best place for exceptions are used public libraries (an own implementation of result objects dealing with library ones would be a nightmare)
Result Objects Advantages
- validating user input or data from external systems is made more accessible (for example, frontends with user — feedback)
- when errors are expected but should not lead to crashes
- if a domain model prohibits a specific operation — let the user know
- bring up domain-oriented code with result objects to eliminate boilerplate code associated with try-catch statements.
“The principle of least astonishment (POLA), aka principle of least surprise (alternatively a law or rule), applies to user interface and software design. It proposes that a component of a system should behave in a way that most users will expect it to behave. The behavior should not astonish or surprise users. The following is a formal statement of the principle: “If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature.” — Source
To Be Honest…
…it must be said that the caller must explicitly check a return value. No other one is truly responsive. An occurred exception requires special handling. Otherwise, the application is terminated by the runtime. Which is the worst case you want to happen.
If, on the other hand, a returned Result Object may not be evaluated, an error potentially remains unnoticed. When this happens, an inconsistent state of the application may follow. And this is even worse than the worst case of crashing during runtime.
Decide on your own, but you have now all the tools to decide and shine