I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
Clean Coding in Swift – Functions
I’ve been thinking a lot about how the principles of clean coding (Bob Martin’s “Clean Code”) apply in Swift. How do I express clean code in this language? Conversely, how do I avoid writing cryptic code in Swift? What language features help me write clear and self-explanatory code and what language features present the potential for tempting me to write obscure code in Swift?
I am beginning a commentary series that I hope will encourage clean coding practice in myself and in the Swift developer community. Dialog on these topics is welcomed – I have not “arrived”, so please – help me help the Swift community for the better!
The first in the series is on writing clean functions in Swift. As it turns out, Swift provides some really great mechanisms for defining self-explanatory, clear-purposed functions. We’ll start by analyzing the features around naming functions and their parameters, and will conclude on thinking through function decomposition.
Function Names
Something that I really enjoyed about Objective-C was that while method names were often long and verbose, they were extremely descriptive. My code often read like a narrative, and I enjoyed that. Some may have hated it, but I found it extremely helpful in facilitating my recollection of a method’s intended purpose and the expectations around its argument requirements.
I do my best to think hard about the names that I give my functions, ascribing to them a name that is specific, targeted, and focused on the single “thing” that each one does. Nothing in Swift mandates that we leave the verbosity of Objective-C naming conventions behind. In fact, the exact opposite is true! One example is that swift intentionally provides us with the ability to add external parameter names so that we can be as descriptive as we need to be about the names of our functions.
Parameter Names
Swift’s external parameter names, in my opinion, are wonderful for helping write self-documenting code. They simply help guide my mind back to what a given function does and needs in order to do its job. Anything to help me get back in the zone is worth spending a few extra keystrokes on, especially since Xcode’s auto-completion assists me so well when calling functions with long signatures.
Apple recommends the following:
Consider using external parameter names whenever the purpose of a function’s arguments would be unclear to someone reading your code for the first time.
I would only add that sometimes, that someone “reading your code for the first time” is you. Not technically, of course, but think about this: Write some code, leave it, and come back to it some time later. Will it make sense? Will you immediately go, “Ah, I know what I meant there”, or will you do like I’ve often done and say, “WHAT in the WORLD was I THINKING??!?”.
Spending a few seconds on typing a few more words to save brain power and potentially more seconds/minutes some time later is a worthy investment. And if you work on a team, your teammates will appreciate the extra care you put in to providing as many hints as possible through inventing good names. Good naming includes parameter names. Why not take advantage of the fact that Swift provides you this opportunity to write self-documenting code?
Function Decomposition
In order to make sure our functions are doing “one thing”, we need to make sure that the statements within our function are all at the same level of abstraction. -Bob Martin, Clean Code pg. 36
This is just general advice without respect to a specific language. A couple of things to think about:
- Watch out for key words like “and” / “or” in your function names. These red-flag words often indicate that your function is doing more than one thing and can be further decomposed. Consider:
1func washAndDryCar() {
2 // Logic to wash and dry a car
3}
A preferred decomposition could look something like this:
1func washCar() {
2 // Logic to wash a car
3}
4
5func dryCar() {
6 // Logic to dry a car
7}
- Doing one thing can still involve multiple steps. But if each step takes a few steps of its own, that group of steps can be extracted out into another function. Consider:
1func washCar() {
2 // Pre-rinse code (example implementation - 4 lines)
3 // Soap code (example implementation - 5 lines)
4 // Rinse code (exmple implementation - 4 lines)
5}
A preferred decomposition might look something like this:
1func washCar() {
2 rinse(durationInSeconds: 25.0)
3 soapCar()
4 rinse(durationInSeconds: 60.0)
5}
What’s neat about the final result of the decomposition is that not only is my washCar
function shorter and more readable (from 13 lines to 3 lines), but I got code re-use by making the rinse<
function take a duration argument.
Hopefully these thoughts spark a few ideas in you. Constructive feedback is welcome! How are you thinking about writing clean code in Swift?