Clean Swift

Functions

Alp Avanoğlu
6 min readApr 2, 2018
Photo by Barn Images on Unsplash

Originally published on Swift Post.

Please remember how elegant the swifts above look for future references.

Building Up

Starting to build an argument with an analogy, probably is not the right way to go. Right now, I feel rebellious: Functions are like omelettes; you may think you have mastered it after cracking hundreds of eggs. How complex could it be? It is just eggs, some cheese, mushroom or greens and spicing right? And yet, after the first bite of a skilled chef’s omelette, your nihilist thoughts and beliefs are replaced with high hopes towards humanity, love of nature and respect to the chicken which produced that gold nugget.

I am going to argue that writing functions is one of the most important tools you have in your toolbox which is yet to be mastered. So as a software developer, you should sharpen it everyday until the blade is formed out of a single line of atoms.

Full Disclosure

The ideas I discuss here are mostly built over the famous book Clean Code by Robert C. Martin. So this writing is my take on the subject backed by examples in Swift.

Symptoms of a Bad Function

A bad function…

  • Is big. Big is of course relative so let’s define: Bob states that 5–6 lines per functions is a good limit.
  • Takes too many arguments. One of the 7 deadly sins is writing more than 3 argument function.
  • Is Undescriptive. Named poorly.

Fat Functions

Pigeons, I hate pigeons.

Hard to understand, impossible to modify without producing eighty six bugs, cannot be tested, looks ugly and smells like a pigeon who is at 6th grade and have just finished playing football with his buddies on lunch break. So when you write fat functions, you should feel bad. You should feel bad as if you have just devoured a giant Big Mac at 1:30am in the morning and you were not even hungry.

First of all, a fat function almost definitely does more than it’s name states. Which means we have to read through the implementation to understand it. And when modification is necessary, there is a good chance that you produce unintended behavior in other parts of the function by say mutating a condition.

Here is a sample fat function:

This function will need refactoring when changes to following analysis occur:

  1. Username and password validation.
  2. Alert presentation and its texts.
  3. How we authenticate users.
  4. Creation and navigation to ProfileViewController.

We can conclude that our pigeon breaks the Single Responsibility by at least 4 factors.

Let us examine the above function piece by piece.

guard let username = username,
let password = password else { return }

It first checks if both inputs are not nil. If one of them is nil, the function does nothing. Which makes an assumption about how the function should be used. Instead this function must accept non-optionals so that nobody can feed it nils, thus removing the possibility of dummy calls. Another solution might be to rename the function to indicate that there is also a validation within the function.

if username.count < 3 || password.count < 6 {
...
}

This is a validation check and it should not be implemented in this function. Apart from that, the condition deserves its own function. Something like this would suffice:

func areCredentialsValid(username: String, password: String) -> Bool

Why? Because we want our code to be self documented. It is much easier to read documentation than it is to read pure-logic code. The above signature is a named version of the condition and we should not care about how that validity is computed as callers of that function. Now its name is a documentation and it is encapsulated.

let alertController = UIAlertController(
title: "Invalid credentials",
message: "Please check your username and password",
preferredStyle: .alert
)
// Add action
present(alertController, animated: true, completion: nil)

This should be extracted also under a new function. It has a high possibility of reuse. Both creation and presentation parts may be used separately throughout the application. We better move it under a helper or an extension (possibly UIViewController).

Finally the piece that this function really needs to implement:

userService.authenticate(
username: username.trimmingCharacters(in: .whitespaces),
password: password
) { [weak self] in
let profileViewController = ProfileViewController()
self?.navigationController?.pushViewController(
profileViewController,
animated: true
)
}

Authentication call is what this function should solely do. profileViewController creation and navigation should also be extracted. We should prefer single line function calls in if conditions, switch cases, closures and so on.

An improved version might look like this:

Notice how the above code reads from top to bottom; narrating its intent by the signatures. Four listed points are now separated and will require modification only in its respective function, which makes our function(s) closer to Single Responsibility Principle. However, validateCredentials function seems problematic because it also handles alert presentation. Which is actually the result of our view controller’s attempt to deal with both logic and UI.

Prefer piranhas over a shark (many pieces over one big piece). It is easier to control small parts separately. Be Sarah Kerrigan, and rule the Swarm!

Too Many Arguments

Photo by John Carlisle on Unsplash

As the number of arguments a function accepts increases, it becomes less likely that function conforms to SRP. What’s even worse is the unused arguments. It happens very often in such functions that someone refactors and deletes a line, then forgets to remove the argument which was used only at that line. Just like a fat function, it is very very hard to write tests for it since you need to cover all the possible combinations for all those arguments.

So how many arguments a function must accept at tops? Bob draws the line to 3. Every time you write a function with more than one argument, check to see if you can make a logically cohesive whole out of those arguments into one type. If that is the case, merge them. So for the example above, we can merge username and password into a single type called AuthenticationData.

struct AuthenticationData {
let username: String
let password: String
}

The act of wrapping arguments has the same logic behind it with creating functions for condition checks. We want to replace the programming language with native language, to make sense of the code in a more humane manner. So naming is crucial.

Even though we are Software Engineers, we are humans first and we think and conceive the universe with our native languages. We would just use assembly or machine language instead of high level programming languages, if reading code was easier for the mind. Avoid poor naming as a poet avoids weak words.

Descriptive Names

Do not perform more than you state! Just like a book author, think about the title of the book (function signature) after you finish the rest. Unfortunately we name the function first and then implement it. But the bright side is, we write small functions which makes it much easier to implement it in our minds even before we write it. When you are done with the implementation, check if the function name really states what it does. Not more, not less. A function named fetchUserDetails must only retrieve user details. It must not filter anything, or mutate unrelated class properties. It must not encrypt and store user data. If expecting such behavior out of function’s name does not makes sense, either change the signature or the implementation.

The French Omelette

Photo by Katie Smith on Unsplash

A codebase with finely crafted functions will at worst be a readable one. It would rarely require more than 15 seconds of your time to understand a 5 line function with 0, 1 or even 2 arguments. And you won’t even need to read through the implementation if it has a descriptive name that really does what it states.

Practice makes it perfect, if you accept the room for progress.

--

--