포스트

Swift Style Guide (번역)

API Design Guidelines

Fundamentals (기본 사항)

  • 사용 시점의 명확성이 가장 중요한 목표입니다.

    메서드나 프로퍼티와 같은 엔티티는 한 번만 선언되지만 반복적으로 사용됩니다. 이러한 사용처를 명확하고 간결하게 만들 수 있도록 API를 설계하세요. 설계를 평가할 때 선언문을 읽는 것만으로는 충분하지 않으며, 항상 사용 사례를 검토하여 문맥상 명확하게 보이는지 확인해야 합니다.

  • 간결함보다 더 중요한 것은 명확성입니다.

    Swift 코드는 간결할 수 있지만, 최소한의 문자로 가능한 한 가장 작은 코드를 구현하는 것은 목표가 아닙니다. Swift 코드의 간결성은 강력한 타입 시스템과 상용구를 자연스럽게 줄여주는 기능의 부작용입니다.

  • 모든 선언에 대해 문서 주석을 작성하세요.

    문서 작성을 통해 얻은 인사이트는 디자인에 큰 영향을 미칠 수 있으므로 미루지 마세요. > API의 기능을 간단한 용어로 설명하는 데 어려움이 있다면 잘못된 API를 설계한 것일 수 있습니다.

    DETAIL1

    • 스위프트 마크다운을 사용하세요.

    • 엔티티를 설명하는 요약부터 시작하세요. 주로 API는 선언과 요약을 통해 완전히 이해할 수 있습니다.

      1
      2
      3
      
      /// Returns a "view" of `self` containing the same elements in
      /// reverse order.
      func reversed() -> ReverseCollection
      

      DETAIL2

      • 요약에 집중하세요. 요약이 가장 중요한 부분입니다. 훌륭한 문서 댓글은 대부분 훌륭한 요약으로 구성되어 있습니다.

      • 가능하면 마침표로 끝나는 단일 문장 조각을 사용하세요. 완전한 문장을 사용하지 마세요.

      • 함수나 메서드가 수행하는 작업과 반환하는 결과를 설명하며, 널 효과나 void 반환은 생략합니다.

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        
        /// 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와 같이 드물게 주석이 세미콜론으로 구분된 여러 문장으로 구성되는 경우가 있습니다.

      • 사용자가 접근하는 항목을 설명합니다.

        1
        2
        
        /// Accesses the `index`th element.
        subscript(index: Int) -> Element { get set }
        
      • 이니셜라이저가 생성하는 내용을 설명합니다.

        1
        2
        
        /// Creates an instance containing `n` repetitions of `x`.
        init(count n: Int, repeatedElement x: Element)
        
      • 다른 모든 선언에 대해서 선언된 엔티티가 무엇인지 설명합니다.

        1
        2
        3
        4
        5
        6
        7
        8
        
        /// 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
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      /// 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")
      

      DETAIL3

Naming

Promote Clear Usage (명확한 사용법 지향)

  • 이름을 사용하는 코드를 읽는 사람이 모호하지 않게 하기 위해 필요한 모든 단어를 포함하세요.

    DETAIL1

    예를 들어 컬렉션 내에서 지정된 위치에 있는 요소를 제거하는 메서드를 생각해 보겠습니다.

    1
    2
    3
    4
    5
    
    // Right Example
    extension List {
      public mutating func remove(at position: Index) -> Element
    }
    employees.remove(at: x)
    

    메서드 서명에서 at이라는 단어를 생략하면 독자에게 메서드가 제거할 요소의 위치를 나타내기 위해 x를 사용하는 것이 아니라 x와 같은 요소를 검색하여 제거한다는 것을 암시할 수 있습니다.

    1
    2
    
    // Wrong Example
    employees.remove(x) // unclear: are we removing x?
    
  • 불필요한 단어는 생략하세요. 이름에 포함된 모든 단어는 사용 사이트에서 중요한 정보를 전달해야 합니다.

    DETAIL2

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

    1
    2
    3
    4
    
    // Wrong Example
    public mutating func removeElement(_ member: Element) -> Element?
    
    allViews.removeElement(cancelButton)
    

    이 경우 Element라는 단어는 호출 쪽에서 의미를 더해주지 않습니다. 따라서 이 API가 더 좋을 것입니다.

    1
    2
    3
    4
    
    // Right Example
    public mutating func remove(_ member: Element) -> Element?
    
    allViews.remove(cancelButton) // clearer
    

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

  • 변수, 매개변수 및 관련 타입은 타입 제약 조건이 아닌 역할에 따라 이름을 지정합니다.

    DETAIL3

    1
    2
    3
    4
    5
    6
    7
    8
    
    // Wrong Example
    var **string** = "Hello"
    protocol ViewController {
      associatedtype **ViewType** : View
    }
    class ProductionLine {
      func restock(from **widgetFactory**: WidgetFactory)
    }
    

    이런 식으로 타입 이름을 작성하면 명확성과 표현력이 떨어집니다. 대신 엔티티의 역할을 표현할 수 있는 이름을 선택하도록 노력하세요.

    1
    2
    3
    4
    5
    6
    7
    8
    
    // Right Example
    var **greeting** = "Hello"
    protocol ViewController {
      associatedtype **ContentView** : View
    }
    class ProductionLine {
      func restock(from **supplier**: WidgetFactory)
    }
    

    associatedtype이 프로토콜 제약 조건에 너무 강하게 연관되어 프로토콜 이름이 역할인 경우, 프로토콜 이름에 프로토콜을 추가하여 충돌을 피하세요.

    1
    2
    3
    4
    
    protocol Sequence {
      associatedtype Iterator : Iterator**Protocol**
    }
    protocol Iterator**Protocol** { ... }
    
  • 타입 정보를 보완하여 매개변수의 역할을 명확히 합니다.

    DETAIL4

    특히 매개변수 유형이 NSObject, Any, AnyObject이거나 Int 또는 String과 같은 기본 유형인 경우 사용 시점의 유형 정보 및 컨텍스트가 의도를 완전히 전달하지 못할 수 있습니다. 이 예에서는 선언은 명확할 수 있지만 사용하는 곳에서는 모호합니다.

    1
    2
    3
    4
    
    // Wrong Example
    func add(_ observer: NSObject, for keyPath: String)
    
    grid.add(self, for: graphics) // vague
    

    명확성을 회복하려면 의미가 약한 각 매개변수 앞에 그 역할을 설명하는 명사를 붙이세요.

    1
    2
    3
    
    // Right Example
    func addObserver(_ observer: NSObject, forKeyPath path: String)
    grid.addObserver(self, forKeyPath: graphics) // clear
    

Strive for Fluent Usage (능숙한 사용을 의한 노력)

  • 사용하는 쪽에서 문법적으로 영어 구문을 형성하는 메서드 및 함수 이름을 선호합니다.

    DETAIL1

    1
    2
    3
    4
    
    // Right Example
    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
    
    1
    2
    3
    4
    
    // Wrong Example
    x.insert(y, position: z)
    x.subViews(color: y)
    x.nounCapitalize()
    

    매개변수가 사용 의미의 핵심이 아닌 경우 한두 개의 인수 이후에는 유창성이 저하되는 것은 허용됩니다.

    1
    2
    3
    
    AudioUnit.instantiate(
      with: description,
      options: [.inProcess], completionHandler: stopProgressBar)
    
  • 팩토리 메서드의 이름은 “make”로 시작합니다(예: x.makeIterator()).
  • 이니셜라이저 및 팩토리 메서드 호출의 첫 번째 인수는 기본 이름으로 시작하는 문장을 형성해서는 안 됩니다(예: x.makeWidget(cogCount: 47)).

    DETAIL2

    예를 들어 이러한 호출의 첫 번째 인수는 기본 이름으로서 같은 구문의 일부로 읽히지 않습니다.

    1
    2
    3
    4
    
    // Right Example
    let foreground = Color(red: 32, green: 64, blue: 128)
    let newPart = factory.makeWidget(gears: 42, spindles: 14)
    let ref = Link(target: destination)
    

    아래에서는 API 작성자가 첫 번째 인수를 사용하여 문법적 연속성을 만들려고 시도했습니다.

    1
    2
    3
    4
    
    // Wrong Example
    let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
    let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
    let ref = Link(to: destination)
    

    실제로 이 지침은 인자 레이블에 대한 지침과 함께 호출이 값 보존 유형 변환을 수행하지 않는 한 첫 번째 인자에 레이블을 갖는다는 것을 의미합니다.

    1
    
    let rgbForeground = RGBColor(cmykForeground)
    
  • 사이드 이펙트에 따라 함수와 메서드에 이름을 붙입니다.

    • 사이드 이펙트가 없는 함수는 명사 구문으로 읽어야 합니다(예: x.distance(to: y), i.successor())

    • 사이드 이펙트가 있는 것은 print(x), x.sort(), x.append(y)와 같은 명령형 동사 구문으로 읽어야 합니다.

    • mutating/nonmutating 메서드 쌍의 이름을 일관되게 지정합니다.

      mutating 메서드에는 종종 비슷한 의미를 가진 nonmutating 변형이 있지만 인스턴스를 제자리에서 업데이트하지 않고 새 값을 반환합니다.

    • 연산이 동사로 자연스럽게 설명되는 경우, 동사의 명령형을 돌연변이 메서드에 사용하고 “ed” 또는 “ing” 접미사를 적용하여 돌연변이가 일어나지 않는 상대방의 이름을 지정합니다.

      MutatingNonmutating
      x.sort()z = x.sorted()
      x.append(y)z = x.appending(y)

      DETAIL3

      동사의 과거 분사를 사용하여 nonmutating 이름을 지정하는 것을 선호합니다(일반적으로 “ed”를 추가)

      1
      2
      3
      4
      5
      6
      7
      8
      
      /// 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에 이름을 지정합니다.

      1
      2
      3
      4
      5
      6
      7
      8
      
      /// 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()
      
      • 연산이 명사로 자연스럽게 설명되는 경우, 비변이 메서드에 명사를 사용하고 “form” 접두사를 적용하여 변이 메서드에 해당하는 이름을 지정합니다.
      NonmutatingMutating
      x = y.union(z)y.formUnion(z)
      j = c.successor(i)c.formSuccessor(&i)
  • 부울 메서드 및 프로퍼티의 사용은 mutating이 발생하지 않는 경우 수신자에 대한 주장으로 읽어야 합니다(예: x.isEmpty, line1.intersects(line2)).
  • 어떤 것이 무엇인지 설명하는 프로토콜은 명사로 읽어야 합니다(예: 컬렉션).
  • 기능을 설명하는 프로토콜은 접미사 able, ible 또는 ing를 사용하여 이름을 지정해야 합니다(예: Equatable, ProgressReporting).
  • 타입, 속성, 변수 및 상수의 이름은 명사로 읽어야 합니다.

Use Terminology Well (올바른 용어 사용)

  • 더 일반적인 단어로도 충분히 의미를 전달할 수 있다면 모호한 용어는 피하세요. ‘피부’가 목적에 부합한다면 ‘표피’라고 말하지 마세요. 전문 용어는 필수적인 커뮤니케이션 도구이지만, 놓칠 수 있는 중요한 의미를 포착할 때만 사용해야 합니다.

  • 전문 용어를 사용할 경우 정해진 의미에 충실하세요.

    DETAIL1

    일반적인 단어 대신 전문 용어를 사용하는 유일한 이유는 모호하거나 불분명할 수 있는 내용을 정확하게 표현하기 위해서입니다. 따라서 API는 해당 용어가 허용되는 의미에 따라 엄격하게 사용해야 합니다.

    • 전문가를 놀라게 하지 마세요. 이미 이 용어에 익숙한 사람이라면 우리가 새로운 의미를 만들어낸 것처럼 보이면 놀랄 것이고 아마도 분노할 것입니다.
    • 초보자를 혼동하지 마세요. 이 용어를 알려고 하는 사람은 누구나 웹 검색을 통해 기존 의미를 찾을 수 있습니다.
  • 약어는 피하세요. 약어, 특히 비표준 약어는 줄임말이 아닌 형태로 정확하게 번역해야 이해가 가능하기 때문에 사실상 전문 용어에 해당합니다.

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

  • 선례를 수용하세요. 기존 문화에 순응하는 대신 초보자를 위해 용어를 최적화하지 마세요.

    DETAIL2

    초보자가 List의 의미를 더 쉽게 이해할 수 있더라도 List와 같은 단순화된 용어를 사용하는 것보다 연속적인 데이터 구조의 이름을 Array로 지정하는 것이 더 낫습니다.

    배열은 최신 컴퓨팅의 기본이므로 모든 프로그래머는 배열이 무엇인지 알고 있거나 곧 알게 될 것입니다. 대부분의 프로그래머가 익숙한 용어를 사용하면 웹 검색과 질문에 대해 이점을 얻을 수 있습니다.

    수학과 같은 특정 프로그래밍 영역에서는 PositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)와 같은 설명 문구보다는 sin(x)와 같이 널리 사용되는 용어를 사용하는 것이 좋습니다. 이 경우 축약어를 피하라는 지침보다 선례가 더 중요하다는 점에 유의하세요. 완전한 단어는 사인이지만 “sin(x)”는 수십 년 동안 프로그래머와 수학자 사이에서 일반적으로 사용되어 왔습니다.

Conventions (규칙)

General Conventions (일반 규칙)

  • O(1)이 아닌 모든 계산된 프로퍼티의 복잡도를 문서화합니다.

    사람들은 프로퍼티를 멘탈 모델로 저장해 두었기 때문에 프로퍼티 액세스에 큰 계산이 필요하지 않다고 생각하는 경우가 많습니다. 이러한 가정이 깨질 수 있는 경우 반드시 경고하세요.

  • 자유 함수(멤버 함수가 아닌 일반 함수)보다 메서드와 프로퍼티를 선호하세요. 자유 함수는 특별한 경우에만 사용됩니다:

    DETAIL

    • 뚜렷한 자아가 없을 때 self:min(x, y, z)
    • 함수가 제약되지 않은 제네릭인 경우 print(x)
    • 함수 구문이 기존 도메인 표기법의 일부인 경우 sin(x)
  • 대소문자 규칙을 따릅니다.

    유형과 프로토콜의 이름은 대문자 대소문자를 사용합니다. 그 외 모든 것은 소문자입니다. - DETAIL 미국 영어에서 일반적으로 모두 대문자로 표시되는 약어와 이니셜은 대소문자 규칙에 따라 대문자 또는 소문자로 통일해야 합니다.

    1
    2
    3
    
    var utf8Bytes: [UTF8.CodeUnit]
    var isRepresentableAsASCII = true
    var userSMTPServer: SecureSMTPServer
    

    그 외의 약어는 일반 단어로 취급해야 합니다:

    1
    2
    
    var radarDetector: RadarScanner
    var enjoysScubaDiving = true
    
  • 메서드는 동일한 기본 의미를 공유하거나 서로 다른 도메인에서 작동하는 경우 기본 이름을 공유할 수 있습니다.

    DETAIL

    예를 들어, 다음과 같은 방법은 본질적으로 동일한 작업을 수행하므로 권장됩니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    extension Shape {
      /// Returns `true` if `other` is within the area of `self`;
      /// otherwise, `false`.
      func contains(_ other: Point) -> Bool { ... }
    
      /// Returns `true` if `other` is entirely within the area of `self`;
      /// otherwise, `false`.
      func contains(_ other: Shape) -> Bool { ... }
    
      /// Returns `true` if `other` is within the area of `self`;
      /// otherwise, `false`.
      func contains(_ other: LineSegment) -> Bool { ... }
    }
    

    또한 Shape 타입과 컬렉션은 별도의 도메인이므로 동일한 프로그램에서도 사용할 수 있습니다.

    1
    2
    3
    4
    5
    6
    
    // Right Example
    extension Collection where Element : Equatable {
      /// Returns `true` if `self` contains an element equal to
      /// `sought`; otherwise, `false`.
      func contains(_ sought: Element) -> Bool { ... }
    }
    

    그러나 이러한 인덱스 메서드는 서로 다른 의미를 가지므로 이름을 다르게 지정해야 합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    
    // Wrong Example
    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 { ... }
    }
    

    마지막으로, 타입 추론이 있는 경우 모호성을 유발하므로 ‘반환 유형에 대한 오버로딩’을 피하세요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // Wrong Example
    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 (매개변수)

1
func move(from start: Point, to end: Point)
  • 문서를 제공할 매개변수 이름을 선택합니다. 매개변수 이름은 함수나 메서드의 사용 지점에 나타나지 않더라도 중요한 설명 역할을 합니다.

    DETAIL1

    이러한 이름을 선택하면 문서를 읽기 쉽게 만들 수 있습니다. 예를 들어, 이러한 이름을 사용하면 문서를 자연스럽게 읽을 수 있습니다.

    1
    2
    3
    4
    5
    6
    7
    
    // Right Example
    /// 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
    8
    
    // Wrong Example
    /// 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])
    
  • 디폴트 매개변수는 일반적인 사용을 단순화할 때 활용하세요. 일반적으로 사용되는 단일 값이 있는 모든 매개변수는 기본값을 적용할 여지가 있습니다.

    DETAIL2

    기본 인수는 관련 없는 정보를 숨겨 가독성을 향상시킵니다. 예를 들어

    1
    2
    3
    
    // Wrong Example
    let order = lastName.compare(
      royalFamilyName, options: [], range: nil, locale: nil)
    

    위 예제가 훨씬 더 간단해질 수 있습니다.

    1
    2
    
    // Right Example
    let order = lastName.compare(royalFamilyName)
    

    기본 인수는 일반적으로 메서드 패밀리를 사용하는 것보다 선호되는데, 이는 API를 이해하려는 모든 사람에게 인지적 부담을 덜 주기 때문입니다.

    1
    2
    3
    4
    5
    6
    7
    8
    
    // Right Example
    extension String {
      /// ...description...
      public func compare(
         _ other: String, options: CompareOptions = [],
         range: Range? = nil, locale: Locale? = nil
      ) -> Ordering
    }
    

    위의 내용은 간단하지 않을 수 있지만 아래 예제에 비해 훨씬 간단합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    // Wrong Example
    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()가 항상 동의어가 아닌 경우)로 인해 대부분 동일한 문서에서 사소한 차이점을 찾아내는 지루한 과정을 거쳐야 합니다. 기본값이 있는 단일 메서드를 사용하면 훨씬 더 뛰어난 프로그래머 환경을 제공합니다.

  • 기본값이 있는 매개변수는 매개변수 목록의 맨 끝에 위치하는 것이 좋습니다. 기본값이 없는 매개변수는 일반적으로 메서드의 의미론에 더 필수적이며, 메서드가 호출될 때 안정적인 초기 사용 패턴을 제공합니다.
  • API가 프로덕션 환경에서 실행될 경우 다른 대안보다 #fileID를 선호하세요. #fileID는 공간을 절약하고 개발자의 개인정보를 보호합니다. 전체 경로가 개발 워크플로를 간소화하거나 파일 I/O에 사용되는 경우 최종 사용자가 실행하지 않는 API(예: 테스트 헬퍼 및 스크립트)에는 #filePath를 사용하세요. Swift 5.2 이하 버전과의 소스 호환성을 유지하려면 #file을 사용하세요.

Argument Labels (매개변수 레이블)

1
2
func move(from start: Point, to end: Point)
x.move(from: x, to: y)
  • 인수를 유용하게 구분할 수 없는 경우 모든 레이블을 생략합니다 (예: min(number1, number2), zip(sequence1, sequence2)).
  • 값 보존 타입 변환을 수행하는 이니셜라이저에서는 첫 번째 인자 레이블을 생략합니다(예: Int64(일부UInt32)).

    DETAIL1

    첫 번째 매개변수는 항상 변환의 주체여야 합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    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 타입 변환(더 작은 타입으로의 변환을 의미)에서는 를 설명하는 레이블을 사용하는 것이 좋습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    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)
    }
    

    값 보존 타입 변환은 소스 값의 모든 차이가 결과 값의 차이를 초래하는 단형성입니다. 예를 들어 Int8에서 Int64로의 변환은 모든 고유한 Int8 값이 고유한 Int64 값으로 변환되므로 값 보존형입니다. 그러나 다른 방향으로의 변환은 값 보존이 불가능합니다: Int64는 Int8로 표현할 수 있는 값보다 가능한 값이 더 많기 때문입니다.

    참고: 원래 값을 검색할 수 있는 기능은 변환이 값 보존인지 여부와 관련이 없습니다.

  • 첫 번째 인수가 전치사 구의 일부를 구성하는 경우 인자 레이블을 지정합니다. 인자 레이블은 일반적으로 전치사에서 시작해야 합니다(예: x.removeBoxes(havingLength: 12)).

    DETAIL2

    처음 두 인수가 단일 추상화의 일부를 나타내는 경우 예외가 발생합니다.

    1
    2
    3
    
    // Wrong Example
    a.move(toX: b, y: c)
    a.fade(fromRed: b, green: c, blue: d)
    

    이러한 경우 추상화를 명확하게 하기 위해 전치사 뒤에 인수 레이블을 시작하세요.

    1
    2
    3
    
    // Right Example
    a.moveTo(x: b, y: c)
    a.fadeFrom(red: b, green: c, blue: d)
    
  • 반면 첫 번째 인수가 문법 구의 일부를 구성하는 경우 해당 레이블을 생략하고 기본 이름에 앞의 단어를 추가합니다(예: x.addSubview(y)).

    DETAIL3

    이 가이드라인은 첫 번째 인수가 문법 구의 일부를 구성하지 않는 경우 레이블이 있어야 한다는 것을 의미합니다.

    1
    2
    3
    4
    
    // Right Example
    view.dismiss(animated: false)
    let text = words.split(maxSplits: 12)
    let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)
    

    문구가 정확한 의미를 전달하는 것이 중요하다는 점에 유의하세요. 다음은 문법적으로는 맞지만 잘못된 것을 표현할 수 있습니다.

    1
    2
    
    view.dismiss(false)   Don't dismiss? Dismiss a Bool?
    words.split(12)       Split the number 12?
    

    기본값이 있는 인수는 생략할 수 있으며, 이 경우 문법 구문의 일부를 구성하지 않으므로 항상 레이블이 있어야 합니다.

  • 이외의 모든 매개변수에 레이블을 지정합니다.

Special Instructions (특별 지침)

  • 튜플 멤버에 레이블을 지정하고 API에 표시되는 클로저 매개변수에 이름을 지정합니다.

    DETAIL1

    이러한 이름은 설명력이 있고, 문서 주석에서 참조할 수 있으며, 튜플 멤버에 대한 표현식 액세스를 제공합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    /// 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` if a new block of memory
    ///     was allocated; otherwise, `false`.
    ///   - capacityChanged: `true` if `capacity` was updated;
    ///     otherwise, `false`.
    mutating func ensureUniqueStorage(
      minimumCapacity requestedCapacity: Int,
      allocate: (_ byteCount: Int) -> UnsafePointer<Void>
    ) -> (reallocated: Bool, capacityChanged: Bool)
    

    클로저 매개변수에 사용되는 이름은 최상위 함수의 매개변수 이름처럼 선택해야 합니다. 호출하는 쪽에 표시되는 클로저 인수의 레이블은 지원되지 않습니다.

  • 제약이 없는 다형성(예: Any, AnyObject 등)을 사용할 때는 오버로드의 모호성을 피하기 위해 각별히 주의하세요.

    DETAIL2

    예를 들어 이 오버로드를 살펴보겠습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // Wrong Example
    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인 경우 단일 엘리먼트는 엘리먼트 시퀀스와 동일한 유형을 가질 수 있습니다.

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

    모호함을 없애려면 두 번째 오버로드의 이름을 더 명확하게 지정하세요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    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 작성자의 주의를 해당 문제로 끌어들였습니다.


참고링크

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.