LearnUnderstanding The New Swift 3 API Design Guidelines

Photo by Oliur Rahman / CC0
   
Treehouse

Treehouse
writes on September 7, 2016

In a previous post, we talked about some of the changes that came with the release of Swift 3. The changes with potentially the largest impact on most developers are the new Swift 3 API Design Guidelines. Seemingly simple at first, the changes involve rethinking nearly all of the API in our Swift codebases.

All of these changes are well documented on the API Design Guidelines page but it can be overwhelming, so let’s take a look at the major sections. Swift 3 places utmost importance on the clarity of code. As we explore many of the new rules, keep these guidelines in mind:

Code is written once but read often. It’s important that Swift code is readable and clear at the point of use. While this seems like a familiar edict, it is a goal that differs in its application, say compared to Objective-C. We don’t want to adopt Objective-C’s verbose style because while all of the necessary information is conveyed through well-designed API, it rarely ever is readable at the point of use. It’s not enough in Swift that our API reads well at the point of definition; it has to read well and make sense in its various uses. We’ll see what this means in a bit.

At the same time, we also don’t want to swing to the opposite end of the pendulum. Swift code is compact compared to Objective-c, but that is not a goal in API Design. Clarity is more important than brevity. When designing our API, our code will naturally be compact through certain features of the language itself, but we shouldn’t try to explicitly make this so in our design decisions.

Start learning to code today with a free trial on Treehouse. 

Naming

Figuring out what to name things in code, and doing it well, is not easy. Swift 3 aims to make the task somewhat less difficult with a set of rules: some general, some quite specific. Let’s start with the general ones first.

Promote Clear Usage

Unlike Objective-C, redundancy is a non-goal. Yes, we should strive to make our code unambiguous and clear, but any needless words have to go. Keep in mind, these naming decisions apply to code at the point of use. We’re going to use the standard library for most of our examples since they represent the intended application of the guidelines.

Let’s look at a couple examples.

let someString = "a String "
someString.stringByAppendingString("anotherString")

Here we have a bit of code in Swift 2, which is essentially an Objective-C API imported in a more compact format. We have a string and we’re appending another string. Let’s look at the same code in Swift 3.

var someString = "a String "
someString.append("another String")

The same method here is quite different. Let’s break it down. As per Swift 3’s guidelines, we don’t want to be redundant. The method signature in Swift 2 looks like this: func stringByAppendingString(aString: String) -> String

The method has a return type of String so it’s already obvious that we’re going to get a string back. This makes the initial word “string” in the method name redundant. The method also takes a parameter of type String, so it’s pretty obvious we’re going to append a string which makes the last bit of the method name redundant as well. This leaves us with “byAppending” which becomes append in Swift 3.

By relying on Swift’s type system we’ve gotten rid of a few needless words to reduce the amount of boilerplate in a method signature. Not that the goal here wasn’t brevity, but it was attained nonetheless as a useful side effect.

Why append instead of byAppending? If the goal is readability at a call site, then String.byAppending(String) reads somewhat nicer. There are a couple rules that govern this, but don’t worry we’ll get to that.

Naming according to roles

The next sub-goal in promoting clear usage is in how we name our variables, parameters, and associated types. We should strive to name these by role. This one is quite easy to understand so here’s a couple examples.

var someNumber = 65.2

protocol WebService {
  associatedtype FetchableType: Endpoint
}

Swift 3, in contrast, defines names so as to optimize clarity and to express the entity’s role.

var temperature = 65.2

protocol WebService {
  associatedtype Resource: Endpoint
}

Here it’s obvious what the variable temperature holds. But more importantly, we’ve given the associated type an appropriate name that describes what it’s role is within the type.

Compensating for weak type information

Earlier, when we examined the append(aString: String) method on String, we indicated that there was no need to specify both the return and parameter type in the method name because we could rely on type information. Unfortunately, this isn’t true all of the time.

For example, sometimes we may have to define methods that use higher types like NSObject, AnyObject or even Any. In such cases, there isn’t any useful type information that would express to the reader what the parameter captures.

Furthermore, there are cases when even concrete types like Int or String do not convey enough information. Here we have a line of code that we’ve applied our existing knowledge of Swift 3 naming guidelines to:

func add(_ observer: NSObject, for keyPath: String)

At first glance, it seems perfectly fine and the method reads well. It’s obvious that we’re adding an observer for a particular key path and we’ve eliminated any redundancy. Remember, however, that the goal is clarity at the point of use! When we call this method it looks like this:

someObject.add(self, for: "object.property")

To the reader, this is hard to parse. To fix this, the new guidelines recommends preceding each weakly typed parameter with a noun describing its role.

func addObserver(_ observer: NSObject, forKeyPath path: String)

In this second version, “observer” has been moved out of the parameter list and into the method name. Note that Swift 3 requires all argument labels including the first, but here we’re omitting it by using underscore. We’ll take more about why this is done in a section below.

We’ve also given an explicit external parameter name for the path argument. These changes mean the function now reads as:

someObject.addObserver(self, forKeyPath: "object.property")

This is not only more readable but at the point of use, we’ve eliminated any redundancy and expressed intent quite clearly.

These rules round up our section on Promoting Clear Usage. They are a bit nuanced at first, and especially with the next set of guidelines, they can often make it tricky to figure out how to name things. But the standard library has plenty of examples to get you started.

Fluent Usage

Swift not only suggests that your code should be easy to read, it also offers rules on how you can make it so. The following rules require some knowledge of the rules of the English language so if you’re a non-native speaker (like me!), you might have to do a bit of reading about what certain phrases mean.

Form Grammatical English Phrases

The goal with this first rule is to make our method/function names read like an English phrase with correct grammar at the call or use site. This includes not just the method name, but the argument labels as well. Let’s start with a simple example:

func insert(element: Element, position: Int)

Here we have a method that performs an insert operation, presumably on an indexed collection. If we call that method (in Swift 3), the use site looks like this:

x.insert(element: y, position: z)

Here it’s not immediately obvious what we’re inserting and what the position refers to. Sure, it might be intuitive that y is being inserted into position z, but there’s some room for ambiguity. Let’s refactor this method.

func insert(_ element: Element, at position: Int) {}

Now the use site looks like this;

x.insert(y, at: z)

This is the recommended approach. When you read this line of code, you can read it out like an English phrase – “x, insert y at z”. When writing our method names, we need to write them such that they read as a grammatical phrase. By relying on both type information and the way the API reads, we understand that we’re inserting y at position z.

Before we move on to more examples, since parameters and corresponding argument labels form an important part of this rule, let’s take a small detour to talk about conventions.

Conventions

There are several rules here but we’re just going to go over some of the more important ones. If I haven’t mentioned this before, Swift 3 includes the first parameter’s argument label at the call site, in contrast to Swift 2, where it was omitted in an effort to maintain parity with existing Cocoa conventions. Even though it is included, there are certain nuances to when and how it’s written. Let’s go over these distinctions.

  1. When the first argument forms part of a prepositional phrase, include an argument label where the preposition is part of the label

For example:

x.subViews(havingColor: y)

The “havingColor” argument label here forms a prepositional phrase when read at the use site: “x’s subviews having color y”. Again this adheres with our goal of reading well.

  1. If the argument doesn’t form a prepositional phrase, but a grammatical phrase instead, then we want to omit the argument label of the first parameter altogether. Instead, any preceding words should be added to the method name instead. The method should then read as a correct English phrase with the method’s base name alone. For example:
x.addSubview(y)

Here we don’t have a prepositional phrase so we’ve omitted the argument label entirely. If we were to include a label like so:

x.addSubiew(view: y)

Then it would read as “x, add subview view y” instead of the “x, add subview y” we had earlier. Our API should be designed without the argument label in this case because it reads better.

There is a caveat though that is implied (but that I think I should point out because there are plenty of non-native speakers). The rule states that the label should be omitted “if the method doesn’t form a prepositional phrase but does form a grammatical one”. You can, however, have a phrase that is neither prepositional nor grammatically correct.

controller.dismiss(animated: true, completion: nil)

Here we have the dismiss method on the UIViewController class. When dismissing a view controller we can specify whether we want this transition animated by providing a Boolean value to the animated parameter. This method does not form a grammatical phrase. If we omitted the first argument label, it would look as follows:

controller.dismiss(true, completion: nil)

This doesn’t make much sense. Does “true” mean that we’re actually dismissing the controller and false mean otherwise? If that were the case, why would we even call dismiss? Obviously true refers to something associated with the dismiss action but it’s not clear what. In cases like these, including the argument label is necessary to indicate our intent.

Now that we understand conventions regarding the first argument label, let’s go back to naming rules. To summarize the first rule:

  • Prefer method and function names that make use sites form grammatical English phrases
  • If the phrase reads as a grammatical phrase, omit the first argument label and make sure the method name reads well with just the base
  • If the phrase forms a prepositional phrase, include a label where the preposition is part of the label
  • If the phrase doesn’t form a prepositional phrase, but doesn’t read as a grammatical phrase either, then include the argument label.

There are, however, a few minor exceptions to this.

Multiple Arguments For A Single Abstraction

The first exception is if we have a prepositional phrase but there are multiple arguments for a single abstraction. In our previous example, we had a single argument label for a single abstraction.

x.subviews(havingColor: y)

Here the preposition is included as part of the argument label. Often times, however, the single abstraction can be broken up amongst many parameters and argument labels. Here we have a fire method that we’re calling on a tower (in a tower defense game!).

tower.fire(atX: a, y: b)

fireAt is a prepositional phrase so we’re following our rules and including the preposition as part of the first argument label. Because x is simply part of a compund abstraction that includes the y argument as well, the position of the prepositional phrase doesn’t make sense. In such cases, where we have a prepositional phrase where multiple arguments represent a single abstraction, we move the preposition to the base method name to indicate that it is a single abstraction. We would write the method as follows:

tower.fireAt(x:a, y: b)

The intent here, by including the preposition as part of the base method name, is to indicate that the arguments are part of a single abstraction.

Initializer and Factory methods

The next variation on our rules is if our method is an initializer or factory method. In this case, we’re explicitly not striving for grammatical continuity.

let foreground = Color(red: 32, green: 64, blue: 128)`

This is what we’re used to, so nothing really to see here. It’s just worth nothing that unlike the standard rule, we simply want arguments to be listed out, rather than worrying about how they read.

The last section we’re going to look at is naming by role.

Naming by Role

In addition to the guidelines that describe how we format argument labels, we have a few rules about how a type or methods nature or function determines what we name them.

Name functions and methods according to side effects

A method that simply takes input data, performs an operation and returns a set of output data without mutating the state of the program is known as a pure function. In contrast to pure functions, we have methods that mutate state and perform side effects.

Pure functions, i.e., methods without side effects should read as noun phrases. For example:

x.distance(to: y)

On the flip side, functions and methods with side effects should read as imperative verb phrases such as print(x) or x.append(y).

We can take this rule one step further by applying the styles to mutating and non-mutating pairs of methods. If you’re unfamiliar with the behavior of certain functions in the standard library, a mutating method will often have a non-mutating method with similar semantics, but returns a new value rather than updating in place.

We apply these styles based on the type of word we use for the method/function name. For instance:

  1. If the name of the method is best described using a verb, we use that verb (the imperative form of it) for the name of the mutating method. So for example:

The sort method is best described by the word sort which is an imperative verb, so we name the mutating version of the method sort(); the one that sorts in place.

When we use the imperative verb for the name of the mutating pair, we add the -ed or -ing suffix for the nonmutating version. The nonmutating version of the sort function, that returns a new collection rather than sorting the original one, should by named sorted(). Similarly, x.append(y) is the mutating version that uses an imperative verb, while x.appending(y) is the non-mutating version that returns a new array. The choice between when to use -ed versus -ing is simple: if adding -ed does not form a grammatically correct phrase because the verb has a direct object, we use -ing.

  1. On the other hand, we have occasions where the operation is best described using a noun. In this case, we want to use the noun as the name of the non-mutating counterpart, like so: y.union(z). For the mutating version, we use the word “form” as a prefix: y.formUnion(z).

Naming Booleans

A Boolean value or function should read as an assertion only if the use is nonmutating. A common example is x.isEmpty for a Boolean property, but also includes methods such as line1.intersects(line2). Here it’s almost as if we’re saying “line 1 intersects line 2. Yes or no?”.

Naming Protocols

The last two rules are regarding protocols! This is one I always have trouble with (not that any of the aforementioned rules are easy).

  • Protocols that describe what something is should read as nouns: Collection.

and finally.

  • Protocols that describe a capability should be named using the suffixes able, ible, or ing: e.g. Equatable, ProgressReporting and so on.

When it comes to naming anything else, whether it’s other types, properties, variables, and constants, the names should always read as nouns.

Phew! I think that covers most of the new guidelines. While some of these follow rules established in earlier versions of Swift, they are now “hard coded” more or less so we’ve got a good base to all congregate around.

Check out the entire document on the Swift homepage and start internalizing these asap!

Have questions about Swift 3? Share them below.


If you’re ready to start your career as an iOS developer, enroll in the iOS Development Techdegree to start your free trial today.

GET STARTED NOW

Learning with Treehouse for only 30 minutes a day can teach you the skills needed to land the job that you've been dreaming about.

Get Started

Leave a Reply

You must be logged in to post a comment.

man working on his laptop

Are you ready to start learning?

Learning with Treehouse for only 30 minutes a day can teach you the skills needed to land the job that you've been dreaming about.

Start a Free Trial
woman working on her laptop