[Swift] 내 맘대로 정리하는 API Design Guidelines

지극히 제 기준으로 놓치기 쉬운 내용들만 뽑아내봤읍니다

naljin
9 min readDec 19, 2021

들어가기 전에

주말동안 인프런을 통해 API Design Guidelines 를 공부했따.

여러 내용들이 있었지만, 개인적으로 놓치기 쉬운 부분들을 까먹지 않도록 원문을 보면서 정리하기로 했다.

그런데 쓰다보니 왜 이렇게 익숙하나 했더니 삼년 전에 스터디했던 내용이었따 ㅋㅎ,, 또 까먹어서 이러고 있다니,,, 역시 내 기억력 ㄹㅈㄷ,,

머,, 지금이 그때보다 내용이 많아진거 같긴하다,,! 아닌가? ㅎ 튼 복습겸 이번엔 까먹질 않길 바라며 시작!

Naming

Strive for Fluent Usage

영어 문법으로 자연스럽게 읽히는 메소드가 좋음. (전치사 등 이용 가능)

x.insert(y, position: z) ❌
x.insert(y, at: z) ✅
x.subViews(color: y) ❌
x.subViews(havingColor: y) ✅
x.nounCapitalize() ❌
x.capitalizingNouns() ✅

하지만 처음 한 두개의 argument 뒤에 위치한, 핵심이 아닌 argument 의 경우 이러한 문법적 유창성이 떨어져도 됨.

AudioUnit.instantiate(
with: description,
options: [.inProcess],
completionHandler: stopProgressBar)

예시를 보면 optionscompletionHandler 같은 경우 그냥 명사로 박아버림

factory method 의 이름은 make 로 시작

x.makeIterator()

initializerfactory method 의 첫번째 argument 는 함수 이름과 이어져서 구문을 형성하면 안됨.

let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128) ❌
let foreground = Color(red: 32, green: 64, blue: 128) ✅
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14) ❌
let newPart = factory.makeWidget(gears: 42, spindles: 14) ✅
let ref = Link(to: destination) ❌
let ref = Link(target: destination) ✅

마지막 예시인 Link(to: destination) 같은 경우는 "link to destination" 이라고 영문법으로 자연스럽게 읽힘. 하지만 이 함수가 이니셜라이저나 factory 메소드라면 이렇게 자연스럽게 읽히면 안되고, Link(target: destination) 로 설정하는게 더 적합

side-effect 여부에 따라 함수 이름 짓기

Side effect 없는 경우는 명사로 읽힘

x.distance(to: y)
i.successor()

Side effect 있는 경우는 명령형 동사로 읽힘

print(x) // 프린트 해라
x.sort() // x를 sort 해라
x.append(y) // x에 y를 append 해라

인스턴스를 갱신하는 mutating 메서드는 갱신 대신 새로운 값을 반환하는 nonmutating 메서드 쌍을 가질 수 있는데 이때 메서드 쌍을 일관되게 네이밍해야함

작업이 동사로 자연스럽게 읽힐 때는 mutating method명령형 동사를 사용하고 “ed” 또는 “ing” 접미사를 적용하여 nonmutating 쌍의 이름을 지정함. 주로 “ed” 를 붙이는것을 선호하지만 동사에 직접 목적어가 있어서 “ed”를 추가하는 것이 문법적으로 맞지 않을 때는 “ing”을 추가.

x.sort() // Muatating
z = x.sorted() // Nonmutating
x.append(y) // Muatating
z = x.appending(y) // Nonmutating

작업이 명사로 자연스럽게 읽힐 때는 nonmutating method명사를 사용하고, “form” 접두사를 적용하여 mutating 쌍의 이름을 지정함

x = y.union(z) // Nonmutating
y.formUnion(z) // Mutating
j = c.successor(i) // Nonmutating
c.formSuccessor(&i) // Mutating

Conventions

General Conventions

타입이나 프로토콜은 UpperCamelCase, 나머지는 lowerCamelCase

다만 일반적으로 영어에서 모두 대문자로 표기하는 단어(ex. UTF, ASCII, SMTP) 같은 경우는, 규칙에 따라 균일하게 up or down case 를 적용해야함

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

utf8Bytes 같은 경우는 변수 이름이기 때문에 소문자로 시작해야함. 그래서 u가 소문자로 시작했고 나머지 tf 도 이를 따라 모두 소문자로 표기. ASCIISMTP 같은 경우는 변수 이름의 첫 문자가 아니기 때문에 camel case 에 따라 대문자로 시작. 이를 따라 나머지 문자도 모두 대문자로 표기.

동일한 동작을 하는 경우 parameter 타입에 따른 overload 는 허용되지만, return type 에 따른 overrload 는 모호성을 유발하므로 피해야함.


extension Shape {
func contains(_ other: Point) -> Bool { ... }
func contains(_ other: Shape) -> Bool { ... }
}

extension Box {
func value() -> Int? { ... }
func value() -> String? { ... }
}

Argument Labels

값은 보존하되 type 변환만을 수행하는 initializer에서는 첫번째 argument label 을 생략함. 이때 첫번째 argument 는 언제나 변환의 source 가 되어야함

Int64(someUInt32)
String(veryLargeNumber)
String(veryLargeNumber, radix: 16)

다만 값을 축소(narrowing)하는 타입 변환의 경우, 축소를 설명하는 label 이 있는게 적합함

extension UInt32 {
init(_ value: Int16)
init(truncating source: UInt64)
}

UInt32 에서 init(_ value: Int16) 같은 경우는 Int16 -> UInt32 로 값의 범위가 커지기 때문에 argument label 을 생략할 수 있음. 하지만 init(truncating source: UInt64)UInt64 -> UInt32 로 값의 범위가 좁아지기 때문에 truncating 이라는 argument label 이 있는게 더 적합

첫 번째 augument가 전치사구 일부를 형성할 때 argument label을 지정. argument label은 일반적으로 전치사로 시작해야함.

x.removeBoxes(havingLength: 12)

하지만 처음 두개의 argument 가 추상화 수준이 같을 경우에는, 추상화를 명확하게 유지하기 위해 아예 함수 이름에서부터 전치사를 붙이고 argument label 시작.

a.move(toX: b, y: c) ❌
a.moveTo(x: b, y: c) ✅
a.fade(fromRed: b, green: c, blue: d) ❌
a.fadeFrom(red: b, green: c, blue: d) ✅

첫번째 argument 가 문법 구(grammatical phrase)의 일부를 형성하는 경우 레이블 제거.

하지만 첫번째 argument가 grammatical phrase를 형성하지 않는다면 레이블을 가져야함

// argument label 제거
x.addSubview(y)
// argument label 필요
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

--

--