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 appreciate working through these ideas. Thanks for the insightful Qs.
With the caveat that I'm not familiar with "refined types": once we're talking about internal calls, then asserting qualities about input parameters is fully on the table. My preference echoes the clojure.spec advice to leverage expressiveness to constrain values instead of using assertions as a poor man's type checker.
There is an additional consideration: the Clojure library ecosystem contains a sadly non-negligible quantity of
assert
misuse (e.g. for input validation). It would be cool if those libraries didn't do that. As long as they do it's something consumers have to deal with. I do have the opinion that this doesn't justify the (unfortunately common) use ofcatch Throwable
.When it comes to crashing or not I'm persuaded by Ned Batchelder's advice to split semantics and behavior. This document focuses on the semantics side. I don't think Clojure expects
AssertionError
s to be handled, but I also think Clojure won't snitch on you if you do. I think you might enjoy this discussion (timestamp 38:36) of the lack of assertions in Go, and whether to crash or not when failing an assertion condition. Sean Corfield makes a similar argument that assertions should be left on in prod which I find generally convincing.assert
itself. Clojure's assertions AFAICT are implemented along same design as Java's, meaning they're a better facility for mimicking Eiffel than Java could be. See Java documentation Programming With Assertions especially General Questions.You make an important point that the kind of assertions one makes in a language with immutable data structures is different from those without. (Perhaps a subset?) And too the focus on bare maps rather than OOP. I don't think we can get entirely comfortable but it's a dose of relief. Nevertheless we chop wood and carry water: consider how to handle expected errors and what invariants deserve being asserted.
I want to avoid giving general advice on the behavior side here. On the topic of "in Clojure you'd want to..." I'll repeat Alex Miller's insight:
Assertions are a useful tool because they're not exceptions. The more they're used for quotidian error handling the more we're forced to treat them indistinguishably from exceptions, eroding their raison d'etre. Maintaining a clear distinction between the two is the goal.