오늘은 API Design Guidelines을 알아보려한다.
아무래도 내 멋대로 사용하여 실력을 끌어올리는 것도 뭐.. 나쁘지않겠지만.. 제대로 알고 사용하는 것보다 좋은건 없을거다.
Swift를 어떻게 사용하면 Swift처럼 잘 쓸 수 있는지를 Apple에서 알려주는 것이다.
Apple에서 만든 언어니, 당연히 Apple Guideline을 참고해서 공부하는 것이 정석이겠다싶어서 번역도 하고 참고도 하며 알아보겠다.
API Design Guidelines
Fundamentals
● 사용 시점의 명확성(Clarity at the point of use)이 가장 중요한 목표이다.
메소드와 프로퍼티같은 엔티티(개체)는 한번만 선언되고, 반복적으로 사용된다. API는 앞서 말한 메소드, 프로퍼티 등의 개체를 명확하고 간결하게 사용하기 위해 설계한다. 설계를 평가할 때, 선언을 읽는 것으로는 충분치 않다. 항상 use case를 테스트하며 문맥에서 명확하게 보이는지 확인해라.
● 명확성은 간결성보다 더 중요하다. (Clarity is more important than brevity)
비록 Swift code는 압축될 수 있지만, 가장 적은 수의 문자로, 가장 적은 코드를 사용하는 것이 목표는 아니다.
Swift code에서 볼 수 있는 간결성은 강력한 타입 시스템의 Side Effect이며, 자연스럽게 boilerplate를 줄이는 기능이다.
Boilerplate = 최소한의 변경으로 재사용 할 수 있는 것
- 간결한 것을 추구하긴 하지만.. 코드를 줄인다고 막 생략하는게 좋은게 아니다. 뭐 그런 것을 추구하는건 아니다 라는 것 같음
● 모든 선언에 대한 문서 주석을 작성해라. (Write a documentation comment)
주석을 작성함으로써 얻을 수 있는 통찰력(insight)은 설계에 큰 영향을 미칠 수 있으므로, 이를 미루지 말아라.
API의 기능을 설명하는데 어려움이 있는경우, 잘못된 API을 설계했을지도 모른다.
#Comment 하위
○ Swift의 dialect of Markdown을 사용해라.
○ 선언되는 엔티티를 설명하는 요약(Summary)부터 작성해라. API는 종종 선언문과 요약에서 완전히 이해 될 수도 있다.
//Returns a "view" of `self` containing the same elements in
//reverse order.
func reversed() -> ReverseCollection
■ 요약에 초점을 맞춰라. 이것은 가장 중요한 부분이다. 많은 훌륭한 문서 주석은 훌륭한 요약으로 구성되어있다.
■ 가능하다면 마침표로 끝나는 단일 문장 조각을 사용해라. 완전한 문장을 사용하지말아라.
■ 함수 또는 메소드가 수행하는 작업과 반환하는 작업, null을 생략하는 효과와, void 반환 작업을 설명해라.
/// Inserts `newHead` at the beginning of `self`.
mutating func prepend(_ newHead: Int)
/// Returns a `List` containing `head` followed by the elements
/// of `self`.
func prepending(_ head: Element) -> List
/// Removes and returns the first element of `self` if non-empty;
/// returns `nil` otherwise.
mutating func popFirst() -> Element?
Note: 드문 경우, 위 코드의 popFirst 같은 요약은 세미콜론으로 구분된 여러 문장으로 구성된다.
■ 서브스크립트가 접근하는 것에 대해 설명해라.
/// Accesses the `index`th element.
subscript(index: Int) -> Element { get set }
■ 이니셜라이저가 생성하는 작업을 설명해라.
/// Creates an instance containing `n` repetitions of `x`.
init(count n: Int, repeatedElement x: Element)
■ 다른 설명문의 경우 (서브스크립트, 이니셜라이저를 제외한 다른 모든 선언문), 선언된 엔티티가 무엇인지 설명해라.
/// A collection that supports equally efficient insertion/removal
/// at any position.
struct List {
/// The element at the beginning of `self`, or `nil` if self is
/// empty.
var first: Element?
...
-> (A collection), (The element at the beginning)
○ 선택적으로, 하나 이상의 단란 및 글머리 기호 항목으로 계속한다. 단락은 빈 줄로 구분되고, 완전한 문장을 사용한다.
/// Writes the textual representation of each ← Summary
/// element of `items` to the standard output.
/// ← Blank line
/// The textual representation for each item `x` ← Additional discussion
/// is generated by the expression `String(x)`.
///
/// - Parameter separator: text to be printed ⎫
/// between items. ⎟
/// - Parameter terminator: text to be printed ⎬ Parameters section
/// at the end. ⎟
/// ⎭
/// - Note: To print without a trailing ⎫
/// newline, pass `terminator: ""` ⎟
/// ⎬ Symbol commands
/// - SeeAlso: `CustomDebugStringConvertible`, ⎟
/// `CustomStringConvertible`, `debugPrint`. ⎭
public func print(
_ items: Any..., separator: String = " ", terminator: String = "\n")
■ 인식된 symbol documentation markup 요소를 사용해 요약 외의 정보를 추가해라.
■ symbol documentation syntax로 인식된 글 머리 기호 항목을 알고 사용해라.
Xcode와 같은 인기 개발도구는 다음 키워드로 시작하는 글 머리 기호 항목에 특별한 대우를 한다.
Naming
Promote Clear Usage
● 이름이 사용되는 코드를 읽을 때, 모호함을 피하는데 필요한 모든 단어를 포함한다 (Include all the words needed to avoid ambiguity)
예를 들어, Collection내 지정된 위치에서 요소를 제거하는 메소드를 생각해봐라.
(맞는 것)
extension List {
public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)
이 메소드 서명에서 at을 생략한다면, x를 사용하여 제거할 요소의 위치를 나타내는 것이 아닌, 메소드가 x와 동일한 요소를 찾아 제거한다는 것을 암시할 수 있다.
(틀린 것) -> 이유: 모호함
employees.remove(x) //unclear: are we removing x?
● 불필요한 단어는 생략하라. 이름에 나오는 모든 단어는 사용 Site에서 중요한 전달을 전달해야한다.
의도를 명확하게하거나, 의미를 명확하게 하기위해 더 많은 단어가 필요할 수는 있지만, 읽는 사람이 이미 소유하고있는 정보로 중복되는 단어는 생략해야한다. 특히, 타입 정보만 반복되는 단어는 생략해라.
(틀린 것)
public mutating func removeElement(_ member: Element) -> Element?
allViews.removeElement(cancelButton)
이 경우 Element라는 딴어가 호출 Site에서 돋보이는 것을 추가하지않는다.
-> 어.. 호출, 반환에서 이미 Element를 다루는 것이다!! 하고 티를 이빠이 내고있는데, 굳이 중복해서 또 쓰지말라는 것 같음
(맞는 것)
public mutating func remove(_ member: Element) -> Element?
allViews.remove(cancelButton) // clearer
이 코드가 더 나을 것이다.
흔히 모호함을 피하기 위해 반복되는 타입 정보가 필요하지만, 일반적으로 매개변수의 타입보다 매개변수의 역할을 설명하는 단어를 사용하는 것이 더 좋다.
● 타입 제약 조건보다는 역할에 따라서 변수, 매개 변수 및 관련 타입을 지정해라.
(틀린 것)
var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
이렇게 타입명을 다른 목적에 맞게 만드는 것은 명확함과 표현력을 최적화하지 못한다.
대신, 엔티티의 역할을 나타내는 이름을 선택하려 노력해라.
(맞는 것)
var greeting = "Hello"
protocol ViewController {
associatedtype ContentView : View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}
만일 연관타입이 프로토콜 제약 조건에 밀접하게 결합되어 프로토콜 명이 곧 역할이라면, 관련 프로토콜명에 "Protocol"을 추가하여 충돌을 방지해라.
protocol Sequence {
associatedtype Iterator : IteratorProtocol
}
protocol IteratorProtocol { ... }
associatedtype의 이름이 Iterator이기때문에, 해당 타입이 채택하는 IteratorProtocol의 이름을 Iterator로 선언하게되면 타입의 이름과, 프로토콜명이 같아지기에, 그로인해 발생되는 문제(collision)을 방지하기위해, 위와 같은 상황에선 Iterator에 Protocol을 붙여 충돌을 방지한다. -> 연관타입을 선언할때 프로토콜명과 같다면, 프로토콜에 "Protocol"을 붙여 충돌을 방지하란 의미이다.
● 취약한 타입 정보를 보완하여, 파라미터 역할을 명확히 한다.
특히, 매개변수 타입이 NSObject, Any, AnyObject 또는 Int, String과 같은 기본 타입인 경우, 사용하는 시점에서 타입 정보와 Context가 의도를 명확히 전달하지 못할 수 도 있다.
밑의 예제는, 선언은 명확하지만, 사용 Site가 모호하다.
func add(_ observer: NSObject, for keyPath: String)
grid.add(self, for: graphics) // vague
명확함을 복구하려면, 각 약식 매개변수(weakly typed parameter)의 앞에 그 역할을 설명하는 명사를 붙인다.
func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // clear
Strive for Fluent Usage (능숙한 사용을 위한 노력)
● 사용 Site를 문법적인 영어 구 형식으로 만드는 방법 및 함수 이름을 선호해라.
(맞는 것)
x.insert(y, at: z) “x, insert y at z”
x.subViews(havingColor: y) “x's subviews having color y”
x.capitalizingNouns() “x, capitalizing nouns”
(틀린 것)
x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()
첫번째 또는 두번재 전달인수 후의 인수가 호출 의미의 핵심이 아닌 경우 유창성(해석? 문법? 독해?)가 저하된다.
AudioUnit.instantiate(
with: description,
options: [.inProcess], completionHandler: stopProgressBar)
● 팩토리 메소드의 이름은 "make"로 시작해라. e.g. x.makeIterator()
● 이니셜라이저 및 팩토리 메소드의 호출의 첫번째 매개변수는 기본이름으로 시작하는 구문을 형성하면 안된다.
e.g. x.makeWidget(cogCount: 47)
예를 들어, 아래 코드에 대한 첫번째 매개변수는 기본 이름과 동일한 구문의 일부로 보지않는다.
(맞는 것)
let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
let ref = Link(target: destination)
(틀린 것)
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)
위 코드에서 API 작성자는 첫번째 매개변수와 함께 문법적인 연속성을 만들려했다. -> having RGBValue (Red, green and blue)
실제로 이 가이드라인은 파라미터 레이블에 대한 가이드라인과, 호출이 값 보존 타입 변환을 수행하지 않는 첫번째 매개변수에 레이블이 있음을 의미한다.
let rgbForeground = RGBColor(cmykForeground)
● Side-effects에 따른 함수 및 메소드 이름 지정
○ Side-effects가 없는 것들은 명사구로 읽어야한다. e.g. x.distance(to: y), i.successor()
○ Side-effects가 있는 것들은 필수 동사구로 읽어야한다. e.g. print(x), x.sort(), x.append(y)
○ Mutating/nonmutating 메소드 쌍 이름을 일관되게 지정한다. mutating 메소드는 종종 유사한 의미를 가진 nonmutating 메소드를 가지지만, 인스턴스를 현재 위치에서 업데이트 하는 대신 새 값을 반환한다.
■ 연산이 동사에 의해 자연스럽게 설명되는 경우, mutating 메소드에 동사의 명령어 수식을 사용하고, "ed", "ing" 접미사를 사용하여 해당 nonmutating 이름을 지정해라.
Mutating | Nonmutating |
x.sort() | z = x.sorted() |
x.append(y) | z = x.appending(y) |
■ 동사의 과거분사(보통 "ed")를 사용하여 nonmutating 이름을 지정하는 것이 좋다.
/// Reverses `self` in-place.
mutating func reverse()
/// Returns a reversed copy of `self`.
func reversed() -> Self
...
x.reverse()
let y = x.reversed()
■ 동사에 직접적인 목적어가 있어 "ed"가 문법적으로 맞지 않는 경우, "ing"를 추가하여 nonmutating 이름을 지정해라.
/// Strips all the newlines from `self`
mutating func stripNewlines()
/// Returns a copy of `self` with all the newlines stripped.
func strippingNewlines() -> String
...
s.stripNewlines()
let oneLine = t.strippingNewlines()
■ 연산이 자연스럽게 명사에 의해 설명되는 경우, nonmutating 메소드에 명사를 사용하고, "form" 접두사를 사용항 mutating이름을 지정해라.
Nonmutating | Mutating |
x = y.union(z) | y.formUnion(z) |
j = c.successor(i) | c. formSuccessor(&i) |
● Bool 메소드 및 프로퍼티 사용은 수신자에 대한 주장으로 봐야한다. e.g. x.isEmpty, line1.intersects(line2)
● "무엇"인지를 설명하는 프로토콜은 명사로 읽어야한다. e.g. Collection
● 기능을 설명하는 프로토콜은 "able", "ible", "ing" 접미사를 사용하여 이름을 지어야한다.
● 다른 타입, 프로퍼티, 변수 및 상수의 이름은 명사로 읽어야한다.
Use Terminology Well
Term of Art: 명사 - 특정 분야 및 직업에서 정확하고 특별한 의미를 가진 단어 또는 구문
● 보다 일반적인 단어가 의미를 더 잘 전달 할 수 있는 경우, 모호한 용어를 사용하지 말아라. -> 어려운 용어 쓰지말아라 같음
"skin(피부)"가 당신의 목적에 부합한다면 "epidermis(표피)" 라고 말하지 말아라. 용어는 필수적인 의사 소통 기구지만, 그렇지 않을 경우 손실될 중요한 의미를 포착하는 데만 사용해야 한다.
● Term of Art를 사용한다면, 확립된 의미를 고수해라.
보다 일반적인 단어보다 전문 용어를 사용하는 이유는 모호하거나 불명확할 수 있는 것을 정확하게 표현하기 위해서이다.
따라서 API는 수용된 의미에 따라 엄격하게 용어를 사용해야 한다.
○ 전문가를 놀라게 하지말아라: 이미 그 용어에 친숙한 사람은 우리가 새로운 의미를 발견한 것처럼 보이면 놀라고, 아마 분노할 것이다.
○ 초보자를 혼란스럽게 하지말아라: 용어를 배우려는 사람은 누구나 웹 검색을 통하여 본래의 의미를 찾을 수 있다.
● 약어를 피해라. 약자, 특히 비표준 약자는 효과적인 Term of Art이다. 왜냐하면 올바르게 그 약어를 축약되지 않은 본래의 형태로 바꾸는 것에 따라 이해가 달라지기 때문이다.
● 선례를 받아들여라. 기존 문화를 따른다는 대가를 치뤄도, 초보자를 위해 용어를 최적화하지말아라.
수학과 같은 특정 프로그래밍 도메인 내에서, sin(x) 같이 널리 선행된 용어는verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)보다 바람직하다. 또한 완전한 단어가 sine(사인)일지라도, sin(x)는 수십년동안의 프로그래머와 수세기동안의 수학자들 사이에서 공통적으로 사용되어왔다.
Convention
General Convention
● O(1)이 아닌 연산 프로퍼티의 복잡성을 문서화해라. 사람들은 프로퍼티를 mental model(?????????)으로 저장했기때문에, 종종 프로퍼티 접근에 중요한 연산이 필요하지않다고 가정한다. 그 가정이 위반되었을 때 경고해야한다.
● free 함수보다 메소드와 프로퍼티를 선호해라.
free 함수는 다음과 같은 특수한 상황에서만 사용된다.
1. 명백히 self가 없을 때
min(x, y, z)
2. 함수가 제한조건이 없는 generic 일 때
print(x)
3. 함수 구문이 설정된 도메인 표기법의 일부인 경우
sin(x)
● case Convention을 따라라. (대소문자 규칙)
타입과 프로토콜의 이름은 UpperCamelCase이다. 나머지 모두는 LowerCamelCase이다.
미국 영어에서 일반적으로 대문자로 표현되는 약어와 이니셜은 case Convention에 따라 균일하게 대소문자를 구분해야한다.
var utf8Bytes: [UTF8.CodeUnit]
var isRepresentableAsASCII = true
var userSMTPServer: SecureSMTPServer
다른 약어는 일반적인 단어로 취급해야한다.
var radarDetector: RadarScanner
var enjoysScubaDiving = true
● 메소드는 동일한 기본 의미를 공유하거나, 고유한 도메인에서 작동할 때, 이름을 공유할 수 있다.
예를 들어, 메소드가 본질적으로 같은 작업을 수행하므로, 다음을 권장한다.
extension Shape {
/// Returns `true` iff `other` is within the area of `self`.
func contains(_ other: Point) -> Bool { ... }
/// Returns `true` iff `other` is entirely within the area of `self`.
func contains(_ other: Shape) -> Bool { ... }
/// Returns `true` iff `other` is within the area of `self`.
func contains(_ other: LineSegment) -> Bool { ... }
}
그리고, 기하학적 타입과 collection은 별도의 도메인이기에, 동일한 프로그램에서도 괜찮다.
extension Collection where Element : Equatable {
/// Returns `true` iff `self` contains an element equal to
/// `sought`.
func contains(_ sought: Element) -> Bool { ... }
}
그러나 이러한 index 메소드는 다른 의미를 가지고, 이름을 다르게 지정해야한다.
(틀린 것)
extension Database {
/// Rebuilds the database's search index
func index() { ... }
/// Returns the `n`th row in the given table.
func index(_ n: Int, inTable: TableID) -> TableRow { ... }
}
마지막으로, 타입 유추가 있을 때, 모호함을 발생시키기 때문에, "변환 타입에 대한 오버로드"를 피해야한다.
-> 변환을 Int, String 타입 둘 다 할순 없으니
extension Box {
/// Returns the `Int` stored in `self`, if any, and
/// `nil` otherwise.
func value() -> Int? { ... }
/// Returns the `String` stored in `self`, if any, and
/// `nil` otherwise.
func value() -> String? { ... }
}
Parameter
func move(from start: Point, to end: Point)
● 문서를 제공할 때, 파라미터의 이름을 선택해라. 파라미터의 이름은 함수나 메소드의 사용 시점에 나타나지 않지만, 중요한 설명 역할을 한다.
문서를 읽기 쉽게 만들기 위해 이름을 선택해라.
예를 들어, 이름을 사용하면 문서를 자연스럽게 읽을 수 있다.
/// 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])
그러나 밑 코드와 같은 것은 문서를 어색하게 만든다.
/// 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])
● 일반적인 사용을 단순화 할때, 기본 매개변수를 활용해라. 공통적으로 사용되는 값이 하나인 매개변수는 default(기본 값)의 후보이다.
기본 전달 인자는 관련없는 정보를 숨김으로써 가독성을 향상시킨다.
예를 들어
let order = lastName.compare(
royalFamilyName, options: [], range: nil, locale: nil)
위 코드는 더 단순해 질 수 있다.
let order = lastName.compare(royalFamilyName)
기본 전달 인자는 일반적으로 메소드 패밀리의 사용보다 선호된다. 왜냐하면 API를 이해하려 하는 사람에게 더 낮은 부담을 주기 때문이다.
extension String {
/// ...description...
public func compare(
_ other: String, options: CompareOptions = [],
range: Range? = nil, locale: Locale? = nil
) -> Ordering
}
위 내용은 간단하진 않지만, 밑 코드보다 더 간결하다.
extension String {
/// ...description 1...
public func compare(_ other: String) -> Ordering
/// ...description 2...
public func compare(_ other: String, options: CompareOptions) -> Ordering
/// ...description 3...
public func compare(
_ other: String, options: CompareOptions, range: Range) -> Ordering
/// ...description 4...
public func compare(
_ other: String, options: StringCompareOptions,
range: Range, locale: Locale) -> Ordering
}
메소드 패밀리의 모든 구성원은 사용자에 의해 별도로 문서화되고, 이해되어야한다. 그 중 하나를 결정하기위해선 사용자가 그 모든 것을 이해해야한다. 예를 들어, foo(bar: nil) 과 foo() 는 항상 동의어가 아니다. 대부분이 별거 아닌 차이점을 제거하는 지루한 과정이다. 기본 값이 있는 단일 메소드를 사용하면, 훨씬 뛰어난 프로그래머 경험을 얻을 수 있다.
● 매개변수 목록의 끝에, 기본값이 있는 매개변수를 선호해라. 기본값이 없는 매개변수는 보통 매소드의 의미가 더 중요하며, 메소드를 호출할 때, 안정적인 초기 사용 패턴을 제공한다.
Argument Labels
func move(from: start: Point, to end: Point)
x.move(from: x, to: y)
● 전달인자를 유용하게 구별할 수 없을 때, 모든 레이블을 생략해라.
e.g. min(number1, number2), zip(sequence1, sequence2)
● 값 보존 형 변환을 수행하는 이니셜라이저에서 첫번째 인자 레이블을 생략해라.
e.g. Int64(someUInt32)
첫번째 전달인자는 항상 변환의 재료(근원)여야 한다.
extension String {
// Convert `x` into its textual representation in the given radix
init(_ x: BigInt, radix: Int = 10) ← Note the initial underscore
}
text = "The value is: "
text += String(veryLargeNumber)
text += " and in hexadecimal, it's"
text += String(veryLargeNumber, radix: 16)
그러나 "narrowing"타입 변환에서는 narrowing을 나타태는 레이블을 사용해주는 것을 추천한다.
extension UInt32 {
/// Creates an instance having the specified `value`.
init(_ value: Int16) ← Widening, so no label
/// Creates an instance having the lowest 32 bits of `source`.
init(truncating source: UInt64)
/// Creates an instance having the nearest representable
/// approximation of `valueToApproximate`.
init(saturating valueToApproximate: UInt64)
}
narrowing타입 변환이.. 작은 데이터형에서 큰 데이터형으로 변환해주는 것은 Widening, 큰 데이터에서 작은 데이터.. 긍까 위 코드에선 UInt64를 UInt32로 변환 하는 것을 narrowing 타입변환이라고 하는 것 같다.
Note: 값 보존 형 변환은 단일 형태이다. 즉, 소스 값의 모든 차이에 따라 결과 값이 달라진다. 예를 들어, Int8에서 Int64의 변환은 모든 고유 Int8 값이 고유한 Int64 값으로 변환이 되기에 값 보존이지만, 반대의 변환인 경우, Int64값은 Int8값보다 더 많은 값을 표현 할 수있으므로 값 보존이 되지않는다.
Note: 원래 값을 가져오는 기능은 변환이 값보존인지 아닌지에 영향을 주지 않는다.
● 첫번째 인수가 전치사 구(prepositional phrase)인 경우, 전달 인자 레이블을 지정해라.
전달 인자 레이블은 일반적으로 전치사에서 시작해야한다. e.g. x.removeBoxes(havingLength: 12)
처음 두 전달인자가 단일 추상화의 일부를 나타내는 경우 예외가 발생한다....... 아니 뭐 한국어가 이래 어렵냐
a.move(toX: b, y: c)
a.fade(fromRed: b, green: c, blue: d)
이 경우, 추상화를 명확하게 유지하기 위해, 전치사 다음에 전달인자 레이블을 쓴다.
a.moveTo(x: b, y: c)
a.fadeFrom(red: b, green: c, blue: d)
● 그렇지 않고, 첫번째 전달인자가 문법적 구문의 일부를 형성하는 경우, 그것의 레이블을 생략하고, 앞의 단어를 기본 이름에 추가한다.
이 가이드라인은 첫번째 전달인자가 문법적 구문의 일부를 형성하지않으면, 레이블이 있어야됨을 의미한다.
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)
어구가 올바른 의미를 전달하는 것이 중요하다.
다음은 문법적이지만, 잘못된 것을 표현한다.
view.dismiss(false) Don't dismiss? Dismiss a Bool?
words.split(12) Split the number 12?
기본값을 가진 전달인자는 생략할 수 있고, 이 경우 문법적 구문의 일부가 아니므로, 항상 레이블이 있어야한다.
● 다른 모든 전달인자는 레이블을 가진다.
Special Instruction
● tuple멤버에 레이블을 지정하고 API에 나타나는 클로저 매개변수의 이름을 지정한다.
이 이름들은 설명의 힘을 가지며, 문서 주석으로부터 참조될 수 있고, tuple멤버에 대한 표현적인 접근을 제공한다.
/// Ensure that we hold uniquely-referenced storage for at least
/// `requestedCapacity` elements.
///
/// If more storage is needed, `allocate` is called with
/// `byteCount` equal to the number of maximally-aligned
/// bytes to allocate.
///
/// - Returns:
/// - reallocated: `true` iff a new block of memory
/// was allocated.
/// - capacityChanged: `true` iff `capacity` was updated.
mutating func ensureUniqueStorage(
minimumCapacity requestedCapacity: Int,
allocate: (_ byteCount: Int) -> UnsafePointer<Void>
) -> (reallocated: Bool, capacityChanged: Bool)
클로저 매개변수에 사용되는 이름들은 최상위 함수의 매개변수 이름과 같이 선택해야한다. 호출 Site에서 나타나는 클로저 전달인자에 대한 레이블은 지원되지 않는다.
● overload sets에서 모호함을 피하기위해 제약없는 다형성에 특히 주의해라.
e.g. Any, AnyObject, 제약없는 generic 매개변수
예를들어, 이 overload set을 고려해라.
(틀린 것)
struct Array {
/// Inserts `newElement` at `self.endIndex`.
public mutating func append(_ newElement: Element)
/// Inserts the contents of `newElements`, in order, at
/// `self.endIndex`.
public mutating func append(_ newElements: S)
where S.Generator.Element == Element
}
이러한 메소드는 Sematic family를 형성하고, 전달인자 타입은 처음에는 딱딱 구별된다. 그러나 Element가 Any인 경우, 단일 요소는 일련의 요소와 동일한 타입을 가질 수 있다.
(틀린 것)
var values: [Any] = [1, "a"]
values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?
모호함을 제거하려면, 두번째 overload를 보다 명확히 명명해라.
struct Array {
/// Inserts `newElement` at `self.endIndex`.
public mutating func append(_ newElement: Element)
/// Inserts the contents of `newElements`, in order, at
/// `self.endIndex`.
public mutating func append(contentsOf newElements: S)
where S.Generator.Element == Element
}
새 이름이 문서 주석과 더 잘 일치하는지 확인해라. 이 경우, 문서 주석을 작성하는 행위는 실제로 API 작성자의 주의를 이끄는 문제를 가져온다.
어..
코드를 명확하게 표현해라.. 간결한 것도 좋은데 그것보다 누구나 다 알 수 있도록 작성해라 -> 코드, 주석 열심히 써라.
애매~하게 이름 짓지마라.
역시.. 네이밍이 제일 어렵다.
변수 이름 정하는 것부터 어렵자너.. 약간 게임캐릭터 닉네임 정하는 느낌임
여튼.. 열심히.. 작성해봤으니 잘 봐주세요..
https://swift.org/documentation/api-design-guidelines/
'iOS > Swift' 카테고리의 다른 글
Design Pattern - MVC (0) | 2021.08.31 |
---|---|
[Swift] String and Characters (0) | 2021.08.24 |
[Swift] Optional (2) (0) | 2021.08.19 |
[Swift] Optional (1) (0) | 2021.08.18 |
[Swift] Struct Mutating (0) | 2021.08.11 |
댓글