Another take on guard

Today, iOS Dev Weekly pointed to this writeup by Alexei Kuznetsov about eliminating guard from your code. “Why Swift guard Should Be Avoided” posits that Robert C. Martin’s rule of code parsimony (namely, “The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.”) should drive your Swift output.

Kuznetsov writes, “The guard statement is a handy instrument for reducing the number of nested structures in functions. The problem lies not in the instrument itself but in its usage. The guard statement promotes the creation of bigger functions that do several things and act on multiple levels of abstractions. By writing small and focused functions, one can find herself not needing the guard statement at all.”

I’m sharing this article because I courteously disagree with this philosophy and I want to explain the factors that motivate my disagreement.

The Code

In the following snippet, Apple’s original example from the Swift Programming Language book designs a virtual Vending Machine. Its  vend function enables the machine, on successful payment, to dispense an item to a consumer.  If my math is right, the original function included 17 18 lines of code (lines 25-42). This count includes three original guard statements, and four action statements, along with surrounding whitespace:

Kuznetsov refactors Apple’s tutorial vending machine code to eliminate guard statements and minimize each function’s statement count. With respect, I’m not a fan of this refactor. And I want to explain why.

The refactor is too long and complicated.

Kuznetsov primary goal is to promote small function size. However, the refactor has replaced those 17 18 lines with 46, splintering the logic around no fewer than eight separate functions.  In doing so, the update introduced a high cognitive overhead. A simple linear story became a tangled  collection without clear task progression.

In the rewrite, seven dependent calls follow the new vend function. They must be read by moving your eyes and attention down and then up and then down again in order to comprehend the flow as you imagine a user pressing the vend button.

Kuznetsov took one unified function and splintered it. I’m going to quickly throw out a reference to George Miller’s “Magical Number Seven” here not just because eight is significantly larger than one but also because Martin’s rule of functional simplicity attempts to achieve a similar kind of focus, and I think this refactor misses the mark on addressing both concerns.

The refactor treats preconditions as separate tasks

This is the more substantial criticism, which is the misunderstanding of the role of guard. In Kuznetsov’s write-up, guard acts only to reduce nesting. I disagree, and as I pointed out in my write-up the other day, it is an important member of the assert/precondition family: “A general guard statement establishes requirements for execution. It also provides a safe path for transferring control if and when those requirements fail.”

In Kuznetsov’s redesign the assertions are subsumed into a validation tree. A primary function validateItemNamed calls validate. In turn, validate collects calls to each validation step, namely validateCount and validatePrice. I propose that this tree-based layout is hard to read, hard to maintain, and introduces unnecessary complexity.

You must trace the progression of the point of error back to the original try vend call. Insufficient funds that fail in validatePrice travel back to validate, back to validatedItemNamed, and from there fail in vend. It’s a long path for an extremely simple error and it inappropriately separates the validation tasks from the usage tasks.

In Apple’s version, the three guard statements limit access to core functionality by performing sanity checks on input and state before proceeding. Importantly, they act as formal specifications about code prerequisites. By including guard statements with the code they annotate, Apple established a direct relationship between the assertions and actions. The code says, in essence, “if these tests pass, perform these actions.”

Their co-location is vital. At some future code review, these tests can be inspected in the context of the actions and if needed, updated, modified, or removed with ease. Their proximity provides a vital connection to the code they’re guarding.

I would recommend using guard for essential code safety checks. I believe they’re being presented this way in Apple’s version of the vending machine code. To summarize: You may be able to write your way around guard but your code will not benefit from doing so.

8 Comments

  • I courteously agree with you, Erica. I’m a big fan of small functions myself, but I find it hard to imagine a simpler, clearer function than Apple’s version of vend. Starting a function with simple and clear input validation, followed by *four* lines of code, is much easier to understand than the several one and two-line functions that Alexei’s refactor produces. Jumping around from one tiny function to another is painful. It’s one of the main reasons closures are so popular and, in the view of many, so much better than splitting the closure code out into a separate function.

  • I could not agree more with you. Being able to understand the whole function at a glance is critical. Why would one want to separate the validation from the execution? The original code is simple and small enough. One can read the preconditions to understand the expected environment. One of the key value of guard is the clarity of the requirements expressed in a positive way.

  • Agree with you, Erica, and, for the record, lines 25–42 is a total of 18 lines, since you want to include both the start and the end.

    • Unless you place the lines in a circle? (Fixing. Thanks.)

  • Thank you for this article. I’m just beginning to get swift. At least I think so and after reading the article you cite I was being to question my understanding of swift’s design idioms. I had a feeling that it was one of articles in the game vain of those you don’t need goto pieces people used to write. This blog is fast becoming a favorite of mine

  • “The first rule of functions is that they should be small”. If that were true, we’d write in assembly or other mnemonics. In my opinion, the first rule of functions is that they be READABLE. After all, code is far easier to write, than read. And code is far easier to write than MAINTAIN.

  • I am with you here.
    The guard statement is imo “not part of” the function.
    If the function is structured correctly, a reader can simply skip over the guard statements. It should be completely unnecessary to read the guard statements in order to understand the function. Which also shows how I think guard statements should be used. guard statements should not be used to inside the function as part of its prime functional makeup.

  • Very good! I agree with you that functions shouldn’t be too short. Too long is bad as well – it have to be reasonable…