Don’t write if-statements

How to be a better Swift Developer in 1-rule

All iOS developers have written (or come across) the thousand line view controller. It’s hard to reason about, hard to add new features, and bug-prone. It’s clear that it is trying to do too much — but how to stop this mess before it happens?

The one rule: Don’t write if-statements.

Each if-statement adds an extra branch, and an extra level of complexity when attempting to read the code. More broadly, they hint at the bad pattern beginning to form: this one function is trying to do two different things, depending on the input. Before it gets out of hand, ask yourself why you are trying to write it this way. Is it:

The simplest reason for needing an if-statement is to do error or conditional checking: making sure that the inputs are within the specified range. You’ll see code like:

// DON'T
func playVideo()
if video.url == nil {
} else {
/* your code here */
}

Instead, use guard statements and early returns, which will drastically improve the readability of your code. At the beginning of your functions, you can guard for only acceptable inputs — then the rest of the function becomes clean because it only has to deal with the happy path.

// DO!
func playVideo() {
guard video.url != nil else { return }
/* your code here */
}

When you find yourself checking more errors in the body of the function based on some computation, that’s another hint to break up the function to use the guard pattern once again.

Even the if let pattern for safely unwrapping nullable variables are better served by using guard let instead, and early returning and error checking when something is unexpectedly null.

Laying UI out conditionally is one of the biggest red flags in iOS development, and is a huge source of UI bugs. Consider the following:

// DON'T
class UserProfilePictureViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (isUserActive) {
/* layout user profile */
} else {
/* layout */
}
}

Using flags to layout adds exponential complexity to your code, and leads to bugs where features flicker on and off unexpectedly. It also becomes much harder to add new functionality, as you must consider both sides of each flag, and make sure not to accidentally break something that was relying on the flag. Finally, it leads to huge code bloat: two very different pieces of layout logic are living right next to each other, making it challenging to skim the code.

Instead, factor the functionality into a subview. Rather than switching in the layoutSubview, you should have two different views depending on the functionality. When the mode changes, so should the view.

Often you’ll need flags in your viewcontroller to determine state. If the user is logged in, do one thing, otherwise do another. However, this quickly breaks down as soon as you have even two flags: each branch needs to worry about each additional flag! The complexity increases quadratically.

// DON'T!
let label = UILabel()
if (isInEditor) {
label.text = "Edit your Photo"
} else {
label.text = "Publish your Photo"
}

Rather than checking the flag and switching on it, you can create a state data model. Use an enum and then set variables in them, depending on the state of the users activity.

// DO!
enum PhotoEditingState {
case editor
case publish

var title : String {
get {
switch self {
case .editor:
return "Edit your Photo"
case .publish:
return "Publish your Photo"
}
}
}
}

Now rather than adding an if statement inside of your view controller, you can abstract the differences in functionality out into the model. It is much easier to reason about logic when it is contained inside of a data model.

Even better, this pattern results in super readable code. Inside of a viewcontroller, you can simply ask the data model questions:

let label = UILabel()
label.text = state.title

Finally, this pattern is very easy to extend. If you add a new screen in your photo editing flow, you can simply add a new case in the enum and fill in all the variables — the compiler will even make sure you are handling all the cases! If you add a new button that needs to change on the state, it’s as simple as adding a new variable in your state model, and once again, the compiler will make sure you’ve filled it in for each possible state.

Like any good rule, this one is also made to broken. Timelines and deadlines will make it more appealing to tack on just one more feature into an existing one — and sometimes it can be the right decision in the moment. But it always degrades the quality of the code.

--

--

Lessons learned from building iOS apps at scale. Twitter: @nickconfrey

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nick Confrey

Lessons learned from building iOS apps at scale. Twitter: @nickconfrey