ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift) API Design Guidelines
    Swift 2021. 6. 10. 17:53

    Swift의 공식 Design Guidelines은 코드를 작성하는데 있어서 정해진 합의이다.

    언어에는 화법이라는게 있고 말하는 사람마다 다르지만, 기본적으로 문장을 구성하는 요소(문법, 단어)가 합의 되어있는 규칙들이 존재한다. 

    언어의 이러한 요소들은 다른 사람과의 소통에 있어서 기초적인 역할을 한다.

    프로그램 언어 또한 개인의 코딩 스타일이 다르지만, Design Guidelines이라고 하는 공식적으로 정해준 준수 사항이 있다. 

    물론 따르지 않는다고 해서 프로그램이 작동되는데 있어서 큰 영향을 미치지 않지만, 프로그래머로써 디자인 가이드라인을 준수 한다는 것은 다른 사람과의 협업, 그리고 누군가(자신 포함)가 코드를 이해 하는데 있어서 큰 강점을 가져다 준다.

     

    천천히 살펴보자.

    이 포스트는 Swift 공식 문서의 API Design Guidelines를 번역/의역 한 것입니다.

     

    기초

    • 사용시 명확성은 가장 중요한 요소이다. - Entity(메소드, 프로퍼티)는 단 한번 선언 되지만, 반복적으로 사용된다. Design APIs는 간결하고 명확하게 사용 할 수 있게 만들어진다. 디자인을 평가할 때 선언문을 읽는 것 만으론 충분치 않다. 항상 사용 예를 고려 하여 문맥상 명확한지 확인 해야한다. 
    • 간결함 보다 중요한 것은 명확성. - swift 코드가 압축 될 순 있어도, 최소한의 문자로 최소한의 코드를 만드는 것이 목표가 아니다. swift에서 간결함은 강한 타입 시스템과 보일러 플레이트(boilerplate) 코드를 자연스레 줄이는데서 나오는 부가효과이다.
    • 모든 선언문에 문서화 된 주석을 작성하라. - 문서를 작성하며 얻는 insights는 디자인에 상당한 영향을 미친다. 주석 다는것을 미루어 두지마라. 
      당신의 API's 요소들을 간단한 용어로 설명 할 수 없다면, 당신의 API 디자인이 잘못 되었을 수도 있다.-
      • swift의 마크업 형식을 사용해라. - 마크업 방언을 사용하면 xcode를 통해 자동으로 설명을 보여준다.
        ex) 
        Swift symbols을 위한 마크업
        코드 자동완성시 사용자 설정 description
        자세한 내용은 dialect of Markdown.


      • 선언할 entity의 description을 개요(summary)로 시작하라. API는 종종 선언과 개요만으로 이해가 될 수 있다.
      • 추가적인 설명에 있어서 하나 이상의 문단과 말머리를 사용해라. 문단은 공백으로 완전히 분리되고, 완전한 문장을 사용한다.
      • 적절한 곳에 사용할 수있는 마크업 문서 심볼 요소들을 사용해라. (자세한 내용은 Markup Syntax)

    작명

    명확한 사용 유도

    • 사용 되는 이름은 읽는데 있어서 모호함을 피하기 위해 필요한 모든 단어 포함시킨다. 
      예)
      element를 제거하는 메소드에 'at'이란 파라미터를 생략한다면 사용자에게 있어서 x가 list의 인덱스인지 다른 무엇인지 불명확 하다.
      extension List {
        public mutating func remove(at position: Index) -> Element
      }
      employees.remove(at: x)
      
      employees.remove(x) // 불명확: x가 무엇인가?​
    • 불필요한 단어들을 생략하라. 이름의 모든 단어들은 사용함에 있어서 핵심 정보를 전달해야 한다. 
      의도를 명확히 하거나 모호함을 덜기위해 많은 단어가 필요할 수 있지만, 읽는 사람이 이미 가지고있는 정보와 중복되는 단어는 생략 해야 한다. 특히 타입에 관한 반복적인 단어는 생략 한다.
      // 잘못된 예
      public mutating func removeElement(_ member: Element) -> Element? 
      
      allViews.removeElement(cancelButton)
      
      // 올바른 예
      public mutating func remove(_ member: Element) -> Element?
      
      allViews.remove(cancelButton) // clearer​
      위의 경우 'Element'라는 단어는 특별히 필요치 않다.
      가끔 모호함을 피하기 위해 타입 정보가 필요한 경우가 있지만, 대부분 파라미터의 역할을 설명하는 단어를 사용하는 것이 바람직하다.
    • 역할에 따른 변수, 파라미터, 연관타입의 이름은 타입 제약보다 역할에 따라 짓는다.
    • 파라미터의 역할을 확실히 하기 위해서 약한 타입 정보를 추가로 제공한다.
      특히 파라미터 타입이 NSObject, Any, AnyObject이거나 기본 타입인 Int, String 일때 사용 시점에서 타입 정보와 문맥만으론 의도를 정확히 전달 할 수 없다. 이런 경우 정의는 명확하지만 사용할 때에 있어선 의미가 막연하다.
      예시)
      func add(_ observer: NSObject, for keyPath: String)
      grid.add(self, for: graphics) // 무엇을 추가하는지 막연함
      
      func addObserver(_ observer: NSObject, forKeyPath path: String)
      grid.addObserver(self, forKeyPath: graphics) // observer를 추가하는 것을 확실히 한다​
       명확성을 위해 약한 타입 파라미터 앞에 역할을 묘사하는 명사를 추가해준다.

    능숙한 사용을 위한 노력

    • 사용자측에 서서 문법에 맞는 영어 구문을 이용한 메소드와 함수 이름을 지어라.
      // 올바른 예
      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)
    • 팩토리 메소드는 'make'로 시작하게 한다. ex) x.makeIterator()
    • 초기화와 팩토리 메소드에서 첫 번째로 호출되는 인자기본 이름으로 시작하는 구문의 형태를 띄면 안된다. 
      예를들어 아래와 같은 경우 첫 번째 인자는 앞서 보여준 것과 같이 구문의 형태로 읽지 않는다.
      let foreground = Color(red: 32, green: 64, blue: 128)
      let newPart = factory.makeWidget(gears: 42, spindles: 14)
      let ref = Link(target: destination)​
       잘못된 예로 아래의 경우 첫 번째 인자를 문법적인 구문 형태로 만들려고 하였다.
      let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
      let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
      let ref = Link(to: destination)​​
    • side effect를 고려하여 함수와 메소드 이름을 짓는다.

      - side effect가 없다면 명사 구절로
      x.distance(to: y)
      i.successor()​

      - side effect가 있다면 명령 동사구로 읽어야 한다.
      print(x)
      x.sort()
      x.append(y)​

      - mutating(변환가능)/ non-mutating(불변) 메소드 쌍의 이름은 일관되게 지어라. mutating 메소드는 종종 의미적으로 유사한 non-mutating 메소드를 가지지만, 인스턴스 in-place를 갱신하는 것보다 새로운 값을 반환하는 것이 낫습니다.

      - 연산자가 동사로 표현 되면 mutating 메소드에 대해 명령동사를 사용하고 nonmutating에 ed나 ing를 붙혀서 이름을 짓는다.
      Mutating                         Nonmutating
      x.sort() z = x.sorted()
      x.append(y) z = x.appending(y)
      - 연산자가 명사로 표현되면 명사를 사용해서 nonmutating 메소드를 나타내고 mutating메소드 앞에 form을 붙혀서 짓는다.
      Nonmutating                            Mutating
      x = y.union(z) y.formUnion(z)
      j = c.successor(i) c.formSuccessor(&i)
    • nonmutating 형식의 Boolean 메소드와 프로퍼티의 사용은 결과를 반환받는 대상에 대해 assertion(단언문)으로 읽혀야 한다.
      ex)
       x.isEmpty
       line1.intersects(line2)
    • 무엇인지 설명하는 프로토콜은 명사로 읽혀야 한다. (e.g. Collection)
    • 가능함에 대한 프로토콜은 able, ible 또는 ing가 붙는다. (e.g. Equatable, ProgressReporting)
    • 나머지 타입, 프로퍼티, 변수, 상수 등은 명사로 읽혀야 한다.

    전문용어 사용하기

    Term of Art
    명사 - 특정 분야 또는 직업 내에서 정확하고 전문화 된 의미를 갖는 단어나 문장
    • 일반적인 용어가 뜻을 전달함에 있어서 용이 하다면 모호한 전문용어를 쓰지 않는것이 좋다. Terms of art는 필수적인 의사소통 도구지만, 반드시 사용해야만 의미가 전달 될 때 사용 해야한다.
    • 이미 정의 되어있는 전문용어를 사용해라. 
      전문 용어를 사용하는 하나의 이유는 사용하지 않으면 모호하거나 불분명한 것을 정확하게 표현하기 때문이다.
    • 약어를 사용하지마라. 특히 표준이 아닌 약어는 소수만 아는 전문용어이며, 완전한 내용을 아는 경우에만 정확히 전달 된다.
    • 선례를 받아들여라. 
      기존 문화에 부합하지 않는 수고를 들이면서 까지 초보자들을 위한 용어의 최적화를 하지마라.

    관례 

    일반적 관례

    • O(1)이 아닌 계산 프로퍼티의 복잡도를 문서화 해라.
      사람들은 보통 프로퍼티에 접근 할 때, 저장 프로퍼티라고 생각하기 때문에 계산 프로퍼티로써 중요한 계산을 하지 않을거라고 생각한다. 사람들이 이러한 가정을 가지고 접근할 수 있는 경우 확실히 경고 해야한다.
    • 자유(전역) 함수보다 멤버 메소드와 프로퍼티를 사용해라.
    • 대소문자 법칙을 따른다. 타입과 프로토콜의 이름은 upperCamelCase이다. 다른 나머지는 전부 lowerCamelCase이다.
    • 메소드의 근본적의미가 같거나 다른 영역에서 동작하는 경우 이름을 공유할 수 있다.

    파라미터

    • 문서에 사용 할 파라미터 이름을 선택해라. 파라미터 이름이 함수나 메소드의 사용 시점에 들어나지 않는다고 해도 중요한 설명을 하는 역할을 한다.
      좋은 예
      /// 'predicate'(서술어)를 만족하는 'self' 요소를 포함하는 'Array'를 리턴 
      /// Return an `Array` containing the elements of `self` that satisfy `predicate`.
      func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]
      
      /// 'newElements'로 주어진 'subRange'만큼을 대체하는 메소드 
      mutating func replaceRange(_ subRange: Range, with newElements: [E])
      
      나쁜 예
      /// 'includedInResult'를 만족하는 'self' 요소를 포함하는 'Array'를 리턴 
      /// Return an `Array` containing the elements of `self`
      /// that satisfy `includedInResult`.
      func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]
      
      /// 요소의 'r'만큼의 범위를 'with'로 대체
      mutating func replaceRange(_ r: Range, with: [E])
      

     

    'Swift' 카테고리의 다른 글

    Data(contentsOf:)? URLSession?  (0) 2022.08.01
    Swift) json 다루기  (0) 2021.06.16
    Swift) 함수 타입  (0) 2020.11.18
    Swift) Optional  (0) 2020.11.11
    Swift) swift 5에 추가된 String Interpolation  (0) 2020.11.04

    댓글

Designed by Tistory.