Back in the Objective-C days, any object variable could be nil. If you sent a message to a nil pointer, the method basically turned into a no-op (returning nil again, so nested Objective-C calls like [[MYClass alloc] init] would just collapse into a no-op if the first call failed).

This seemed a convenient way to do error handling: Just return nil on error and whoever calls you doesn’t have to handle errors at every step of the way. However, it was also so very implicit, that often programs encountered errors and just … stopped reacting.

Some variable had ended up being set to nil, and the app just kept calling into the void, where nothing happened.

Enter Swift

Swift learned from this, and made nullability an explicit attribute of a variable’s type. If a variable could become nil, the API contract now said so. If you tried to use a nullable value without checking that it wasn’t nil beforehand, that was a compiler error.

But since Swift was used on top of the Cocoa frameworks, which were written in Objective-C, many properties were marked as nullable that actually could never become nil. But since the Objective-C code didn’t mark up nullability, how was Swift to know? So nullability was retrofitted into Objective-C, and the system headers marked up, just enough so Swift could know which properties needed to be optional and which didn’t.

Still, I see a lot of Swift code that basically contains “extra line noise” in the form of nil checks that “should never happen”. Code that is basically replicating Objective-C’s nil-collapsing behaviour in a more verbose way. There is no recovery from these cases. Often they are guard statements that simply abort execution of the current method.

This is good: It is explicit. As soon as you read the line, you see that the function will abort in this degenerate error case.

This is bad: A lot of code that is never run, or is only run in a debug build when you make a mistake editing some code, clutters up the lines of a method, making it harder to follow the actual intent of the method.

Furthermore, methods that return a value often need to “lie” about their result upon such a failure. If they don’t, they in turn become failable (and their return types optional):

If a method they call can fail in the degenerate case, the method around it can fail in that same case. At least in Swift this is now explicit in the code, but it also means that we’ve basically arrived back at procedural C’s error handling, which cluttered up our algorithms, when the reaction to each failure usually ended up being the equivalent of “I’m sorry, Dave. I’m afraid I can’t do that.”

This is why throwable errors (like C++ exceptions and Swift errors) were introduced: To move the (usually identical) error handling out-of-band and reduce the amount of code dealing with error checking in favor of the code actually handling the errors. Our guard or if block is hidden behind that tiny try statement.

I don’t know why exactly, but throwable errors seem to have a bad reputation among Swift programmers. Admittedly, if you look at C++ exceptions, it is very easy to overlook who may throw. But Swift has the try label on failable lines, so can that really be the reason?

Is it a holdover from Objective-C and Swift 1.0, where exceptions were only used for programming errors and system code was simply not prepared to handle them for mostly historical reasons? Is it a dislike for the added exception mark-up that is less subtle than making a variable’s type optional and adds deeper nesting?

Is it maybe an awareness of the fact that, once everything can fail, it becomes hard again to tell what actually will fail, and when, and we’re back where C++ is, where nobody will really deal with failures that “shouldn’t happen”? But isn’t that where we already are with all those optional checks?

Fail Early

I think current Swift code still suffers from being architected like it was Objective-C when it comes to optionals, but while the cognitive overhead of optionals (nil messaging) was comparatively low beyond reading a line and doing a quick “what happens if this goes nil“-check, optionals in Swift cost us time blanking their associated checks out so we can “see the algorithm between them.”

This is odd, given Swift is at its core a language that prefers a high density of operations per statement, and makes a lot implicit (like variable types, through inference).

Whether you like the number of operations Swift crams into a single statement, that is definitely what the language was designed for, and I think we should try to strive for this level of focus in our algorithms as well. And that to me includes designing them to avoid optionals (and their associated error checks) where it furthers clarity. Make it invalid to even start running a method or algorithm before we’ve checked that our parameters aren’t nil. This up-fronts the error checks and chances of failure, instead of littering them all through our code.

Similarly, this should be possible with callbacks. Design our methods so they have separate result and completion callbacks. If there are no results, we then don’t have to give a nil result in a completion block that they have to check for, but rather just don’t call the result callback at all. Alternately, if we’re dealing with collections, we can give empty arrays instead of a nil array. Then anyone iterating over the list just doesn’t do anything.

In its own way, these two techniques are like Objective-C’s nil-collapsing again. Of course, that means it is similarly implicit. What if the loop contains a call that must be done, even for 0 items? Then we would again have to add a conditional for the “0 items” case that covers it. Might as well let the compiler remind us of that, then, and make it an optional.

But where we can front-load these checks, our algorithms can become a lot clearer, simpler, at the back end of the equation. Fewer optional checks. More raw control flow dealing with the core of our functionality.

Optionals as a code smell

Given most projects have existing code that isn’t front-loaded yet, and we’re writing against operating system frameworks that were designed to take advantage of Objective-C’s nil-collapsing, front-loading nil-checks is not something we can just do in a week of refactoring.

But I think all it takes to improve our Swift code is to raise awareness of this issue, to make Swift programmers pause each time before they declare an optional and think:

“An optional! Ewww! Can I somehow make this non-optional or front-load it?”

If optionals are treated like any other code smell, I think our Swift code will put our actual application logic back in the spotlight, and thus help us detect the complex bugs instead of hiding them among lines of “this should never happen” guard statements.