써니쿠키의 IOS 개발일기

[번역] API Design Guidelines 번역 (3/3) - (규칙)Convention 본문

swift 공식문서

[번역] API Design Guidelines 번역 (3/3) - (규칙)Convention

sunnyCookie 2022. 8. 16. 17:02

원본, 출처 : 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 작성자의 주의를 끌었다.

반응형
Comments