This is language-agnostic but I will make two assumptions, here:
- Assertions can either be left out of the build of the program or can be disabled at runtime (the latter is like Java assertions)
- You can catch assertions: an assertion will not necessarily kill the program
I basically agree with most of the other answers: assertions should always be used to check for bugs (stating assumptions) and never to check for things that are outside of your own control, like e.g. user input.
So, by extension, exceptions (or general error handling) should handle everything that could go wrong as a matter of course.
But I don’t necessarily think that only assertions need to be used to check assumptions; you might sometimes want to use exceptions for that. Why? Because of point number one: assertions might be disabled. So you might want to use exceptions for, say, stating assumptions that never should be violated, no matter what kind of build or runtime configuration you are using.
So what’s the sweet-spot for assertions? Well, it seems that assertions are enabled and disabled based on two criteria: (1) the cost (performance-wise) of the check and (2) whether one should crash when the assumptions are violated or whether one should do something less intrusive and simply carry on, like for example just log the error. But recall point number two above: we assume that we can catch assertion errors (like catching
AssertionError in Java). So let’s for simplicity’s sake assume that we can catch and log (i.e. not crash) assertion errors if we want.(†1) Then it seems that perhaps the sweet-spot for assertions are checks that you might not always want to run based on their cost. So use assertions for things that you might want to sometimes disable. Maybe you for example want to:
- Always disable assertions in production
- Selectively disable assertion code that has run in production for six months or more (with assertions enabled) and which has been fuzzed in local testing
- Disable assertions in production which you are confident that will run the same as when it is run locally or in testing (i.e. the whims of user load and input isn’t that concerning to you)
- Never use assertions at all since you are fine with the cost (performance hit) of always checking assumptions (i.e. you don’t need the on/off feature of assertions since they would just always be on)
Or whatever other scheme you want to use.
Or if you can’t catch assertions then you can implement something like
assertOrLog() which crashes when asserts are enabled or logs the error if not:
assert false : errorMessage