개발자들은 보통 프로퍼티에 접근하는 것에 엄청난 연산이 필요하다고 생각하지 않는다.
전역함수는 특별한 경우에만 사용된다. 아래의 코드 예시를 보자
// 특정 객체에 대한 동작이 아닐 때
min(x, y, z)
// 함수가 제네릭이며 특정 타입에 제약을 받지 않을 때
print(x)
// 함수의 문법이 이미 잘 알려진 도메인 표기법의 일부일 때
sin(x)
ok
이는 메서드의 네이밍에 있어서 일관성과 의미를 유지하게 도와준다. 같은 이름을 사용하는 예시와, 잘못된 예시를 알아보자
1. 메서드가 본질적으로 같은 일을 하고 있기 때문에 같은 이름 사용 권장
✅
extension Shape {
/// Returns `true` iff `other` is within the area of `self`.
/// `other`가 `self`의 영역 내에 있는 경우 `true`를 반환합니다.
func contains(_ other: Point) -> Bool { ... }
/// Returns `true` iff `other` is entirely within the area of `self`.
/// `other`가 완전히 `self`의 영역 내에 있는 경우 `true`를 반환합니다.
func contains(_ other: Shape) -> Bool { ... }
/// Returns `true` iff `other` is within the area of `self`.
/// `other`가 `self`의 영역 내에 있는 경우 `true`를 반환합니다.
func contains(_ other: LineSegment) -> Bool { ... }
}
✅
extension Collection where Element : Equatable {
/// Returns `true` iff `self` contains an element equal to
/// `sought`.
/// `self`에 `sought`와 동일한 요소가 포함된 경우 `true`를 반환합니다.
func contains(_ sought: Element) -> Bool { ... }
}
❌
extension Database {
/// Rebuilds the database's search index
/// 데이터베이스의 검색 인덱스 리빌드
func index() { ... }
/// Returns the `n`th row in the given table.
/// 주어진 테이블의 `n`번째 행을 반환합니다.
func index(_ n: Int, inTable: TableID) -> TableRow { ... }
}
❌
extension Box {
/// Returns the `Int` stored in `self`, if any, and
/// `nil` otherwise.
/// `self`에 저장된 `Int`가, 만약에 있으면, 반환하고
/// 그렇지 않으면 `nil`을 반환합니다.
func value() -> Int? { ... }
/// Returns the `String` stored in `self`, if any, and
/// `nil` otherwise.
/// `self`에 저장된 `String`이, 만약에 있으면, 반환하고
/// 그렇지 않으면 `nil`을 반환합니다.
func value() -> String? { ... }
}
위의 코드의 경우 Int를 반환하고 싶었는데 모르고 아래의 String반환 메서드를 사용할 가능성이 있을 듯
func move(from start: Point, to end: Point)
매개변수의 이름은 함수의 역할과 기능을 명확하게 표현하는 데 중요한 역할을 한다.
✅
/// 'predicate'를 만족시키는 'self'의 요소를 포함하는 'Array'를 반환
func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]
이 함수의 매개변수 이름은 해당 함수의 역할을 잘 설명하고 있음
❌
/// 'includedInResult'를 만족시키는 'self'의 요소를 포함하는 'Array'를 반환
func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]
includedInResult라는 매개변수 이름은 함수의 조건을 나타내지 않고 결과에 포함되는 요소를 나타내므로, 설명이 어색함
✅
/// 주어진 요소의 'subRange'를 'newElements'로 변환
mutating func replaceRange(_ subRange: Range, with newElements: [E])
매개 변수 이름들이 함수의 기능을 잘 설명하고 있음
❌
/// 'r'로 표시된 요소의 범위를 'with'의 내용으로 변환
mutating func replaceRange(_ r: Range, with: [E])
subRange 대신 r이라는 짧은 이름, newElements 대신 with라는 이름을 사용하여 어색하고 불분명하게 보임
기본 매개변수 값은 사용사례를 간단하게 만들고, 가독성을 향상시킨다.
❌
let order = lastName.compare(
rotalFamilyName, option: []m range: nil, locale: nil)
✅ let order = lastName.compare(royalFamilyname)
기본 매개변수 값을 사용하는 것이 같은 기능을 하는 여러 메서드를 만드는 것보다 권장한다. 사용자에게 더 낮은 인지 부하를 주기 때문
즉 기본 매개변수를 사용한 단일 메서드는 프로그래머에게 훨씬 우수한 경험을 제공한다.
기본 값이 없는 매개면수는 일반적으로 메서드의 의미가 더 필수. 또한 안정적인 초기 패턴을 제공
프로덕션은 실제 사용자가 사용하는 환경으로 개발환경, 테스트환경과 구분된다.
Swift는 #fileID, #filePath, #file 같은 리터럴을 제공한다.
이러한 리터럴을 프로덕션에서 사용하면 공간 절약, 개인 정보 보호의 장점이 있다.
func move(from start: Point, to end: Point)
x.move(from: x, to y)
함수에서 첫 번째 인자와 두 번째 인자가 구별되는 경우 이를 명확하게 표시하기 위해 from과 to 같은 전달인자 레이블을 사용
ex) min(number1, number2), zip(sequance1, sequence2)
ex) Int64(someUInt32)
타입 변환의 대상은 반드시 첫 번째 전달인자여야 한다.
extension String {
// 'x'를 주어진 radix(진법)의 텍스트 형태로 변환한다.
init(_ x: BigInt, redix: Int = 10)
}
text = "The value is: "
text += String(veryLargeNumber)
text += " and in hexadecimal, it's"
text += String(veryLargeNumber, radix: 16)
하지만 범위가 좁아지는 타입 변환에서는, 변환의 특성을 설명하는 라벨 사용이 권장된다.
범위가 좁아지는 타입 변환?
: 큰 데이터 타입에서 작은 데이터 타입으로의 변환을 의미
ex) Int64 -> Int8, Double -> Int
extension UInt32 {
/// 지정한 `value` 값을 가진 인스턴스를 생성합니다.
init(_ value: Int16)
/// `source`의 최하위 32비트 값을 가진 인스턴스를 생성합니다.
init(truncating source: UInt64)
/// `valueToApproximate` 값에 가장 근접한 값을 가진 인스턴스를 생성합니다.
init(saturating valueToApproximate: UInt64)
}
값을 보존하면서 타입을 바꾸는 것을 단형성(monomorphism)이라고 한다. 타입변환 대상의 값이 다르면 결과도 다르다, Int8 -> Int64로 타입을 바꾸는 경우 값이 보존되지만 반대의 경우 값이 보존되지 않는다.
뭔가 이부분은 잘 이해가 되지 않았는데 추가로 이해한 내용을 설명 해보자면 범위가 좁아지는 타입변환에서는 원본 타입의 모든 값이 변환되지 않을 가능성이 있고, 이런경우 변환 결과가 어떻게 될지 명확히 표현하는것이 좋기 때문에 레이블을 작성하는것을 권장하고 있는것이다.
이때 라벨은 전치사에서 시작하는 것이 원칫이다.
만약 첫 번째와 두 번째 인자가 하나의 통합된 개념의 일부로 볼 수 있다면, 위에서의 원칙이 적용되지 않는다.
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)
ex) add라는 동사와, Subview라는 명사가 문법적으로 문구형성이 되기 때문에 "add(subview: a)" 보다는 "addSubview(a)" 적절
중요한 점은, 해당 문구가 올바른 의미를 전달해야 한다는 것. 잘못된 라벨이나 생략된 라벨로 인해 문장이 문법적으로 올바르더라도 잘못된 의미를 전달할 수 있다.
✅
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)
❌
view.dismiss(false) // 해제하지 말아라? Bool을 해제해라?
words.split(12) // 숫자 12를 나눠라?
위에서 설명한 경우를 제외하고는 전달 인자 레이블을 작성해야 한다.
참고: Swift API Guidelines, yagom-academy