써니쿠키의 IOS 개발일기
[번역] API Design Guidelines 번역 (3/3) - (규칙)Convention 본문
원본, 출처 : https://www.swift.org/documentation/api-design-guidelines/
앞으로도 여러 번 두고두고 읽을 내용이기에 번역을 했다.
완벽한 번역본이 아니므로 수정사항 있으면 알려주세요 !
(규칙)Convention
1. 일반적인규칙
0(1)이 아닌 연산 프로퍼티의 복잡성을 문서화한다.
사람들은 보통 프로퍼티에 접근 할 때, 저장 프로퍼티라고 생각하기 때문에 계산 프로퍼티로써 중요한 계산을 하지 않을거라고 생각한다. 사람들이 이러한 가정을 가지고 접근할 수 있는 경우 확실히 경고 해야한다.
전역(자유)함수(free functions) 보다는 메소드와 프로퍼티를 선호한다.
자유 함수는 특별한 경우에만 사용된다.
// 1. 명확한 self가 없는경우 min(x, y, z) // 2.함수가 제네릭(generic)으로 제약조건이 걸려있지 않은경우 print(x) // 3.함수의 문법이 특정 도메인의 표기법인경우 sin(x)
규칙을 따른다.
타입과 프로토콜의 이름은UpperCamelCase이다. 나머지는 lowerCamelCase이다.
일반적으로 미국식 영어에서 모두 대문자로 나타내는 머릿글자(Acronyms and initialisms)는 규칙에 따라 모두 대문자 또는 소문자가 되어야 한다.
var **utf8**Bytes: [**UTF8**.CodeUnit] var isRepresentableAs**ASCII** = true var user**SMTP**Server: Secure**SMTP**Server
다른 머릿글자들은 일반 단어로 취급되어야 한다.
var **radar**Detector: **Radar**Scanner var enjoys**Scuba**Diving = true
기본 뜻이 동일하지만 서로 다른 영역에서 동작할때 메소드는 base name을 동일하게 할 수 있다.
예를들어 아래에서 메소드가 본질적으로 같은 일을 하기 때문에 같은 이름을 사용하기를 권장합니다
//✅ Good 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 { ... } }
그리고, 기하학적인 타입과 컬렉션은 별도의 영역이다. 아래 예제 또한 좋은예시다.
//✅ Good extension Collection where Element : Equatable { /// Returns `true` iff `self` contains an element equal to /// `sought`. func contains(_ sought: Element) -> Bool { ... } }
그러나, index 메소드는 다른 의미를 가지고 있고,이름을 다르게 해야 한다.
//❌ Bad 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 { ... } }
마지막으로, 리턴타입에 오버로드(overloading on return type)는 피한다. 기존 타입을 추측하는데 어려움이 있기때문이다.
// ❌ Bad 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? { ... } }
2. 파라미터
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])
기본매개변수를 활용한다.
일반적인 사용을 단순화 할때 기본 매개변수의 장점을 활용한다. 일반적으로 사용되는 파라미터가 defaulft로 사용 될수 있습니다.
예를들어 아래의 예처럼 default parameter를 사용하여 가독성을 높일 수 있습니다.
/// ❌ 바람직하지 않은 예제 let order = lastName.compare(royalFamilyName, options: [], range: nil, locale: nil) /// ✅ 바람직한 예제 ( 더 간단하게 만들 수 있다.) let order = lastName.compare(royalFamilyName)
default parameter는 일반적으로 메소드집합(method families)보다 선호된다 왜냐면 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 }
메소드 집합(method families)의 모든 멤버는 각기 별도로 문서화하고, 사용자를 이해 시킬 필요가 있다. 그것들 사이에서 결정하려면, 사용자는 모든 것을 이해할 필요하고, 때때로 놀라운 관계를 ( 예를들어, foo(bar:nil)과foo()는 항상 같지 않다 )같은 거의 동일한 문서에서의 작은 차이를 찾아내는 과정은 지루하다. 하나의 메소드에 기본값을 사용하면, 훨씬 우수한 프로그래머 경험을 제공한다.
매개변수 목록에서 default 가 있는 매개변수는 매개변수 목록에서 끝쪽을 선호한다. default값이 없는 매개변수가 일반적으로 메소드 의미에 대해 더 중요하고, 메소드가 호출되는 경우 안정적인 초기화 패턴을 제공한다.
운영 환경에서 API를 실행하려면 #file을 사용하십시오. 대체 항목보다 ID가 우선합니다. #파일 ID는 공간을 절약하고 개발자의 개인 정보를 보호합니다. 전체 경로를 통해 개발 워크플로우가 단순해지거나 파일 I/O에 사용되는 경우 최종 사용자(예: 테스트 도우미 및 스크립트)가 실행하지 않는 API에서 #filePath를 사용하십시오. #file을 사용하여 Swift 5.2 이전 버전과 소스 호환성을 유지합니다.
3. 인자레이블 (Argument Labels)
func move(**from** start: Point, **to** end: Point)
x.move(**from**: x, **to**: y)
인자가 유용하게 구별 할 수 없는 경우, 모든 레이블은 생략한다.
예)min(number1, number2)
,zip(sequence1, sequence2)
첫번째 인자가 전치사 구(prepositional phrase)의 일부일때, 인자 레이블을 제공한다.
값을 유지하면서 타입을 변환해주는 이니셜라이저(initializer) 라면 첫번째 argument lable은 생략한다.
예)Int64(someUInt32)
첫번째 전달인자는 언제나 변환의 소스(source)가 되는게 좋다
/// ✅ 바람직한 예제 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)
값의 범위가 좁혀지는 타입 변환에서는 좁아지는것을 설명하는 레이블은 추천한다.
/// ✅ 바람직한 예제 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) }
값을 저장하는 타입 변환은 단일형(monomorphism)이다. 즉, 원본 결과의 값에서 모든 차이점은 결과 값의 차이이다. 예를 들어, 언제나 Int8값은 Int64값으로 변환되기 때문에, Int8에서 Int64로 변환은 ‘값을 유지’한다. 하지만 반대 방향으로 변환하면(Int64 → Int8) 값을 유지할수 없다. Int64는 Int8로 표현할 수 있는 것보다 더 많은 수를 표현할 수 있기 때문이다.
주의 : 원래 값을 검색할 수 있는 기능은 변환이 값을 보존하는지 여부와 무관합니다.
첫번째 인자가 전치사 구(prepositional phrase)의 일부일때, 전달인자 레이블을 사용한다
인자 레이블은 일반적으로 전치사(preposition)로 시작한다.
예)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)
반면, 첫번째 인자가 문법적인 구(grammatical phrase)를 만든다면, 레이블을 생략하고 함수이름에 base name 을 추가한다.
예)
x.addSubview(y)
이 가이드라인은 첫번째 인자가 문법구문의 일부를 형성하지 않는경우, 레이블을 가지라고 말하는 겁니다.
구(phrase)가 정확한 의미를 전달하는것이 중요합니다./// ✅바람직한 예제 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?
주의 : 인자의 default값의 argument는 생략할수 있음을 유의해야하고, 이 경우 문법에 맞지 않기 때문에, 항상 레이블을 가져야 한다.
다른 모든 전달인자에 레이블을 달기.
특별 지침(Special Instructions)
클로져 매개변수에 이름을 붙이고 튜플 멤버에게 레이블을 달기
이러한 이름은 설명하는 힘을 가지고 있고, 문서의 주석에서 부터 참조될 수 있고, 튜플 멤버를 쉽게 알 수 있게한다.
/// 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)
clouser 매개변수에 사용되는 이름은 최상위 함수에 대한 매개변수 이름처럼 선택해야 한다. 호출시 나타나는 clouser 매개변수의 레이블은 지원되지 않습니다.
오버로드(overload) set 에의 모호함을 피하기 위해 자유로운 다형성(unconstrained polymorphism)에 좀 더 주의를 기울인다.
예)
Any
,AnyObject
,자유로운 제네릭 매개변수
예를들어, 오버로드 설정을 고려해서.
/// ❌바람직하지 않은 예제 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 }
위 메소드의 경우 argument type이 뚜렷하게 구분되어 보이지만 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 작성자의 주의를 끌었다.
'swift 공식문서' 카테고리의 다른 글
[swift] KVO, Notification (0) | 2022.09.02 |
---|---|
[번역] API Design Guidelines 번역 (2/3) - Naming (0) | 2022.08.16 |
[번역] API Design Guidelines 번역 (1/3) (0) | 2022.08.16 |