
Errors – Gotta Catch 'Em All - eaguyhn
https://blog.twitter.com/engineering/en_us/topics/insights/2019/gotta-catch--em-all.html
======
victorstanciu
Exceptions are clearly superior to error codes in most cases. Easier to
maintain, easier to handle, and you can attach much more contextual
information to them.

Shameless plug for my open source PHP error handler:
[https://github.com/slashtrace/slashtrace](https://github.com/slashtrace/slashtrace).
I use it as a catch-all mechanism that displays detailed information about the
error when running in a development environment, but sends the data to an
external service (Sentry in my case) when in production.

Obviously there are a ton of places in the code where you want to catch and
handle exceptions yourself, but having a catch-all acts as a safety net.

I do like their approach of logging stats for exceptions that don't represent
system errors, but which they monitor anyway (like the
BadClientRequestException).

~~~
enriquto
> Exceptions are clearly superior to error codes in most cases.

I disagree with this, on a very strong and deep sense. When you program, there
are no exceptional cases. All possible behaviors should be treated on equal
footing, using the control structures of your language. When you try to open a
file, either you can or you cannot, and none of these two cases is exceptional
in any way.

I would rather say that a programming language that supports exceptions is
broken by design. If the language even _forces_ you to use the exceptions,
then it leads inevitably to spaghetti code.

~~~
rcfox
Say you have library that takes a filename and returns the contents after some
kind of decoding. If the file is inaccessible (doesn't exist, in use, user
doesn't have permission), you don't want the library to take any real action
on that. It shouldn't display a message, attempt to correct the filename or
retry the same file. It should just let the caller know that an error
occurred, and ideally what that error was.

Okay, so we'll return early with an error code and probably some kind of nil
result. (Because we're not using a system that's so archaic that it can only
return one value!)

But the caller of the library was some middleware that also can't be expected
to handle the error usefully. It returns the same error and result to its own
caller. And let's say there are a few layers like this until we finally get to
the top layer where we can translate that error code into a big, red message
for the user saying "file not found".

Now how is that any different from raising an exception? (Other than the extra
'if error: return error, result' lines you need.)

~~~
kaoD
> Now how is that any different from raising an exception?

Exception bubbling is implicit.

There's no way to know which exceptions can be thrown from a function just by
looking at its code, nor looking at the called-functions code. You have to
keep in your head all the call chain up to the first function that does not
call any other function (good luck with that).

Even worse: we don't even know _where_ the function can stop running and start
unwinding. This leads to defensive programming. Hello try-catch soup!

It's a slippery slope that gets worse and worse with each abstraction layer.

Languages with typed errors have been nothing but a joy for me.

~~~
wvenable
> There's no way to know which exceptions can be thrown from a function just
> by looking at its code, nor looking at the called-functions code.

It's good you don't actually need to know that! And if you did, it wouldn't
matter anyway because somebody could come along in the future and change that
code that currently uses a database and change it a web service and all that
knowledge would be wasted.

All you need to know is what exceptions you can handle and where you handle
them. What error codes or exceptions each individual function everywhere up
the call chain can raise is almost inconsequential.

~~~
kaoD
> All you need to know is what exceptions you can handle

Ah, so that's "all" I need to know!

And that's what typed errors do.

~~~
wvenable
You might only be able to handle restartable network exceptions. In my case,
that might be a single base type. So I put a try block in where I can handle
that exception (where my network code starts) and I'm done.

Do I need to check the return type of the thousands of methods that might make
up that operation? Nope. Typed errors make me do a lot of work passing stuff
around and declaring types that literally don't matter. If somebody adds a new
method that does another network call, I'm still good and I didn't have to do
anything.

~~~
kaoD
You like implicit. I don't.

I don't think we can discuss deeper than that.

~~~
wvenable
Actually, for the most part I don't like implicit. But I also like
encapsulation and polymorphism and you can't have those things if you are
exposing implementation details all over the place.

The other thing I don't like is pointless boilerplate code.

But I do like strongly-typed languages and originally I thought I'd like
checked exceptions before realizing just how wrong that concept is. It's not
hard to see the parallels between checked exceptions and error returns.

------
vp8989
The blog post is proposing exceptions in the context of an MVC style app where
the code that throws the exceptions is part of a request pipeline that also
has a global exception handler. This handler would be capable of logging and
translating the typed exception to some acceptable output to the consumer.

It seems most people arguing against this clearly didn't read the post or they
don't understand the context and are giving their opinions on exceptions _in
general_.

The crux of the post is to leverage the fact that your controller code (in
typical modern MVC frameworks) doesn't run in a vacuum, to reduce a lot of
boilerplate/repetion around error handling.

------
inlined
One subtlety is that you had better watch the bad client request rate during
deploy. I’ve seen SLOs completely blown and swept under the rug (accidentally)
because infra bugs caused “user” errors.

------
makecheck
I rarely observe developers investing in _forcing_ the error conditions that
they’re supposedly handling. Without that mechanism, your error “handling”
might be code that looks good and does nothing (or worse, the wrong thing).

For example, in this case: the claim that error handling is
“centralized”...how can you be sure? It is generally trivial to locally ignore
or capture errors that may not propagate even if they should. Dependencies may
not be well-behaved. If you cannot _force_ (or fake) key conditions that
“should” cause a component to fail, you don’t really know how that error
affects the system or if it can even be seen by the “centralized” handler. One
of the issues with exception handling is that programmers may think
synthesizing each exception “tests” the condition, when you may not even be
able to prove that the real error _will_ lead to the expected exception. You
have to be able to simulate or force the bad behaviors themselves, independent
from your exception hierarchy.

I’m also skeptical that having local error code “disappear” is a net win. When
debugging, you’d probably have to add it all back to know how the heck an
error might have originated from that local code. At the very least, local
code to _reveal_ errors should never go away.

