Controlling chaos: Error checking in Swift 4 with if let, guard, and failable initializers

Swift tutorials by iosbrain.com In this tutorial, the third in a series of tutorials, we’re going to finish 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 nil values, optionals, optional binding, the guard statement, failable initializers, and finally, give you some advice about keeping your error checking code consistent, for example, when to use Swift “Error Handling” or when just to return true/false or use guard statements.


Remember that in the first tutorial, I concentrated on why error checking is so important. We talked about software complexity (chaos), measuring complexity, and how unchecked complexity can easily lead to buggy software that will put you out of business in a heartbeat. Remember that in the second tutorial, I showed you how to implement error checking in terms of what Swift’s authors call “Error Handling,” i.e., the use of do, try, catch, throw, throws, try!, try?, defer, Error, NSError

This article is also a sequel to a tutorial I wrote on “iOS file management with FileManager in protocol-oriented Swift 4,” where I promised to add error checking to my code. iOS file management code absolutely needs error checking because of the nature of file manipulation:

… consider the task of reading and processing data from a file on disk. There are a number of ways this task can fail, including the file not existing at the specified path, the file not having read permissions, or the file not being encoded in a compatible format. Distinguishing among these different situations allows a program to resolve some errors and to communicate to the user any errors it can’t resolve.

With today’s discussion, we’ll have rounded out the list of tools for detecting in-execution problems:

REFACTORING MY CODE TO USE ERROR CHECKING

Let’s start refactoring my protocol-oriented iOS file management code to use error checking. I originally presented and explained the code here and made it available for download from GitHub here. I originally left out error checking so the code would be readable and you could concentrate on the file system and protocol-oriented programming. While the adding of error checking in this section may seem obvious and or trivial to some of you, so be it. After working in and consulting in the field of computer science for 30 years, I constantly have seen, and constantly do to this day see, thousands of lines of code written without error checking. It seems that many developers and/or development boutiques are happy to eat and waste thousands (or more) of dollars and thousands (or more) of hours — and lead very stressful lives. They waste time and money which could be much better well spent on positive activities like continuing education, market research, and exploring new product features rather than taking a bit of time to be fastidious and write good, solid code.

I write this blog in the hopes of reaching those willing to succeed and willing to become the best.

Error Handling

I covered Swift Error Handling in-depth in my tutorial “Controlling chaos: Error Handling in Swift 4 with do, try, catch, defer, throw, throws, Error, and NSError.” With this technique, you can gather much information about an error, such the file name, line number, and function name in which it occurred, as well as the error’s reason.

Optionals, nil, and if let

One convention adopted by Swift’s designers is the almost axiomatic assumption that failure is associated with nil, “a valueless state.” Another reason for supporting nil is convenience: a certain operation sometimes requires specific information, like in the form of an argument, and at other times doesn’t need that information. So we declare that argument as an optional and when not needed, let it contain nil during app execution. According to Apple:

You use optionals in situations where a value may be absent. An optional represents two possibilities: Either there is a value, and you can unwrap the optional to access that value, or there isn’t a value at all.

Let’s refactor my method for getting the iOS Documents/ directory. Apple states that you should “Use this directory to store user-generated content.”

Here’s my original code with error checking left out for didactic purposes (I wanted you to concentrate on base functionality, not distract you with other details):

Notice that I force unwrapped an optional for expediency (first!), though that act in production would be fraught with danger. Let’s first handle errors with optional binding, defined by Apple thusly:

You use optional binding to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable. Optional binding can be used with if and while statements to check for a value inside an optional, and to extract that value into a constant or variable, as part of a single action.

Here’s a simple example of optional binding using if let in a method:

Here are a few statements testing my ifLetCoordinate function. The output from each statement is shown as a comment immediately following that statement:

Back to my file system use case … Before trying to access an optional (i.e., URL?) that could be nil and thus crash our app, we unwrap that optional to see if it has a value or has no value:

Some have complained that optional binding is a bit awkward, as the optional’s unwrapped value, if not nil, is only available inside the body of the if let statement. If you have a method that has to check a bunch of values, and those values happen to be all non-nil, then you have to squeeze all your functionality into that if let block.

The fact that the documentsDirectoryURL() method now returns URL? warns the developer making use of this function to check the returned URL before assuming it has a valid value. Notice that my iOS file management code already contains several abstraction mechanisms for the documentsDirectoryURL() method, wrappers like getURL(for:) and buildFullPath(forFileName:inDirectory:), and that at even a higher level than this API, I’d minimize the need for the developer for having to use optional binding (if let) over and over again. We’ll talk about that later.

Optionals and guard

Some say the guard statement or “early exit” is more intuitive than the if let statement. Here’s my original method refactored for guard instead of if let:

From Apple:

A guard statement, like an if statement, executes statements depending on the Boolean value of an expression. You use a guard statement to require that a condition must be true in order for the code after the guard statement to be executed. Unlike an if statement, a guard statement always has an else clause–the code inside the else clause is executed if the condition is not true.


Here’s a simple example of optional binding using guard in a method:

Here are a few statements testing my guardCoordinate function. The output from each statement is shown as a comment immediately following that statement:

Here’s a simple example of optional binding and tests for non-zero values using guard and the > operator in a method:

Notice that I separated the optional binding and greater than operator tests with commas. Here are a few statements testing my guardCoordinateNonNegative function. The output from each statement is shown as a comment immediately following each statement:

Back to my initial file system guard example above, if the guard condition is true, i.e., the documentsDirectoryURL value is not nil and contains a valid URL, all the code after the guard closing brace (} // end guard) is executed… and all the optionals I unwrapped using optional binding in the guard statement are available until the closing brace of my documentsDirectoryURL() method.

If any of the guard conditions fail, its else statement is executed and that “branch must transfer control to exit the code block in which the guard statement appears.” So I get all my error checking over with first, and if everything’s cool, I get to execute code with my unwrapped optionals. As Apple puts it:

Using a guard statement for requirements improves the readability of your code, compared to doing the same check with an if statement. It lets you write the code that’s typically executed without wrapping it in an else block, and it lets you keep the code that handles a violated requirement next to the requirement.

By putting a guard statement at the beginning of a block like a method, the opportunity is given to check for preconditions and exit almost immediately if some of those conditions are not met. If all conditions are met, the bulk of the the block’s functionality forms the rest of the method, so the issue of having one extra exit point near the top of the method keeps at least the spirit of a method having one entry point and one exit point alive.

Failable initializers

Suppose I write an app that is utterly and completely dependent on my protocol-oriented iOS file management code. In other words, my app is useless without the iOS file system and the capability for individual file manipulation… And/or just suppose I want to make it very simple for a developer to determine if the iOS file system on the host device is working properly and if he/she can easily make use of my iOS file management code? What about creating a class, struct, or even enum that has a failable initializer? From Apple:

It is sometimes useful to define a class, structure, or enumeration for which initialization can fail. This failure might be triggered by invalid initialization parameter values, the absence of a required external resource, or some other condition that prevents initialization from succeeding.

To cope with initialization conditions that can fail, define one or more failable initializers as part of a class, structure, or enumeration definition. You write a failable initializer by placing a question mark after the init keyword (init?). …

A failable initializer creates an optional value of the type it initializes. You write return nil within a failable initializer to indicate a point at which initialization failure can be triggered.

In the case of my iOS file management code, the failure I’m looking for would be the lack of an essential resource, the iOS file system (for a variety of reasons, including device hardware problems). After everything we’ve discussed in this tutorial, all the examples I’ve provided, and all the references (hyperlinks) I’ve given you, do I really need to explain the following code? Note that I’ve abbreviated my original protocol-based code for didactic purposes — i.e., so you can concentrate on checking for nil values and concentrate on how a failable initializer works. Here’s some working code that you could pop into an Xcode playground and see work immediately — and note the failable initializer on line 30:

Here’s the output (below) from the last 10 lines of sample code (shown immediately above):

Note that this code was run on macOS for simplicity’s sake, but will run fine under iOS.

CONSISTENCY WHEN DESIGNING ERROR CHECKING

I’m going to mention some inconsistencies in Apple’s error checking. I do so not to criticize Apple, but to try to give you some sound advice when designing the error checking you write in your own code.

Consider an operation (method) that depends on what could be relatively easily-expressed erroneous input, like a String argument that is typed in at the keyboard by the developer, is considered suspect, prone to failure, and thus, if it fails, it returns nil. A good example is the macOS-based instance method of the FileManager.default singleton named homeDirectory(forUser:), which “Returns the home directory for the specified user,” and that username is specified by some developer typing in the userName argument. Why only return nil if, say, the user’s account was deleted by an administrator during app execution, or if the username was misspelled? All’s we’re told is nil. That doesn’t help a developer understand why her/his attempt to obtain a user’s home directory failed.

Consider another operation (method) that depends upon possibly erroneous input, also a String argument that is typed by the developer, in the macOS-, iOS-, tvOS-, and watchOS-based instance method of the FileManager.default singleton named contentsOfDirectory(atPath:). If the developer types in a bad value for the path argument, or, say, the file system is corrupted, this method throws an Error (or NSError). The Apple docs state that “this method returns a nonoptional result and is marked with the throws keyword to indicate that it throws an error in cases of failure.”

OK, so what’s the difference between the two methods? I can envision use cases in which the failure of either method or both methods could be catastrophic or non-catastrophic to an app’s performance. Did Apple flip a coin? More likely Apple is in the process of living in a very competitive world, getting out software as fast as it can to keep up with competitors, and doing its best to standardize things like error handling over the long term. That’s all speculation.

What should you do? I would advise flexibility. No need to use a jackhammer to drive in a finishing nail; conversely, don’t try to pry up a railroad tie with a simple hammer. When designing straightforward methods whose purpose is singularly well-defined and confined to a very small scope, you can get away with returning nil if a method fails. When dealing with a mission critical operation that has to be confined to one atomic call, has many possible points of failure, and is complex, mark your method with throws and, in the case of failure, create an Error (or NSError) instance with very specific information about why the method failed, if it fails.

There’s one more possibility worth exploring. What about methods that return a Bool, true when succeeding and false when failing? Understanding true is easy; understanding false is not always so easy. Consider another operation (method) that depends upon possibly erroneous input, a String argument that is typed by the developer, a Data? argument constructed or obtained by the developer, and a file attributes argument (including FileAttributeKey) as specified by the developer. The parameters are passed to the macOS-, iOS-, tvOS-, and watchOS-based instance method of the FileManager.default singleton named createFile(atPath:contents:attributes:). If the developer types in a bad value for the path argument, constructs invalid (like badly encoded) data, creates conflicting file attributes or, say, the file system is corrupted, this method just returns false. That’s not very helpful, especially if this call crashes on some device on the other side of the world and all the developers get is an email reporting a crash when a user tries to save a file, and of course, no crash log is included.

If you’re writing a method like this one, don’t just return true or false, mark your method with throws and, in the case of failure, create an Error (or NSError) instance with very specific information about why the method failed, if it fails.

CONCLUSION

There is one more thing I’d like to discuss eventually: using best practices during design to avoid having an overbearing amount of error handling code in projects. This is quite a broad topic, but I believe I can convey to you my successful techniques for proactive and prophylactic design and coding.

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?

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

Leave a Reply

Your email address will not be published. Required fields are marked *