In JVM Clojure, Exceptions are for operating errors ("something went wrong") and Assertions are for programmer and correctness errors ("this program is wrong").
An assert
might be the right tool if throwing an Exception isn't enough. Use
them when the assertion failing means
- there's a bug in this program (not a caller)
- what happens next is undefined
- recovery is not possible or desired
Use assert
when its condition is so important you want your whole program to
halt if it's not true. (Note for library authors: this is a high bar, because
the program isn't yours.)
An assertion is not the orthodox tool for...
- checking arguments to a public function
- note:
defn
's:pre
and:post
are assertions
- note:
- validating web requests, user data, or similar
Asserts are for "this should never happen" situations, not run-of-the-mill failures.
- catching logically-impossible situations
- checking that the program is written correctly
- ensuring invariants hold
- validating assumptions at dev time without affecting production performance (optional)
- building part of a system in a design-by-contract style
- e.g. in internal code, testing conditions that you believe will be true no matter what a client does
- this is one intended use case for
:pre
/:post
-conditions
Oracle's Java documentation, Programming With Assertions
While the assert construct is not a full-blown design-by-contract facility, it can help support an informal design-by-contract style of programming.
John Regehr, Use of Assertions:
An assertion is a Boolean expression at a specific point in a program which will be true unless there is a bug in the program. This definition immediately tells us that assertions are not to be used for error handling. In contrast with assertions, errors [JVM Exceptions -ed.] are things that go wrong that do not correspond to bugs in the program.
Ned Batchelder, Asserts:
ASSERT(expr)
Asserts that an expression is true. The expression may or may not be evaluated.
- If the expression is true, execution continues normally.
- If the expression is false, what happens is undefined.
Tiger Beetle, Tiger Style:
Assertions detect programmer errors. Unlike operating errors, which are expected and which must be handled, assertion failures are unexpected. The only correct way to handle corrupt code is to crash. Assertions downgrade catastrophic correctness bugs into liveness bugs. Assertions are a force multiplier for discovering bugs by fuzzing.
- "Ceci n'est pas une Error" section in my Idiomatic errors in Clojure article
@didibus , I certainly agree that catching Throwable other than AssertionError is a dangerous gamble. Even the simple act of logging Throwables can make the underlying condition worse, resulting in a death spiral. But sometimes that is the price you have to pay for observability in extreme conditions. Personally, I don't catch Throwables other than AssertionErrors in app code, and never yet in my library code. YMMV.
As for the admonition to not make assertions about args to public fns, I see nuance here, where making fundamental (e.g. type) assertions about args is useful. Passing the wrong type is a programmer error, not a user input: an invariant has been violated, and some unit of code is incorrect in some unforeseen way. Proper side effects within the unit of code, and a valid return value from the unit of code is no longer possible. The best outcome is to cleanly terminate the failed unit of code at some higher layer of abstraction.
It's a sign of experienced software developer to know which exceptions/faults/errors/throwables/failures to catch/handle and which to leave alone so that a higher abstraction has an unmolested view of the failure. It's the sign of an even more experienced developer to be able to write those higher abstractions and guide less experienced developers in their proper use.
@daveliepmann , thank you for prompting this discussion. I find error handling to be the most challenging part of writing good code. As somebody once said, any sailor can steer the ship when the seas are calm, but the captain takes the wheel in a storm.