Your API sucks (so I’ll catch all exceptions)

These days I’m working with some crazy APIs and I stumble into this warning from pylint:

W0703: Catching too general exception Exception

I know what the problem is. They want you to make sure you catch specific exceptions – it’s bad form to catch all, you should catch only those you can handle. But here’s the problem.

Your API defines an exception kind, from which all exceptions should be derived. But when I create an object you throw me some random system exception and I can’t tell which one. And I can’t test all exceptional cases, because I literally cannot. I don’t know – I don’t want to read your implementation, nor the implementation of the framework you’re using. So what am I to do? That’s right, catch all exceptions. Because I don’t really need know what the heck happened, only that things didn’t happen. Exception handling became error handling, only with a different pattern. But I don’t care about handling errors, I’ll just shut down the connection to your service and inform the user about it.

That’s what happened – sometime in the 2000s exceptions became synonymous with errors. You don’t really say there was an error, you prefer to return void and throw an exception. Then we can never know which exception you’re throwing, because it’s Python, so who the heck cares about types? Types are just labels, man1)imagine a pothead saying that.

Not only I don’t know which exceptions you might throw, but I really don’t care nor want to care about these exceptions. I don’t want to know what intricate exceptions you throw, because I think quite binary: the operation succeeded or it did not. And if I expect exceptions to be really exceptional, I’ll just let them wreck my whole runtime, and stop the program.

But in this case, your insignificant secondary support library will not be allowed to do that to my program. So I’ll catch all exceptions.

NOTES   [ + ]

1. imagine a pothead saying that

6 Replies to “Your API sucks (so I’ll catch all exceptions)”

  1. There are two use cases I encountered “in the wild”:
    1/ Throw a specific exception according to the cause, i.e. IOException? Something’s fucky with the stream; NumberFormatException? Something’s wrong with your input; PersistenceException? The datastore says “no”, etc.
    2/ Throw a generic exception (Exception) because why bother?

    I soon found out that both approaches are wrong (for reasons I won’t get into now). For a while now I’d just return “null’ if the operation failed because, frankly, Joe The Consumer doesn’t care why it failed. They have no control over your execution. However, this approach is not ideal – what if the call failed because the input failed some validation? There’s no way to tell.
    My 2018 resolution is to have my APIs return state objects – they would either contain the result or they would contain an error message. I’m still working on how such a state object would look like and how it would scale.

    Also, void functions in APIs are bad and the provider should feel bad.

    1. Building an API is hard, and people who dump „libraries” in the open source repos don’t know that.

      Returning a state object is a good idea. To be honest, I don’t know what’s better, but definitely the random exception thrown for the normal error cases are poisonous. Exceptions are used for error handling nowadays and that means that there’s a generation of people who don’t know what an exception is.

    1. It’s not pylint’s problem, really. Pylint says it as it is. One should not catch all exceptions, it’s bad style. The problem is that exceptions are used as error flags, and those cannot be auto-documented by simply parsing the code. I’ll not rant about Python, though, I’m pissed off with it as it is.

    2. Well … technically “all exceptions” is catching with except BaseException: or a bare except:.

      What does bad style even mean? The problem with style and best practices is that sooner or later people will forget what the rule was for. And then we get libraries that rewrap all the internal exceptions in some generic HttpException. Because it’s bad to raise some random system exception mmmkay? Good luck debugging that, especially on Python 2 where it was way too convenient to discard the old traceback.

    3. If you handle an exception, you have a handler for a certain class of errors. Handling an exception is hard – it means retries, for example, timeouts, it can be very complicated. If you can’t really handle the exception (because you don’t know what it is), you let it ruin your program, because you can’t tell what the error is anyway, and you don’t know how to handle it.

      My problem is where a connection error can be derived from various types throughout a few frameworks – one from the „basic” framework, one from the library defined exception and one from a framework they use.

      But I thought I kind of explained this.

Comentariul tău