Swift API Design Guidelines
Fundamentals
- 사용 시의 명확함이 가장 중요한 목표. 메서드나 프로퍼티의 선언은 한 번이지만 사용은 반복적임. 사용 할 때 문맥상 명확하도록 하라.
- 명확함이 간결함보다 더 중요.
- 모든 선언에 대해 문서 주석을 사용하라. 주석을 읽음으로써 얻어지는 통찰은 디자인에 지대한 영향을 미칠 수 있다.
Naming
Promote Clear Usage
모호성을 피하는 데 필요한 모든 단어들을 포함하라.
좋은 예
1
2
3
4extension List {
public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)나쁜 예
1
employess.remove(x) // 불확실함: x를 지우는 것인가?
불필요한 단어는 생략하라. 이름의 모든 단어는 사용 시에 중요한 정보를 전달해야 한다.
나쁜 예: 더 많은 단어는 의도를 명확하게 하거나 뜻의 모호성을 없애주는 데 필요할 수 있지만 이미 담고 있는 정보가 불필요하게 더 전달될 수도 있다. 특히 단지 타입을 반복하는 단어는 생략하라.
1
2public mutating func removeElement(_ member: Element) -> Element?
allViews.removeElement(cancelButton)좋은 예
1
2public mutating func remove(_ member: Element) -> Element?
allViews.remove(cancelButton) // clearer
변수, 인자, 그리고 연관 타입의 이름을 타입이 아닌 그들의 역할에 따라 지어라.
나쁜 예
1
2
3
4
5
6
7var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}좋은 예
1
2
3
4
5
6
7var greeting = "Hello"
protocol ViewController {
associatedtype ContentView : View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}
부족한 타입 정보를 인자의 역할을 확실히 함으로써 보조하라.
특별히 인자의 타입이NSObject
,Any
,AnyObject
타입이나Int
,String
같은 기본 타입일 경우 타입 정보나 사용 시의 정보가 의도를 전부 전달해주지 못할 수도 있다. 다음 예에서 선언 시에는 명확하나 사용시에는 모호하다.- 나쁜 예
1
2func add(_ observer: NSObject, for keyPath: String)
grid.add(self, for: graphics) // vague
명확함을 위해 부족한 타입 설명의 인자 각각에 역할을 보여주는 명사를 앞에 써넣어라.
- 좋은 예
1
2func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // clear
- 나쁜 예
Strive for Fluent Usage
메서드와 함수 이름이 사용 시에 문법이 맞는 영어 구문이 되도록 이름을 짓는 것을 선호하라.
좋은 예
1
2
3x.insert(y, at: z) “x, insert y at z”
x.subViews(havingColor: y) “x's subviews having color y”
x.capitalizingNouns() “x, capitalizing nouns”나쁜 예
1
2
3x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()
팩토리 메서드의 이름은 “make”로 시작하라.
초기화와 팩토리 메서드 호출 시의 첫번째 인자는 기본 이름으로 시작하는 구문을 만들어서는 안 된다.
좋은 예
1
2
3let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
let ref = Link(target: destination)나쁜 예: 첫 번째 인자와 문법적 연속성을 만드려고 시도했다.
1
2
3let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)
함수와 메서드의 부작용(원래 값에 변형이 일어나느냐에 따라 구분)을 고려하여 이름을 지어라.
부작용이 없는 것은 명사 구문처럼 읽혀야 한다.
1
2x.distance(to: y)
i.successor()부작용이 있는 것은 명령형의 동사 구문처럼 읽혀야 한다.
1
2
3print(x)
x.sort()
x.append(y)Mutating/nonmutating 메서드 쌍의 이름을 일관되게 지어라.
작업이 동사로 자연스럽게 설명될 수 있는 경우, mutating 메서드의 경우 동사의 명령형으로, nonmutating 메서드의 경우 “ed”나 “ing” 같은 접미사를 붙여라.
1
2
3
4
5
6
7// Mutating
x.sort()
x.append(y)
// Nonmutating
z = x.sorted()
z = x.appending(y)- “ed”: 가장 일반적으로 선호됨.
- “ing”: 동사가 직접적인 목적어를 가져 “ed”가 문법적으로 옳지 않아 보일 떄
- 작업이 명사로 자연스럽게 설명될 수 있는 경우, nonmutating 메서드는 명사를 그대로 붙이되, mutating 메서드의 경우 접두사로 “form”을 붙이는 것이 좋다.
1
2
3
4
5
6
7// Mutating
y.formUnion(z)
c.formSuccessor(&i)
// Nonmutating
x = y.union(z)
j = c.successor(i)
Boolean 메서드와 프로퍼티는 사용할 때 주장(assertion)처럼 읽혀야 한다.
1
2x.isEmpty
line1.intersects(line2)무엇인지 설명하는 프로토콜은 명사로 읽혀야 한다(e.g. Collection).
- 무엇을 하는지설명하는 프로토콜은 able, ible, 또는 ing 같은 접미사를 사용해 이름을 지어야 한다(e.g. Equatable, ProgressReporting).
- 타입, 프로퍼티, 변수, 상수의 이름은 명사처럼 읽혀야 한다.
Use Terminology Well
- 애매한 용어는 피하라.
- 확립된 뜻에 충실하라.: 새로운 용어를 만들 필요가 없는 경우에는 기존의 널리 알려진 용어를 사용하자.
- 약어를 피하라.: 사용되는 약어는 널리 알려지고 쉽게 찾을 수 있어야 한다.
- 선례를 따르라.
Conventions
General Conventions
- O(1)이 아닌 연산 프로퍼티의 경우 그것의 복잡도를 명시히라.: 일반적으로 연산 프로퍼티를 접근하여 사용하는 경우 O(1)로 생각하기 쉽기 때문에 아닌 경우에는 알려 주어야 한다.
- 함수를 사용하는 대신 메서드와 프로퍼티를 선호하라.
- 대소문자 컨벤션을 따르라.: 타입과 프로토콜의 이름은 UpperCamelCase로, 그 외의 것은 lowerCamelCase로 한다.
- 기본적인 뜻을 공유하거나 특정 도메인에서 동작하는 메서드의 경우 기본 이름을 공유할 수 있다.
Parameters
설명을 제공하는 인자 이름을 골라야 한다.
좋은 예: 해당 설명이 자연스럽게 읽혀진다.
1
2
3
4
5
6/// Return an `Array` containing the elements of `self`
/// that satisfy `predicate`.
func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]
/// Replace the given `subRange` of elements with `newElements`.
mutating func replaceRange(_ subRange: Range, with newElements: [E])나쁜 예: 해당 설명이 이상하고 문법적으로 맞지 않는다.
1
2
3
4
5
6
7/// Return an `Array` containing the elements of `self`
/// that satisfy `includedInResult`.
func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]
/// Replace the range of elements indicated by `r` with
/// the contents of `with`.
mutating func replaceRange(_ r: Range, with: [E])
기본값을 가진 인자의 장점을 활용하라.: 메서드의 일반적인 사용을 단순화시킬 수 있다.
- 기본값을 가진 인자는 뒤로 위치시켜라.: 기본값이 제공되지 않은 인자가 대개의 경우 메서드 사용 시에 더 중요하다.
Argument Labels
- 효과적으로 구별될 수 없는 인자의 경우 모든 라벨을 생략하라(e.g. min(number1, number2), zip(sequence1, sequence2)).
- 값 보존 타입 변환을 수행하는 초기화의 경우, 첫 번째 인자 라벨을 생략하라(e.g. Int64(someUInt32)).
첫 번째 인자가 전치 구문의 부분을 구성할 경우 인자 라벨을 달아주는 것이 좋다(e.g. x.removeBoxes(havingLength: 12)).
- 예외: 첫 번째 인자말고도 다른 인자들이 같은 추상화 수준일 경우에는 메서드 이름 다음에 전치사를 달아주는 것이 좋다.
나쁜 예
1
2a.move(toX: b, y: c)
a.fade(fromRed: b, green: c, blue: d)좋은 예
1
2a.moveTo(x: b, y: c)
a.fadeFrom(red: b, green: c, blue: d)
위의 경우가 모두 아닌 경우, 첫 번째 인자가 문법적 구문을 구성할 경우, 라벨을 생략하고 메서드 기본 이름에 붙이는 것이 좋다.
- 다른 모든 인자에 라벨을 붙이는 것이 좋다.
Special Instructions
- 튜플의 멤버에 라벨을 붙여주고 클로저의 인자에 이름을 붙여라.
- 제약이 없는 다형성에 신경써야 한다.(e.g.
Any
,AnyObject
와 같은 경우)