반응형

Swift API Design Guidelines

Swift 개발문서 API Design Guidelines


기본 원칙(Fundamentals)

  • 사용 시점을 명확하게 하는 것이 가장 중요한 목표이다. 메소드와 프로퍼티 같은 요소들은 반복되서 사용되지만 한번만 선언된다. 용도가 명확하고 간결하게 API를 설계한다. 설계를 평가할때, 선언된 것을 읽는 것으로 충분하지 않다. 맥락(context)이 명확히 보이는지 확인하기 위해 항상 사례를 검토한다.

  • 명확한 것은 간결한 것보다 더 중요하다. Swift는 코드를 간결하게 할수 있지만, 가장 적은 문자들로 가장작은 코드가 가능하지만 그게 목표가 아니다. Swift 코드의 간결성은 강한 타입 시스템의 부작용(side-effect)이 발생하고 자연스럽게 상용구를 줄이게 해준다.

  • 모든 선언에 대해 주석 문서를 작성한다. 통찰력을 얻을수 있는 문서 작성은 설계에 중요한 영향을 미칠수 있으므로, 미루지 않는다.

    간단한 용어로 API의 기능을 설명하는데 문제가 있는 경우, API를 잘못 설계 했을 수 있다.

    • Swift는 마크다운(Markdown) 은어를 사용한다.

    • 선언되는 요소를 설명하는 요약으로 시작한다. 종종, 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와 같은 경우는 드물며, 요약이 여러 문장으로 형성된다. 요약은 세미콜론(;)으로 구분된 여러 문장의 형태가 된다.

    • 서브스크립트가 무엇을 접근하는지 설명한다.

      /// 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?
        ...
  • 선택적으로, 하나 이상의 단락과 머리기호를 연속적으로 사용한다. 단락은 비어 있는 줄로 구분되고, 완전한 문장을 사용한다.

    /// 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")
    • 적절할때, 요약 보다 나은 정보를 추가하기 위해 공인된 심볼 문서 마크업 요소들을 사용한다.

    • 공인된 글 머리 기호와 심볼 명령 문법를 알고 사용한다.
      XCode 같이 인기인는 개발도구는 다음 키워드로 시작하는 글 머리 기호를 특별하게 취급한다.

    글머리 기호의미사용
    Attention주의- attention:
    Author작성자- author:
    Authors작성자들- authors:
    Bug버그- bug:
    Complexity복잡성- complexity:
    Copyright저작권- copyright:
    Date날짜- date:
    Experiment실험- experiment:
    Important중요- important:
    Invariant불변- invariant:
    Note주의- note:
    Parameter매개변수- parameter
    Parameters매개변수들- parameters:
    Postcondition수행 후 조건- postcondition:
    Precondition전제 조건- precondition:
    Remark논평- remark:
    Requires필수 사항들- requires:
    Returns반환- returns:
    SeeAlso참조- seealso:
    Since기록- since:
    Throws예외 던지기- throws:
    Todo할일- todo:
    Version버젼- version:
    Warning경고- warning:

이름 짓기(Naming)

명확하게 사용하는 것을 장려한다.(Promote Clear Usage)

  • 코드를 읽는 사람을 위해 이름은 애매모호함을 피하기 위해 필요한 모든 단어를 포함하여 사용한다.

예를 들어, 컬렉션에서 주어진 위치의 요소(element)를 제거하는 메소드를 구성해 보자.

바람직한 예제

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

메소드 표시에서 at 단어를 생략하면, 읽는 사람에게 x가 삭제하기 위한 요소(element)의 위치 보다는 x와 같은 요소를 삭제하는 메소드를 암시 할수 있다. 
삭제할 위치가 아닌 요소를 삭제하는 것으로 오해할수 있음

바람직하지 않은 예제

employees.remove(x) // unclear: are we removing x?
  • 불필요한 단어는 생략한다. 이름에 있는 모든 단어는 사용하는 곳에 핵심적인 정보를 전달해야 한다.

분명한 의도나 명확한 의미를 위해 많은 단어가 필요하지만, 읽는 사람이 이미 알고 있는 장황한 정보는 생략해야 한다. 특히, 단순히 반복(merely repeat)되는 타입정보의 단어들은 생략한다.

바람직하지 않은 예제

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

allViews.removeElement(cancelButton)

이 경우, Element단어는 호출하는 곳에서 핵심적인 것을 추가하지 않았다. 이 API가 더 좋을 것이다.

바람직한 예제

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

allViews.remove(cancelButton) // clearer

가끔씩, 애매모호함을 피하기 위해 반복되는 타입 정보는 필요하지만, 일반적으로 매개변수의 타입보다 규칙(role)을 설명하는 단어를 사용하는게 좋다. 자세한 내용을 다음 항목을 참조한다.

  • 변수 이름, 매개변수, 역할에 따라 관련된 타입은 타입 제약(constraint)보다 낫다.

바람직하지 않은 예제

var string = "Hello"
protocol ViewController {
  associatedtype ViewType : View
}
class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}

타입 이름을 다른 용도로 사용하는 방법은 명확성과 표현성을 최적화 하는데 실패했다. 대신, 요소(entity)의 역할(role)을 표현하는 이름을 선택하도록 노력한다.

바람직한 예제

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

연관된 타입이 프로토콜 제약사항과 밀접하게 결합되어 있는 경우, 프로토콜 이름은 역할(role)이고, 관련된 타입 이름에 Type을 추가하여 충돌을 피한다.

protocol Sequence {
  associatedtype IteratorType : Iterator
}
  • 매개 변수의 역할을 명확히 하기 위해 부족한 타입 정보를 보충한다.

특히 매개변수 타입이 NSObjectAnyAnyObject 또는 Int나 String 같은 기본 타입일때, 타입 정보와 사용 시점의 상황(context)은 의도를 완전히 전달하지 못할 수 있다. 이 예제에서, 선언은 명확할수 있지만, 사용하는 곳이 분명치 않다.

바람직하지 않은 예제

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

grid.add(self, for: graphics) // vague

명확하게 하려면, 각 부족한 타입 매개변수 앞에 역할(role)을 설명하는 명사를 붙인다.

바람직한 예제

func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // clear

유창하게 사용하기 위해 노력한다(Strive for Fluent Usage)

  • 메소드와 함수 이름은 영어 문법 형태를 선호한다.

바람직한 예제

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

인자값이 호출에 의미를 주지 않을때, 첫번째 또는 두번째 인자 뒤를 능숙하게(fluency) 다음줄로 내리는(degrade) 것을 허용한다.

AudioUnit.instantiate(
  with: description, 
  options: [.inProcess], completionHandler: stopProgressBar)
  • make를 사용하여 팩토리 메소드 이름으로 시작한다. 예를 들어, x.makeIterator()

  • 초기화와 팩토리 메소드 호출은 첫 번째 인자를 포함하지 않는 구문 형태여야 한다. 예를 들어, x.makeWidget(cogCount: 47)

예를 들어, 이러한 호출에 의해 그 구문은 첫번째 인자를 포함하지 않는 것이 암시된다.

바람직한 예제

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

다음은, API 작성자가 첫번째 인자와 문법적인 연속성을 만드려고 노력했다.

바람직하지 않은 예제

let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)

실제로, 이 가이드라인은 인자 레이블을 따른다. 호출할때 첫번째 인자에 대한 레이블이 필요하지 않는

실제로, 이 가이드 라인은 첫번째 인자가 레이블이 필요하지 않는 것을 의미하는 인자 레이블을 따르며, 값을 유지하기 위한 타입 변환을 실행한다.

let rgbForeground = RGBColor(cmykForeground)
  • 함수와 메소드 이름에 따른 부작용

    • 부작용이 없이, 명사구 처럼 읽어야 한다. 예를 들어, x.distance(to: y)i.successor()

    • 부작용이 있으면, 필수 동사 구 처럼 읽어야 한다. 예를 들어, print(x)x.sort()x.append(y)

    • 가변/불변한 메소드 한 쌍의 이름은 일관되게 짓는다. 가변 메소드는 종종 불변과 비슷한 의미로 변형되지만, 인스턴스를 갱신하는 것보다 새로운 값을 반환하는게 낫다.

      • 동작이 자연스럽게 동사로 설명될때, 가변 메소드에 대해 동사의 명령형을 사용하고 불변 메소드 이름을 보완하기 위해 접미사로 eding를 추가한다.

        가변(Mutating)불변(Nonmutating)
        x.sort()z = x.sorted()
        x.append(y)z = x.appending(y)
        • 동사의 과거 분사(일반적으로 ed를 붙임)를 사용하여 불변 메소드와 다른 이름을 선호한다.

          /// Reverses `self` in-place.
          mutating func reverse()
          
          /// Returns a reversed copy of `self`.
          func reversed() -> Self
          ...
          x.reverse()
          let y = x.reversed()
        • ed를 추가할때 동사가 목적어를 가지고 있기 때문에 문법에 맞지 않는 경우, 동사의 현재 분사를 사용하여 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()
      • 동작이 자연스럽게 명사로 설명될때, 불변 메소드에 대해 명사로 사용하고, 가변쪽에는 이름앞에 form을 붙인다.

        불변(Nonmutating)가변(Mutating)
        x = y.union(z)y.formUnion(z)
        j = c.successor(i)c.formSuccessor(&i)
  • 불변(nonmutating)으로 사용할때 Boolean 메소드와 프로퍼티 사용은 수신자에 관한 주장처럼 읽어야 한다. 예를 들어, x.isEmpty,line1.intersects(line2)

  • 무언가를 설명하는 프로토콜은 명사처럼 읽어야 한다. 예를들어, Collection

  • able, ible, ing 이름에 접미사를 사용하여 프로토콜이 할수 있는 능력을 설명한다. 예를들어, EquatableProgressReporting

  • 다른 타입, 프로퍼티, 변수, 상수의 이름은 명사처럼 읽어야 한다.

전문용어를 잘 사용한다(Use Terminology Well)

용어집(Term of Art) 
명사(noun) - 하나의 단어나, 정확한 문구, 특정 영역 또는 직업에서의 특별한 의미

  • 일반 적인 단어가 의미를 더 잘 전달 할수 있다면, 애매한 용어는 피한다(Avoid obscure terms). skin(피부)가 목적을 전달 할수 있다면,epidermis(표피)라고 하지 않는다. 용어집은(Terms of art) 필수적인 커뮤니케이션 도구이지만, 중요한 의미를 잡는데 사용되어야 하며, 그렇지 않으면 의미를 잃어버릴것이다.

  • 용어집을 사용하는 경우, 기존 의미에 충실해야 한다.

    기술적인 용어를 사용하는 이유는 무언가에 대해 일반적인 단어보다 더 정확하게(precisely) 표현하기 때문이고, 그렇지 않으면 모호하거나 불분명할것이다. 그러므로, API는 허용된 의미에 따라 엄격한 용어를 사용해야 한다.

    • 전문가를 놀라게 하지 말라 : 새로운 의미를 만들어 내면, 용어에 익수한 사람은 놀라게 되고, 화를 낼것이다.

    • 초보자에게 혼동을 주지 말라 : 용어를 배우려는 사람은 웹 검색을 하여, 전통적인 의미를 찾으려 할것이다.

  • 약어를 피하라. 약어, 특히 표준이 아닌 것은, 축약되지 않은 상태의 정확한 번역에 의존해서 이해하기 때문에, 용어집이 효과적이다.

약어에 대한 의미는 웹 검색으로 쉽게 찾을 수 있다.

  • 선례를 받아들인다. 기존 문화를 받아들이는데 부담이 되는 모든 초보자를 위해 용어를 최적화 하지 말라.

연속적인 데이터 구조의 이름은 List처럼 단순한 용어를 사용하는 것보다 Array가 더 낫다. 비록 초보자가 더 쉬운 List의 의미를 파악할수 있더라도 말이다. 배열은 현대 컴퓨터의 기초이다. 모든 프로프래머가 배열이 무엇인지 알고 (또는 곧 배울 것이다)있다. 대부분의 프로그래머들은 익숙한 용어를 사용하고, 웹 검색하고 질문으로 보상을 받을 것이다.

수학같은 특정 프로그래밍 영역에서, 널리 알려진 sin(x)용어는verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x) 같은 설명 문구보다 더 바람직하다. 이 경우, 약어를 피하라는 이전 가이드라인을 넘어선것을 주의 한다. 비록 정확한 단어는 sine이지만, sin(x)는 수십년동안 프로그래머과 수 셋기 동안 수학자들 사이에서 일반적으로 사용되고 있다.

규칙(Conventions)

일반 규칙(General Conventions)

  • 0(1)이 아닌 계산된 프로퍼티의 복잡성을 문서화한다. 사람들은 종종 프로퍼티에 대한 접근이 중요한 계산과 관련되어 있지 않다고 가정한다. 이는 저장된 프로퍼티가 정신적인 모델(mental model)을 가지기 때문이다. 이러한 가정이 어긋날때 알려줘야 한다.
  • 자유 함수(free functions) 보다는 메소드와 프로퍼티를 선호한다. 자유 함수는 특별한 경우에만 사용된다.

    1.self가 분명하지 않을때

    min(x, y, z)

    2.함수가 제약이 없는 제네릭(generic)일때

    print(x)

    3.함수의 문법이 기존 영역의 일부분일때

    sin(x)
  • 규칙을 따른다. 타입과 프로토콜의 이름은 UpperCamelCase이다. 나머지는 lowerCamelCase이다.

일반적으로 미국식 영어에서 모두 대문자로 나타내는 머릿글자(Acronyms and initialisms)는 규칙에 따라 모두 대문자 또는 소문자가 되어야 한다.

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

그리고, 기하학적인 타입과 컬렉션은 별도의 영역이다. 이 같은 프로그램 또한 괜찮다.

바람직한 예제

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

마지막으로, 기존 타입을 추측하는데 애매하기 때문에, 반환타입의 재정의(overloading on return type)는 피한다.

바람직하지 않은 예제

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)
  • 문서에 제공할 매개변수 이름을 선택한다. 매개변수 이름이 함수나 메소드를 사용할때 나타나지 않더라도, 중요한 역할을 설명한다.

문서를 쉽게 읽을 수 있는 이름을 선택한다. 예를들어, 이러한 이름은 문서를 자연스럽게 읽을수 있게 한다.

바람직한 예제

/// 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])
  • 일반적인 사용을 간소화 할때 기본 매개변수의 장점을 활용한다. 모든 매개변수는 일반적으로 사용되는 하나의 값을 기본으로 사용할수 있다.

기본 인자들은 무관한 정보를 숨겨주어 가독성을 높여준다. 예를들어

바람직하지 않은 예제

let order = lastName.compare(
  royalFamilyName, options: [], range: nil, locale: nil)

더 간단하게 만들 수 있다.

바람직한 예제

let order = lastName.compare(royalFamilyName)

API를 이해하는데 부담이 적기 때문에, 기본 인자들은 일반적으로 메소드 집합(families)을 사용하는 것을 선호한다.

바람직한 예제

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)
  • 인자가 유용하게 구별 할 수 없는 경우, 모든 레이블은 생략한다. 예를들어, min(number1, number2)zip(sequence1, sequence2)

  • 초기화 값을 저장하는 타입 변환을 실행하며, 첫번째 인자 레이블은 생략한다. 예를 들어, 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)
}

값을 저장하는 타입 변환은 단일형(monomorphism)이다. 즉, 원본 결과의 값에서 모든 차이점은 결과 값의 차이이다. 예를 들어, 언제나 다른Int8값은 다른Int64값으로 변환되기 때문에, Int8에서 Int64로 변환은 값을 유지한다. 다른 방향으로 전환하면, 값을 유지할수 없다.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)의 일부 일때, 레이블을 생략한다. 기본 이름의 앞에 단어를 추가한다. 예를들어,x.addSubview(y)

이 가이드라인은 첫번째 인자가 문법에 맞지 않는 경우, 레이블을 가지는 것을 의미한다.

바람직한 예제

view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

그 구(phrase)가 정확한 의미를 전달하는것이 중요하다. 다음은 문법에 맞지 않는 표현이다.

바람직하지 않은 예제

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

인자의 기본값을 생략할수 있음을 유의해야하고, 이 경우 문법에 맞지 않기 때문에, 항상 레이블을 가져야 한다.

  • 다른 모든 인자는 레이블이 있다.

특별 지침(Special Instructions)

  • API에서 나타나는 클로져 매개변수와 튜플 멤버에 레이블한다

이러한 이름은 설명의 능력을 가지고 있고, 문서의 주석을 참조할수 있고, 튜플 멤버에 대한 접근을 제공한다.

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

비록, 클로져에서 사용될때 기술적으로 인자 레이블이지만, 이러한 레이블을 선택하고 매개변수 이름인 것처럼 문서에서 사용한다. 함수 본문에서 클로져 호출은 첫번째 인자를 포함하지 않는 기본이름에서 구문을 시작하는 함수를 일관되게 읽을수 있을것이다.

allocate(byteCount: newCount * elementSize)
  • 오버로드(overload) 설정에서 애매모호함을 피하기 위해 자유로운 다형성(unconstrained polymorphism)에 좀 더 주의를 기울인다. 예를들어,AnyAnyObject, 자유로운 제네릭 매개변수)

예를들어, 오버로드 설정을 고려한다.

바람직하지 않은 예제

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일때, 하나의 요소는 요소의 시퀀스(sequence) 처럼 같은 타입을 가질수 있다.

바람직하지 않은 예제

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 작성자의 관심이 문제가 된다.


반응형

'Swift > Style Guide' 카테고리의 다른 글

Swift Style Guide for Raywenderlich  (1) 2016.12.16
Posted by 까칠코더
,