API Design Guideline

!·2022년 12월 29일
0

Swift 문법

목록 보기
27/27

스위프트 API Design Guideline을 읽고 정리한 내용입니다.
잘못된 내용이나, 개선사항이 있는 경우 피드백 부탁드립니다!

Fundamentals

  • 사용시점의 명확성은 매우 중요하다. 내가 디자인한 API가 다른 사람이 사용할 떄도 쉽게 사용할 수 있도록 디자인 해야 한다. 이게 가장 큰 목표이다.
  • 명확성은 간결함보다 중요하다. 짧은 코드로 이해하기 어렵게 만드는 것보다는 코드가 조금 길더라도 사용하기 쉬운 API를 디자인 해야한다.
  • 주석을 잘 작성해야한다. 주석을 작성하는데에 어려움이 있다면 잘못 디자인한 API일 수도 있다.

Comment

  • 항상 요약과 함께 시작해야한다. 이것만으로 사용자가 이해할 수 있기때문이다.
/// Returns a "view" of 'self' containing the same elements in
/// reverse order.
func reversed() -> ReverseCollection
  • 단일 문장을 사용해야하며, 완전한 문장꼴은 피하자. 또한 마침표로 끝나는게 올바르다.
  • 함수나 메소드가 무엇을 return하는지 서술하고, 아무 효과 없거나 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?
  • subscript가 무엇에 접근하는지 서술하여야 한다.
/// 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")

Namings

  • 모호성을 피하기위해 많은 단어를 포함시켜도 된다.
extension List {
  public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)
employees.remove(x) // 잘못된 사용. x를 지우는 건지 index x를 지우는 건지 모호하다!
  • 동일한 의미를 전달하거나, 반복적인 의미, 혹은 사용자가 이미 알고 있는 개념을 포함하는 단어는 생략하는 게 좋다.
public mutating func remove(_ member: Element) -> Element?
allViews.remove(cancelButton)

public mutating func removeElement(_ member: Element) -> Element?
allViews.removeElement(cancelButton) // 잘못된 사용. Element를 굳이 붙힐 필요 없다.
  • 변수명, 매개변수명, associatedtype은 역할에 따라 이름을 짓는 것이 바람직하다.
var greeting = "Hello"
protocol ViewController {
  associatedtype ContentView : View
}
class ProductionLine {
  func restock(from supplier: WidgetFactory)
}

// 바람직하지 않다. 타입을 이해하기 쉬울 수 있지만, entity의 역할을 이해하기 힘들다.
var string = "Hello"
protocol ViewController {
  associatedtype ViewType : View
}
class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}
  • Any, Int, NSObject와 같은 모호한 타입을 사용할때는 분명하게 이름을 지어야한다.
func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // clear

// 애매하다!
func add(_ observer: NSObject, for keyPath: String)
grid.add(self, for: graphics)
  • 영어 문법에 기반한 이름을 지어야 한다.
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()
  • 하지만, 생성자나 팩토리메소드의 경우 영어 문법을 준수하지 않아야한다.
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)
  • 부작용이 없는 함수나 메소드는 명사형으로 읽히는 것이 좋다.
x.distance(to: y)
x.successor().
  • 부작용이 있는 함수나 메소드는 동사형으로 읽히는 것이 좋다.
x.sort()
x.append(y)
  • Mutating/nonmutating 메소드의 명을 일관되게 작성하는 것이 좋다.
MutatingNonmutating
x.sort()z = x.sorted()
x.append()z = x.appending(y)
  • nonmutating 메소드의 경우 동사의 과거형으로 작성한다.
/// Reverses `self` in-place.
mutating func reverse()

/// Returns a reversed copy of `self`.
func reversed() -> Self
...
x.reverse()
let y = x.reversed()
  • 과거형으로 작성할 수 없다면, -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 접미사를 붙인다.
NonmutatingMutating
z = x.union()x.formUnion(y)
j = c.successor(i)c.formSuccessor(y)

Conventions

매개변수

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])
  • 기본 인수를 사용해서 사용자에게 부담을 덜 수 있다.
extension String {
  /// ...description...
  public func compare(
     _ other: String, options: CompareOptions = [],
     range: Range? = nil, locale: Locale? = nil
  ) -> Ordering
}

/// 위의 extention문은 아래의 extention문보다 훨씬 간결하다.
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
}

인수 레이블

  • 인수를 구분할 필요가 없는 경우에는 생략한다. (ex. min(a,b))
  • 인수 레이블 명명은 첫번째 인수가 매우 중요하다!
  • 인수 레이블은 일반적으로 전치사로 시작해야 한다.
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)
  • 첫번째 인수가 구와 상관이 없을 경우 인수 레이블을 명시한다.
view.dissmiss(animated: false)
let text = words.split(maxSplits: 12)
let studentByName = students.sorted(isOrderedBefore: Student.namePrecedes)

/// 구문의 올바른 의미를 전달할 수 있어야한다.
view.dismiss(false) //  Don't dismiss? Dismiss a Bool?
words.split(12)     // Split the number 12?

profile
개발자 지망생

0개의 댓글