When writing code in any language, there’s a couple of ideals that I always try to follow around error handling:
- You should always process all possible error paths and respond accordingly.
- If the options are between crashing and getting into an inconsistent state, then crashing is better
These two tenants of error handling were drilled into me whilst at Bromium where I lead the team that built the Mac version of their vSentry security product. Every error, no matter how innocuous it may seem, will happen to you when you least expect it, so you should *never* ignore an error path. As a code author you're always chasing the success case, as that's the functionality your user wants, so it's easy to forget that a function might error or that some input to your bit of code might not be what you expect. One of the things I like about Swift (and to a lesser extent also in Go) is that error handling is made explicit and it’s opt out rather than opt in: if you want to ignore an error you can do, but you have to decided to do that, rather than just forget. Such a wonderful feature of the language (modulo the initial confusion because they used exception syntax for something that isn’t exception handling.
At the same time as doing this, there are points in your code where either you know that a failure case will never happen, or if it does it’s because of a programming error rather than something unanticipated in then input or the environment (or at least you expect some code called before yours will have done any input validation). For these cases it’s perfectly fine to just assert a particular state rather than handling it (but still you must acknowledge it!).
It always amazes me that people disable such asserts in production builds though. On most modern event driven code there’s no performance justification for doing this, so the reason given is that you don’t want your program to crash on the user. But to me the alternative is far worse: if an assertion would fail and your asserts have been removed, then your program is now in a state you never designed for it. If you’re lucky nothing serious will happen, but at worst you can cause the user confusion and potentially data loss (very early in my career I failed to validate that I’d detected the temporary folder on disk correctly, and thus cleared out empty string on a disk, which is the root folder…). Yes, if you crash the user will be disappointed, but you will at least get feedback, hopefully a stack trace, and very quickly a bug fix. Otherwise your code can be mis-performing for years before you realise.
Crashes are obviously bad and to be avoided at all reasonable costs, but if you follow rule one, you should never have a crash, except where something you asserted would never go wrong does. In which case you’ve learned you should have handled that error despite what you assumed. If you don’t like asserts, then write more error handlers, but doing neither is not an option in my book.
I’ve finally come to my peace with the two variants of the optional value unwrapping in Swift, which initially to me seemed like an odd design decision for a language focused on program safety. In Swift if you have an optional variable (i.e., a variable that either contains a value or is null), you can either use the conditional unwrap (the ? operator) or an unconditional unwrap (the ! operator). I was of the opinion that you should always use the conditional version and provide suitable error handlers, and that the unconditional unwrap was akin to never bothering to check return values on functions in other languages. But I've been writing more iOS UI code in anger and it has made me realise that the unconditional operator is actually like an assert: I’m going to unwrap this optional and I assert it will always hold a value. This is very useful for where you have resources loaded from storyboards etc. where you “know” that the value won’t be nil and will point to a UI element but convention requires the variable be optional.
You still need to be fully cognisant of this decision, and distrusting of any code you see where an unconditional unwrap us used until you’ve convinced yourself it’s actually just an assert call, and in general explicit error handling is always better for the user, but I’m glad I’ve now found a place for what used to seem like a Swift feature that went against all the other things that make the language safer.