I recently reviewed some code which converted gRPC error codes into custom errors using a decorator and the simplicity and genius of it made me facepalm “why didn’t I think of it myself?”.
Really, why didn’t I? When I wrote how to handle errors with grace I mentioned using decorators to validate input parameters and how important it was to keep error handling separate from your main code as much as possible. I just didn’t think to combine the two. Oh well, better late than never.
Let’s see how it’s done:
First we’ll define my_method()
. All this method does is raise an exception, which I hope is not any real world example – but I’m doing it to make a point. For a single line of logic, I had to write 3 lines of error handling, obscuring what the method actually does.
Usually, we’ll have a few more lines of logic, but also a lot more error handling. Checking different types of exceptions, reporting exception code which is a bit more complicated etc.
Error handling fills our code with clutter, making it hard to follow what it actually does. What if you could Marie Kondo it and hide all that distracting mess in a convenient, reusable wrapper?
Does it spark joy? Yes!
Is it magic? No!
We can do it using decorators.
The simplest example is a catch all error handling decorator that catches all types of exceptions and prints them out.
All the exception handling logic is hidden in the decorator and out of my_method()
, making it much easier to see that writing a method which only raises an error makes absolutely no sense.
But that’s not all! We can add some more functionality in our decorator. In our next example we’ll see how to add retry attempts using a decorator.
Instead of just catching the exception and reporting it, this decorator allows defining a number of retry attempts and it will retry the decorated function until it succeeds or the number of retries is exceeded, adding a growing sleep
time between attempts.
There are infinite possibilities for customizing the error handling decorator:
- Add retriable/permanent exception types to decide which to retry
- Help callers of your method by wrapping exceptions with a single exception type (see why here)
- Add more complex logging code
- Re-raise exceptions
- etc.
I hope you found this useful!