I have been doing some introspection on the way I write code to find ways that I need to improve. I consider this a task that one must do periodically so that we keep organized. There is a very, very simple problem that occurs in every application I know:
How to return the results of an operation to the user?
I've seen many implementations. Some return strings, some throw exceptions, some use out parameters, reuse the domain classes and have extra properties in there, etc. There is a myriad of ways of accomplishing this. This is the one I use.
I don't like throwing exceptions. There are certainly cases where you have no choice, but I always avoid that. Throughout my architectures there is a single prevalent type that hasn't changed for years now, and I consider that a sign of stability. It is so simple, yet so useful everywhere. The name may shock you, take a look:
Yes, this is it. Take a moment to compose yourself.
Mind you, this is used everywhere, in every layer. We are talking about Web APIs, Client Applications, Console Middlewares, the internal and external layers of every god damn piece of code I put my hands on.
Here are some very, very interesting properties of having it:
- Consistency - If it is common practice to use a single, stable type, everyone knows implicitly what they are working with, what are the use cases and it just feels more comfortable.
- Predictability - If you have multiple developers working on different tasks, they will probably have the need to return results of some shape. You don't need to argue the shape of the results, this is it.
- Abstraction - Even if we have developers at different layers of the stack, by using a single well-defined abstraction to return results we are making our lives easier. The approach is simply the same in backend, frontend, etc.
How does it feel to actually use this?
Let's define some very very simple methods and see how this looks:
Ok.... at first sight it seems to have some ceremony? This looks verbose, doesn't it? And all those strings hammered into the code that will eventually spread everywhere.
Dealing with ugliness
It is notable that we spend all the time dealing with the Result type, returning it, creating it, etc.I take some extra steps to clean up the code.
First of all, define some conventions that apply everywhere.
- An empty list means an operation has succeeded. It should be the same as a list with only successful results (assuming the Object does not carry important information in such cases).
- We never return null from a method that returns a Result or List<Result>.
- We try to model exceptions we can deal with as Result instances. A database connection exception will be caught, transformed to a Result and then returned.
Then, to deal with the code clutter we start by defining static instances of Result. An obvious instance that is used everywhere is the OK result:
And finally define some extension methods:
Now let's take a look at the final result after using these Extension Methods:
This may not make a big difference in such a small example, but notice how much more fluent the code gets. As it gets more real-worldy it also benefits more from the approach.
The main highlights are the lack of duplicated strings and the absence of initial clutter.
Some of the methods take inspiration from the F# Option module, which has very cool functions allowing you to manipulate the values with ease in a fluent way.
What is your way of dealing with this problem?
Update: here is a follow-up on this post with enhancements on the approach and the feedback that I gathered
Comments
Post a Comment