Live data from Hacker News

Code Design Decision – Always throw custom exceptions

github.com

11–20 of 92 posts

Re: Code Design Decision – Always throw custom exceptions

#11

Like others have said, in theory this is great. In reality, I never see custom exceptions being handled differently than whatever exception was wrapped. And I have worked on some large distributed systems where failure is common. For new engineers these custom exceptions add abstract complexity and exception class hierarchy into a code base when it really isn’t needed.

I disagree. I have seen plenty of code that is forced to match on the text of an exception because it uses a too-generic type.

That said, I still wouldn't use a custom exception type in most languages simply because it's so tedious for a small pay-off. It's one of those things that you should do, but isn't really worth the hassle. Like putting alt text on HTML images.

Do any languages with exceptions let you define new exception types at the throw site?

Re: Code Design Decision – Always throw custom exceptions

#12
post #6

In short: if you have a meaningful recovery pathway for a particular exception this can be useful, but I found that 9 out of 10 exceptions/errors in code cannot be recovered from. This is checked exceptions all over again... Frankly, I usually do exactly the opposite. Most cases I've seen the exceptions that can arise are not widely known in advance, and for most spots where exceptions can be raised from I simply do…

> If my DB is not accessible - do I really, really need to wrap it?

I would say yes. Most of your code doesn't care that the database failed with a PG-300292-AB error. What you do care about is that there is a system failure. If you wrap your system failure, and document it as THE exception, the API caller will know what to look for.

In general there really are only a few exceptions: system, invalid input, non found. I'm probably missing one or two others. Most exceptions/errors are one of these. If you make these 3-5 exceptions/errors explicit, you're APIs will be pretty clear. A function can pretty much always returns a system exception (bad IO, sunspots, etc.). It can also throw an invalid input exception. Your client can handle these cases differently. For a HTTP RPC service, you return a 500 for system and 400 or 422 in the example. Not bad.

One can even wrap the exception with a more detailed message (which I know you said you didn't like) that preserves flow. So you get a invalid input exception, you can add details around the null value by properly nesting the messages. They should show up in your logs.

Re: Code Design Decision – Always throw custom exceptions

#13
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…

I think there are two kinds of exceptions in languages that have them as their error handling mechanism.

1. Exceptions you expect your consumers to handle.

2. Exceptions you don't expect your consumers to handle.

The first one I would argue you should wrap third party exceptions. There is in nearly every case important context in your code that the thrower of the third party exception will not know and whoever is reading or handling the exception will want to know.

The second one should do whatever the equivalent of crashing is for your use case. Either exiting the program hard or bubbling to some top level handler where a "Something weird and unexpected happened and we can't continue whatever action was going on" alert or log get's recorded and the activity is terminated.

If something is throwing an exception and you don't know it can be throwing an exception it probably belongs in category 2. You may over time transition it to category 1 when you figure out that it is actually handleable.

In my experience though if you aren't disciplined then every exception ends up being lumped into category 2 whether it should be or not. Any language that helps you force the categorization gets bonus points from me.

Re: Code Design Decision – Always throw custom exceptions

#14
post #13
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…

I think there are two kinds of exceptions in languages that have them as their error handling mechanism. 1. Exceptions you expect your consumers to handle. 2. Exceptions you don't expect your consumers to handle. The first one I would argue you should wrap third party exceptions. There is in nearly every case important context in your code that the thrower of the third party exception will not know and whoever is rea…

This is one of the problems with checked exceptions. The library author is in no position to expect me to handle an exception. Whether or not I can relies on the design of my system, which they have no window into.

Re: Code Design Decision – Always throw custom exceptions

#16
This idea can also be explored in the Go programming language. Go has an error type, not exceptions, but error checking famously can be rather verbose.

Two cases to consider come to mind. First, the common pattern

    result, err := SomeFunc()
    if err != nil {
            return err
    }
Here the code is just passing along the error to the caller, unchanged.

Second, signaling errors ab initio

    result := // some calculation or behavior
    if result != expected
    return fmt.Errorf("An error happened. Expected %v, go %v", expected, result)
In the first case, the discussions around whether or not to throw custom exceptions applies analogously: should you wrap the error or not?

The second case, I argue, is always wrong. The error is "stringly typed", and can be examined and read by a person, but that's it. The correct way is to define an error type meaningful for the context. Errors in Go are type implementing the error interface

    type error interface {
            Error() string
    }
therefore, an error should be a type relevant or the context. A useful starting point looks something like

    type DomainError struct {
            Code DomainErrorCode
            Message string
            Details []DomainType
    }

    func (d DomainError) Error() string {
           return Message
    }
then code can look like

    // some work
    if result != expected {
            return DomainError {
                Status: AnErrorCode
                Message: fmt.Sprintf("Error %v. Expected %v, go %v", AnErrorCode expected, result)
                Details: []DomainType{ expected, result }
            }
    }
And the caller gets back a type providing useful information.

Re: Code Design Decision – Always throw custom exceptions

#17
post #13

Earlier quoted context omitted.

I think there are two kinds of exceptions in languages that have them as their error handling mechanism. 1. Exceptions you expect your consumers to handle. 2. Exceptions you don't expect your consumers to handle. The first one I would argue you should wrap third party exceptions. There is in nearly every case important context in your code that the thrower of the third party exception will not know and whoever is rea…

This is one of the problems with checked exceptions. The library author is in no position to expect me to handle an exception. Whether or not I can relies on the design of my system, which they have no window into.

And 99.9% of the time the client is just going to catch generic Exception and doesn't care what the type is. Rollback a transaction, return 500 or display an error dialog. Done. It doesn't matter what the client library thinks.

Re: Code Design Decision – Always throw custom exceptions

#18
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…

"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"

Yes you should wrap ALL third party exceptions for the reasons given in the post. There is no 'just in case' reason. Any third party exception returned may cause your client to be dependent on it.

Re: Code Design Decision – Always throw custom exceptions

#19
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…

4. When you catch the third party exception you typically throw away the stack trace which can make it harder to debug.

Re: Code Design Decision – Always throw custom exceptions

#20
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…

> You don't know which exceptions will be raised in advance.

> Most of exceptions are unrecoverable (that's why they are called exceptions), the best course of action is to crash, which happens by default.

I prefer to propagate such unknown exceptions to a top-level catch to clanly log that something happened.

I usually have two types of exceptions: the ones I expect at some point (a HTTP call failing for some reason) that I may ("when I have time") group in "known exceptions we should not worry about" and log them as "informational", and exceptions I did not anticipate that I want to log as well, but with a critical level because they were, well, unexpected.

So crashing right when they happen may not be the best strategy.

Post reply on HN