이번 포스팅에서는 Swift 공식문서에서 지정한 함수명, 변수명 convention에 대해 정리해보겠습니다
출처 : Swift API Design Guideline
변수든 함수든 간에 호출, 할당하는 부분의 코드를 읽을 때 명료함이 온전히 드러나야 합니다
Why?
선언은 한번이지만 사용은 반복적이므로 사용시점(use case)에 명료하게 읽히는지가 기준이 되어야 합니다
물론 가장 best는 간결하면서도 그 의미가 온전히 드러나는 것이다
하지만, 길이를 줄이다가 명료함을 해친다면 차라리 길게 만드는 편이 훨씬 낫다
비록 Swift 코드는 굉장히 간결하게 만들어져 있지만서도 smallest code를 짜는게 Goal이 아님을 인지하자
// TODO: 무슨 뜻인지 파악하고 다시 정리
Swift 코드의 이 간결함은 strong type system과 features that naturally reduce boilerplate의 부작용이다
설계자의 심오한 의도는 주석을 통해서만 독자에게 전달할 수 있습니다.
미루지말고 꼭 답시다
hoxy... 주석달기가 어려운가요?
그건 당신이 API를 잘못 설계했다는 증거입니다
documentation 작성에 대해서 convention이 있는데 이건 나중에 따로 다루도록 하겠습니다
예제코드들을 통해 감을 익혀봅시다
💢 Bad
employees.remove(x)
👍 Good
employees.remove(at: x)
💢 Bad
allViews.removeElement(cancelButton)
👍 Good
allViews.remove(cancelButton) // clearer
이름은 타입의 제약사항 같은 것들보다 역할을 토대로 짓는게 좋습니다
💢 Bad
var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
👍 Good
var greeting = "Hello"
protocol ViewController {
associatedtype ContentView : View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}
widgetFactory
이라는 파라미터 이름은 물론 나쁘지 않은 선택지입니다. 이름으로 파라미터 타입 정보를 같이 알려주죠.
그런데 파라미터 타입을 꼭 이름에 넣어야 할까요?
파라미터 타입은 따로 알리지 않더라도 쉽게 파악되지 않을까하는 생각이 듭니다
함수호출부 근처에 파라미터 선언부가 있을 것이기 때문이죠
그리고 widgetFactory
라는 이름은 restock()
로 전달되어 어떤 용도로 쓰일지(어떤 role을 가질지) 드러나지 않는다는 문제가 있습니다
결론적으로, 파라미터의 역할이 포함된 widgetFactoryForSupplier
혹은 supplier
가 더 좋은 파라미터명으로 생각됩니다
//TODO: 프로토콜 공부 후 재방문
예외 케이스
associatedtype은 프로토콜의 이름 자체가 role을 나타내므로, 우리가 규칙대로면 타입명을 이름으로 정하는게 맞다
하지만, 프로토콜명과 타입명이 같으면 충돌이 나므로 이런 경우엔 타입+Protocol
로 쓰자
protocol Sequence {
associatedtype Iterator : IteratorProtocol
}
protocol IteratorProtocol { ... }
우리는 Argument가 함수에서 어떻게 쓰일지 이름과 타입으로 유추합니다
그런데, Argument 타입이 Int
, String
같은 기본 타입이면(특히 Any
) 용도가 광범위합니다
따라서, 타입이 줄 수 있는 정보가 적으므로 역할(role)을 유추하기 어렵습니다
이런 경우에는 이름에 그 정보를 담아 보완해야 합니다
💢 Bad
func add(_ observer: NSObject, for keyPath: String) {
...
}
grid.add(self, for: graphics) // vague
👍 Good
func addObserver(_ observer: NSObject, forKeyPath path: String) {
...
}
grid.addObserver(self, forKeyPath: graphics) // clear
우선, 코드를 읽는 사람 입장에서 함수선언부와 호출부 간 차이가 있습니다
함수선언부는 Argument Label + Argument이름 두 가지 모두가 한눈에 보이므로 줄 수 있는 정보가 많습니다
반면, 호출부(use case)에선 Argument이름 하나만 보이므로 여기에 모든걸 담아야 합니다
Bad case에서
for
라는 Argument이름을 사용했는데 이게 명확한지/아닌지는graphics
의 타입에 달렸습니다
graphics
의 타입이 KeyPath
라던지 사용자 지정 타입이었다면 for
로도 충분합니다.
타입 자체가 주는 정보가 많아 함수 내 역할을 어느정도 유추할 수 있습니다
반면, 지금처럼 String
같은 기본타입이면 for
와 String
만으로는 역할을 유추할 수 없어 개선이 필요합니다
Good case에서 제시된 forKeyPath
가 좋은 예시입니다
함수/메소드 이름과 파라미터는 호출부를 읽었을때,
문법상 영어 문장을 읽는 것처럼 느껴지도록 짓습니다
💢 Bad
x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()
👍 Good
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.makeIterator()
정도가 되겠습니다
Factory Method
인스턴스를 생성하는 메소드를 말합니다
자세한건 다른 포스팅에서 따로 다루겠습니다
💢 Bad
일반적인 함수에서는 분사를 붙혀 영어 문장을 읽듯이 변형시켰습니다
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
let ref = Link(to: destination)
👍 Good #1
하지만, Initializer와 Factory Method는 분사없이 그대로 짓습니다
let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
let ref = Link(target: destination)
👍 Good #2
실제로 많이 볼 수 있는 예시로, String을 Int로 value preserving type conversion하는 경우가 있겠습니다
원래는 from
이라던지 영어 어순처럼 지어야겠지만 Initializer이므로 _
와 description
으로 지었습니다
init(_ description: String)
let userNumber = Int("123")
//TODO: side-effect이란게 구체적으로 어떤 경우일까. 예시가 필요하다
함수에 side-effect이 있을 수 있다고 판단되면 명령형 동사로 짓고, 아니라면 명사로 짓습니다
//side-effect X
x.distance(to: y)
i.successor()
//side-effect O
print(x)
x.sort()
x.append(y).
기능이 비슷한 두 메소드가 있다고 가정해봅시다
1) 하나는 메소드 외부에서 선언된 어떤 값을 변경시키고
2) 다른 하나는 직접 변경하지 않고 변경될 값을 return합니다
기능이 비슷하니 이름도 비슷해야할 것 같은데 어떻게 구분시킬까요?
구체적으로 어떤 경우인지 떠올리기 어려울 수 있지만 이미 우리가 익히 알고 있는 메소드가 있습니다
x.sort()
: x를 직접적으로 정렬x.sorted()
: x를 직접 변경하지 않고 정렬된 것을 return위 예시에서 느껴지겠지만, 이렇게 pair로 존재하는 메소드의 이름은 mutating: 동사
/ non-mutating: 동사+ed/ing
로 지정합니다
(기본적으로 ~ed를 붙이되 문법적으로 틀리면 ~ing를 붙입니다)
Mutating | Non-mutating |
---|---|
x.sort() | x.sorted() |
x.append(y) | x.appending(y) |
함수의 기능이 동사보다는 명사로 표현하는 것이 더 자연스러운 경우가 있습니다
이 경우엔 mutating 메소드에 form
이라는 prefix를 붙혀 구분합니다
Mutating | Non-mutating |
---|---|
y.formUnion(z) | x = y.union(z) |
c.formSuccessor(&i) | j = c.successor(i) |
예제로 이해해봅시다
x.isEmpty
line1.intersects(line2)
//TODO: 이게 무슨말일까
~able
/ ~ible
/ ~ing
같은 suffix를 붙인다
Equatable
ProgressReporting
문서에서 "should read"라는 표현이 자주 사용되었습니다
확실친 않습니다만, 그 뜻은 명사로 읽기위해 명사로 Naming하라는 의미로 생각됩니다
쓰지 않으면 그 의미를 온전히 전달할 수 없는 경우에만 전문용어를 사용합니다
굳이 전문용어를 쓰는 이유는, 안 쓰고는 도저히 그 심오하고 깊은 의미를 정확히 전달하기 어려운 경우입니다
그러므로 그 심오함과 깊이를 이해하여 정확하게 사용해야 문제가 없습니다
For 전문가
For 초보자
물론 사람들에게 원래 용어가 쉽게 떠오르는 경우는 약어를 사용하는 것이 매우 효과적일 수 있습니다
그 기준을 제시하자면, 웹 서치로 쉽게 찾아지는 경우에만 약어를 사용해야 합니다
초보자에게 쉽게 설명하기 위해 용어를 최적화하다보면 기존의 문화를 배제시키는 경우가 발생할 수도 있는데 그러지 말아야 합니다
Array
vs List
sin(x)