
Exceptions should be Exceptional (2016) - kogir
http://mattwarren.org/2016/12/20/Why-Exceptions-should-be-Exceptional/
======
pingyong
When your main argument for not using exceptions for every kind of error
handling is performance instead of architecture, then maybe it is a sign that
exceptions should simply be constructed to be more performant instead of
giving people advice that isn't and can't ever be consistent.

As the writer of a library (or just other utility functions), you simply
cannot know whether something is an exceptional situation or not, because that
is a property of the program and the environment, not the function. Being
unable to parse a string to a number, or to create a socket, or to even
allocate memory can all be exceptional or not exceptional depending on the
context, and often you simply do not know the context this code will run in,
or the code will actually run in multiple directly contradictory contexts.

~~~
dgellow
As a library writer you can let the caller the responsibility to determine if
something is worth throwing an exception for or not.

1\. return an error code

2\. if the caller consider the issue to require an exception, they will do it
themselves

You can also have entrypoints to the library that will throw exceptions, and
other versions that won't, and let the caller decide what they want to use.

Though of course that's more work.

Regarding the point of performances, that makes me think of
[https://news.ycombinator.com/item?id=22483028](https://news.ycombinator.com/item?id=22483028),
discussing "Low-cost Deterministic C++ Exceptions for Embedded Systems". It
would definitely be very interesting to see what is now possible if exceptions
are cheaper.

~~~
pingyong
The problem with doing that as a library writer is that exceptions, used well,
generally change the entire interface and to some extent programming model. It
starts with simple things like returning values instead of an error code (and
setting value by reference) and goes through having to make "factory"
functions for classes (if you want to use error codes) and ends with all sorts
of considerations like copy constructors in C++.

So in many cases, if you don't want to compromise on design and architecture,
you essentially need to write the entire library twice, or at least the entire
interface. Not really a great state.

>discussing "Low-cost Deterministic C++ Exceptions for Embedded Systems"

This exact reason is why I think that paper is great and also how exceptions
should have been implemented in C++ in the first place.

------
wwweston
An exceptional condition is arguably anything that means some call in your
program can't do its job.

Passing error conditions up the call stack to the control layer that displays
and/or logs a relevant message is a pain.

Exceptions make it less painful.

 _Catch_ blocks should be used sparingly, sure. If you're using them close to
where the error is thrown then maybe you should consider an error code-
oriented approach. But catch blocks in controller code (or better yet, meta-
controller code)? Sure.

~~~
cmroanirgo
The problem is that one person might think an exception is acceptable for
OpenFile, whereas another thinks it's not. It's this ambiguity that makes for
all sorts of conflicting ideas and recommendations.

Fun Fact: the Windows API uses HRESULT to return success/failure of a
particular call. The COM API uses the same. VB6 was the first platform by
Microsoft to implement exceptions throughout the code (that I'm aware of)...
and beneath it, it used COM. So that means that VB6 generated an exception
whenever a COM status code returned E_FAIL or similar, and didn't if it was
classed as SUCCESS (eg S_OK, S_FALSE, etc).

The original .NET implementors clearly used the VB6 model when developing the
codebase (it was nearly a one-to-one match from VB6 to VB.NET) and very easy
to port over.

That said, when I read the article, "Exceptions are the primary means of
reporting errors in Frameworks" vs "Do not use exceptions for the normal flow
of control, if possible" I see this never ending problem appear. Most things
we do in our programs use Frameworks, which throw exceptions, which may or may
not actually be _exceptional_ for the case of the calling function.
Nevertheless, it must be handled.

However, back in the C++ Win API and COM world, you could easily use: `HRESULT
hr= SomeFunc(); if (FAILED(hr)) return hr;` to bubble up a failure code, or if
you handled it in other ways, you can safely ignore the return code and
continue on. In the .NET world exceptions must always be handled otherwise
they ripple all the way up and end up as a crashed application. Of course,
some think this is a good design principle to have it so: Others are not so
convinced.

And so, the debate continues.

~~~
acqq
Exactly. I am certainly in the "exceptions should be _really_ exceptional"
camp and therefore I didn't "agree" with Python's open function:

[https://docs.python.org/3/library/functions.html#open](https://docs.python.org/3/library/functions.html#open)

"Open file and return a corresponding file object. If the file cannot be
opened, an OSError is raised."

in the sense that in a program that is not only a few lines, I would prefer
such kinds of library functions not to produce exceptions, thank you very
much, so I had to wrap all such. But it is not optimal not being able to
directly use language primitives as much as possible.

On another side, writing really short programs gets to be faster: if I write
only a few lines, having an "implicit" error handling shortens the code.

And that's what I think is the main reason for many ills and misunderstanding
in many popular languages: the practices that look good for small examples (or
in the books) aren't the same as those which work well for large projects.
Python's not detecting the typos in the variable names in compile time is also
a typical bias towards the short programs.

For large projects detecting as much at compile time and having only very
exceptional exceptions and unsurprising control flow (visible reading ifs, and
with a guarantee that there can't be "landings from anywhere" like in
exceptions) is of huge advantage.

~~~
BiteCode_dev
Exceptions when opening a file has been chosen because they have the nice
propery of encouraging a race condition free EAFP rather than a hard to get
right LBYL.

It also fits nicely into the context manager pattern.

Besides, remember exceptions are cheap in python. So cheap that for loops are
basically syntaxic sugar for a try/catch.

~~~
LessDmesg
LBYL can easily be enforced by Maybe types, leaving exceptions for really
exceptional things (which a nonexisting file definitely isn't). So we see how
not having a real type system hurts and deforms a language in ways unrelated
to types.

~~~
Nullabillity
That's still EAFP (you didn't check the precondition before trying), just
through a different mechanism.

------
dkarl
"Exceptional" has no reasonable objective definition, especially not at the
library level. When you try to open a file by name and it doesn't exist, is
that exceptional? It depends on context. Whether something is "predictable and
intentional" can even change with product evolution. It's a recipe for
bikeshedding. This is a crazy criterion to try to make coding decisions by.

Different applications need different trade-offs, and not every API will be
suitable for all users. Standard libraries will make decisions according to
what kind of applications the language targets. Libraries making other trade-
offs can be written to cater to people with other needs.

I get that some people are repeatedly encountering situations where they feel
like exceptions are used in ways that reduce readability and performance, and
they feel like this is a rule that could improve the situation by reducing the
use of exceptions across the board, but please, apply your usual criteria of
clarity, readability, and performance! If you believe that exceptions are the
clearest way of expressing a certain bit of logic in your language and
codebase, and that they are acceptably performant, don't get distracted from
that by bottomless, ultimately insubstantial arguments about what it means to
be "exceptional."

Edit: Just wanted to add that I really enjoy the more expressive type systems
that make it ergonomic to use return values in a lot of situations where
exceptions are otherwise the most readable choice.

------
dgellow
Maybe the issue is that "exception" is a really confusing name. Go has the
keyword "panic" for errors that stop the current flow and unfold the stack,
that makes it clear that you shouldn't use them for anything else than
exceptional situations, and they can be "recovered" if they have to be handled
explicitely.

[https://blog.golang.org/defer-panic-and-
recover](https://blog.golang.org/defer-panic-and-recover)

~~~
7777fps
From my (admittedly limited) experience, Go has the worst story for error
handling, where functions end up repeatedly checking for error return
conditions from called functions.

It feels trapped between "We don't want exceptions" without going full
"Result<T> or Maybe<T>" functional style that might be more typical of
something like elixir.

But I didn't try Go for long and that itself was years ago, so perhaps modern
Go has better paradigms.

~~~
dgellow
I'm just mentioning the naming, not the general approach to error handling.
Panic are roughly similar to what an "exceptional exception" is (stop control
flow, unfold stack until being handled), but the naming is slightly better
IMHO.

I personally like Go's approach, but that's not for everybody (I'm also quite
fond of the monadic approach though).

------
danjc
Relatedly, I'm a strong proponent of debugging with "break on all exceptions"
enabled. It's very easy to miss bugs if you're not aware of exceptions being
thrown but for this to be practical the code must only throw exceptions
exceptionally or it's a nightmare to debug.

------
speedplane
Fact 1: There are always bugs or unforeseen events in software.

Fact 2: When there is an unforeseen event, the developer did not consider what
to do.

Fact 3: Developers often deal with other people's buggy code.

It's clear that exceptions aren't the perfect solution to anything, but given
these 3 facts, exceptions are around to stay.

------
leggomylibro
I wonder why callbacks aren't a more popular way for languages to handle
exceptions. What if each type of exception registered a weak link to a
function with a name like `IOExceptionHandler` which prints a noisy error if
it isn't overridden with other behavior?

I guess it would be harder to use a single exception callback if you had a lot
of similar "try/catch" blocks in different areas of your code, and you'd still
probably need to collect some sort of stack trace, but it might work out
alright if you go with this article's advice and recommend that people keep
exceptions out of their program's normal control flow.

I dunno, maybe it's a dumb idea and I've just been spending too much time with
microcontrollers lately. They use interrupt callbacks for all kinds of
hardware triggers and software events, and I kind of like how you can
compartmentalize error handling into a few predictable event handlers.

That Java stack trace image at the end of the article also brings back some
memories...but I'm feeling much better these days.

~~~
lopsidedBrain
That is sounds more appropriate for either terminating the program or
resumable exceptions like division by zero. In languages with a try-catch
construct (specially C++), the complexity of implementing exceptions has to do
with stack unwinding. This is where you don't want to restart in an outer
context, but want to guarantee cleanup for the call frames you are abandoning.

~~~
leggomylibro
Hm. Good point, I guess you couldn't easily clean up things like local memory
allocations from a common exception handler, huh?

Oh well, they can't all be winners.

------
zorbash
For Ruby there's this book, by the exceptional Avdi Grimm
[https://pragprog.com/book/ager/exceptional-
ruby](https://pragprog.com/book/ager/exceptional-ruby) it's a fantastic
resource on the subject.

------
coding123
> So exceptions should be exceptional, unusual or rare, much like a asteroid
> strike!!

That's a super huge misread of the text recommendation.

I watched a live presentation directly from Jeffrey Richter at the bay area
.net user group, oh god about 15 years ago when he described how he came up
with this text. In fact it was a call to use exceptions a lot more than we
used to, and to avoid error codes. So no, exceptions are not supposed to be
rare like that.

His main point was to throw if the method name was not achievable, except in
specific situations where that breaks user expectations too much, like getting
null from missing map entries.

------
harperlee
Anecdotally one of my stumbling blocks with clojure was how to deal with
errors. The language style heavily pushes to just coding the happy path and
using nil punning, instead of exceptions, but sometimes you need to signal
errors, so some libraries use keywords for error codes - but now that we have
spec the community seems to be shifting opinion to “exceptions are ok-ish if a
result of not conforming”. Both error codes and nils make spec definitions
ugly, thus all the “maybe not” opinion by Rick... it seems someehat
overwhelming, to be honest.

------
scotth
Don't .NET dictionaries throw when a requested key isn't found?

~~~
SigmundA
Yes and probably make the most sense as .Net has no "undefined" value. If the
dictionary has a the key with a null value it returns null, but no key throws.

With generic dictionaries (which everyone uses now days) you have TryGetValue
which is a little weird since it uses a out parameter but pretty natural after
c# 7 inline out params.

~~~
7777fps
I often think it's a shame there isn't a "GetOrDefault" for dictionaries.

An extension like

> dict<k,v>.GetOrDefault(k key,v valueIfNotPresent)

Would be really helpful. It's easy enough to write your own extension to do
this, but something built in would be the kind of nice to have method that has
been creeping into .net.

~~~
SigmundA
Yes I agree, ImmutableDictionary has GetValueOrDefault, GetOrAdd is nice on
ConcurrentDictionary and FirstOrDefault is nice in Linq.

------
iamaelephant
Can't we all just agree that Maybe/Optional types are actually a great idea
that generalizes all of this at minimal cost?

~~~
taeric
No. In the same way that my directions to the mall don't include "maybe take
this turn" at all turns.

That is, in many (all?) imperative lists of steps for how to accomplish
something, I often _do not_ want to acknowledge that every step could fail.
Rather, I would like for a failure to be something I can touch and reason
with. Possibly restart from.

Why did I not take that turn? Because I need gas. Or the kids need to use the
bathroom. Doesn't matter, too much. I'm the user. Give me my options at this
point. (And as a real user of maps, also give me the option of "piss off for
10 minutes.")

(Yes, I like the condition/restart system of common lisp...)

~~~
boomlinde
_> No. In the same way that my directions to the mall don't include "maybe
take this turn" at all turns._

I think that's a really poor and simplistic comparison. You're content not
considering every possible outcome of trying to go shopping because in the
face of changes to your circumstances, you can easily come up with a new
strategy and adjust your goals on the fly.

 _> I often _do not want* to acknowledge that every step could fail.*

That's what you want, but I have often found that the demands of quality
software are different. I've seen too much software end up in strange and
unconsidered failure modes because it threw up at the wrong time.

~~~
taeric
I should say that my want there is local to the directions. For the trip, I
definitely want them handled.

So, for the function, I want to be able to say the happy path. I would also
like to be able to say what could go wrong, with advice to the user on how to
proceed.

------
virmundi
I’ve taken Go’s approach to error handling in new typescript projects. I want
to force my team into thinking about IO errors. I want failures to be part of
the domain since stack traces only kinda work in Node. We will only throw
exceptions if someone didn’t program for bulls properly. Everything else is a
Failure instance passed up the call chain. Annoying, but our testing and code-
thoughtfulness have gone up.

~~~
bitwize
Go handles it shitty, like the past five decades of computer science and PL
design never happened. For imperative languages, exceptions are perfectly fine
as a way to encode failure as part of the domain of operations. If you're in
functional fantasy land, the correct approach is to use a Maybe monad or,
better yet, an Either monad. But Go supports neither of these and that's one
of many reasons why it sucks for modern development. In TypeScript you can do
better and, for the sake of others who have to maintain your code, you
_should_ do better.

~~~
QuinnWilton
> Go handles it shitty, like the past five decades of computer science and PL
> design never happened.

This has generally been my complaint with Go. Go is... fine. It offers a lot
of features that seem revolutionary if you've never seen them before, but
mostly feel like someone took the best parts of every language, and then
implemented the least interesting aspects of those features.

\- Interfaces are like typeclasses, but without actually offering any sort of
higher-kinded polymorphism.

\- Goroutines are kind of like Erlang processes, but without any notion of
supervision and with shared mutable state.

\- The error handling feels like it's trying to emulate an Either monad, but
in a way that doesn't actually allow for the compositional properties that
make an Either monad useful.

I know that Go's goal was never to innovate on language design, but I feel
like it ignored a lot of low-hanging fruit in modern language design.

Obviously you can do worse than coding in Go, but for everything Go is good
at, there's likely an even better tool for the job. I'm not convinced that
there's any domain where Go is the best choice available, but it's definitely
average at most jobs, and maybe that's where it finds its niche.

Edited: I had missed the word "higher-kinded" in front of polymorphism. Thanks
donatj!

~~~
donatj
> Interfaces are like typeclasses, but without actually offering any sort of
> polymorphism

Can you explain what you mean by this? I certainly use them in a manner I
would consider polymorphic.

~~~
QuinnWilton
You're completely right; I accidentally dropped the word "higher-kinded" in
front of polymorphism.

Go's interfaces provide structural typing, which offers polymorphism over
values. Typeclasses in comparison, offer polymorphism over types themselves.
The distinction is subtle, but it drastically limits the sorts of abstractions
that you can write with Go. In Go's defence, this is a limitation that even
functional languages like F# and Elm face too. This sort of polymorphism is
what makes ideas like monads and functors so powerful in a language like
Haskell, and what causes those same ideas to lose some of their lustre in a
less expressive language.

Unfortunately it's really difficult to find good articles on higher-kinded
types, since anything written about them already assumes a ton of knowledge
about the subject. This StackOverflow thread has some good insight though:
[https://stackoverflow.com/questions/21170493/when-are-
higher...](https://stackoverflow.com/questions/21170493/when-are-higher-
kinded-types-useful)

It's entirely likely that in the real world these sorts of limitations don't
actually matter, but my general frustration with Go stems from the fact that
it's so close to being so much better than it is, and it saddens me that we as
an industry chose to be so excited by something that's a tiny step forward,
when there exist so many other options that are entire leaps and bounds
forward.

~~~
smabie
Yeah, HKT is a godsend. It surprises me that Scala, one of the few languages
that actually supports it, doesn't really use monads and all that good stuff
in the base language. Like the fact that Scala uses duck-typing for the 'for'
construct (it just looks for map, flatMap, filter) has always seemed odd.

