Let’s talk about creating understandable and maintainable code in Swift 4, code that can be easily read by other programmers, can be readily debugged, can be reused, and can be enhanced and extended in the future. We’re going to limit today’s discussion to: 1) how best to come up with good names for functions and how best to name their arguments, parameters, and argument labels, and 2) how to use meaningful and descriptive variable (var
) names — and constant (let
) and enumeration (enum
) names, etc. The goal is to create code that reads as to close to English as possible.
You may believe that this topic is overly simplistic or pedantic, but sometimes it’s the “little things” in life that really matter — especially when you have applications that are made up of millions of lines of code, made up of “little things” like function definitions and function calls.
I’m not a purist who’s bought into the thinking that one can write code that is entirely “self-documenting.” I know from sheer experience that there’s almost always a need for inline code commentary as well as other forms of documentation (e.g., requirements analysis, algorithmic, design, etc.). Programmers who just bang on a keyboard without doing any type of design and planning; who write single functions that go on for four pages; who use variable names like “x,” “y, “z,” and “i;” who never write any inline comments; and who write phrases like “new code” when committing and pushing to their repos are just plain lazy. Their code is very difficult to read, let alone support or, G-d forbid, extend/enhance. I’m a computer scientist.
With all that being said, I do believe that I should still strive to write code that is readable on it’s own without supplementary documentation, fully well knowing there are times, like in a function that realizes a complex algorithm, when inline comments will be necessary.
Functions, functions everywhere
Apple Swift documentation contains a wonderful definition: “Functions are self-contained chunks of code that perform a specific task.” As developers, we spend much of our time writing code in functions — and rightfully so as we’re encoding tangible activities, algorithms if you will, in our space-time continuum.
Note: I’m not going belabor why you should use descriptive variable names. If the reason for doing so is not obvious to you, leave a comment. Hint: variable (var
) names — and constant (let
) and enumeration (enum
) names, etc. — should read like English, too.
Before moving on, let’s agree on what the terms “function name,” “parameter” and “argument” mean because these are a constant source of confusion, especially the later two. Here’s Apple definition, and let’s use that definition throughout this article:
Every function has a function name, which describes the task that the function performs. To use a function, you “call” that function with its name and pass it input values (known as arguments) that match the types of the function’s parameters. A function’s arguments must always be provided in the same order as the function’s parameter list.
I’m going to use real Swift language code, code that can be compiled, to reinforce the meaning of the terms “parameter” and “argument” (and “argument label”). Try this code yourself in an Xcode project or playground.
Swift functions have this structure — and don’t you fret about those danged-ed underscore characters (_
) right now; we’ll get to those later:
1 2 3 4 5 6 7 8 9 10 11 12 |
func functionName( _ parameter1: Int, _ parameter2: Int ) -> Int { return parameter1 + parameter2 } let argument1 = 2; let argument2 = 2 let functionNameReturnValue = functionName( argument1, argument1 ) // 4 is returned, as in functionNameReturnValue = 4 let functionNameReturnValue1 = functionName(2, 2) // 4 is returned, as in functionNameReturnValue1 = 4 |
The gist of Apple’s quote and my code is this: Parameters are placeholders for the values that you pass to your function. Parameters also show how, in the function body, again as placeholders, that the values they represent will be used in the algorithm that your function materializes. Arguments are the actual values, or contain the actual values, that will be substituted into your function parameters when calling your function. Above, for example, the arguments 2
(argument1
) and 2
(argument2
) will be substituted into — passed into — functionName’s parameter1
and parameter2
.
Again, I know this sounds pedantic, but to be the best of the best, you truly need to understand what’s going on in your Swift code. Let me show you some more real Swift code, which you can compile and try, to solidify the “parameter” and “argument” concepts. I’m also introducing you to the “argument label” concept while simultaneously reinforcing the basic Swift function structure:
1 2 3 4 5 6 7 |
func functionName( argumentLabel1 parameter1: Int, argumentLabel2 parameter2: Int ) -> Int { return parameter1 + parameter2 } let sum = functionName(argumentLabel1: 2, argumentLabel2: 2) // returns 4, so sum = 4 |
For me, one of the most important Swift 4 features is “The use of argument labels [which] can allow a function to be called in an expressive, sentence-like manner, while still providing a function body that is readable and clear in intent.”
Functions from the prehistoric to Swift
For those of you who’ve been around for awhile, you’re probably used to seeing function calls like this C language example:
1 2 3 4 5 6 7 8 |
int add(int i1, int i2) { return i1 + i2; } int sum = add(2,2); printf("sum: %i", sum); // Console: sum: 4 |
C is still very useful, even essential sometimes, but in terms of expressivity, ah, it’s OK.
While Swift allows us to write bare-bones, C-like code, it encourages us to write more readable, expressive, meaningful, and maintainable code. Let’s start with what Swift permits in terms of a minimal function definition:
1 2 3 4 5 6 7 |
func multiplyTwoIntegers( first:Int, second:Int ) -> Int { return first * second } _ = multiplyTwoIntegers(first: 2, second: 8) // returns 16 |
Notice what Xcode’s auto-complete shows me when writing a call to the “multiplyTwoIntegers” function:
Notice that, compared to C, Swift spurs me to make my call to the “multiplyTwoIntegers” function more expressive by encouraging me to label my parameters. If I try to call “multiplyTwoIntegers” without parameter labels, the Xcode IDE warns me in the editor (click image to enlarge)…
… and warns me at the console:
1 2 3 4 |
error: missing argument labels 'first:second:' in call _ = multiplyTwoIntegers(2,8) ^ first: second: |
I have to specifically tell Swift to drop the requirement for parameter labels, or rather “argument labels.” To hide argument labels, I use the underscore (_
) character which suppresses them:
1 2 3 4 5 6 7 |
func multiplyTwoIntegers1(_ first:Int, _ second:Int) -> Int { return first * second } _ = multiplyTwoIntegers1(8, 8) // returns 64 |
Notice what Xcode’s auto-complete shows me when writing a call to the “multiplyTwoIntegers1” function:
Watch what the Xcode IDE shows me if I try to use parameter labels (click image to enlarge)…
… and what the Xcode console reports:
1 2 3 |
error: extraneous argument labels 'first:second:' in call _ = multiplyTwoIntegers1(first:8, second:8) ^~~~~~~ ~~~~~~~ |
Getting expressive with Swift
I created a class that allows you to experiment with writing expressive and readable function and variable names, fully recognizing that beauty is in the eye of the beholder. There’s no perfectly “right answer.” There is a strongly creative aspect to software development and the decision as to what constitutes “more readable” code needs to be made based on common sense, taking into account subjective as well as objective criteria.
(Note that I’ve introduced the Swift 4.0.3 concept of a “string that spans several lines… a multiline string literal.”)
Here is some code representing ideas for Swift code readability with which you can review and experiment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
// The reason for moving can provide tax benefits. enum MoveType { case Personal case Work case Military case Clergy } class Person { var firstName:String? var lastName:String? var currentResidence:String? var lastResidence:String? var purposeForMove:MoveType? // For itemizing deductions when filing taxes. var canItemizeMovingExpenses:Bool? // This is overly verbose to me. func changedLastNameTo(newLastName:String) { lastName = newLastName } // This is slightly less verbose. func changedLastNameTo(_ newLastName:String) { lastName = newLastName } // This is my favorite. func changedLastName(to newLastName:String) { lastName = newLastName } // This is overly verbose to me. func movedFrom(oldPlaceName:String, newPlaceName:String) { currentResidence = newPlaceName lastResidence = oldPlaceName } // This is a favorite. func moved(from oldPlaceName:String, to newPlaceName:String) { currentResidence = newPlaceName lastResidence = oldPlaceName } // This reads like a phrase -- especially with // other class code. func moved(from oldPlaceName:String, to newPlaceName:String, becauseOf reason:MoveType) { currentResidence = newPlaceName lastResidence = oldPlaceName if reason == MoveType.Work { canItemizeMovingExpenses = true } else { canItemizeMovingExpenses = false } } func showInfo() { // Swift 4.0.3 Multiline String Literals let info = """ First name: \(firstName!) Last name: \(lastName!) Current residence: \(currentResidence!) Last residence: \(lastResidence!)\n """ print(info) } } // end class Person var person = Person() person.firstName = "Susan" person.lastName = "Anthony" // Last name: This is overly verbose to me. person.changedLastNameTo(newLastName: "Johnson") // moved from: This is overly verbose to me. person.movedFrom(oldPlaceName: "Malibu", newPlaceName: "Chicago") person.showInfo() // Last name: This is slightly less verbose. person.changedLastNameTo("Thomas") // Last name: This is my favorite. person.changedLastName(to: "Martin") // moved from: This is a favorite. person.moved(from: "Chicago", to: "Atlanta") person.showInfo() // moved from: READ or step into this function // to see English-like code. person.moved(from: "Atlanta", to: "St. Louis", becauseOf: MoveType.Work) person.showInfo() print(" Itemize moving expenses? - \(person.canItemizeMovingExpenses!)") |
Console output from the code shown above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
First name: Susan Last name: Johnson Current residence: Chicago Last residence: Malibu First name: Susan Last name: Martin Current residence: Atlanta Last residence: Chicago First name: Susan Last name: Martin Current residence: St. Louis Last residence: Atlanta Itemize moving expenses? - true |
Documenting Swift functions
When discussing our Swift code in written format, say in emails between developers or in formal design and/or code documentation, there are some conventions to follow. For this function:
1 |
func multiplyTwoIntegers1(_ first:Int, _ second:Int) -> Int |
we would write multiplyTwoIntegers1(_:_:)
.
For this function:
1 |
func multiplyTwoIntegers(first:Int, second:Int) -> Int |
we would write multiplyTwoIntegers(first:second:)
.
For a function signature with mixed arguments, like Apple’s
1 |
optional func collectionView(_ collectionView: UICollectionView shouldHighlightItemAt indexPath: IndexPath) -> Bool |
where we’re omitting the first argument’s label but providing a label for the second parameter, we would refer to this function as collectionView(_:shouldHighlightItemAt:)
.
What the future holds
So we’ve explored Swift’s expressiveness to achieve the lofty goal of writing code that can be read without supplementary comments. We’ll, er, ah, work on that one. As I said, give me a call when you can maintain or enhance projects with hundreds of thousands of lines of code with no supplementary documentation, whether code comments or meta-documents. We are an evolving species (hopefully). What’s coming down the pike years ahead?
We’ve seen it or read about out it in science fiction: computers that can fully understand the syntax, semantics, grammar — all the subtle context and nuances of human language. How many of you have watched, for example, Star Trek, and seen a character like Geordi create a simulation to solve a problem? See the YouTube clip below. He actually creates software-based simulations within the holodeck, itself a simulation. Geordi creates programming logic just by talking. And he falls in love with a simulation of a “real-life” engineer, Dr. Leah Brahms. Hey… whatever cranks your gears…
And all we’re trying to do in this article is write code that reads like English. Software that can understand human language and turn it into code? That time is not now. We are working towards that lofty goal, but technology keeps getting more and more complex, and for the foreseeable future, we’re going to need people who understand how to solve problems by creating software solutions. Don’t worry about your jobs just yet.
Wrapping up
I hope you enjoyed today’s discussion. Remember that our goal is to create code that reads as to close to English as possible.