Crashes are a two-faced beast. One face makes you cower in fear because users absolutely hate crashes: a single crash during the first-time experience is enough to drive away users who don’t have a watertight reason to use the app.
The other face is that it might take months to recognize for what it is: crashes are a necessary manifestation of underlying issues that prevent even more confusing and debilitating bugs from rising up in the future.
Take this example: you have an app that lets a user generate a video on the web, automatically downloads it onto his camera roll so he can share it to other apps, and opens a share sheet that allows the user to share it to those apps. Then, it caches the location of the video on his device so that he doesn’t have to download it again.
One programmer, let’s call him Tim, might rush through the implementation and use an implicitly-unwrapped optional to store the video’s filename. The implicitly unwrapped optional is passed to a non-optional function parameter when presenting the share sheet and all the optionality is forgotten.
Another, say, Dimitri, might have the share sheet automatically pull the latest video created on the user’s device after the download finishes.
Ok, fantastic, so what happens if the video fails to download for whatever reason–connectivity issues, failed to write properly, storage media was unmounted?
Tim’s program will crash immediately. It’s bad, the users won’t like it, but at least it’s a piece of consistent behavior and the user knows something went wrong with that video. It’s very simple to fix, just add a null-check and ship a new version. No existing data will be affected.
Dimitri’s program won’t cause the app to vanish without a trace and leave the user dazed. It’ll just open the share sheet with the wrong video. That’s not great, but the user can just reconnect to the internet and retry it, right? Not so fast. Recall that the app caches the location of the video on his device–great job, Dima, you just cached the wrong location for this video.
Neither of these is a win. Both solutions work in nearly all cases, but both solutions fall apart catastrophically as soon as outside conditions exit the “developer testing” cycle. You can choose the lesser of two evils, but it’s undeniable that both crashes and corrupted state are terrible things that should be avoided like the plague.
Fortunately, there’s a way to mitigate both. Get in the habit of using assertions (more on that next paragraph), use a crash-reporting tool that lets you upload and test debug builds (Crashlytics, unaffiliated, is what we use for this), and get a few non-coders from the company on it. Non-techies ALWAYS find a bug, and that’s a great thing. Take advantage of it. (I theorize that it’s partly because we programmers have the code we wrote fresh in our minds all the time and we subconsciously try to avoid doing things that will break it. Like a weird survival mechanism.)
Assertions in Swift are dead simple:
assert(condition) is literally all there is to it in the base case. A favorite idiom of ours at Riff is to use
assert(false, "A descriptive error message") so we have a quick-look explanation of what probably went wrong . If you have a void-returning function, you can use the shorthand
return assert(false, "Message") (because technically Void is just a typealias for
() (empty tuple) in swift, it’s a perfectly valid value to pass around.)
As a general guideline before you develop a gut feeling for when to use them, place assertions anywhere you feel “things might go out of whack if the code got to this point”. Be liberal with them, especially around bang (
!) characters (implicitly-unwrapped optionals:
String!, explicit unwrapping:
anOptionalValue!) and guard statements.
The beauty of assertions is that they will cause a crash in debug builds, but aren’t even compiled into release builds, so only your beta testers will be affected by them. That way, you can extract the preemptive issue-identifying power of crashes without having your end-users experience the debilitating experience therein.