Swift API Design Guideline 번역과 정리

Lily·2021년 9월 9일
0
post-thumbnail

이 글은 Swift 공식 사이트에서 제시하는 Swift API Design Guideline 문서를 번역하고 이해하기위해 정리한 글입니다. 번역에 대한 피드백이나 잘못된 부분이 있다면 언제든 댓글 남겨주세요!😊

원본: Swift API Design Guideline(Swift.org)



프로그램을 만들면서 코드를 짜는 것만큼이나 고민을 하게 되는 것이 바로 Naming이다. 어떻게 간결하고 명확하게 함수의 기능을 이름으로 표현할 수 있을까?

Swift공식문서에서 제공하는 Swift API Design Guideline이 이에 대한 가이드를 제시해주고 있다.

오늘은 이 문서를 번연해서 이해해보고, 읽기 좋고 사용하기 좋은 네이밍에 대해 공부해보자! (구글번역기를 참고하였고, 원문의 영단어가 의미를 더 잘 표현하는 경우엔 해석하지 않고 그대로 사용하였습니다.)


< 목차 >

  • Fundamental
  • Naming
    • Promote Clear Usage
    • Strive for Fluent Usage
    • Use Terminology Well
  • Conventions
    • General Conventions
    • Parameters
    • Argument Labels
  • Special Instructions

Fundamental (기본 원칙)

🚩 사용 시점의 명확성(clarity)이 가장 중요한 목표.

메소드 및 프로퍼티와 같은 독립체는 한 번 선언되지만 반복적으로 사용됩니다. 이들의 사용을 알아듣기 쉽고 간결하게(clear & concise) API를 디자인하세요. 디자인을 평가할 때 선언문을 읽는 것만으로는 충분하지 않습니다. 항상 사용 사례를 검토하여 컨텍스트에서 명확하게 보이는지 확인하세요.

🚩 간결함(brevity)보다 명료함(clarity)이 더 중요.

Swift 코드는 간결할 수 있지만, 가장 적은 문자로 가능한 가장 작은 코드를 작성하는 것이 목표가 아닙니다 . Swift 코드의 간결함은 유형 시스템의 부작용이고, 자연스럽게 상용구를 줄이는 특징입니다.

🚩 모든 선언에 문서 주석을 작성할 것.

주석을 작성하며 얻은 통찰력은 당신의 코드 디자인에 엄청난 임팩트를 끼칠 수 있으므로 미루지 마세요!

  1. Use Swift’s dialect of Markdown.

  2. 선언되는 독립체를 설명하는 요약으로 시작합니다. 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?

참고: popFirst와 같은 드문 경우에 요약은 세미콜론으로 구분된 여러 문장 조각으로 구성됩니다.

  • Describe what a subscript accesses:
/// Accesses the `index`th element.
subscript(index: Int) -> Element { get set }
  • Describe what an initializer creates:
/// 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?
  ...
  1. Optionally, continue with one or more paragraphs and bullet items. Paragraphs are separated by blank lines and use complete sentences.

Naming

🚩 Promote Clear Usage, 명확한 사용을 추구하자!

  1. 모호함(ambiguity)을 피하기 위해 필요한 모든 단어를 포함시키세요. 그 이름이 사용되는 시점에 코드를 읽는 사람을 위해

예를 들어 컬렉션 내의 주어진 위치의 요소를 제거하는 메소드가 있습니다.

extension List {
  public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)

만약에 메소드 시그니처에서 'at'을 생략한다면, 이 메소드가 독자에게 x가 제거해야 할 요소의 위치를 가리킨다기보다는 x와 똑같은 요소를 찾아 제거하는 것이라 해석될 수도 있습니다.

⛔️
employees.remove(x) // 불분명 :  우리가 x를 제거하는 건가요?

  1. 필요 없는 단어는 생략하세요. 이름의 모든 단어는 사용시 중요한 정보를 전달해야 합니다.

의도를 명확히 하거나 의미를 명확하게 하기 위해 더 많은 단어가 필요할 수 있지만 독자가 이미 보유하고 있는 정보와 중복되는 단어는 생략해야 합니다. 특히, 단순히 유형 정보를 반복 하는 단어는 생략 하십시오.

⛔️
public mutating func removeElement(_ member: Element) -> Element?

allViews.removeElement(cancelButton)

이 경우 'Element'는 호출시 전혀 중요한 의미를 내포하지 않습니다. 아래 API가 더 좋겠네요.

public mutating func remove(_ member: Element) -> Element?

allViews.remove(cancelButton) // 더 명확함, clearer

경우에 따라 모호성을 피하기 위해 유형 정보를 반복해야 하지만, 일반적으로 유형보다는 매개변수의 역할을 설명하는 단어를 사용하는 것이 좋습니다 . 자세한 내용은 다음 항목을 참조하십시오.


  1. 타입의 제약보단 역할에 따라 변수, 매개변수 및 관련 유형의 이름을 지정합니다 .
⛔️
var string = "Hello"
protocol ViewController {
  associatedtype ViewType : View
}
class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}

이러한 방식으로 유형의 이름을 변경하면 명확성과 표현력을 최적화할 수 없습니다. 대신 독립체의 역할을 나타내는 이름을 선택하도록 노력하십시오 .

string ➡️ greeting
ViewType ➡️ ContentView
widgetFactory ➡️ supplier

var greeting = "Hello"
protocol ViewController➡️ {
  associatedtype ContentView : View
}
class ProductionLine {
  func restock(from supplier: WidgetFactory)
}

  1. 매개변수의 역할을 명확히하기 위해 약한 유형 정보를 보충합니다.

특히 매개변수의 유형이 NSObject, Any, AnyObject일 때, 또는 기본 유형인 Int 또는 String일 때, 사용 시점에서 타입의 정보와 context가 완전히 의도를 전달하지 않을 수 있습니다. 아래 예에서 선언은 명확할 수 있지만 사용시에는 모호합니다.

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

grid.add(self, for: graphics) // 모호함

명확성을 보충하기위해 각각의 약한 유형의 매개변수 앞에 역할을 설명하는 명사가 옵니다.

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

grid.addObserver(self, forKeyPath: graphics) // clear

🚩 Strive for Fluent Usage, 유창한 사용을 위해 노력하자!

  1. 메소드와 함수의 이름은 사용시 영문법적으로 구절이 형성되는 이름을 선호합니다.
✅
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)

  1. 팩토리 메소드의 이름은 " make"로 시작합니다( 예: x.makeIterator( ).)

  2. 초기화 메소드 및 팩토리 메소드의 첫 번째 인자는 함수의 이름으로 시작하는 구를 형성해서는 안 됩니다. x.makeWidget(cogCount: 47)

예를 들어, 이러한 호출에 대한 첫 번째 인자는 함수 이름에 이어지는 같은 구문의 일부로 읽혀지지 않습니다. (1번의 함수의 이름이 영문법적으로 구절이 형성되는 이름과는 다르게 첫번째 인자가 네이밍 되어야한다는 것으로 이해된다)

let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
let ref = Link(target: destination)

아래의 예는 API작성자가 첫번째 인자로 문법적인 연속성을 만들려고 노력했습니다. 안좋은 예

⛔️
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)

In practice, this guideline along with those for argument labels means the first argument will have a label unless the call is performing a value preserving type conversion.

실제로, 인자레이블을 위한 이 가이드라인은 함수의 호출이 값의 타입 변환을 보존하는 역할을 하는 경우가 아니라면 첫번째 인자는 label을 가질 수 있다는 의미이다.? 라고 해석되어지는데... 더 정확한 의미를 아시는분은 댓글남겨주세요!ㅜㅜ

let rgbForeground = RGBColor(cmykForeground)

  1. 함수와 메소드의 side-effect에 따라 이름을 지으세요.

여기서 말하는 'side-effect' 란...
컴퓨터 과학에서 함수가 결과값 이외에 다른 상태를 변경시킬 때 부작용(side-effect)이 있다고 말한다. 예를 들어, 함수가 전역변수나 정적변수를 수정하거나, 인자로 넘어온 것들 중 하나를 변경하거나 화면이나 파일에 데이터를 쓰거나, 다른 부작용이 있는 함수에서 데이터를 읽어오는 경우가 있다. - 위키피디아 -

  • side-effect가 없는 함수와 메소드는 명사구로 읽혀져야합니다. 예) x.distance(to: y), i.successor().

  • side-effect가 있는 함수와 메소드는 명령을 나타내는 동사구로 읽혀져야합니다.예) print(x), x.sort(), x.append(y).

  • mutating/nonmutating 메소드의 이름은 짝을 지어 일관되게 짓습니다. mutaiting 메소드는 자주 비슷한 의미의 nonmutating형 함수를 갖습니다. 하지만 nonmutating 메소드는 인스턴스를 그 자리에서 업데이트하기보단 새 값을 반환합니다. (즉, mutating메소드는 인스턴스를 그자리에서 업데이트하고, nonmutating메소드는 새 값을 반환한다는 의미)

    여기서 mutating 메소드는 mutating 키워드로 정의된 메소드를 의미

1) 기능이 동사로 자연스럽게 설명되는 경우엔, 동사의 명령형을 mutating 메소드에, 동사에 접미사 "ed" 나 "ing" 붙인 것을 그 상대 nonmutaitng 메소드에 사용합니다.

nonmutating형 메소드는 동사의 과거분사형을 사용해 이름을 짓는것이 좋습니다.(보통 "-ed"를 추가)

/// Reverses `self` in-place.
mutating func reverse()

/// Returns a reversed copy of `self`.
func reversed() -> Self
...
x.reverse()
let y = x.reversed()

단 동사가 직접목적어를 갖기때문에 "ed"를 추가하는 것이 문법적으로 부자연스러운 경우엔, nonmutating형 메소드를 동사의 현재분사형을 사용해 이름을 짓는 것이 좋습니다.(보통 "-ing"를 추가)

/// 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()

2) 기능이 명사로 자연스럽게 설명되는 경우엔, nonmutating메소드엔 명사를 사용하고, mutating메소드엔 명사에 접두사 "form"을 붙여 (form+명사) 이름을 짓습니다.

  1. Boolean 메소드와 프로퍼티의 사용은 nonmutating일 경우에 수신에 대한 주장처럼 읽혀져야 합니다. 예) x.isEmpty, line1.intersects(line2).

  2. 무엇인가를 설명하는 프로토콜은 명사로 읽혀져야 합니다. 예)Collection

  3. 능력을 설명하는 프로토콜은 접미사 able, ible, or ing를 사용해 지어야 합니다. 예) Equatable, ProgressReporting

  4. 다른 유형, 프로퍼티, 변수, 상수는 명사로 지어야합니다.

🚩 Use Terminology well, 용어를 잘 사용하자!

Term of Art
noun - a word or phrase that has a precise, specialized meaning within a particular field or profession. (특정 전문 분야에서 정밀하고 전문적인 의미를 가진 단어나 구)

  1. 더 흔하게 사용되는 단어가 의미를 잘 전달한다면, 잘 알려져있지 않은 용어는 사용하지 않도록하세요 “skin(피부)”가 당신이 전달하려는 의미를 잘 전달해준다면 “epidermis(표피)”를 사용하지 마세요. Terms of art(전문 용어)는 중요한 커뮤니케이션 도구이지만, 전문 용어 대신 다른 단어를 사용시 핵심적인 의미가 빠질 경우에만 사용되어야 합니다.

  2. 전문 용어를 사용할 땐, 확실히 자리를 잡은 의미로 사용하세요.

    보다 일반적인 단어보다 전문 용어를 사용하는 유일한 이유는 모호하거나 불명확한 것을 정확하게 표현하기 때문입니다. 따라서 API는 허용된 의미에 따라 이 용어를 엄격하게 사용해야 합니다.

  • 전문가를 놀래키지 마세요 : 용어에 새로운 의미를 발견한 걸 전문 용어에 이미 익숙한 사람들이 본다면 놀라거나 화를 낼 것입니다.
  • 초보자를 혼돈시키지 마세요 : 용어를 알려는 사람은 웹검색을 하고 이것의 전통적인 의미를 찾을 가능성이 큽니다.

만약 용어에 새로운 의미를 부여해서 사용한다면 그 분야의 전문가가 놀랄것이고, 전문용어를 처음 보 사람이 용어에 대해 찾아본다면 전통적으로 사용되는 의미를 참고할 것이니...즉 전문용어를 사용할 때 이미 확실히 자리잡은 의미로 해당 용어를 사용해야 한다는 말.

  1. 축약어를 피하세요. 특히 비표준 약어는 효과적인 기술 용어입니다. 이해가 약어를 약어가 아닌 형식으로 올바르게 번역하는 데 달려 있기 때문입니다

사용하는 약어의 의도된 의미는 웹 검색을 통해 쉽게 찾을 수 있어야 합니다.

  1. 선례를 받아들입니다. 기존 문화에 대한 순응을 희생하면서까지 전체 초보자를 위해 용어를 최적화하지 마십시오.

대부분의 프로그래머가 익숙한 용어를 사용하세요. 그러면 웹에서 질문에 대한 답 얻을 수 있을 것입니다.( 연속적인 데이터의 구조를 List보다 Array라고 하는게 좋은 것 처럼. list가 초보자에게 의미가 더 쉽게 다가올지라도, array는 현대 컴퓨터에서 기본적이고 그래서 모든 프로그래머가 알고있기 때문에)


Conventions (관습)

🚩 General Conventions (일반적 협약)

  1. O(1)이 아닌 연산 프로퍼티는 복잡도를 문서화해주세요. 사람들은 저장 프로퍼티에 익숙하기 때문에, 속성 접근에 커다란 연산이 수반되지 않는다고 가정합니다. 그 가정이 위반될 수 있는 경우엔 반드시 그들에게 문서화로 알려주세요.

  2. free functions(전역함수)보다 메소드와 프로퍼티를 더 선호합니다. 전역 함수는 특별한 경우에만 사용합니다.

    전역함수(Global Function)을 Swift에서는 Free-function이라고 함

  • 명백한 self가 없을 때:
min(x, y, z)
  • 함수가 제약 없는 제네릭인 경우:
print(x)
  • 함수 구문이 확립된 도메인 표기법의 일부인 경우:
sin(x)
  1. 대/소문자 규칙을 따릅니다. 유형 및 프로토콜의 이름은 UpperCamelCase입니다. 다른 모든 것은 lowerCamelCase입니다.

  2. 메소드는 동일한 기본 의미를 공유하거나 별개의 도메인에서 작동할 때, 기본 이름을 공유할 수 있습니다 .

예를 들어, 아래의 경우는 메소드가 본질적으로 같은 일을 하고있으므로 같은 base name을 사용하는 것이 권장됩니다.

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 { ... }
}

그리고 geometric types과 collections은 별개의 도메인임으로, 동일 프로그램에서 base name이 공유되는 건 괜찮습니다:

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 { ... }
}

마지막으로, 반환타입을 오버로딩하는 것은 피하세요. 왜냐하면 타입추론시 모호성을 유발하기때문입니다.

⛔️
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? { ... }
}

🚩 Parameters (매개변수)

func move(from start: Point, to end: Point)
  1. 문서화를 할때 parameter 이름을 고르세요. 파라미터 이름이 함수나 메소드의 사용시점에서 나타나지 않더라도, 중요한 설명 역할을 합니다.

문서를 쉽게 읽을 수 있도록 이러한 이름을 선택하십시오. 예를 들어, 다음 이름은 문서를 자연스럽게 읽히게 합니다.

/// 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])
  1. 일반적인 사용을 단순화할 때 매개변수에 기본값을 지정하세요. 일반적으로 사용되는 단일 값은 매개변수 기본값의 후보입니다.

기본값이 있는 매개변수는 관계없는 정보들을 숨김으로써 가독성을 높입니다. 예를 들어:

⛔️
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()는 항상 동의어가 아님)는 대부분이 같은 내용의 문서에서 약간의 차이를 찾아내야하는 실증나는 과정을 만들어냅니다. 기본값이 있는 단일 메소드를 사용하는 것은 훨씬 우수한 프로그래머 경험을 제공합니다.

  1. 기본값이 지정된 매개변수는 매개변수 목록 중 가장 끝에 있는 것이 좋습니다. 기본값이 없는 매개변수는 일반적으로 메소드의 의미에 더 필수적이며, 메소드가 호출될 때 안정적인 초기 사용 패턴을 제공합니다.

🚩 Argument Labels (인자 레이블)

func move(from start: Point, to end: Point)
x.move(from: x, to: y) 
  1. 모든 레이블이 유용하게 구분되지 않을 경우엔 생략하세요. 예) min(number1, number2), zip(sequence1, sequence2).

  2. 값의 타입 변환을 보장하는 initializer는 첫번째 인자 레이블을 생략하세요.
    예) 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)

그러나 "좁히는" 타입 변환에서는 좁히는 것을 설명하는 레이블이 권장됩니다.

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로 표현할 수 있는 것보다 더 많은 가능한 값이 있기때문입니다.

참고: 원래 값을 되찾아오는 기능은 변환이 값을 보장하는지 아닌지와 영향이 없습니다.

  1. 첫번째 레이블이 전치사구를 형성할 때엔, 인자레이블을 지정하세요. 인자 레이블은 일반적으로 전치사에서 시작해야 합니다( 예: 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)
  1. 첫번째 인자가 문법 구문을 형성하면, 레이블을 생략하고, 선행하는 단어를 기본 이름앞에 추가합니다. 예) x.addSubview(y)

첫번째 인자가 문법 구문을 형성하지 않는다면, 레이블을 가져야합니다.

✅
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?

기본값이 있는 인자는 생략될 수 있어서, 문법상 구문을 이루지 못해서 레이블이 반드시 있어야합니다.

  1. 그외의 인자는 모두 레이블을 표기합니다.

Conventions (관습)

1. 튜플 멤버에 레이블을 지정하고, 클로져 매개변수에 이름을 지정해주세요.

이름은 설명할 수 있는 힘이 있어서 문서화 주석에 참조할 수 있고 튜플 멤버에 접근하기 용이합니다.

/// 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)

클로저 매개변수의 이름은 최고 수준 함수의 매개변수의 이름과 같이 선택되어야 합니다. 호출시 클로저 인자레이블은 지원하지 않습니다.

  1. Any, AnyObject, unconstrained generic parameters와 같은 제약없는 다형성(unconstrained polymorphism)은 오버로드 할때 모호함을 피하기위해 더욱 주의하세요.

아래는 오버로드의 좋지 않은 예입니다.

⛔️
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
}

이러한 메소드는 의미적으로 비슷하고, 인자 타입이 명확하게 구분되어져 보입니다. 하지만 Element가 Any일 때, single element가 sequence of element와 같은 타입을 가질 수 있다.

⛔️
var values: [Any] = [1, "a"]
values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?

이러한 모호함을 피하기 위해, 두번째 오버로드에 명시적으로 이름을 지어주세요.

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작성자가 주의해야할 부분입니다.

profile
i🍎S 개발을 합니다

0개의 댓글