Swift에서는 이름이 정말 중요하다 이번에는 Naming에 대해 알아보면서 코드의 가독성과 유지 보수를 위한 올바른 이름짓기를 알아보도록 하자
컬렉션 내 주어진 위치의 요소를 제거하는 메서드를 예시로 보자
✅
extension List {
public mutaing func remove(at position: Index) -> Element
}
employees.remove(at: x)
해당 메서드에서 'at'이라는 단어를 생략하면, 제거할 요소의 위치를 나타냈다기 보다 'x'같은 요소를 제거한다는 의미로 보일 수 있다.
❌
empolyees.remove(x) // X를 제거?(불분명)
이름안의 모든 단어는 사용 위치에서 핵심정보를 전달해야 한다.
코드를 읽는 사람이 이미 알고 있는 정보와 중복되는 단어는 생략, 특히 타입 정보를 단순히 반복하는 단어는 생략
❌
public mutating func removeElement(_ member: Element) -> Element?
allViews.removeElement(cancelButton)
Element라는 단어는 호출 위치에서 중요한 정보를 제공하지 않는다. 다음의 코드처럼 개선하는게 더 나음
✅
public mutating func remove(_ member: Element) -> Element?
allViews.remove(cancelButton) // 명확함
타입 정보를 표현하는게 더 명확한 경우도 있지만 일반적으로는 타입보다는 매개변수의 역할을 설명하는 단어를 사용하는 것이 더 좋다.
❌
var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
위 코드 같은 방식으로 타입 이름을 재사용하는 것은 명확성과 표현력을 최적화 하기 좋지 않다. 대신 역할을 표현하는 이름을 지정하기 위해 노력해야 한다.
✅
var greeting = "Hello" // 실제 의미에 더 가깝게
protocol ViewController {
associatedtype ContentView : View // "내용을 표시하는 뷰"의 의미를 더 잘 나타냄
}
class ProductionLine {
func restock(from supplier: WidgetFactory) // 마찬가지 역할을 중점으로
}
연관 타입이 특정 프로토콜과 너무 밀접하게 연결되어 충돌할 위험이 있다면, 프로토콜 이름에 Protocol을 추가하는것이 좋다.
protocol Sequence {
associatedtype Iterator : IteratorProtocol
}
protocol IteratorProtocol { ... }
Iterator는 연관 타입 이름이지만, 이미 Interator라는 이름의 프로토콜이 존재할 수 있음 -> InteratorProtoco 이름 사용
특히 약한 타입 정보를 가진 매개변수(매개변수 유형이 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) // 명확함
명확성을 위해 역할을 나타내는 명사를 사용한다. 약한 타입 정보를 가진 매개변수 앞에서는 그 매개변수의 역할을 설명하는 명사를 사용하여 함수를 더 명확하게 만들어야 한다.
✅
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)
x.makeIterator()
팩토리 메서드?
: 특정 객체나 인스턴스를 생성하고 반환하는 메서드. 이런 메서드의 이름을 정할 때 'make'라는 단어로 시작하는 것이 관례적으로 권장 됨
(처음엔 무슨말인가 했다... 즉 함수명과 첫번째 인자가 하나의 문장처럼 보이게 만들지 말라는 뜻!)
아래 코드의 첫 번째 인자들은 기본 이름과 동일한 문장으로 읽히지 않음
✅
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)
대부분의 경우 첫 번째 인자에는 라벨을 붙여야 한다. 단, 함수가 단순히 값을 다른 형태로 바꾸는 것만을 목적으로 할 때는 예외적으로 라벨 생략 가능하다.
let rgbForeground = RGBColor(cmykForeground)
부수 효과가 없는 함수나 메서드의 이름은 명사구처럼
ex) x.distance(to: y), i.successor()
부수 효과가 있는 함수나 메서드의 이름은 명령형 동사구처럼
ex) print(x), x.sort(), x.append(y)
또한, 객체를 직접 수정하는 변환 메서드와 새로운 값을 반환하는 비변환 메서드의 이름은 일관성 있게 지어야 한다.
1. 동사로 자연스럽게 설명되는 경우: 변환 메서드는 동사의 명령형 사용, 비변환 메서드는 ed, ing 접미사 사용
2. 명사로 자연스럽게 설명되는 경우: 변환 메서드는 form 접두사 사용, 비변환 메서드는 해당 명사 사용
ex) x.isEmpty(비어있는가?), line1.intersects(line2)(교차하는가?)
따라서 이런 프로토콜의 이름은 간단하고 명사형태로 지어야 한다.
ex) Collection
ex) Equatable, ProgresswReporting
ex) ✅피부, ❌표피
ex) ❌verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x), ✅sin(x) : 선례가 줄임말을 쓰지 않는 규칙보다 우선 적용.
참고: Swift API Guidelines, yagom-academy