Live data from Hacker News

Code Design Decision – Always throw custom exceptions

github.com

81–90 of 92 posts

Re: Code Design Decision – Always throw custom exceptions

#81
post #77

Earlier quoted context omitted.

> This is checked exceptions all over again... That is an interesting topic, as I went through the cycle of adding and removing them, they always felt like the right thing, but they felt like "work". They make coding less fun, having to think through failure conditions, like you say, you don't care about. I guess the answer is really there is no one sized fits all solution, we have some applications where the correct…

"exception specifications" are deprecated since c++11 and completely removed in c++17 if I'm not mistaken.

Yes, it was that bad.

Re: Code Design Decision – Always throw custom exceptions

#82
post #40
post #5

Yes. The Python modules I write generally have: class Error(Exception): pass class FooError(Error): … and then, in the generic case: try: failing_external_function() except Exception as e: raise Error(*e.args) from e (And if the code encounters a foo situation, it explicitly raises FooError, possibly with extra parameters, etc.) It is really annoying to have to catch all of, say, socket.error, SSL errors, FileNotFoun…

What happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error? This sounds like viral boilerplate: any function that can fail forces all other functions in the stack trace to be wrapped. Seems quite unpythonic, the whole point of exceptions is to avoid this boilerplate by propagating. Are you just writing boilerplate to assign exceptions to modules? But this…

> What happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error?

Yes?

If you call a function in another module, generally you’ll always have to catch and handle errors specific to that module. If that module does not wrap its errors (like socket.error, FileNotFound, etc.), you’ll have to handle those, as well. And, like Parthenon points out, if that module ever changes its internal implementation, it will suddenly raise different errors, which your code does not catch. On the other hand, if the module wraps its errors, you’ll have a guarantee that you’ll be able to catch them.

I don’t know why you keep bringing up debugging; Python’s “raise from” keeps the original exception intact and available.

> Are you just writing boilerplate to assign exceptions to modules?

Wait, are you talking about monkey-patching? I’m not doing that. I’m talking about the case where you write your own module for something, and then use that module in another program (possibly itself a module).

Re: Code Design Decision – Always throw custom exceptions

#83
post #82
post #40

Earlier quoted context omitted.

What happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error? This sounds like viral boilerplate: any function that can fail forces all other functions in the stack trace to be wrapped. Seems quite unpythonic, the whole point of exceptions is to avoid this boilerplate by propagating. Are you just writing boilerplate to assign exceptions to modules? But this…

> What happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error? Yes? If you call a function in another module, generally you’ll always have to catch and handle errors specific to that module. If that module does not wrap its errors (like socket.error, FileNotFound, etc.), you’ll have to handle those, as well. And, like Parthenon points out, if that module ev…

Your code example essentially renames a regular wildcard Exception to module-specific wildcard exception.

But here's a thing:

For direct callers of your module, regular wildcard and module wildcard are exactly the same. There's absolutely no difference for them whether they are catching module.Error or Exception, because your code is structured so that mean exactly the same thing.

For indirect callers higher in the stack, your custom wildcard exception makes error handling harder, because instead of familiar exceptions they will see your custom one. Your wrapper makes it harder to access what happened, but easier to access where it happened. This is just a bad tradeoff. In some cases for you where might be more valuable than what. But you are in no position to assume that this is how it's going to be for your customers.

Examples where this practice might be useful (e.g. explicitly shifting the blame to a 3rd party) are simply too rare to justify those wrappers. You could still achieve the same results by inspecting the stack, without impeding the ability of your customers to deal with known exceptions.

Re: Code Design Decision – Always throw custom exceptions

#84
post #78

Earlier quoted context omitted.

If the database is unresponsive, I would prefer to return 5xx to the caller as soon as possible. May be this service is not so important and it's better to present to user an incomplete page rather than waiting for hours or days until database is available. If it is important, caller will call the service until he got response. Again: fail fast. Even in distributed systems. Unless you're 100% sure that you know bette…

Totally agree about returning a 5XX error. In my app that would be a single RetryableException back to the caller instead of the internal db error.

Ugh. You should never call something "RetryableException".

1. You are in no position to decide for your customer whether exception is "Retryable".

2. For cases where you are in that position, you should just retry automatically.

3. RetryableException tells absolutely nothing about what happened. It's no better than raising MondayException for exceptions that happen on Monday.

Re: Code Design Decision – Always throw custom exceptions

#85
post #32
post #8

Just because it's useful to wrap exceptions in 5% of the cases doesn't mean you should wrap the rest 95% just in case . YAGNI. 1. You don't know which exceptions will be raised in advance. Anything that involves IO can fail in a plethora of ways, and you don't even know which calls involve IO (e.g. a library might choose to cache something on disk). 2. Consumers of your code will not know how to deal with those excep…

6. It is much faster and easier to develop an application's "happy path" while completely ignoring failures. Failure handling is a software maintenance issue. YAGNI.

As a counterpoint to that - the chances that a newly developed feature will work perfectly anywhere other than your own development environment are often pretty low. And when it almost inevitably fails, not having any error-handling logic will use up huge amounts of your time trying to track down the reason for failure. For a first cut of any new feature, I'd want at a minimum to have some form of detailed error-reporting, even if it's presenting a stack trace to the user. Ideally it's handled by whatever framework you're working in, which typically means all you need to do is not swallow exceptions, but if you do have to write your own error-handling code, don't throw away any details about the error (sometimes the best you can do is just log the full exception details, and personally I'll always ensure that the names/URIs of any resources involved are included in those details).

Re: Code Design Decision – Always throw custom exceptions

#86
post #83
post #82

Earlier quoted context omitted.

> What happens if function from another module calls failing_external_function()? Will you wrap it in another_module.Error? Yes? If you call a function in another module, generally you’ll always have to catch and handle errors specific to that module. If that module does not wrap its errors (like socket.error, FileNotFound, etc.), you’ll have to handle those, as well. And, like Parthenon points out, if that module ev…

Your code example essentially renames a regular wildcard Exception to module-specific wildcard exception. But here's a thing: For direct callers of your module, regular wildcard and module wildcard are exactly the same. There's absolutely no difference for them whether they are catching module.Error or Exception, because your code is structured so that mean exactly the same thing. For indirect callers higher in the s…

> For direct callers of your module, regular wildcard and module wildcard are exactly the same.

Well, no. I usually only wrap exceptions which I know to possibly expect, and only wrap ‘Exception’ in code sections where I know I want to catch any exception, no matter what (which is rare, but happens). Unexpected exceptions (either of an unexpected type or in an unexpected place) will still propagate upwards. This allows the code calling my module to catch any reasonably expected exceptions (since I will wrap them in my module.Error, or, really, a more specifc exception class inhereting from module.Error), while still allowing unexpected exceptions to be shown.

I realize that I was unclear in my initial description; I do not catch ‘Exception’ all the time, but only occasionally. I most often list the exceptions which I know that the called function could raise in failure states which I know how to handle.

> Your wrapper makes it harder to access what happened

How? With “raise from”, Python shows you not only the stack trace of the error, but also the stack trace of the original exception, IIRC.

Re: Code Design Decision – Always throw custom exceptions

#87
post #86
post #83

Earlier quoted context omitted.

Your code example essentially renames a regular wildcard Exception to module-specific wildcard exception. But here's a thing: For direct callers of your module, regular wildcard and module wildcard are exactly the same. There's absolutely no difference for them whether they are catching module.Error or Exception, because your code is structured so that mean exactly the same thing. For indirect callers higher in the s…

> For direct callers of your module, regular wildcard and module wildcard are exactly the same. Well, no. I usually only wrap exceptions which I know to possibly expect, and only wrap ‘Exception’ in code sections where I know I want to catch any exception, no matter what (which is rare, but happens). Unexpected exceptions (either of an unexpected type or in an unexpected place) will still propagate upwards. This allo…

> How? With “raise from”, Python shows you not only the stack trace of the error, but also the stack trace of the original exception, IIRC.

Because you can't catch the original exception, you're stuck with weird module.Error which is too generic to do something about it. You would have to catch the module.Error and then look at e.__cause__ to actually handle the exception. So you end up with the exact same problem Parthenon is talking about, but with extra steps.

And pray no other dependencies are following your practice, because you would then have to go into e.__cause__.__cause__ and so on.

What exactly is the benefit that module.Error provides to your users compared to letting the original exception propagate?

Re: Code Design Decision – Always throw custom exceptions

#88
post #67
post #66

Earlier quoted context omitted.

Exceptions are a return type of functions. They return via a different path but there is no possible way you could disagree that they are a form of return.

Exactly my point?

I understood it to be intended as agreement with emphasis.

Re: Code Design Decision – Always throw custom exceptions

#89
post #66
post #63

Earlier quoted context omitted.

This forces client code to handle arbitrary exceptions and it loses the information about which specific exceptions are actually thrown. This is like having Object as the return type on all functions, and having callers do instanceof to see what type of result they got. Exceptions should be part of the semantic contract of functions/methods just like return types are.

Exceptions are a return type of functions. They return via a different path but there is no possible way you could disagree that they are a form of return.

As they don't return to the same place, don't return to a single place, and may (if not caught) not actually return but instead take down the entire thread, I think it's reasonable that for some purposes it might well make sense to treat exceptions as something other than "a form of return." ¯\_(ツ)_/¯

However we're treating them, however, I agree that they are a part of the interface and should be documented (and, where relevant, checked) as such.

Re: Code Design Decision – Always throw custom exceptions

#90
post #66
post #63

Earlier quoted context omitted.

This forces client code to handle arbitrary exceptions and it loses the information about which specific exceptions are actually thrown. This is like having Object as the return type on all functions, and having callers do instanceof to see what type of result they got. Exceptions should be part of the semantic contract of functions/methods just like return types are.

Exceptions are a return type of functions. They return via a different path but there is no possible way you could disagree that they are a form of return.

The point is that the type system doesn't properly reflect that. You should be able to declare a generic method that takes some T, and throws everything that T.foo() throws, for example.
Post reply on HN