Does it surprise you that I’m dedicating a tutorial to a simple addition as the guard
statement? I hope you’ll better understand my excitement at the end of this tutorial. During this tutorial, I hope to convince you that guard
is anything but a redundant addition to the Swift programming language.
Minimizing Complexity
Conditionals are a fundamental component of every programming language. Objective-C and Swift are no exception to this rule. If you plan to write an application of any complexity, conditionals will cross your path, there’s no escaping them.
Unfortunately, conditionals are often the very cause of complexity. Nested conditionals in particular can lead to difficult to find bugs, hard to understand code, and easily overlooked edge cases.
To keep nested if
statements to a minimum, I frequently use the following pattern in Objective-C.
- (void)fetchListOfCustomers:(NSArray *)customers { if (!self.reachable) return; if (!self.connected) return; if (!customers || ![customers count]) return; ... }
The idea is to bail out as soon as possible. The if
statements in the example represent a set of requirements that need to be met before the rest of the method’s body is executed.
The above example translates to the following slightly more complex equivalent.
- (void)fetchListOfCustomers:(NSArray *)customers { if (self.reachable && self.connected) { if (customers && [customers count]) { ... } } }
Do you see the problem lurking in this example? We’re already nested two levels deep without having done anything interesting.
It’s easy to translate the above pattern to Swift. The syntax looks similar, but due to customers
being an optional, we need to unwrap the customers
argument before we can access its value.
func fetchListOfCustomers(customers: [Customer]?) { if !reachable { return } if !connected { return } if let customers = customers where customers.count > 0 { print(customers) } }
Exiting Early
Swift 2 introduces the guard
statement. It was designed specifically for exiting a method or function early. The guard
statement is ideal for getting rid of deeply nested conditionals whose sole purpose is validating a set of requirements. Take a look at the updated example in which I’ve replaced every if
statement with the new guard
statement.
func fetchListOfCustomers(customers: [Customer]?) { guard !reachable else { return } guard !connected else { return } guard let customers = customers where customers.count > 0 else { return } print(customers) }
There are several things worth noting. Let’s start with the syntax.
Syntax
The guard
keyword emphasizes that we are validating a requirement. We are guarding against something. In the example, we explicitly check if reachable
and connected
are true
. If they aren’t, then we exit the method early. The point is that the syntax is more explicit about the requirements than a regular if
statement.
Exit
Note that a guard
statement always has an else
clause. The else
clause is executed if the condition of the guard
statement evaluates to false
. Using guard
makes much more sense when you’re validating requirements.
In the else
clause, you must transfer control away from the scope in which the guard
statement appears. We use a return
statement in the above example, but you could, for example, use a continue
statement if you’re in a loop or throw an error. Take a look at the updated example below in which we throw an error in the else
clause. Note the throws
keyword in the method declaration to indicate that fetchListOfCustomers(_:)
is a throwing method.
func fetchListOfCustomers(customers: [Customer]?) throws { guard !reachable else { throw APIError.APIErrorUnreachable } guard !connected else { throw APIError.APIErrorNotConnected } guard let customers = customers where customers.count > 0 else { throw APIError.APIErrorNoCustomers } ... }
Powerful
A guard
statement is just as powerful as an if
statement. You can use optional bindings and even the use of where
clauses, introduced in Swift 1.2, is permitted. I’m sure you agree that the example is easy to understand, eliminating unnecessary nested conditionals.
Scope
An important difference with if
statements is the scope of variables and constants that are assigned values using an optional binding. In the above example, the customers
constant was assigned a value using an optional binding. The customers
constant is accessible from the scope the guard
statement appears in. This is an important detail and one of the key advantages of using guard
.
Conclusion
If you thought that guard
was a simple variation on Swift’s if
statement, then I hope I’ve convinced you otherwise. While if
statements will continue to be your tool of choice in most situations, guard
has a number of advantages in certain situations. This is especially true if used in combination with error handling, which was also introduced in Swift 2.
by Bart Jacobs via Tuts+ Code
No comments:
Post a Comment