When learning a new programming language, you're not just learning syntax, built-in libraries, tooling, terminology, formatting style, and so on. There is also a somewhat vaguely defined idea of what constitutes good code, a way of performing some tasks that fits well with the language and has evolved together with it over time. In Swift, such code is often referred to as Swifty code. This is in no way a well-defined term, and experts in the language may disagree on some points. Here, we will only cover things where there seems to be a consensus. The list is by no means exhaustive, and there are exceptions to many of these.
Many of these points are covered in Apple's official guidelines (https://swift.org/documentation/api-design-guidelines/). We strongly recommend reading it; it's a fairly short page and a very easy read.
Names of types and protocols are in UpperCamelCase. Everything else is in lowerCamelCase. This makes it easy to tell values and types apart.
Try to name functions and their parameters so that they form English phrases when called. So, instead of this:
x.insert(y, position: z) x.subViews(color: y) x.nounCapitalize()
Do this:
x.insert(y, at: z) x.subViews(havingColor: y) x.capitalizingNouns()
Functions returning Booleans should read well in an if
statement:
if x.isEmpty {...} if line1.intersects(line2) {...}
Methods that are mutating or have other side effects should read like commands:
print(x), x.sort(), x.append(y)
If this isn't possible because the operation is best described by a noun, prepend form
instead:
y.formUnion(z), c.formSuccessor(&i)
Append ed
or ing
to methods that return a new value instead of mutating:
Mutating |
Nonmutating |
---|---|
|
|
|
|
For nouns, just use the noun on its own for the non-mutating version:
Mutating |
Nonmutating |
---|---|
|
|
|
|
Avoid free functions, and place them where they belong. A function that processes text should be placed in an extension on StringProtocol
(so it can be used by both strings and substrings). If the function doesn't take a value as input, make it static.
Group methods and properties that belong together in one extension. For example, if you are adding protocol conformance to a type, group everything that is required by that protocol together in one extension.
If you have a function that is only going to be used from one other function, place it inside that function. This makes it clear as to why it exists.
Don't put semicolons at the end of lines. That is pointless in Swift. You can, however, use a semicolon to write two statements on one line, but that is not something you should do very often.
Languages without optionals have various ways of signaling the absence of a value: ""
for strings, -1
for positive integers, null
for objects, and so on. Swift, thankfully, only has one – nil
. Always use optionals if a value can be empty.
Use Int
for most integers, even if you only need positive values or smaller values that can fit in Int8, Int16, or Int32. Otherwise, you will have to do a lot of conversions since Swift does not do this automatically, not even when it is guaranteed to be safe.
Unless the order is significant, place a parameter taking a closure last in the function definition so that it can be used with trailing closure syntax. Place parameters with default values second to last.
Put underscores in long numeric literals, so they are easier to read:
1_000_000, 0.352_463
If you need to change a value after you have returned it, use this code:
let oldvalue = value value += 1 return oldvalue
Use defer
instead:
defer { value += 1 } return value
Finally, we're ready to write Swifty code. Here is the step to do so:
/// An immutable entry in an error log. struct LogError { var header: String let errorMessage: String init(header: String = "", errorMessage: String) { self.header = header self.errorMessage = errorMessage; if header.isEmpty { self.header = " ::Error::errorCode::" } } } LogError(errorMessage: "something bad") LogError(header: "head", errorMessage: "something bad")
Here is the solution:
/// An immutable entry in an error log. struct ErrorLogItem { let header: String let errorMessage: String init(errorMessage: String, header: String? = nil) { // Only if empty strings are invalid as headers. precondition(header != "", "A header cannot be empty.") self.header = header ?? " ::Error::errorCode::" self.errorMessage = errorMessage } } ErrorLogItem(errorMessage: "something bad") ErrorLogItem(errorMessage: "something bad", header: "head")
This ends our brief journey into code naming and organization, or in other words, how to write code in the Swifty way.