240 comments

[ 64.8 ms ] story [ 3549 ms ] thread
I love using exceptions as control-flow, Python makes it very easy, and it's really helpful.

edit: grammer

Many of the problems that people who eschew exceptions are trying to avoid or fix, Python simply shrugs and declares isn't a problem. If the software you're writing agrees with that assertion (which it probably does if you're writing Python with no static type annotations and no pass through mypy or a similar tool to verify those type annotations are sound), you're fine.

Unfortunately, when you do eventually find yourself in a place where you have to care (which is the fate of any codebase that becomes large or complex enough), Python actively hinders making reliable code that is easy for strangers to modify without introducing subtle bugs.

Can you please elaborate on those problems ? Personally I believe exceptions are the worst way to handle errors, except for all other ways.
Python's soft static type rules and extremely flexible variable instantiation unfortunately turns every line of code into a potential runtime error, because the code can't know at compile time if a variable was introduced to the global context that could bind to any given name. Typos are therefore deadly at runtime in Python in a way that they fundamentally aren't in other languages; if you try to write

foo = 0

if foh == 1: # oh no, I misspelled the variable

... many languages (including F#) will fail to compile the program because 'foh is uninitialized.' Python can't know if 'foh' is intended to be a global variable and so will execute the program and only determine while evaluating that line that 'foh' doesn't exist. This is especially insidious in error handling, where the error codepath isn't necessarily exercised; essentially, Python's flexibility implies that if your unit tests don't have 100% line coverage, you can't even know if your program is basically devoid of simple variable typos naming never-existing variables (a check most languages give you for free).

In a language with that feature, a rich ecosystem of exceptions is almost necessary, because every line of code could hide a runtime exception!

Maybe I am missing something but I think every dynamic typing system has this problem. I can't see what this has to do with Python and exceptions in general. It is not like there aren't statically typed languages with exceptions. This comment is an excellent critique of dynamic typed systems but it has nothing to do with exceptions.
The top-thread post was referencing Python, so I'm speaking in the Python domain specifically. Other dynamic languages have this problem also, and other languages with stronger static type guarantees do support exceptions.

I haven't encountered a language with dynamic typing of the sort Python has that doesn't also have a robust runtime exception system, and it'd be interesting to see what that looks like. There's probably some old flavors of BASIC that fit that mold (i.e. variable declaration is not required and also the only thing it offers for exception handling is setting a label to GOTO if a runtime exception occurs).

I guess I was too fixated on the exceptions part of your comment. You are right that due to Python's dynamic nature every line can turn into a runtime exception. Maybe it's because English is my second language but I couldn't understand that in your first comment.
This has been my experience as well.

I often fall into the "Python trap" (you'd think I would have learned by now!): "this is a tiny project, almost a script, and it's so much nicer and faster to code it in Python. And since it's so small, who cares about static typing? Surely this won't grow larger". A couple of months later: "oh, no!" [1]

[1] If you know the webcomic "webcomic name" by Alex Norris, it fits perfectly here.

I also use exceptions for control-flow and I feel no shame about it. It's often the best (most concise, most performant, most understandable) solution to a problem, in my eyes.
Most concise: absolutely. Most performant: never. Most understandable: yes, but only if you are cognizant of which call paths can result in an exception and which can’t (i.e. until your code base becomes too large or you’re not the one that wrote it).
> Most performant: never.

I disagree. An exception can be faster than manually unwinding a set of nested scopes.

Most exceptions, at least in the JVM where this is commonly debated, will incur a penalty of building up the exception and unwinding the call stack. What aspect of performance are you using as an example?
An exception thrown and caught within a compilation unit is effectively a goto. It can translate into nothing more than a jcc instruction, or even nothing at all if it's unconditional. This can be higher performance in practice than threading through a set of Either-style return objects.
Throwing exceptions may be the most concise, but handling them rarely is.

But most importantly, static type systems lose the ability to infer what's coming so these may turn out to be really pesky problems.

You are probably looking for continuations. For many languages exceptions are the nearest approximation.

Personally I have no problem with your approach on the semantic level, but most compiler/interpreter developers consider all exceptional paths as cold as it gets: performance can be "interesting" if you take a lot of them.

Exceptions for control flow is bad, IF that control flow is an expected business rule.

Exceptions shoudl be exceptions. And python programmers often write a whole exception class model to define all business logic rules... Really use Enum for that!

Having worked in Twisted's framework, I utterly despise exceptions as control flow. One service I work in is 140k lines of Twisted python (aptly named). I can't wait for us to finally kill it and never deal with Twisted again.
(comment deleted)
For example, in a web service I maintain, we maintain an internal exception hierarchy like:

  class OurExceptions:
      http_status_code = 500

  class DatabaseRecordNotFoundError:
      http_status_code = 404
Database queries are all written like:

  rows = db.select(...)
  if not rows:
      raise DatabaseRecordNotFoundError
and the top-level Flask error handler has code like:

  try:
      return call_view()
  except OurExceptions as exc:
      return response(status_code=exc.http_status_code)
This is grossly oversimplified, but you get the idea. So, this means that we can write views like:

  def some_view(object_id):
      obj = fetch_obj_from_db(object_id)
      return {"found": obj.name}
If the object isn't found, the caller gets a 404 response without the person writing the view having to do a single thing. However, they can still handle the problem themselves if they really want to:

  def another_view(object_id):
      try:
          obj = fetch_obj_from_db(object_id)
      except DatabaseRecordNotFoundError:
          return {"error": "Not found. Try again later?"}, 404
      return {"found": obj.name}
I absolutely love this coding style because exceptions are still being handled everywhere, but don't have to be explicitly dealt with deep inside a nested call stack. That lets us write very uncluttered, testable view code like:

  def change_password(userid, oldpass, newpass):
      # This raises an exception if the user can't be found
      user = get_user_by_id(userid)

      # This raises an exception if the old password is wrong
      verify_password(user, oldpass)

      # This raises an exception if the DB couldn't be updated,
      # perhaps because of a race condition with another request
      update_password(user, newpass)

      # By the time we get to this line, everything above has to have
      # succeeded, with zero manual error checking inside this view
      return {"result": "Password successfully updated."}
When I moved to Rust the constant error wrapping or converting annoyed me. But after using it for a couple of years it turned out to be a huge blessing. Quite often I need to know exactly which error messages will be thrown so that I can do things like internationalization. While exceptions are quite convenient for prototyping for production I’m now firmly in the typed errors camp.
Agreed. I nowadays consider code that might throw any type of exception to be inherently unsafe code; if you don't know what can go wrong, how do you have any idea whether you've addressed all reasonable failure modes?
It's not clear if you are continuing to refer to Rust as your parent post did, but if so, note that this is not the standard Rust meaning for "unsafe". Using it in such a way makes it more complicated to discuss and explain the Rust-specific meaning.
Thank you for the clarification; I was using 'unsafe' colloquially and not in the Rust sense.
If Rust devs took an everyday English word and gave it a different meaning, isn't it their fault when they have to train everyone into discarding their intuitions?

It sounds like it means “code that is unsafe for a particular reason”; name the reason and leave developers capable of criticising code that is unsafe for other reasons, too.

Unchecked Exception throwing code and stringly typed code are both unsafe. The world is better if we can call spades spades, instead of pitchfork style digging implements, because you want to protect playing card manufacturers.

> If Rust devs took an everyday English word and gave it a different meaning

Perhaps, but if we had come up with a new word, you or someone else would complain that we had done that instead of using a word that people already "understood". :-)

English is a language full of elision, and there's nothing inherently wrong with using one word for closely related meanings. When being precise, Rust uses the term "memory unsafety", but we humans usually shorten that to just "unsafety" because we are lazy.

My point was merely to state that a function throwing an exception [1] doesn't introduce memory unsafety.

---

[1] Notably, we call this "panicking" in Rust-speak; does the fact that we call it something different from "exceptions" confuse people? Probably at least one.

We call it panicking because there's no assumption that you're always able to catch panics. Panicking is an AAAH EVERYTHING WENT WRONG kind of thing, which might trigger an immediate abort if the program is compiled in super-ultra-speedy-make-debugging-really-hard mode; if your code panics, it's always the programmer's fault.

The language provides a way of kinda-sorta recovering from an everything-goes-wrong situation only to be nice to you, and not because that's a thing that's generally possible. You can't always recover from a segfault, or a buffer overrun, or the rest of the kind of things that cause panicking, even if you know that they're theoretically possible in advance, because if you could you would've fixed them already. The best you can do is make your web server return a 500, send logs, ditch its cache, restart in a sane state and hope for the best. You're not recovering that half-finished task, because the programmer can't reason about the program state in a situation where their code has been shown to be wrong.

Panics are only for exceptional failure cases. If this confuses people, it's (probably) because they only thought they understood; they needed confusing.

All of that having been said...

If you're talking about Rust, I can't comment directly because I haven't used Rust, but I've totally used Go's panic / recover to implement "return" and "break loop" in a language interpreter.

I should really rewrite that thing to do something more sane like bundle up a continuation state to return to, but I was feeling lazy. ;)

It depends on the problem domain.

If you've got very tightly controlled inputs, it's not unreasonable to expect tightly-bound results (like the last example in the article).

If you're writing intermediary code between unsafe inputs and other libraries, there might be a reasonable boundary to catch all unknown errors and wrap as a failure.

In PhotoStructure's case, it's an easy solution: I simply don't import the given file. There may be a similar reasonable boundary in your problem space.

Agreed. And sometimes, the right solution is to assume inherently unsafe code and do one's best to guarantee safe behavior anyway. Operating systems and web browser tab isolation, for example, operate under this principle (as do many web servers that use multithreading to support multiple requests in the same process, so that one bad request doesn't deny service to N hundred completely unrelated user sessions).

... but if one is doing that, one should do it very explicitly. I prefer a codebase that separates regular flow of operation from the "catch-all" or "panic-recover" loops that indicate "The buck stops here for catastrophic failure."

I don't know Rust at all but what you're talking about sounds equivalent to (much maligned) Java's checked exceptions system?

I never understood the hatred checked exceptions received especially from the younger crowd. I still write Java at work and I still use checked exceptions whenever they indicate an error condition that must not be ignored by the client code. Many new to the project developers hate me for it initially because they can't just "roll out a feature" without being forced to take care of the corner cases but over time they love the discipline that's imposed on them by checked exceptions.

> I don't know Rust but it sounds like equivalent to much maligned Java's checked exceptions?

Ergonomics are important. Chaining calls to functions that return a monadic Result/Either type is easier and neater.

(comment deleted)
The problem with checked exceptions as implemented in Java is that not all exceptions are checked, but all error handling uses the same mechanism.

Midori nicely separated panics (programmer or system errors) from exceptions. Panics _could not be caught_ - they crashed the process. (Processes were therefore really cheap in Midori). Java, on the other hand, put common programmer errors into the exception category in order to make it easier to work with common code (e. g. `IndexOutOfBounds` should be checked or impossible, but arrays are so common, they decided not to require it and they needed to be backwards compatible so they couldn't make array access return a `Result`-style object).

That said, using checked exceptions well is a great aid to making the code base understandable!

Agreed that the java runtime has made some high visibility yet nonsensical decisions on when to use checked vs unchecked exceptions. I'd even advocate for doing away with the latter though the recent trend has been towards abandoning the former.

I advocate for junior devs to start with the assumption that a new exception should be checked and only consider making it unchecked in specific circumstances.

I haven't written very much Java, but here are some differences between Java checked exceptions and Rust errors as I understand them:

In Java, a function may throw a long list of checked exceptions, and these lists tend to grow to inconvenient sizes in larger programs. For example, if foo() calls bar() and baz(), which each throw 3 different exception types, then now foo() might throw 6 different exception types. In contrast in Rust, each function can only return 1 error type. If a function needs to represent several different types of internal errors, then the crate that it's in needs to define an error enum type with a variant for each of those. (Either that, or the function can use a generic wrapper error that can contain anything. This is less common in library code but pretty common in application code.) This shifts a lot of work from library callers to library authors, which is a good thing.

Someone with more Java experience will need to correct me here: I think it's fairly common to bulldoze all that complexity by declaring a function that just "throws Exception". That saves you from writing out N different types (and more importantly, from changing every transitive caller when a low level library introduces a new exception type). But it kind of defeats the purpose of checked exceptions, by throwing away all the info they provide. It's a shame that you have this "all or nothing" choice when it comes to exceptions and how much complexity you want to deal with. In contrast in Rust, wrapper errors can define automatic "From" conversions from the lower level error types they wrap, and the standard `?` operator automatically applies those conversions. That means that in many cases, a low level library adding a new error variant might not require any changes in its callers at all. The new information is there for callers who want to look for it, but existing abstractions around the error type generally just keep working.

This is kind of true but not quite. If you don't want to deal with any exceptions in your function you can declare that it just throws java.lang.Exception or put a verbose list of exception types in your method signature.

On the other hand, you're also free to catch and process some and only propagate a subset outside of the function or even wrap the ones you want to process and throw a different exception type from within your function that wraps the existing ones. It's not mandated but pretty idiomatic in Java that any Exception type you define should be able to accept a different exception as its 'cause' in your exception's constructor signature.

I don't think this is a real difference. You can make the exact same API design mistakes regardless of which error delivery mechanism you use.

The important thing is for the API designer to think about the abstractions, i.e which types of errors should be part of the API and which errors are just implementation details that may change.

If the API designer is too lazy to put enough thought into that then the result will always be what you are ascribing to Java.

There is always an onus on an API designer creating an API to express it well. However I think the difference between good vs bad language (and a good vs bad API come to think of it) is that it makes doing the right thing easy (lazy), and the wrong thing hard.

I think about this when I think about GraphQL. I like GraphQL, I really do, but GraphQL's N+1 problem is why I don't recommend it. The easy thing is to hammer the heck out of your database, the hard things is to parse the GraphQL request and correctly transform it into a SQL statement. I just don't trust everyone on the dev team to not be lazy.

I agree with the principle, but in this case it seems very easy to do the right thing in either language once you have done the hard work of designing a good API. The same goes for doing the wrong thing.
I do not know much about java, but I think that the part about automatic conversion on passing errors is not available in java.
You can also choose to stop playing the game and wrap any checked Exception in an unchecked RuntimeException. Or, if you use Lombok, you can apply the @SneakyThrows annotation to quiet the compiler. As the idea that checked exceptions were a design mistake gains mindshare, you see these tricks more and more in newer codebases.
> For example, if foo() calls bar() and baz(), which each throw 3 different exception types, then now foo() might throw 6 different exception types.

> I think it's fairly common to bulldoze all that complexity by declaring a function that just "throws Exception".

These are both solved by the same approach: foo should be wrapping those underlying exceptions under its own domain, not just passing them along. This has the added bonus that the further up the call stack you go, generally the more context you have about the operation. So you also get to set a new message that explains that context. So foo should only throw FooException, which might be wrapping an underlying BarException, which might be wrapping an underlying BazException. You get a full accounting of what happened, and how each layer interpreted it.

For example: I invoke the user preferences subsystem to give me the user's foo setting. User preferences can be in a flat file or an embedded database. If I don't wrap exceptions, now my user preference system has to expose that implementation detail by exposing both file and database exception types. Instead I should wrap both of them in a UserPreferenceLoadingException type or whatever makes sense.

> I never understood the hatred checked exceptions received especially from the younger crowd.

I worked with Java professionally for several years and in a sense I feel the same: Checked exceptions are not as bad as they are often portrayed.

At the same time they are certainly not as nice as text book examples make them seem to be. For me the biggest downside always has been that their types are part of the method signature and therefore whenever you change the exception type of one class you more often than not end up refactoring half of your other classes too.

The failure mode of a module is a function of its implementation. Beyond simple argument and state validation, interesting failure modes are usually abstraction violations.

The tension between needing to communicate the actual reason for a failure (which violates abstraction) and preserving the abstraction (which demands shoehorning error states into some kind of polymorphic receptacle) is at the heart of the problem with checked exceptions.

If you try and pass through the failure mode, then new implementations cause new failure modes, which means methods grow new exceptions, which in turn break downstream dependencies, because adding a new exception to the throws clause is a breaking change.

If you try and abstract away the differences in failure mode (e.g. with a specific module exception), then you fill the code with boilerplate wrappers, need to translate exceptions at module boundaries, and (in the worst case) invent taxonomies into which all future implementations must awkwardly categorize their failure modes.

Specific to Java, there's another bug. The type system can express sum types only for concrete exceptions and not for generics. That means that if you parameterize code with other code (e.g. with a lambda or callback), the argument code can only throw pre-defined or runtime exceptions; there's no way to declare, in the type system, the transitive set of exceptions across control flow when the set of exceptions is determined by the argument. E.g. if you have a generic map(f) method, you can't generically declare that map() throws whatever f() throws without forcing f() to only throw a single checked exception - generic type arguments can't be the sum types that Java permits in throws and catch clauses.

All this is costly. Meanwhile, in most actual applications, exception handling is extremely rare; exceptions are almost always propagated up to a top-level handler and logged. When exceptions are handled, it's usually near the leaves of the control flow graph, where the application interacts with other systems, like the network or the file system; these inherently non-deterministic failures often need explicit management. So the work to propagate the exception types throughout the control flow graph is pointless.

See, having reasonable typed exceptions is perfectly acceptable, but that's rarely been the reality I encountered. About every method in our code base has the same 3 checked exceptions in the declaration, and none of them are handled - they're even passed to service clients.

And it's just super clunky, but the devs who came up with it just refuse any suggestions to have expressive exceptions (that's what exceptions messages are for). It's impossible to actually know what could go wrong when calling any method.

Checked exceptions deserve all the hate they get. It's not a good solution.

Here's a random rant online that ends up pitching the more common solution of unchecked exceptions + exception rewrapping: https://phauer.com/2015/checked-exceptions-are-evil/. And of course, in Rust, the rewrapping is basically forced upon you making for a really nice error-handling ecosystem.

Unchecked exceptions are a mistake. If the method you call is modified to throw a new recoverable exception how will you be alerted to the issue? You won't. Your app will crash. With checked exceptions the compiler lets you know. That's much better.
> I never understood the hatred checked exceptions received especially from the younger crowd.

I'd say not "younger crowd" but rather developers who have never dealt with a workplace or situation where he/she is demanded to take error-handling (and recovery case) seriously. Different workplaces have different accountability when it comes to code-quality (including error handling and i18n).

Java is more prevalent at some time in the past and it has built an amazing libraries of good/best practices while Python/PHP/JavaScript/Ruby weren't exposed to that level of commercial engineering scale in the past so when the developers came from the latter group, they rebel a bit when it comes to exceptions. Keep in mind that Java was born to address commercial needs thus there is more accountability compare to OSS languages. Just different mindset, different goals hence leading to different design decision.

> so that I can do things like internationalization

Can you expand a bit on how you do this internationalization? I've got a low-priority issue [1] to investigate how to integrate SNAFU and Fluent to get i18n for error messages, but knowing how someone does it today would be super helpful!

[1]: https://github.com/projectfluent/fluent-rs/issues/107

Exceptions are typed in most staticly typed languages as well.
I felt the same way about Go's error handling, or lack thereof, which forces you to implement your own error handling system. But as it turns out, if you are forced to do that, you will create an error handling system that is easy to follow with zero GOTOs (as is the case with exceptions). Although it makes Go code a little more cumbersome to write, the trade-off is that when you read Go, you're never unsure as to where the "code path" is, it's very clearly laid out for you.
> Quite often I need to know exactly which error messages will be thrown so that I can do things like internationalization.

Internationalization of error message? I didn't know that was a thing!

I'm curious about that, do you have some examples I could check?

eirik's making some very good points regarding the downsides to avoiding exceptions (particularly in the notion that one loses stack trace details and if one doesn't think about how debugging will be done without them, one is in for a world of hurt).

I think I only have some significant disagreement with one observation they've made, which is around runtime errors:

"It’s by such misadventure that the working F# programmer soon realizes that any function could still potentially throw. This is an awkward realization, which has to be addressed by catching as soon as possible."

I prefer to think of runtime errors as special case, and I appreciate Go's approach here: Go lacks any exception-handling more complex than panic and recover, and considers anything that is walking the panic / recover path to be an "exception handling failed" scenario. In other words, a correct program should have no runtime exceptions, and if one happens, it should explode as messily, noisily, and identifiably as allowed (and rare is the situation where the correct answer isn't "whole process dumps stacktrace and dies").

My suggestion for the case the author points to is to not catch that exception; if you take a runtime exception you don't anticipate, let it fly. It indicates you're "holding the API wrong" and you want to know about it ASAP, not try to catch around an unexpected failure mode. For expected failure modes, results and error types are often more comprehensible (though the issue of needing to address stacktraces still exists).

> In other words, a correct program should have no runtime exceptions, and if one happens, it should explode as messily, noisily, and identifiably as allowed

This is not always true. For example if you are writing high assurance software, and the error is recoverable, you really don’t want to crash. You want to catch it, log it, and continue/recover execution.

Of course, now you need to make sure that you’re not catching an exception that is not recoverable.

> Such criticisms typically revolve around scoping considerations, exceptions-as-control-flow abuse or even the assertion that exceptions are really just a type safe version of goto.

Outside of the FP community where people care less about purity - the criticism is more that exceptions are hidden, they're all-or-nothing (either any function can throw, or no functions can, and not all code paths have runtime errors that you care about), and of course the overhead.

I also really disagree with the idea that result types have more boilerplate than exceptions. That just depends on the paradigms of the language you use, anyone can add sugar to make either easier/harder to read.

> That just depends on the paradigms of the language you use, anyone can add sugar to make either easier/harder to read.

The questionmark operator, in particular, is what sold me on the possibility that this type of programming can be concise and ergonomic.

https://doc.rust-lang.org/edition-guide/rust-2018/error-hand...

I've not used Rust before, but this looks a lot like monads! Can I map/flatmap over these or do I have to unpack them later?

If you _can_ operate on those like collections, Rust is a much more fun language than I initially gave it credit for!

Yes, you can map over them, and it's great! I think they satisfy all of the monad rules, but I'm forgetting what they all are at the moment.
My rule of thumb is: if it is the type of error that I want a stacktrace for: use an exception. For example: programming errors, unhandled cases etc.

If it is an error that I want to handle explicitly, for example by showing a nice user message: use a result type. It is okay to use combinators in this case, but use them wisely.

Exception != Error

Old Ada programmer here. Example of reading bytes from a file... just keep reading bytes and don’t include logic for checking for EOF. Let the exception handler catch it where the file will be closed. Clean separation of code.

In Ada, every bock can have exception handlers at the bottom. No need for “try” syntax. Very clean.

That is like C++ destructors, which are called deterministically when they go out of scope (again with no "try" syntax). They can unlock mutexes, close files, etc. But unlike what you describe, there is no need to put anything at the end of the function: instead, the fact that you have instantiated an object already guarantees that its destructor will be called later. This is called RAII, short for "resource acquisition is initialisation".
Yes... RAII is handy, but it doesn’t save you from checking EOF logic in my example.
You could use a File class that throws on EOF, but that would be a really bad idea for C++ since throw/catch are very expensive.
Exceptions are expensive compared to a simple function call, but I imagine they're cheap compared to operations that read from disk. Certainly one exception per file close is not going to hurt.
> don’t include logic for checking for EOF. Let the exception handler catch it where the file will be closed

What happens if the file is smaller than you anticipate?

That's why exceptions aren't used for flow control.

Simply count bytes as they are read. In the exception handler check to see if the count matches the expected value. Clean.
My problem with exceptions isn't so much exceptions themselves but the way they're used.

The way I see it, exceptions should only be used for things that are irreconcilable, which most of the time is interpreter errors(e.g. undefined is not a function). In other words, I don't think it's that common that custom exceptions are needed outside of assertions to prevent the developer from doing something stupid. If you aren't using types, raising an exception can be helpful to provide feedback and useful information when data isn't formatted correctly.

Exceptions suck when they're used for problems that aren't actual problems. For example, if a query is made to an API for a record and that record isn't found, the SDK wrapping that API would raise a "Record not found" exception. What the hell? A record not being found is a normal thing! I've seen this kind of thing all over the place, and I recently had to write some application code to work around one of these useless exceptions. They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself.

I find the mindset that exceptions are the right way to handle ordinary scenarios symptomatic of what I call "happy path coding:" assuming that the stars are always perfectly aligned for your program to work, and nothing ever goes wrong, and if it does it's someone else's problem.

It's a fine approach for prototyping but we should be highly suspicious of it for code people rely upon to work. As complexity approaches infinity, the odds of the system being "happy path"-aligned approach zero.

Network-relevant programming (such as web UIs), in particular, really highlights this fact (since any given network request is always allowed to fail, and "user closes their laptop or mobile device, moves somewhere else, and resumes work on an entirely different physical network" has gone from a rare occurrence to an extremely common one in the past two decades).

On the other hand, do you really want to bubble error results through layers of network stack and parsers, when handling is almost always the same?
The thing is that exception is as close as we get to pattern matching in many languages. When a language has even the most basic Either/Maybe/Option, you start to see fewer exceptions being thrown around.
Actually, I’d argue that exception (that are caught) should only be used for recoverable errors. If you can’t recover, then you should crash.
The great thing about exceptions is that they implement both of these behaviors with a single mechanism.

For recoverable errors, you catch.

For unrecoverable ones, you don't. The exception throwing mechanism unwinds the entire stack and crashes the app with a helpful stack trace.

If you expected the record to be found, the failure of finding it is an exception. If it was an open question, then it isn’t.

This is why API generally have checks that allow developers to avoid that failure if the failure is expected (eg Record.exists). What you are suggesting is simply a reverse order (check after vs check before). I think check before leads to a much cleaner API than one that dumps a null or failure code instead.

It all depends on context. I believe that's why python for example throws an exception if given key doesn't exist when you use `dictionary[key]` but also gives you option to call `dictionary.get(key, [value])` which returns `None` or `value` (if specified) if the `key` doesn't exist.
That one bites me when I've been away from Python for awhile. I prefer 'open' maps/dicts, where every key is valid and any unassigned value is None/nil.

I don't consider looking for an invalid key exceptional, basically, so I wish the syntaxes were reversed, so I could use the more succinct form for (my) more common case.

Golang maps returns the value type's zero value in case the key doesn't exist: https://blog.golang.org/go-maps-in-action

The idiomatic way here being that the zero value should be a sane default for that value. You are then free to create your own types ontop of that, but the defaults are often in the ballpark of where you want to be unless you have specific needs in your context.

> If you expected the record to be found, the failure of finding it is an exception.

This is not always the case. In fact, for query APIs I do not expect it at all. Sometimes I'm doing something similar to a UPSERT operation with more nuanced behavior, then returning no existing entry on a preceding SELECT query could be the 99% use case.

>> If you expected the record to be found, the failure of finding it is an exception.

> This is not always the case. In fact, for query APIs I do not expect it at all.

And, in your case, "if you expected the record to be found" doesn't apply.

If I'm iterating over a list of names/id provided by the APi, and then calling the API for more information each one, then I would expect the record to be found... because the API just told me it exists. In that case, it not being found is an exceptional condition.

Only if you're the only user of that API or these entities are eternal once created. In any application with multiple users it's a normal thing for system state to change between two API calls.
The problem is that these systems are built in a way in which the data access latter is throwing the exception even though it can’t reasonably know if the record is expected to exist or not. It can’t or shouldn’t know that this is an invalid program state. Specifically, if exceptions are being used properly, you should almost never need to use try/catch.
Those are poorly designed APIs then. That is like having a dictionary that throws when a key isn't found but doesn't have an API for determining if a key exists or not.
Yes, poorly designed API, but we still have to handle them and the world is filled with them. So you can have a structure that works for the real world, or you can have an ideological structure the stubbornly insists that you should know better. "bad programmer! you should know if the key exists or fail".
Poorly designed APIs provide little evidence on exceptions being bad or not since the APIs themselves have big problems.
This thread is discussing whether exceptions are bad for handling unexceptional events. We are arguing that APIs that throw exceptions for valid program states, such as a missing key for some user input, is an abuse of exceptions. There are lots of these APIs in the world and we find that objectionable. You seem to agree with all of that, but you also present yourself as disagreeing with us, and it’s not at all clear what you’re disagreeing with specifically? Are you just being contrarian for its own sake or are we misunderstanding each other?
It’s a poorly designed API, full stop. It doesn’t matter whether or not it has an API to check existence, it’s not the dictionary’s job to know whether or not a missing key is a valid state in the broader program, and most often it is. Moreover, it’s insane to do a separate “if exists” check every time you need some resource. And the world is littered with these poorly designed APIs, as they are idiomatic in Python, JS, C#, and Java.
I don't agree. There is nothing inherently irreconcilable about a record not existing. Anyone who expects a database to always have records and that the absence of a record is an "exception" has a very strange way of thinking. It's like if a car was programmed not to start and to turn on an obnoxious alarm bell because the windshield wiper fluid is empty, and the technicians built in a jumper wire to short that circuit and allow the car to run.

Then again, maybe there are cars built that way. Oy vey.

I would never expect that a call to `Record.find(...)` would always find records, and that the failure to find a record means that something is broken. The absence of data is usually all you need to handle control flow, and treating absence as a failure is presumptuous of API engineers. Having to account for a specific exception just adds more lines of code that could be easily avoided.

A better use of an exception would be for a system failure like this: "Connection Failed"

Of course, if you're in a web app, and you've received an email address, and you can't find a User record for that email address, a perfectly valid approach would be for the fetch to throw a 'not found' exception and to generically handle all 'not found' exceptions in request handlers to return 404 status codes.
Then would you be nesting a number of exceptions. Some maybe real 404 exceptions, but some would be zero records found. And I think the zero records found should be handled in the business logic (if statements, switch/case), not in the exceptions.
If I expect exactly one result, zero results is exceptional. If I expect zero or more results, zero results should be handled by the business logic.
The expectation of a single result should be encoded in the API, not the business logic. It's a very common expectation for e.g. identifiers found on http paths.
> There is nothing inherently irreconcilable about a record not existing.

IMHO, that's being a bit too ideological. It's better to think about it in terms of how you'd want to respond to the condition, and pick your tool appropriately.

For instance, if I'm asking for a record by ID, then a record not found exception is a good fit. I was probably going to do something with the record, but now I can't, and the exception idiom tool the condition to be dealt with where it happened even if I forget.

If I'm asking for a set of records matching a criteria, and nothing matches, then a record not found exception is a poor fit. Code that can deal with a populated list usually can deal with an empty list just fine, and using an exception just adds friction.

If I'm asking for a set of records matching a criteria, and the database is is down, then an empty list is a poor fit. I have information about the connection error I need to communicate, so it's better to use an exception.

If you're wrapping every call in `try/catch` or equivalent, then something's probably wrong in your invariants. If customer.phoneNumber is a non-null foreign-key, your application should safely be able to assume that Phone.findByNumber(customer.phoneNumber) will succeed. If you don't get to assume that, then you have business-logic errors elsewhere and your best bet is to crash.
> Anyone who expects a database to always have records and that the absence of a record is an "exception" has a very strange way of thinking.

This isn't something you can make universal statements about, because it depends on context.

If the program knows it just put or saw the record there, is it strange to assume it's still there? If the program completely controls the database and something else in it implies the record is there, is it still strange to assume?

Sometimes broken assumptions can only signify that something has gone very wrong, and it's not worth handling them at a fine-grained level. That leads to the "gotta catch em all" antipattern the article mentions.

>There is nothing inherently irreconcilable about a record not existing

This just isn't true. It depends on what the record is, and what state you're in (aka: CONTEXT).

incredibly clear example: 1. User logs in 2. User record no longer exists 3. Exception

That's not a recoverable situation, and the client should see that as an unexpected error.

> There is nothing inherently irreconcilable about a record not existing

> This just isn't true. It depends on what the record is, and what state you're in (aka: CONTEXT).

These two things mean the same thing. It's in the context - > it is not an inherent property.

It's not an inherent property - > it's not always present.

Some conclude therefore it shouldn't be an exception by default.

Ok, fair - I should have included the very next sentence in the quote as well, since OPs argument seems to be that missing records should NEVER cause exception:

>Anyone who expects a database to always have records and that the absence of a record is an "exception" has a very strange way of thinking.

But pedantics aside, my point stands. There are absolutely contexts and code paths where a missing record is basically unrecoverable. Why bother returning an error to the local calling context when you know that code doesn't make sense anymore and can't recover? A jump to a new code path is the right call, and exceptions do that (with a lot of nice trace information about why we ended up on that new code path to boot).

Doesn't mean a missing record should always result in an exception, but it's certainly not a "very strange way of thinking".

"Check before" is a race condition nightmare - for anything where you care about concurrency, you must do the thing and then see whether it succeeded or not, assuming that the underlying layer is basically sound in this regard.

The world is full of bugs of the form "does this file exist? no? OK, open it for writing", which is exploitable by dropping a symlink in there between the check and the open.

The classic primitive CMPXCHG works like this too; you use it as you would a "store", but one that might fail if another thread has changed a value in the meantime.

That case is even much more exceptional: where the record existed when you checked but didn't in the millisecond afterwards when you actually opened it. But if that is common, then I would guess a concurrent check for existence and open if it is might be necessary.
That’s a very, very common source of subtle bugs (often exploitable as security bugs) that are hard to spot and hard to test for.

It’s good practice to design APIs to make race conditions less likely, by explicitly not splitting operations across multiple calls.

Separating “exists” and “get” into separate calls is a disaster.

I would compare this to a map API: an access could throw if a key wasn't present, but you can check to see if the key was in there first anyways. As long as the map isn't being used concurrently, it isn't that much of a problem, and if it is being used concurrently, then you need a different design. In the former case, separating "exists" and "get" would be considered over-engineering.

I guess databases and file systems are different because concurrent access is the norm, not the exception, and APIs should be designed accordingly. But for most of your in-memory structures, you probably don't want them dealing with concurrency because they aren't going to be able do that very well anyways (instead, deal with concurrency outside of the data structure and have them throw when inconsistencies from bad concurrency policies arise).

For most implementations of maps (hashtables and various tree-ish things), even in the single-threaded case, doing .exists and then .get is about twice as expensive as doing a .get that returns an Option (or nullable reference, default value, whatever)- you have to do the map lookup twice.

(Okay, probably not twice as slow, since the cache will be hot, but still.)

Not twice as slow. But you are forgetting the case where you know the element is already in the map, and you want to call map.get() without wrapping it in an option.
C# does this fairly nicely with an "out" parameter: https://docs.microsoft.com/en-us/dotnet/api/system.collectio...

Or if you "know" it's there call the version that throws an exception if it isn't.

I haven't used that aspect of the Dictionary API at all in the 9 years programming C#, it is just too unwieldy to have to define a separate local variable to hold the result of the get. It also doesn't scope the out assignment (if the get fails, you can still read the out variable bound). I'm rarely in the case where I don't expect something to be in a map anyways, and when I do, I have an extension method with a default value returned if the key doesn't exist (or in some cases populate). Keeping expressions clean is important.
Exceptional doesn’t mean uncommon, it means your program is in an invalid state. Your programming style is inherently unsafe, and you are derelict of your most basic responsibility as a software professional if you apply it on any software of consequence. This is why we can’t have nice things.
Absolutely. Whether a method throws or not signals the intent of the code. In .NET, there are plenty of throwing and non-throwing versions of various methods. For example, there are both Parse() and TryParse() and also First() and FirstOrDefault() in Linq. Which method you use signals to the reader the expected result. If you're using Parse() then you're expecting the parse to succeed; Perhaps the data comes from a data file and if it's not an integer then something terrible has happened. Alternatively, you would use TryParse() for user input knowing that users will definitely not be trusted to always type a number.

Not having non-throwing versions of these methods (e.g. Java) means you lose the ability to signal intent and you have to do a lot of pointless catching of exceptions.

On the other hand, if you are writing library code or even a library-ish component of application code, having to write the same method a bunch of different times is tedious. Ideally, the language would be designed so that there is one sane way of propagating errors, which can be easily either handled or propogated.

I am quite a fan of Rust's solution, with Result<T> types and various macros/methods which can do the commmon stuff for you

In this case, it's the difference between what is an is not an error. Only one of those situations is an error.

I agree having to write the same method a bunch of times is tedious but that is usually not the case. Most situations have clear intentions. It's much more likely in framework APIs where there are a bunch of non-task-specific operations.

I find the whole manual propagation of errors, even with a lot of helpers, to just be tedious boilerplate. Most of the time I literally don't care about the error -- I want to crash my app, log the error, and alert me and the user. I don't need to propagate the potentially limitless number of errors possible in any non-trivial application.

Actually, I think exceptions are the clearest way to represent errors of any kind (reconcilable or not).

> A null value, a plain "error" object, or an error argument

In my mind, this just adds a lot more mental overhead while also being less informative than a simple exception. Plus you have to keep track of which method your current library has chosen to represent errors.

Note I am biased from using Python for a long time.

For me exceptions are for exceptional cases.

Any time you have user input, you can expect errors and so should have proper paths to handle file not found, invalid input, ... For those case, exceptions should not be used.

> A record not being found is a normal thing!

It's not a normal thing for code that needs that record that wasn't found.

> They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself.

They tell you lots: you asked for something your code path wanted and your request couldn't be satisfied. Also, you've been helpfully kicked onto the alternative execution path to handle that situation.

Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error or your code wanders away from the root cause and fails later.

> It's not a normal thing for code that needs that record that wasn't found.

No offense, but I don't know where that idea comes from. Systems are checking for records all the time in ways where the absence of data doesn't necessitate throwing an exception.

For instance, a page, user, or piece of media on a website may have existed at one point but was since deleted, but still has a permalink floating around the net. Is it useful, in the case that someone clicks on such a link, to throw an exception when I can instead choose to render different page content when a record wasn't found? I'll never need a stack trace for that.

> They tell you lots: you asked for something your code path wanted and your request couldn't be satisfied. Also, you've been helpfully kicked onto the alternative execution path to handle that situation.

Why would I want a code path for expected behavior? I agree for cases like a failed connection, where the system is actually broken, but there isn't anything fundamentally broken about data absence.

> Also, you've been helpfully kicked onto the alternative execution path to handle that situation.

That's not only presumptuous, but if I wanted that to happen, I can do so myself.

> Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error.

Hmmm...

  const record = store.findRecord(params.id);
  
  if (record) {
    render('show-record');
  } else {
    render('record-not-found');
  }

or...

  try {
    const record = store.findRecord(params.id);
    render('show-record');
  } catch(err) {
    if (err.name === 'RECORD_NOT_FOUND') {
      render('record-not-found');
    } else {
      throw err;
    }
  }
I'll let people decide which one is better. I personally prefer the first one.
Many years of experience tell me that you're going down the futile path of getting a tiger to change its stripes. As more and more devs get minted, prepare for codebases (and libraries) to get a LOT more gnarly. Even so, it's a good thing to lower barriers of entry and we can be discerning about where we get our code.
Well, in real code you are likely bubbling up an error in some way for your 404 Not Found and 500 Internal Error handlers to kick in.

    item = db.findItem(id)
    assert(NotFoundError, item != null)
    render('show-item', item)
upstream middleware:

    try {
      res = await downstream() 
    } catch(e) {
      if (e is NotFoundError)
        render('not-found')
      else
        render('internal-error')
    }
you can do this with other patterns for upstreaming errors like Result + Rust's handy short-circuiting, but your example doesn't demonstrate much. what about internal errors like your store throwing a database error, your first example doesn't even have a store capable of erroring? your comparison incomplete. and surely you don't do all error checking at every callsite no matter which pattern you use.
> Well, in real code you are likely bubbling up an error in some way for your 404 Not Found and 500 Internal Error handlers to kick in.

Which is essentially GOTO 404

A horrible idea that I see often in clever frameworks disobeying encapsulation and reasonable control flow in favour of magic

That is a wonderful idea. You don't need to deal with 404, 500 or other stuff, you just write for the "happy path". The framework deals with the rest.
Then you fill in a complex form with 6 object references, and the framework just spits out "NOT FOUND", which means "have fun figuring out which one is the missing object".
`raise MissingField(field_name)` solves this problem.
At which point you end up doing the same work you would have had to do in the first place, except with extra lines for exception handling. ;)
It's both wonderful and terrible.

On the one hand, it's terribly convenient when you're writing the code.

On the other hand, as clon says, it breaks encapsulation. Not necessarily, but I've rarely seen these style of Web frameworks used in a way that doesn't result in network communication concerns wormholing their way through the entire codebase. And it sometimes does do weird things with control flow. Again, not necessarily but in practice. For example, JAX-RS frameworks do the exception mapping off in some magic location that's outside the part of the call stack that you directly control, meaning that customizing the logic requires hacking some fairly complex special-purpose mechanisms into the framework.

I suspect that there's a way to achieve convenience without relying on spooky action at a distance. But one could be forgiven for looking at the way popular frameworks on popular platforms work and concluding that cleanliness must be the price of convenience.

> I'll let people decide which one is better. I personally prefer the first one.

That's JavaScript, right? That's probably not the best language to use to judge the concept of exceptions.

It's cleaner in languages like Java:

  try {
    Record record = store.findRecord(params.id);
    render(record);
  } catch(RecordNotFoundException e) {
      render('record-not-found');
  } catch (Exception e) {
      render('unexpected-error');
  }
In general, I think this is why Rust uses `panic` since the name is way... angrier.

I like it. I wish that the concept of exceptions was closer to Panic because then we wouldn't be doing nearly as much flow control.

In the Rails world (where I live) looking top down, exceptions are handled via flow control or 500 pages. Anything that doesn't result in a 500 is effectively flow control.

While I agree with Rust's semantics, it generally isn't that bad to work around and knowing which is what.

The code example is not convincing at all. Obviously a single if is much cleaner than a single try/catch block but that is not the comparison. The if needs to be replicated N times for every N things that could fail and that are calling each other in a potentially deep call hierarchy. The try/catch block only needs to occur once at a level that is above all of the N things that might fail.
In my experience, on the web there are two different kinds of things we might be fetching.

Some things are essential to the page we are rendering, and if they are missing we should 404.

Some things are ancillary, and if they are missing we should render a stub in their place. E.g., saying that a comment is from "deleted user".

Exceptions are a good fit for the former, where we want to unify every failure. They are a poor choice for the other case, where we want to treat each failure special.

Which case a given fetch represents is a choice that should be driven by the fetcher.

Sure, it depends on the situation. In the case you outline, there could also be room for an auxiliary function like fetch_alternatives({ preferred_resouce, second_best_resource , third_best_resource}) that would fetch them in until one can actually be obtained and throw an exception if all of them fail. Not that I am married to exceptions or anything. One should just use what is best in the situation that one is in. Actually, I think articles that claim that one language construct is better than some other language construct are quite silly. It mostly depends on the situation. Though, there also may be language constructs that are best left off deprecated, depending the language that you are using.
Why are they a poor choice for the latter?

They have a type, make it say MissingUserException and the special place can handle it in a special way. Ensure it is a subclass of some general exception type you also handle elsewhere and you're good.

Often people throw generic exceptions instead of using the type system.

In the latter case you will often want to pass "data or the fact the data was missing" along to other functions. With a result value (of whatever form) that's trivial. With exceptions, it's certainly plenty doable but it's messy.
That doesn't always make sense when you're dealing with a generic exception class. What you are assuming is that a try-catch block higher up in the hierarchy can handle a RecordNotFound exception for all its descendants, but that's not necessarily going to be the case. It's basically the inheritance problem in object-oriented programming, but in reverse. This is why I'm in favor if exceptions for non-broken states to not be a thing or at least be optional. Making a request to an API or database and getting a successful response returning no data(or a message that says data wasn't found) means that the code and the services in the system are actually working correctly, and throwing an exception is treating the situation like a code problem when it could(and often is) simply be a data problem, which is common and often expected.
Yes, the try/catch block is better if the catch block can handle exceptions for all its descendants. As always in programming YMMV and this is sometimes true and sometimes it is not and when it is not it is better not to throw an exception.

I agree that a database API should not itself throw an error if an empty result set is found. It may throw an error if there is an SQL error, though, and really should. When one gets an empty result set where this should not happen, for instance because a row was just inserted and one is in a transaction, the user code could throw an exception instead.

Making exceptions optional is something that I have indeed done in the past because some uses of the code could benefit from exceptions while other uses did not so much.

The article is fairly specific:

The first example: - no stack trace - loosing the specifics of error to troubleshoot.

The second example: - explicitly discards the stack trace - explicitly discards the error specifics.

Hence, if this is really what you want. fine, but in a lot of cases you do want to see stack traces and the exact reasons (and meta data why an error was thrown).

That's a pretty good example. Now imagine you want to structure your code in a way that render('record-not-found') happens in a caller to that block. So exceptions as a general approach work pretty well to allow simple code organisation according to your needs.

And you are the one who knows best if something is an expected failure, so wrap that up in a fetch_one_or_none() — as a matter of fact, most DB interfaces I work with have an API of the kind.

Basically, exceptions are a decent way to keep what might need to be a separate control flow, well, separate. And you can choose at any time to "join them in". As a concept, they are quite like interrupts on the very low (hardware) level, so you can learn only one "tool" to deal with them.

(And yes, I can trivially find cases where I want errors to bubble up when a record is not found, eg. a required configuration record or similar)

Your first example loses the real reason for the error and just assumes "record-not-found", so it will give misleading errors when the network drops for a moment, possibly resulting in an attempt to insert a duplicate record if the code tries to add-if-not-found. That'll make for some debugging fun!

And this is the problem with error checks: They're purely optional as far as the language is concerned, so we keep getting them wrong or forgetting things, even when we're being careful.

@Seenso You probably won't find, this, but I think I somewhat misunderstood the first thing you said.

My response to that would be that if it isn't a normal thing for your code to encounter a record not being found, then you should handle that state in whatever you see fit, whether that's by passing down a value or object reflecting that error state or throwing your own exception(yes, I do think such a thing can be appropriate). I still don't favor the latter, but at least with throwing your own exception your application can catch it at a higher level and will be less likely to mistake it for another exception of the same class that should be handled differently. If you are using an SDK that throws an error for a "record not found" type situation, it would certainly work in your favor if your application really does benefit from it, but I think a lot of code doesn't actually call for such a thing.

But my point is that there's no reason I can think of that an exception can't be totally optional in these circumstances. I don't think it should be assumed that everyone wants an exception. There's a reason why we use conditional syntax like if-statements because, if used try-catch syntax as control flow for everything, the flow would be bass ackwards and hard to understand. In my opinion, the example situation I described is on the borderlands where an exception might make sense but probably doesn't actually make things better.

Every project is different, and people might choose to use exceptions a lot, which is fine for them. I've personally been better off not relying on the exception-based control flow taught to me when I learned Ruby, but that doesn't mean I would come into a project and remove everyone's exception handling.

> Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error or your code wanders away from the root cause and fails later.

That's basically limitation of the language. Null checks and result checking can basically be abstracted away with non-nullable types, option types, and result types and bind.

You only need to do match or check right at the edge, so they're not everywhere.

Result types are exceptions with different performance characteristics.

Like, handling a checked exception (Java) and a Google-cpp StatusOr<T> or a rust Result<T> provide extraordinary similar code. Unchecked exceptions provide a bit of extra dynamism, and languages without either are anti-user.

I find result and option types compose nicer using monadic style composition.
>> A record not being found is a normal thing!

> It's not a normal thing for code that needs that record that wasn't found.

This is something of a religious dispute and there are arguments both ways.

But I think it’s important to note that an exception is vastly more expensive than a simple function call or return value. So I’d say you need a really good reason to use exceptions.

I agree with the GP that in general, “lookup failed” is not an exceptional error. If it always succeeds, why are you looking up something in the first place?

I agree with the original poster that exceptions do have their place, even though they’re expensive.

If you have the primary key of a database object that you know to be correct, and you try to retrieve that record but fail, that might legitimately be an exception (unless having records deleted from under you is a common occurrence in your app).

I don't see what the problem is. The API should expose two methods: `fetch()` which returns, say, nil if there's no result (or an error monad or error tuple, depending on your language), and say `fetch!()` (or `fetch_throws()` if your language doesn't support exclamation points) which raises an exception if there isn't a result. The programmer gets to choose, on a case-by-case basis.
Agreed, it’s ideal to have both options available.

Kotlin gets this right for the most part (although I think it would be better if it had checked exceptions).

In Java/C# land exceptions are EXPENSIVE. Like magnatudes more expensive. You have to build a full stack trace etc. Removing places in the code where it is "Throwing exceptions for non exceptional circumstances" has a dramatic performance increase benefit.
C++ too. I suspect it’s true in almost every language.

Maybe not Python? But Python is slow regardless.

In C++, exceptions are often faster than manual error checking when errors are rare.

But C++ does not provide a stack trace.

C++ exceptions are only good if you use them like signals from C or panics in Go. This means you have to use error values for 99% of errors or you lose this benefit. The fact that the STL sprinkles exceptions everywhere doesn't help this. C++ exceptions are so non-deterministic and slow that every real-time system disables them just to be sure.
You don’t always get a stack trace, but it still has to unwind the stack, calling destructors as needed, and check the exception’s type against each catch block.
Hence 'when errors are rare'.
I guess we’re violently agreeing? Exceptions are an order of magnitude (or several orders) slower than function calls and returns; but that can still be efficient overall, if only a tiny fraction of calls fail.

I’d simply add that most people greatly underestimate how expensive exceptions are, even in C++.

Calling the destructors is identical to ending any scope in C++, including function calls. Where is that extra expense?
You likely have to parse exception blocks and maybe chase pointers to find the objects to destroy. In the happy path, you can just call the destructors directly.
> But C++ does not provide a stack trace

Which makes exception in C++ a very, very bad idea!

My reaction to 'the test fails because map::at threw an exception': stupid STL!!

A core dump would be so much easier to analyze.. Gdb's 'catch thow' is wonderful (well, it is after I fixed the part of our codebase which use exceptions as a control flow mechanism)

> My reaction to 'the test fails because map::at threw an exception': stupid STL!!

> A core dump would be so much easier to analyze..

Uncaught exceptions call terminate(), which by default calls abort(), which generates a core dump. So if you want a core dump, just avoid catching the exception.

Except that I didn't put all these "catch".. But you're right, that's what should be done (or at least, rethrowing the exception from the catch block).
C++ can have a stack trace, there are enough libraries providing this functionality and even compiler extensions. Say, libunwind, though you will have to demangle names. And a debugger will see the stack as it is.

Also works in C.

Technically in Java it is possible to create an exception object once, and throw it multiple times, making throws much cheaper as the stack trace is filled during the object's construction only.
Does this actually save much time? I thought it was the throw mechanic that was slow, rather than just creating the exception?
According my unscientific tests, it is about over 100 times faster. On my laptop I can throw one million exceptions in 15ms millisecond, when reusing the same exception objects, compared to 2 seconds when creating a new exception object every time with a quite shallow stack.

When a new exception object is created, the slowest operation is filling information about the stack trace. It is possible to override Exception's fillInStackTrace() with an empty implementation. In this case throwing exceptions with new exception objects, is only slightly slower than using one exception object (17ms vs 15ms for 1M throws).

Deeper stacks make difference even bigger. Adding 100 nested invocations to the stack slow downs the classic approach (a new exception object created just before invocation) to 9 seconds, while alternative approaches are not affected.

The HotSpot JVM may decide to stop building stack traces in some circumstances to improve performance (see the 'OmitStackTraceInFastThrow' option, enabled by default). Not sure about other languages / runtimes though.

That said, I agree exceptions should be reserved for exceptional circumstances. Something like checking if a record exists or not should probably use something like Optional instead, unless you are in a situation where a record "should" exist but doesn't (which is itself an exceptional case).

> It's not a normal thing for code that needs that record that wasn't found.

In many popular programming languages, the way to check whether some value has a certain property is with an “if” statement. It would be very odd to replace every code path inside an “if” statement with exception control flow simply because that code path “needs” some condition to be met for it it to execute.

If a user doesn't exist, any code that relies on having a user is broken, and I want a guarantee that code can't be reached. Throwing does that, mapping an Option does that, but "if" doesn't.
SQLAlchemy does this well:

obj = session.query(MyObject).one_or_none() # no exception if missing

obj = session.query(MyObject).one() # exception if missing

I was watching this the other day:

https://www.youtube.com/watch?v=AnZ0uTOerUI Unconditional Code by Michael Feathers

One solution to this problem is discussed toward the middle: giving data instead of asking for data avoids error conditions. Doing so pushes use of the data farther down the call tree and pulls acquisition up near the top, where we are closer to the user and thus able to more clearly decide what if anything to do about these corner cases.

> It's not a normal thing for code that needs that record that wasn't found.

Why call it then?

Your example is a good argument for using null or empty list in situations where that is applicable, but it doesn't explain why a custom error object or callback would be better than using an exception, even in reconcilable scenarios. In fact I think using a custom error object would go against your general point of taking advantage of preexisting language features where possible.
We tried to use a result type in C# for cases like this. For example, we want to save an object, returning the updated object (on success), or an error on failure.

Unfortunately in static languages this leads to an unholy amount of boilerplate:

public Result<SomethingGood, SomethingBad> PerformUpdate(int goodThingId, UpdateForm form) { ... return new Result<SomethingGood, SomethingBad>(error); ... return new Result<SomethingGood, SomethingBad>(obj); }

The type annotations were hell. Sometimes there were three cases we wanted to consider. We went back to using exceptions, and are keeping a keen eye on the new C# features.

So ultimately - I agree with the author that throwing exceptions is fine, when things actually go wrong. It's also not that bad when things kinda went wrong, but sometimes the effort required to fix it isn't worth it.

It's been awhile since I've been in C#, but in other static languages like Rust and Kotlin with type inference, it would look more like:

public Result<SomethingGood, SomethingBad> PerformUpdate(int goodThingId, UpdateForm form) { ... return Err(error); ... return Ok(obj); }

which is considerably less boilerplate-y. When there are three cases you want to consider, that's no longer a sum type consisting of just success or failure - that's a different sum type. Maybe even represented as Result<ThreeCases, Error>.

Frankly, that's because C# doesn't support proper discriminated unions in a concise way.

F# is a lot better at using result types. You don't even need to specify the return type.

You're right; if the only way to test for existence of something is to try and fetch it and check for an error state, then the API is forcing all the costs of its error handling on very regular uses.

And if you're dealing with non-determinism, then even a pair of APIs which let you separate out existence-checking from fetches isn't enough; you just introduce a race.

I like how .net does it with methods returning bool and an out parameter. Pattern matching on a return value would be fine too. It only makes sense on methods where you reasonably do need to handle the error case. And a variant which triggers an error condition on failure should exist too, for simpler programs which don't need to handle such failures, merely log them.

I like the new style C++ FileSystem API's where you can either have an error code set or a thrown exception depending on the method overload you choose.

I like this a lot since generic utility methods can have use cases where you sometimes want an exception thrown or simply an error code returned.

If I am working in Java and a checked exception is thrown, my method usually allows the caller to supply an Function<UnderlyingException, DomainException> so that the caller can specify that a domain exception should be thrown instead of having to try-catch the thing.

But I agree with the author of the article - Exceptions are the right design technique when the caller considers an error as a failure as opposed to an unusual but handlable situation. Messing with Result in a deep call-stack is painful.

The biggest issue I've seen with exceptions is that people bring up the issue when they are abused. I SORT of agree with you that an 'Record not found' exception is an abuse. This should probably be a null-object like an Optional ALA Java or Scala.

They are trying to avoid returning null in this scenario.

Unfortunately, if the API is designed poorly you're stuck with it.

I can sort of understand why people hate checked exceptions but working without exceptions is just terrible.

Nulls are also called the billion-dollar mistake (and that was decades ago; it's much more than that now). Both nulls and exceptions are ways of trying to make the main line of processing clear, while handling other lines in structured ways.

There's no one-size-fits-all solution. In a lot of ways, the best response to "record not found" is that you get the same result as finding one, except with zero answers. That means your main line can process the same way -- unless that doesn't produce the result you need. In which case maybe you an if(null) somewhere, or a special fake record, or an exception.

> Nulls are also called the billion-dollar mistake (and that was decades ago; it's much more than that now). Both nulls and exceptions are ways of trying to make the main line of processing clear, while handling other lines in structured ways.

At the same time before NULL, devs used to use "guard values", so NULL is really just a convenience.

for instance, just to illustrate what I'm saying:

    let NULL = {} /* should every lib define its own null value? */

    function GetOneRecord(){
        let dbResult = queryRecordsFromDB();
        if (dbResult.length === 0){
            return NULL
        }
        return dbResult.records[0];
    }
Optional types, are better, but one needs functional programming features for it to be really useful.

What's people opinion on zero values by the way?

What "functional programming features" does one need for useful optional types?
Putting aside the "functional" bit, for a language to allow user-defined (i.e. not hardcoded into the language itself (which may not necessarily be a bad thing, but that's beside the point here)) optional types, you'd want the ability to say "this variable is either _this_ XOR _this_" (a.k.a. tagged unions/sum types/what Rust calls "enums"), and then in a statically-typed language you'd additionally want generics.
Yeah the problem really is 'in band signaling' See SQL injection attacks for an another example why the practice is dodgy.
Zero values may act as nice defaults. Programmers who care about their code, often want initial state or default state to reflect something useful. What zero value should reflect depends on context, but you get to state that once and for all, for that type. It's just one less thing off a mind when building code, although you'd want to catch any invalid usage. Ie. referencing zero value, could result in error if due to control flow, and panic() in case of programmer error. I don't think the language or library should choose for you, unless being bound by that is a very useful trade-off.
Null _pointers_ are called the billion-dollar mistake, not null values.
The usefulness of exceptions in the cases you present is to force the developer to handle the cases where things go wrong, such as no records are found.

There are more clever ways to do this nowadays with monadic Result types, but the core idea of forcing a developer to handle error conditions is useful in API design.

Isn't that "forcing a developer to handle error conditions" only true of Java-style checked exceptions, though? I'm not aware of any non-JVM languages taking that route, even the very Java-clone-y C#, and thought it was widely believed to have been a mistake.
Still some people like me think that checked exception are the damn right way to express the possible outcome of a function. Every time I need to work with C# / python I need to play "catch" to keep track of all the possible exceptions that could be thrown at any function call...
Unchecked exceptions are a disaster. In C# when you call a method you have no idea what exceptions can be thrown by the called method unless you inspect the called method and all the methods called by it. The called method can be modified at any time and a new exception can be thrown and your code will compile just fine. This is bad because the new exception may be a recoverable condition and instead of recovering your program will crash. This is why exceptions that can be thrown by a method should be considered part of the signature of the method and you should get a compile-time error (as opposed to a runtime crash) if the method throws a new exception that wasn't there when you originally wrote the code.
> In C# when you call a method you have no idea what exceptions can be thrown

Well, you have no idea in Java either, given that unchecked exceptions exist.

I understand the goal of checked exceptions, I just haven't found much value in them in practice, and given that nobody has copied them I don't think I'm alone. I'm sure it depends what area you're working in, but in my experience it's rare (not unknown, but rare) to be able to do much with e.g. an `IOException` at the call site, and naive approaches like automatic retry can easily be worse than crashing if those retries end up looping excessively and spamming a resource that was already struggling.

My personal preference is for non-exception mechanisms (Maybe/Option/Result objects, or C#'s TryXXX methods) to handle recoverable cases, and unchecked exceptions with very high-level catchers to convert the nonrecoverable ones into sensible diagnostics. YMMV, obviously.

As my opinions on exceptions have evolved throughout the years, I've come to think that there is value to unchecked exceptions, but unchecked exceptions need a fundamentally different mode from checked exceptions.

The best motivation for unchecked exceptions are things like division by 0, or null pointer dereferences: these are things that result from programmers misusing the API, and are generally unrecoverable. What you want instead is some sort of "graceful crash"--for example, if you're a web server, display a 500 page and log the stack trace somewhere internally. Using a pretty distinct mechanism for driving these error cases (e.g., Rust's panic mechanism) I think works better for emphasizing the crash aspect here.

Of course, now you have the regular checked exceptions which are a fundamental part of the function type. It's not clear to me that the try/catch/throw model is necessarily the best model for propagating these exceptions, and there are several cases where the overhead involved in the (misleadingly named) zero-cost exception handling model are not appropriate for the model.

Although there is something to be said for easy upgrading of checked into unchecked exceptions. It's a relatively natural assertion that something can't fail for reasons the compiler doesn't understand (which means informing the programmer when and where it failed is very critical!). I've always been annoyed by Eclipse deciding that the default implementation of catch should be "ignore the exception" instead of "wrap it in a RuntimeException."

There are some areas of software design where essentially any strategy 'works' for a year and a half, or two years. If you come into a project you might not notice the friction caused by an arbitrary solution closer to when you are looking to move on than to when you arrived.

Testing is big this way, and I've had many 'discussions' with people who could demonstrate no experiences dealing with the consequences of their testing decisions two years on. Either they were gone or had externalized those consequences to others.

Getting multiple exceptions from multiple layers of the code only becomes problematic after the depth and breadth of the call tree has passed the point where a single developer can keep it all in their head.

I think that's too simple of a case to really make an argument either way. Just return a Some/None or Optional. Null is ok too, but not the best (it's baked in to most languages already though). But at a higher level, if the program expected something, it may be an exception. Let's consider another case. What if you made the request and your session got terminated? That will be an exception too, and I don't think anyone will add that to the result type.

I feel like encapsulation means that functions often should throw errors, because the small, one responsibility functions shouldn't have knowledge of control flow. For example, division. Anytime you divide, you might try to divide by 0. That's an exception. The / operator returns a number or throws an exception. Who would use a TryDivide function? What would it solve? The same error might be an exception in one scope, and expected in another.

I get that exceptions can be expensive, but they should be rare. Saving time on the pre-check for an exception might save more time than the rare exception. I would never throw an exception strictly for control flow, but avoid exceptions is a bad idea IMO.

But the line is very blurry.

It's a pity they're called exceptions, they should be called errors.

Errors can be of two kinds: recoverable and unrecoverable.

Recoverable should be handled by checked exceptions and unrecoverable errors should be handled by runtime exceptions.

> For example, if a query is made to an API for a record and that record isn't found, the SDK wrapping that API would raise a "Record not found" exception. What the hell? A record not being found is a normal thing!

There are scenarios where it is and scenarios where it is not, it's fairly common for APIs to present both throwing and non-throwing variants (many languages stdlibs, for instance, provide both mechanisms for associative array lookups), and of course in any language with exception handling you can convert either to the other according to the needs of the consuming process at the point where you interface just by wrapping the finction in one which either throws or swallows the appropriate exceptions, depending on which direction you are converting.

The slow, stupid, and impractical way is to first ask if any records exist, before asking for the records. Remember that computers are dumb, but they are also fast. Asking for the records without first asking if they exist is an optimization. The records might exist when you ask if they exist, but then they might be gone when you ask for the actual records, now that would be an error.
> If I need an exception to be raised for this kind of thing, I'll do it myself.

This is the thing. This is the root of the thing. Let developers decide what's right for themselves. There are too many "me-too" developers writing terrible code according to terrible guidelines and terrible "best practices" (lol) and few people are actually thinking about what they're doing.

I agree with everything in your comment. Every single thing, and I wish more people understood your (our?) point of view.

The title contrasts the last paragraph. I believe the author means this to be true only in the CLR, F# specifically.

> I strongly believe that using result types as a general-purpose error handling mechanism for F# applications should be considered harmful. Exceptions should remain the dominant mechanism for error propagation when programming in the large. The F# language has been designed with exceptions in mind, and has achieved that goal very effectively.

I’ve pushed for Either[L,R] based coding without exceptions for a while and met a lot of criticism along the way. I still think it has value over exceptions but this post does bring up good points. The one about throwing the stacktrace away is annoying, but with Future your stacktrace is useless anyway. We have a system where every error has a unique id that looks like a jira ticket number so you can find the source very quickly and avoid the stringly typed error.

This section of code is what i want to avoid:

    user = service.getUser()
    bill = billingService.getBill(user)
    notificationService.message(user)
we use the same http client in all underlying service clients. let’s say you get to the end of this block and it results in SocketTimeoutException. How do you know what call did this? you would have to add to every line:

    .recoverWith({case t => new Exception(“billing exception”, t})
to propagate the context up. Either forces you to handle that well and write the context.

a lot of it comes down to style choice because you could do it with exceptions, but i think this makes it easier not forget cases because the compiler will tell you

Exceptions in Haskell are unavoidable and awful. If you want to write code that responds to the errors that could occur, even some of them, you basically have to read the library source because they're completely undocumented in many libraries - even in the standard library (try something exotic like, um, reading a file). And that means you need to know your whole stack.

If they were at least declared in the type so the compiler could give me a warning.

That does suck, does it commonly occur because of non-total functions in the libraries or what?
I’d say this works in Erlang because it is part of the culture, but in java or python what I’ve seen mostly is people forgetting to catch exceptions, or not being able to pinpoint where exceptions are catched.
Erlang has a very different execution model compared to those other languages you mentioned. In fact, Erlang's culture in many ways encouraged to program less defensively than you would be expected to in other languages (ever heard of "let it crash"?).

That's because the whole expectation of exceptions/errors is built into the system unlike pretty much anywhere else.

While my favorite language (Erlang) has Exceptions, I really like the "let it crash" model -- let the whole light-weight thread die and have the controlling thread (a/k/a "supervisor") figure out what to do.

I've seen insane Java programs where tracking how many layers up Exceptions are caught was mind-twisting. And conversely, when everything was wrapped in a try/catch and then, essentially ignored.

Can't the rethrowing logic be expressed using `fmap`?

    λ :set -XTypeApplications 
    λ :t fmap @ (Either Error)
    fmap @ (Either Error) :: (a -> b) -> Either Error a -> Either Error b
I don't think it should be "exception vs returned value".

First, if you are using a dynamic language, then exceptions make sense. You want to get there quickly, not handle every corner cases.

At the opposite spectrum, if you are writing rust, of course you want something very explicit and extremely aggressive to deal with errors.

They just serve different needs.

Also, exceptions don't have to be implicit, difficulties arise when the code you are using do not or cannot declare what can of exception can happen when you call it.

But if the language let you declare all the stuff you can raise, then there is not much difference.

i think there is a distinct difference between an exception, and a recoverable error.

Result is not intended to be used for exceptions, but rather recoverable errors. There are a number of situations where I have used Result to recover from an exception that was expected, but it would be folly to try to handle all error paths (especially with runtime errors).

I think the author is trying to convey the point that we shouldn't use Result as a catch-all, but rather as a means of conveying possible known error paths for a function. There will always be the possible unknown error paths, which are handled by the runtime's exception handling/reporting.

What's exception handling actually doing internally?

Genuinely interested and i need to know.

Must be some kind of "goto catch block" internally when you throw an error. It has to stop the execution and jump somewhere, but that somewhere is set in the code where it catches the error.

If you're calling a function then that function throws an error, it has to prematurely exit to somewhere so there must be a stack of the locations of the catch blocks or something? That then unroll as you exit the try/catch blocks as well.

For gcc/clang C++, internally, it will unwind the stack until it finds a handler. This way it doesn't have to do anything at the moment of setting a handler (i.e. "try" in the underlying code), as a trade-off it's relatively expensive to throw an exception. But note that C++ standard does not specify this, so it's fair game for compiler to do anything. E.g. it can use long jumps, or just dispatch different functions based on all possible exception states (which produces very large binaries).
Thanks for that, nice to know!
I personally feel that use of exceptions kinda depends on how compiled the language is. With strongly-typed, compile-checked code, it’s only really bare assertions for a test suite to build. Meanwhile in weakly-typed languages without means for overloading, certain type-based errors have to be made to ensure the signatures are met.

Errors for trivial things (ie control flow) are an unfortunate abuse of try and catch; generally exceptions should only be thrown to users of an external API, not an internal one. If an exception is caught from the same layer of abstraction it was thrown from, it’s a very leaky abstraction — as opposed to, say, a regex parsing error, which ensures the abstraction isn’t implicitly failing.

Good article but it is tangential to a basic design issue.

Error handling should be done at the edges of systems rather than in the center. If you do that, it doesn't matter whether you use exceptions or error monads, you have a pure core that doesn't need to deal with error handling and a very slim area to catch errors. The mechanism you use for error handling, at that point, doesn't matter.

The problems of error handling often come from having it spread across the system and mixed with the logical flow.

it's not trivial in every domain to distinguish between pure and non-pure operations. If you're writing software for control systems, robotics, anything that has a very tight, low-level coupling between programming logic and the outside world it's harder or often not possible to separate concerns into some sort of data -> logic pipeline that lends itself to this style of programming, or it may come at the cost of performance.
It's a decent goal, though. For all else, there's Erlang.
> It’s by such misadventure that the working F# programmer soon realizes that any function could still potentially throw.

That's your problem right there. "Invisible control flow", as Joe Duffy puts it.

http://joeduffyblog.com/2016/02/07/the-error-model/#unchecke...

What's needed are two distinct error mechanisms: panic/abandonment for unrecoverable errors (which can happen at any time), and then for recoverable errors, either checked exceptions in some flavor (including Swift and Midori's untyped `throws`) or Result types.

Once you eliminate invisible control flow, you're no longer "better off using Exceptions".

But then the failed code which decides if the error is recoverable, not the part of the code doing the recovering.
Yes!

We shouldn't expect downstream to wrap every array access which could be out of bounds in a "try" block. We shouldn't expect downstream to wrap every division operation in order to catch potential divide by zero errors. Those should be fatal errors, catchable only at a very coarse-grained level, because once they occur the program state is potentially compromised in a way which the programmer did not plan for.

The library author gets to decide whether a potential failure mode should expose a recovery API.

> ...exceptions-as-control-flow abuse or even the assertion that exceptions are really just a type safe version of goto.

I was recently telling someone about a little-known Java feature that I wish C would adopt. Goto is common in C exception handling code because the alternative is unworkably messy. Java has named code blocks that you can break out of, so they're not gogo, but they're also not exception abuse.

    initBlock: {
      if (fail) { break initBlock; }
      doSomething();
    }
In that case, in javascript, i always just write a function.

    {
      init()
    }

    const init = () => {
        if (fail) return
        doSomething()
    }
(there's a few other cases i introduce functions for syntactic reasons in js - e.g. at the expense of a const funtion, i can convert a let to a const - but i have always found this improves the code on standard readability guides)
There are actually three error handling paradigms:

1. Return values, like Either in functional languages

2. Exceptions

3. Error handlers, for example Lisp condition system. (Unfortunately, this option became less popular in programming languages, probably because Unix botched it.)

Now here lies the problem. Paradigm 2 is better than 1, because you don't have to handle the error at the caller (or at least, you don't have to unwrap the type). Paradigm 3 is better than 2, because the stack doesn't get unwounded when the error handler gets called - the error handler can choose to continue the original code. Finally, paradigm 1 is better than 3, because it is just so much simpler to set up and reason about.

So it turns out, you're better off using all of them! Although personally I believe exceptions are conceptually wrong, and in almost all cases, either 1 or 3 should be used. (Sadly, option 3 is not well supported in most languages.)

For the readAllText example, could it return Result<string, Exception>?
Yes, but you can still argue just throwing it might be better. This also affects the stack trace, because, at least in C# (so probably F#) `throw ex;` is different than `throw;`, which rethrows the exception as if it weren't caught.