NOTE: The second installment of this article, “Controlling chaos: Error Handling in Swift 4 with do, try, catch, defer, throw, throws, Error, and NSError,”, has just been released.
In this tutorial, the first in a series of tutorials, we’re going to discuss the arduous topic of looking for unexpected values, events, and conditions that arise during program execution, using a technique I like to call “error checking.” Today, I’ll concentrate on the reasons why you should check for errors. I’ll mention a number of techniques I use but leave detailed discussion of those techniques and sample code to subsequent articles. The purpose of this tutorial is to convince you to make use of error checking in your apps. You ignore errors at your own dire peril. This is sink or swim. If you put out a crappy app, no one’s going to use it because you’ll get a bad reputation at Internet speed, and employers/customers will be more than happy to leave you behind forever for other app developers who aren’t too lazy to write quality code.
My definition of handling erroneous runtime events in Swift goes much farther than Apple’s “The Swift Programming Language (Swift 4.1)” documentation, which only discusses “Error Handling” through the Error
protocol, which looks like what’s called “exception handling” in many other languages, but isn’t quite as resource intensive. In future articles, I’m going to discuss a variety of methods for detecting in-execution problems by looking at:
- the absence of value,
nil
; - expressions like
if let
; - constructs like
guard
; - handling Swift-defined and Objective-C-defined instances of
Error
andNSError
, respectively; - defining my own error types using the
Error
protocol; - creating failable initializers (
init?
); - using the
defer
construct to perform mandatory cleanup that would otherwise get skipped because anError
is thrown; and, - using best practices during design to avoid having an overbearing amount of error handling code in my projects.
Don’t worry about all this Swift terminology. I will explain everything with a fully-functional app written in Swift 4 in coming tutorials. I will demystify the plethora of error handling-related terms like do
, try
, catch
, throw
, throws
, try!
, try?
, defer
…
INTRODUCTION
When developing software, we’re literally controlling chaos — at least trying to control chaos. Software complexity generally grows exponentially as developers add more code (variables, constants, structures, enumerations, classes, protocols, conditionals, repetitive constructs, etc.) to their projects. Look at the graph here and you’ll see that when a software app approaches just 6000 lines of code, complexity approaches infinity — the unknowable, the utterly unpredictable, the uncontrollable. In this particular case, lives are at stake due to software. Error conditions are just yet another form of software complexity we must deal with if we are to write apps that are well-received by users. Buggy software is never popular.
Sadly, over the course of 30 years of experience, I’ve seen many software developers and entire development shops completely avoid the topic, only responding to errors as discovered by customers. I’ve seen code bases with hundreds of thousands of lines of code where the authors themselves have completely lost track of their own software, can’t remember how the code they wrote last week works, and whose applications are riddled with bugs that can never be fixed. Yet somehow these people can sell this crap to customers (suckers).
What I’m describing is really bad business practice and stressful for all involved in software companies who choose to ignore errors. They’re always running around in circles in panic mode. They’re reactive, not proactive. I’m going to show you proactive best practices in this series of tutorials.
Application state
Just determining the “state” of an app, even a relatively simple one at that, at a specific point in time can be a daunting task. Remember that in computer science, the contents of of an application’s variables at any moment in time during that app’s lifetime, i.e., while the app is executing, determine the state. If you could make a list of all an app’s data at a given point in time, you’d have a snapshot roughly describing what is happening in the app at that moment.
I use the term “roughly” because, computer science definitions or not, an app’s state is more than just a list of its data at a point in time. The context in which the app is running — call it the app’s “configuration” — must also be taken into account. In other words, what particular feature of the app was being exercised when the snapshot of variable values was collected?
Think about a word processing application. Because of multiprocessors and threaded code, the word processor may be doing a number of complex tasks at the same time: the user is dictating text into a new document using a Bluetooth headset and voice recognition; sometimes the user types text with the keyboard (input is not exclusively through the Bluetooth headset); text is being constantly checked for syntax and grammatical errors; the stock image/shapes library was updated by the vendor so the app is now downloading a set of files over the Internet; the social media plugin is monitoring for new likes and tweets on articles the user has previously written and posted using the app; etc.
I think you get the point. A “simple” word processing app is not so simple. In fact, defining all the possible states that such an application can take on is computationally impossible. Most applications nowadays fall into this not-so-simple category. They can enter stochastic (random) states no matter how hard developers try to manage complexity.
It was estimated that Windows 7 contained approximately 40 million lines of code while macOS 10.4 (Tiger) contained about 85 million lines. Estimating the number of possible behaviors which such systems can exhibit is computationally impossible. Remember I mentioned “exponential,” i.e., where complexity approaches infinity. Any application with 40 or 85 million lines of code is infinitely complex. No one can ever know every possible state or behavior that such applications can exhibit. We can only do our best to try to control the chaos. Error checking is one aspect of chaos control.
Does error checking increase complexity?
Adding robust error handling to an app can actually increase complexity, even if done using the most careful methodologies, but only in the short term. Generally, error handling adds more code to the app. It certainly takes time and can lengthen the total time required for application development. The added error checking code can make the core functional code harder to read. There’s just more code to read through in total. There are unintended consequences to consider, too. Depending on how code is structured, some error messages may become obviated because of refactoring, e.g., changing the behavior of code where errors occur. So you’ll probably find yourself refactoring and/or restructuring your error checking code at some point in time.
Explicitly or prima facie, looking at things simplistically, the addition of error checking code increases complexity. But who wants to look at the world from a simplistic and myopic point of view? This isn’t about bean counting and being too lazy to support your own code.
Error checking simplifies development, long-term
How do you think users react to apps that just disappear — close, crash — while they’re using those apps? They delete those apps, find alternative apps in the App Store, and sometimes write bad reviews. There are 2.1 MILLION apps in Apple’s App Store. I bet some other developer has an app that can easily replace yours. Consumers vote with their feet (or fingers in this case, I guess). I’m not making this stuff up.
According to Apple, “In 2017 alone, iOS developers earned $26.5 billion — more than a 30 percent increase over 2016.” When that much money is at stake, companies study user behavior so they can retain customers, not scare them off with crappy, buggy apps. From USA TODAY:
“I recommend avoiding applications with many one-star reviews that reference application crashes, poor battery life or poor user experience…”
From an article entitled “Users Have Low Tolerance For Buggy Apps — Only 16% Will Try A Failing App More Than Twice” in TechCrunch:
79 percent [of users] report that they would only retry an app once or twice if it failed to work the first time.
Only 16 percent said they would give it more than two attempts.
That’s a low enough percentage to having mobile-first companies shaking in their boots pre-launch.
Today, more users have encountered problems with apps than those who have not. Compuware says that 62 percent of users had experienced a crash, freeze or error with an app or apps. Another 47 percent have seen slow launch times. And 40 percent said they’ve tried an app that would simply not launch at all.
That information was gathered from a formal study in which 3,534 people were surveyed. The results were analyzed for statistical significance.
So what about error checking?
Of course error checking simplifies your life. You will have to think about your code more profoundly and meticulously, but that’s a good thing. You’ll capture and retain customers. When there is an occasional problem or crash, you’ll be able to figure out how to solve it because your error checking code will tell you exactly where a bug occurred — and why a bug occurred.
Remember there are other keys to app success, like a great and well-organized user interface, but we’ll discuss those some other day. Take a look at this article: “How to Build Stable, Market-Tested Apps That Users Love.” Guess what is one of the author’s top suggestions: testing, which is a form of error checking.
CONCLUSION
I purposefully didn’t show you any code today. Too many developers want to go straight to the code for writing the app they’ve been tasked with developing. In fact, some would argue that many developers nowadays write code by doing Internet searches, copying code from sites like StackOverflow, and pasting it into their projects.
Yes, there’s a big demand for developers, but don’t ever take anything for granted. Whenever the next economic recession or stock market crash occurs, many of these same people will be out of work. The developers with truly exceptional skills will probably keep their jobs through any dips. The copy and paste developers will be out of work and looking for work and probably not finding work until the next boom when the spigot for seed money for silly startups starts spraying all over the place.
We’re living in an exceptional boom time right now. I’m old enough to remember really rough economic times. Don’t believe the people who say “it’s different this time” because it’s not. There will always be ups and downs.
The question of the day is: Are you a truly talented developer who understands the subtleties of software complexity, like error checking, or are you a disposable copy and paste developer?
See you in the next article when we get into the Swift code for error checking.