├── .gitignore ├── README.md ├── SUMMARY.md ├── conventions ├── argument-labels.md ├── general-conventions.md └── parameters.md ├── fundamentals.md ├── naming ├── promote-clear-usage.md ├── strive-for-fluent-usage.md └── use-terminology-well.md ├── playground ├── ArgumentLabels.playground │ ├── Contents.swift │ └── contents.xcplayground ├── Fundamentals.playground │ ├── Contents.swift │ └── contents.xcplayground ├── GeneralConventions.playground │ ├── Contents.swift │ └── contents.xcplayground ├── NamingPromoteClearUsage.playground │ ├── Contents.swift │ └── contents.xcplayground ├── Parameters.playground │ ├── Contents.swift │ └── contents.xcplayground ├── SpecialInstructions.playground │ ├── Contents.swift │ └── contents.xcplayground ├── StriveForFluentUsage.playground │ ├── Contents.swift │ └── contents.xcplayground └── UseTerminologyWell.playground │ ├── Contents.swift │ └── contents.xcplayground ├── presentation └── test.md └── special-instructions.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Swift API Design Guidelines 공식문서 기반으로 한국어로 설명합니다 3 | --- 4 | 5 | # Swift API Design Guidelines (한국어) 6 | 7 | 안녕하세요! iOS 개발자 홍성호 입니다. Swift API Design Guidelines 공식문서를 한국어로 번역했습니다. 8 | 9 | 프로그래밍을 할 때 작성한 API가 잘 이해되는지, 최대한 많은 사람들에게 거부감 없이 읽히는지 고민이 많이 됩니다. 그럴 때 기준점으로 삼을 수 있는 문서가 있습니다. [Swift API Design Guidelines 문서](https://swift.org/documentation/api-design-guidelines/)인데요. API 디자인의 방향성이 헷갈릴 때마다 자주 참고하고 있습니다. 10 | 11 | 특별히 얼마전 부스트캠프 리뷰어를 하면서 이 문서를 자주 언급했었어요. 원문을 보는 것이 제일 좋겠지만 입문자의 경우 영어로 설명된 것이 진입 장벽으로 느껴질 수 있고, 경우에 따라 추가 설명이 필요할 수도 있어서 한국어로 설명하는 문서를 작성했습니다. 도움이 되면 좋겠습니다 :) 12 | 13 | * 문서만 보고 이해하기 어려운 분들은 강의를 참고해주세요!! 👉 [https://www.inflearn.com/course/읽기-좋은-스위프트?inst=a11a18a8](https://www.inflearn.com/course/%EC%9D%BD%EA%B8%B0-%EC%A2%8B%EC%9D%80-%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8?inst=a11a18a8) 14 | 15 | #### 🙋Contact 16 | 17 | * github: [https://github.com/cozzin](https://github.com/cozzin) 18 | * linkedIn: [https://www.linkedin.com/in/cozzin/](https://www.linkedin.com/in/cozzin/) 19 | * medium: [https://medium.com/@hongseongho](https://medium.com/@hongseongho) 20 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Swift API Design Guidelines (한국어)](README.md) 4 | * [🧱 Fundamentals](fundamentals.md) 5 | 6 | ## Naming 7 | 8 | * [🙆 Promote Clear Usage](naming/promote-clear-usage.md) 9 | * [🗣 Strive for Fluent Usage](naming/strive-for-fluent-usage.md) 10 | * [✅ Use Terminology Well](naming/use-terminology-well.md) 11 | 12 | ## Conventions 13 | 14 | * [🌈 General Conventions](conventions/general-conventions.md) 15 | * [📥 Parameters](conventions/parameters.md) 16 | * [🏷 Argument Labels](conventions/argument-labels.md) 17 | 18 | *** 19 | 20 | * [👽 Special Instructions](special-instructions.md) 21 | -------------------------------------------------------------------------------- /conventions/argument-labels.md: -------------------------------------------------------------------------------- 1 | # 🏷 Argument Labels 2 | 3 | ```swift 4 | func move(from start: Point, to end: Point) 5 | x.move(from: x, to: y) 6 | ``` 7 | 8 | ## label을 써도 유용하게 구분이 되지 않는다면 모든 label을 생략하세요. 9 | 10 | e.g. `min(number1, number2)`, `zip(sequence1, sequence2)` 11 | 12 | ## 값을 유지하면서 타입 변환을 해주는 initializer에서, 첫번째 argument label을 생략하세요. 13 | 14 | e.g. `Int64(someUInt32)` 15 | 16 | ### 첫번째 argument는 항상 source of conversion 이어야 한다. 17 | 18 | ```swift 19 | extension String { 20 | // Convert `x` into its textual representation in the given radix 21 | init(_ x: BigInt, radix: Int = 10) ← Note the initial underscore 22 | } 23 | 24 | text = "The value is: " 25 | text += String(veryLargeNumber) 26 | text += " and in hexadecimal, it's" 27 | text += String(veryLargeNumber, radix: 16) 28 | ``` 29 | 30 | ### 값의 범위가 좁혀지는 타입 변환의 경우, label을 붙여서 설명하는 것을 추천합니다. 31 | 32 | ```swift 33 | extension UInt32 { 34 | /// Creates an instance having the specified `value`. 35 | init(_ value: Int16) ← Widening, so no label 36 | /// Creates an instance having the lowest 32 bits of `source`. 37 | init(truncating source: UInt64) 38 | /// Creates an instance having the nearest representable 39 | /// approximation of `valueToApproximate`. 40 | init(saturating valueToApproximate: UInt64) 41 | } 42 | ``` 43 | 44 | ## 첫 번째 argument가 전치사구의 일부일 때, argument label로 지정합니다. 45 | 46 | argument label은 보통 전치사로 시작합니다. eg. `x.removeBoxes(havingLength: 12)` 47 | 48 | 처음에 나오는 2개의 argument들이 단일 추상화를 표현하는 경우는 예외입니다. 49 | 50 | ```swift 51 | // Bad 52 | a.move(toX: b, y: c) 53 | a.fade(fromRed: b, green: c, blue: d) 54 | ``` 55 | 56 | 이런 경우, 추상화를 명확히 하기 위해 전치사 뒤에 argument label을 시작합니다. 57 | 58 | ```swift 59 | // Good 60 | a.moveTo(x: b, y: c) 61 | a.fadeFrom(red: b, green: c, blue: d) 62 | ``` 63 | 64 | ## 만약 첫번째 argument가 문법적 구절을 만든다면 label은 제거하고, 함수 이름에 base name을 추가합니다. 65 | 66 | eg. `x.addSubview(y)` 67 | 68 | 이 가이드라인은 첫번째 argument가 문법적으로 구절을 만들지 않는다면, label을 둬야한다는 것을 암시합니다. 69 | 70 | ```swift 71 | // Good 72 | view.dismiss(animated: false) 73 | let text = words.split(maxSplits: 12) 74 | let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes) 75 | ``` 76 | 77 | 구절이 정확한 의미를 전달하는 것이 중요합니다. 다음 예시는 문법적이지만 모호한 표현을 하고 있습니다. 78 | 79 | ```swift 80 | // Bad 81 | view.dismiss(false) Don't dismiss? Dismiss a Bool? 82 | words.split(12) Split the number 12? 83 | ``` 84 | 85 | default value를 가진 argument는 생략될 수 있으며, 이 경우 문법 구문의 일부를 형성하지 않으므로 항상 레이블이 있어야 합니다. 86 | 87 | ## 나머지 모든 경우, argument들은 Label을 붙여야 합니다. 88 | -------------------------------------------------------------------------------- /conventions/general-conventions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 일반적인 컨벤션 3 | --- 4 | 5 | # 🌈 General Conventions 6 | 7 | ### **computed property의 복잡도가 O(1)이 아니라면 복잡도를 주석으로 남겨주세요.** 8 | 9 | 사람들은 보통 property에 접근할 때 엄청난 계산을 할거라고 생각하지 않습니다. 왜냐하면 멘탈 모델에 stored property가 있기 때문입니다. 그런 가정이 위배될 때 경고를 해주세요. 10 | 11 | ### **전역 함수 대신에 method와 property를 사용**하세요. 12 | 13 | 전역 함수는 특수한 경우에만 사용됩니다.\ 14 | 1\. 명확한 `self` 가 없는경우\ 15 | `min(x, y, z)`\ 16 | 2\. function이 generic으로 제약조건이 걸려있지 않을 때\ 17 | `print(x)`\ 18 | 3\. function 구문이 해당 도메인의 표기법인 경우\ 19 | `sin(x)` 20 | 21 | ### **대소문자 컨벤션을 따르세요.** 22 | 23 | type, protocol의 이름은 UpperCamelCase, 나머지는 lowerCamelCase를 따릅니다. 24 | 25 | 미국 영어에서 보통 all upper case로 사용되는 [Acronyms and initialisms](https://en.wikipedia.org/wiki/Acronym)(단어의 첫글자들로 말을 형성하는 것)은 대소문자 컨벤션에 따라 통일성있게 사용되어야 합니다. (예시 보는게 더 이해 쉬움) 26 | 27 | ```swift 28 | var utf8Bytes: [UTF8.CodeUnit] 29 | var isRepresentableAsASCII = true 30 | var userSMTPServer: SecureSMTPServer 31 | ``` 32 | 33 | 나머지 두문자어는 다른 일반 단어와 동일하게 사용하면 됩니다: 34 | 35 | ```swift 36 | var radarDetector: RadarScanner 37 | var enjoysScubaDiving = true 38 | ``` 39 | 40 | ### 기본 뜻이 같거나 구별되는 서로 구별되는 도메인에서 작동하는 Method는 base name을 동일하게 사용할 수 있습니다. 41 | 42 | #### Good 43 | 44 | 예를들어, 아래 예시에서 기본적으로 같은 동작을 하기 때문에 같은 이름을 사용하는 것이 권장됩니다. 45 | 46 | ```swift 47 | extension Shape { 48 | /// Returns `true` iff `other` is within the area of `self`. 49 | func contains(_ other: Point) -> Bool { ... } 50 | 51 | /// Returns `true` iff `other` is entirely within the area of `self`. 52 | func contains(_ other: Shape) -> Bool { ... } 53 | 54 | /// Returns `true` iff `other` is within the area of `self`. 55 | func contains(_ other: LineSegment) -> Bool { ... } 56 | } 57 | ``` 58 | 59 | geometric type과 collection type은 구별된 도메인이기 때문에, 같은 프로그램 안에서 다음과 같이 사용할 수 있습니다. 60 | 61 | ```swift 62 | extension Collection where Element : Equatable { 63 | /// Returns `true` iff `self` contains an element equal to 64 | /// `sought`. 65 | func contains(_ sought: Element) -> Bool { ... } 66 | } 67 | ``` 68 | 69 | #### Bad 70 | 71 | 아래의 `index` method는 다른 의미를 갖고 있기 때문에, 다르게 네이밍 되어야 합니다. 72 | 73 | ```swift 74 | extension Database { 75 | /// Rebuilds the database's search index 76 | func index() { ... } 77 | 78 | /// Returns the `n`th row in the given table. 79 | func index(_ n: Int, inTable: TableID) -> TableRow { ... } 80 | } 81 | ``` 82 | 83 | "overloading on return type"은 타입 추론의 유무에 따라 모호한 경우가 있어서 권장되지 않습니다. 84 | 85 | ```swift 86 | extension Box { 87 | /// Returns the `Int` stored in `self`, if any, and 88 | /// `nil` otherwise. 89 | func value() -> Int? { ... } 90 | 91 | /// Returns the `String` stored in `self`, if any, and 92 | /// `nil` otherwise. 93 | func value() -> String? { ... } 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /conventions/parameters.md: -------------------------------------------------------------------------------- 1 | # 📥 Parameters 2 | 3 | ```swift 4 | func move(from start: Point, to end: Point) 5 | ``` 6 | 7 | ## 주석을 읽기 쉽게 만들어주는 파라미터 이름을 선택하세요. 8 | 9 | - 파라미터 이름은 function 이나 method를 사용하는 곳에서 보이지 않지만, function 이나 method를 설명해주는 역할을 갖습니다. 10 | - 문서(주석)를 읽기에 쉬운 파라미터 이름을 사용하세요. 11 | 12 | #### Good 13 | 14 | 이런 이름들은 주석을 읽기 쉽게 해줍니다. 15 | (아래 예제에서 `predicate` 와 `subRange` `newElements` 에 해당하는 내용) 16 | 17 | ```swift 18 | /// Return an `Array` containing the elements of `self` 19 | /// that satisfy `predicate`. 20 | func filter(_ predicate: (Element) -> Bool) -> [Generator.Element] 21 | 22 | /// Replace the given `subRange` of elements with `newElements`. 23 | mutating func replaceRange(_ subRange: Range, with newElements: [E]) 24 | ``` 25 | 26 | #### Bad 27 | 28 | ```swift 29 | /// Return an `Array` containing the elements of `self` 30 | /// that satisfy `includedInResult`. 31 | func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element] 32 | 33 | /// Replace the range of elements indicated by `r` with 34 | /// the contents of `with`. 35 | mutating func replaceRange(_ r: Range, with: [E]) 36 | ``` 37 | 38 | ## 일반적인 사용을 단순화 할 수 있다면, defaulted parameters를 사용하세요 39 | 40 | 일반적으로 사용되는 파라미터가 default로 사용될 수 있습니다. 41 | 42 | 예를들어 아래의 경우 default parameter를 사용해서 가독성을 높일 수 있습니다. 43 | 44 | ```swift 45 | let order = lastName.compare( 46 | royalFamilyName, options: [], range: nil, locale: nil) 47 | ``` 48 | 49 | ``` 50 | let order = lastName.compare(royalFamilyName) 51 | ``` 52 | 53 | - default parameters는 일반적인 method families를 사용하는 것보다 선호됩니다. (기본 파라미터 사용 > 기본 파라미터 안쓰고 method 여러개 나열 하는 것) 54 | - 왜냐하면 API를 이해하려고 노력하는 사람들이 신경써야할 부분을 줄여주기 때문입니다. (아래 예제를 보면 조금 더 이해가 쉽습니다.) 55 | 56 | #### Good 57 | 58 | ```swift 59 | extension String { 60 | /// ...description... 61 | public func compare( 62 | _ other: String, options: CompareOptions = [], 63 | range: Range? = nil, locale: Locale? = nil 64 | ) -> Ordering 65 | } 66 | ``` 67 | 68 | #### Bad 69 | 70 | ```swift 71 | extension String { 72 | /// ...description 1... 73 | public func compare(_ other: String) -> Ordering 74 | /// ...description 2... 75 | public func compare(_ other: String, options: CompareOptions) -> Ordering 76 | /// ...description 3... 77 | public func compare( 78 | _ other: String, options: CompareOptions, range: Range) -> Ordering 79 | /// ...description 4... 80 | public func compare( 81 | _ other: String, options: StringCompareOptions, 82 | range: Range, locale: Locale) -> Ordering 83 | } 84 | ``` 85 | 86 | method family의 모든 멤버들은 (위의 모든 method들) 개별적으로 주석을 달아서 문서화해줘야 합니다. 그래야 유저가 이해할 수 있습니다. 사용자가 method들 중에서 선택해야할 때, 사용자는 method들을 모두 이해해야 합니다. 가끔 예상치 못한 관계가 발생합니다. (예를들어, `foo(bar: nil)` 과 `foo()` 가 다른 동작을 하는 경우) 이런 경우 거의 동일한 주석 안에서 사소한 차이를 찾아내는 지루한 과정이 발생하게 됩니다. 하나의 method를 사용하고 default parameter를 제공하는 것은 매우 뛰어난 프로그래밍 경험을 제공합니다. 87 | 88 | ## default parameter를 parameter list 끝 부분에 두는 것을 선호합니다. 89 | 90 | - default값이 없는 parameter는 보통 메소드 의미에 더 필수적이고, 사용하는 쪽에서 안정적인 사용 패턴을 제공합니다. 91 | 92 | 93 | -------------------------------------------------------------------------------- /fundamentals.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 기초 3 | --- 4 | 5 | # 🧱 Fundamentals 6 | 7 | * **사용하는 쪽에서 명확**하다고 느끼게 하는 것이 가장 중요한 목표입니다.\ 8 | 메서드, 프로퍼티 같은 요소들은 한번 선언하고 반복적으로 사용합니다. 그런 요소들을 명확하고 간결하게 사용할 수 있도록 API를 설계하세요. 설계를 평가할 때, 선언한 쪽만 보고 평가하는 경우는 거의 없습니다. 항상 유즈케이스를 바탕으로 평가하게 됩니다. 특정 문맥 속에서 명확해 보이는지 확인합니다. 9 | * **명확한 것이 간결한 것보다 중요합니다.** Swift 코드를 압축적으로 표현할 수 있긴 하지만, 가장 짧은 코드를 작성하는게 목표는 아닙니다. 타입 시스템과 boilerplate 줄여주는 기능들 덕분에 자연스럽게 Swift 코드가 간결해진 것 입니다. _(짧게 표현하는 것을 목표로 두지 말자)_ 10 | * 모든 선언에 대해 **문서 주석(documentation comment)을 작성하세요**. 문서를 작성하면서 얻은 통찰은 당신의 설계에 큰 영향을 줄 수 있습니다. 11 | * 역자 주) 저는 모든 선언에 주석으로 설명을 할 필요는 없다고 생각합니다. 하지만 외부로 공개하는 모듈의 경우 해당 API를 설명하는 주석이 필요합니다. 12 | 13 | {% hint style="info" %} 14 | API를 간단히 설명하는게 어렵다면, API를 잘못 설계했을 지도 모릅니다. 15 | {% endhint %} 16 | 17 | ### 자세한 내용 18 | 19 | * Swift의 마크다운 용어를 사용하세요. 20 | * 요약으로 주석을 시작하세요. API 선언과 요약만으로 완전히 이해될 때가 있습니다. 21 | 22 | ```swift 23 | /// Returns a "view" of `self` containing the same elements in 24 | /// reverse order. 25 | func reversed() -> ReverseCollection 26 | ``` 27 | 28 | * 필요한 경우, 문단이나 bullet items(- 으로 구분되는 문장)을 추가하세요. 문단은 빈줄로 구분하고 완전한 문장을 사용합니다. 29 | 30 | ```swift 31 | /// Writes the textual representation of each ← Summary 요약 32 | /// element of `items` to the standard output. 33 | /// ← Blank line 빈줄 34 | /// The textual representation for each item `x` ← Additional discussion 35 | /// is generated by the expression `String(x)`. 36 | /// 37 | /// - Parameter separator: text to be printed ⎫ 38 | /// between items. ⎟ 39 | /// - Parameter terminator: text to be printed ⎬ Parameters section 40 | /// at the end. ⎟ 41 | /// ⎭ 42 | /// - Note: To print without a trailing ⎫ 43 | /// newline, pass `terminator: ""` ⎟ 44 | /// ⎬ Symbol commands 45 | /// - SeeAlso: `CustomDebugStringConvertible`, ⎟ 46 | /// `CustomStringConvertible`, `debugPrint`. ⎭ 47 | public func print( 48 | _ items: Any..., separator: String = " ", terminator: String = "\n") 49 | ``` 50 | -------------------------------------------------------------------------------- /naming/promote-clear-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 명확한 사용을 촉진하세요 3 | --- 4 | 5 | # 🙆 Promote Clear Usage 6 | 7 | ### **필요한 단어들을 모두 포함해주세요.** 8 | 9 | 코드를 읽는 사람이 모호하게 느끼지 않아야 합니다. 예를들어, collection 안에서 주어진 position의 element를 제거하는 메소드가 있다고 생각해 봅시다. 10 | 11 | #### Good 12 | 13 | ```swift 14 | extension List { 15 | public mutating func remove(at position: Index) -> Element 16 | } 17 | employees.remove(at: x) // x번째에 위치한 employee 제거 18 | ``` 19 | 20 | 만약 우리가 위의 method signature에서 `at` 을 생략하면 `x`가 제거할 element의 position을 암시하는 대신, `x` 와 동일한 element를 찾고 제거해주는 것을 암시할 수 있습니다. 21 | 22 | #### Bad 23 | 24 | ```swift 25 | employees.remove(x) // 명확하지 않음: x를 제거하는 것일까...? 26 | ``` 27 | 28 | ### 29 | 30 | ### 불필요한 단어를 생략하세요 31 | 32 | 네이밍에 들어가는 모든 단어는 사용되는 시점(at the use site)에 핵심적인 정보만 전달해야 합니다. 33 | 34 | #### Bad 35 | 36 | ```swift 37 | public mutating func removeElement(_ member: Element) -> Element? 38 | 39 | allViews.removeElement(cancelButton) 40 | ``` 41 | 42 | 위의 경우를 보면 `Element` 라는 단어를 썼지만 의미있는 정보가 더해지지 않았습니다. 이 API는 이렇게 디자인 하면 더 좋습니다 43 | 44 | #### Good 45 | 46 | ```swift 47 | public mutating func remove(_ member: Element) -> Element? 48 | 49 | allViews.remove(cancelButton) // clearer 50 | ``` 51 | 52 | 때로는 애매함을 피하기 위해 타입 정보를 반복해서 네이밍할 수 있어요. 하지만 일반적으로는 파라미터의 역할로 네이밍 하는 것이 타입으로 네이밍하는 것보다 좋습니다. 다음 설명에서 좀 더 자세히 알아봅시다. 53 | 54 | 55 | 56 | ### 타입 대신 역할에 따라 변수(variables), 파라미터(parameters), 연관타입(associated types)을 네이밍하세요 57 | 58 | #### Bad 59 | 60 | ```swift 61 | var string = "Hello" 62 | protocol ViewController { 63 | associatedtype ViewType : View 64 | } 65 | class ProductionLine { 66 | func restock(from widgetFactory: WidgetFactory) 67 | } 68 | ``` 69 | 70 | 이런 방식으로 타입 이름을 정의하면 명확하게 표현하는 것이 어려워집니다. 대신, 엔티티의 역할을 표현하는 이름을 사용해보세요. 71 | 72 | #### Good 73 | 74 | ```swift 75 | var greeting = "Hello" 76 | protocol ViewController { 77 | associatedtype ContentView : View 78 | } 79 | class ProductionLine { 80 | func restock(from supplier: WidgetFactory) 81 | } 82 | ``` 83 | 84 | 만약 `associated type`이 해당 protocol 제약에 강하게 결합되어 있어서 protocol 이름 자체가 역할(role)을 표현하다면, 충돌을 피하기 위해서 protocol 이름의 마지막에 `Protocol` 을 붙여줄 수 있습니다. 85 | 86 | ```swift 87 | protocol Sequence { 88 | associatedtype Iterator : IteratorProtocol 89 | } 90 | protocol IteratorProtocol { ... } 91 | ``` 92 | 93 | 94 | 95 | ### **파라미터의 역할을 명확히 하기 위해 불충분한 type 정보를** 보충하세요 96 | 97 | (번역이 약간 어색한데, 예제를 보면 더 빨리 이해할 수 있을 것 같아요) 98 | 99 | 특히 파라미터 타입이 `NSObject`, `Any`, `AnyObject`, 또는 기본 타입(`Int` 또는 `String` 같은 타입) 이라면, 타입 정보와 사용하는 시점의 문맥이 의도를 충분히 드러내지 못할 수 있습니다. 아래의 예시를 보면, 정의는 명확하게 되어 있는데도, 사용하는 곳에서는 메소드의 의도가 애매합니다. 100 | 101 | #### Bad 102 | 103 | ```swift 104 | func add(_ observer: NSObject, for keyPath: String) 105 | 106 | grid.add(self, for: graphics) // 애매함 107 | ``` 108 | 109 | 명확하게 하려면, type 자체에서 많은 정보 얻을 수 없는 parameter 앞에 그것의 역할을 명시하세요. 110 | 111 | #### Good 112 | 113 | ```swift 114 | func addObserver(_ observer: NSObject, forKeyPath path: String) 115 | grid.addObserver(self, forKeyPath: graphics) // 명확함 116 | ``` 117 | 118 | -------------------------------------------------------------------------------- /naming/strive-for-fluent-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 유창한 사용을 위해 노력하세요 3 | --- 4 | 5 | # 🗣 Strive for Fluent Usage 6 | 7 | ### **method와 function을 영어 문장 처럼 사용**할 수 있도록 하기 8 | 9 | #### Good 10 | 11 | ```swift 12 | x.insert(y, at: z) “x, insert y at z” 13 | x.subViews(havingColor: y) “x's subviews having color y” 14 | x.capitalizingNouns() “x, capitalizing nouns” 15 | ``` 16 | 17 | #### Bad 18 | 19 | ```swift 20 | x.insert(y, position: z) 21 | x.subViews(color: y) 22 | x.nounCapitalize() 23 | ``` 24 | 25 | 예외) 첫번째 또는 두번째 argument 이후에 주요 argument가 아닌 경우에는 유창함이 떨어지는 것이 허용됩니다. 26 | 27 | ```swift 28 | AudioUnit.instantiate( 29 | with: description, 30 | options: [.inProcess], completionHandler: stopProgressBar) 31 | ``` 32 | 33 | 34 | 35 | ### **factory method의 시작은 `make`로 시작**합니다. 36 | 37 | eg. `x.makeIterator()` 38 | 39 | 40 | 41 | ### initializer의 argument와 factory method 호출에는 구절을 구성하지 마세요. 42 | 43 | (일단 번역했지만 잘 이해가지 않습니다... 예제를 봅시다) 44 | 45 | eg. `x.makeWidget(cogCount: 47)` 46 | 47 | 예를 들어, 처음 호출하는 argument들은 base name의 구절로 읽혀서는 안됩니다. 48 | 49 | #### Good 50 | 51 | ```swift 52 | let foreground = Color(red: 32, green: 64, blue: 128) 53 | let newPart = factory.makeWidget(gears: 42, spindles: 14) 54 | let ref = Link(target: destination) 55 | ``` 56 | 57 | #### Bad 58 | 59 | ```swift 60 | let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128) 61 | let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14) 62 | let ref = Link(to: destination) 63 | ``` 64 | 65 | 실제로, argument labels을 위한 이 가이드를 따르면, 첫번째 argument는 label을 갖게 된다. (값이 보존되는 타입 변환 경우는 제외(아래에 있는 예제)) 66 | 67 | ```swift 68 | let rgbForeground = RGBColor(cmykForeground) 69 | ``` 70 | 71 | 72 | 73 | ### 부수효과(side-effect)를 기반해서 function 과 method의 네이밍을 하세요 74 | 75 | * side-effect가 없는 것은 명사로 읽혀야 함. eg. `x.distance(to: y)` , `i.successor()` 76 | * side-effect가 있는 것은 동사로 읽혀야 함. eg. `print(x)` , `x.sort()` , `x.append(y)` 77 | * mutating/nonmutating method의 이름을 일관성 있게 짓기. 주로 mutating method와 함께 비슷한 맥락의 nonmutating method도 만들어 집니다. nonmutating method는 instance를 변경하지 않고 새로운 value를 return 합니다. 78 | * operation이 동사로 설명되는 경우 79 | * mutating에는 동사의 명령형을 사용 80 | * nonmutating에는 "ed" 또는 "ing"를 접미사로 붙여서 사용 81 | 82 | | Mutating | NonMutating | 83 | | ------------- | -------------------- | 84 | | `x.sort()` | `z = x.sorted()` | 85 | | `x.append(y)` | `z = x.appending(y)` | 86 | 87 | * operation이 명사로 설명되는 경우 88 | * nonmutating에는 명사 사용 89 | * mutating에는 "form" 접두사 붙여서 사용 90 | 91 | | Nonmutating | Mutating | 92 | | -------------------- | --------------------- | 93 | | `x = y.union(z)` | `y.formUnion(z)` | 94 | | `j = c.successor(i)` | `c.formSuccessor(&i)` | 95 | 96 | 97 | 98 | ### nonmutating인 Boolean 메소드와 프로퍼티는 호출되는 객체에 대한 주장문처럼 읽혀야 한다 99 | 100 | eg. `x.isEmpty` , `line1.intersects(line2)` 101 | 102 | ### 어떤 것이 무엇인지를 설명하는 프로토콜은 명사로 읽혀야 합니다 103 | 104 | eg. `Collection` 105 | 106 | ### 능력을 설명하는 프로토콜은 able, ible, ing를 사용한 접미사로 네이밍해야 합니다 107 | 108 | eg. `Equatable` , `ProgressReporting` 109 | 110 | 111 | 112 | ### 나머지 types, properties, variables, constants는 명사로 읽혀야 합니다 113 | 114 | -------------------------------------------------------------------------------- /naming/use-terminology-well.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 용어를 제대로 사용하세요 3 | --- 4 | 5 | # ✅ Use Terminology Well 6 | 7 | **Term of Art: **_noun_ - a word or phrase that has a precise, specialized meaning within a particular field or profession. 명사 - 특정 필드나 전문영역에서 정확하고 특별함을 갖는 단어나 구절. 8 | 9 | * 일반적인 단어가 의미를 더 잘 전달한다면 **잘 알려져 있지 않은 용어를 사용하지 마세요**.\ 10 | "skin"으로 의도를 드러낼 수 있다면 "epidermis"를 쓰지 마세요. 전문용어는 필수적인 대화 수단이지만, 사용하지 않으면 제대로 전달되지 않을 때만 사용해야 합니다. 11 | * 전문 용어를 사용한다면 확립된 의미를 사용하세요.\ 12 | 일반 용어를 사용해서는 정확한 의미 전달이 안되는 경우에만, 기술 용어를 사용해서 정확한 의미 전달하는 것이 좋습니다. 따라서 API는 이 용어를 허용된 의미에 일치하도록 엄격하게 사용해야 합니다. 13 | * 전문가를 놀라게 하지 마세요: 우리가 전문 용어에 새로운 의미를 부여하면 용어에 익숙한 전문가들은 놀라고 어쩌면 화를 낼지도 모릅니다. 14 | * 초급자를 혼란스럽게 하지 마세요: 이 용어를 배우려는 사람이 인터넷에 검색했을 때 전통적인 의미가 나올 수도 있습니다. 15 | * **약어(줄임말, abbreviations)을 피하세요**. 정형화 되어 있지 않은 약어를 이해하려면, 다시 풀어서 해석해야하기 때문에 전문 용어(terms of art, 아는 사람만 아는 것)라고 볼 수 있다. 16 | 17 | {% hint style="info" %} 18 | 사용된 약어의 의도된 의미는 인터넷 검색으로 쉽게 찾을 수 있어야 합니다. 19 | {% endhint %} 20 | 21 | * **관례를 따르세요.** 기존 문화와 다른 용어를 사용하면서까지 초심자를 배려하지 마세요.\ 22 | \ 23 | 연속된 데이터 구조를 표현할 때, 비록 초심자가 `List` 를 쉽게 이해한다고 해도 `List`보다 `Array`로 용어를 사용하는게 낫습니다. Array는 현대 컴퓨팅의 기초기 때문에 모든 프로그래머들이 알고 있거나 곧 알게될 것입니다. 대부분의 프로그래머가 친숙한 용어를 사용하세요. 그러면 그들이 웹 검색이나 질문을 할 때 답변을 찾을 수 있을 것 입니다.\ 24 | \ 25 | 수학같은 특정 프로그래밍 도메인에서, `sin(x)` 같이 광범위하게 사용되는 용어는 그대로 사용하는 것이 `verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)` 같은 네이밍보다 낫습니다. 이 경우 약어를 피하는 것 보다 관례를 따르는 것에 더 가중치가 있다는 것에 주목하세요. 비록 온전한 단어는 sine이지만 "sin(x)"는 프로그래머들에게 수십년간, 수학자들에게는 수세기 동안 보편적으로 사용되어 왔습니다. 26 | 27 | -------------------------------------------------------------------------------- /playground/ArgumentLabels.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | x.removeBoxes(havingLength: 12) 4 | 5 | func removeBoxes(havingLength length: Int) { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /playground/ArgumentLabels.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/Fundamentals.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Summary 내용에 들어가는 부분 4 | /// 5 | /// The textual representation for each item `x` 6 | /// is generated by the expression `String(x)`. 7 | /// 8 | /// - Parameter separator: 수정된 파라미터 설명 ⎫ 9 | /// between items. ⎟ 10 | /// - Parameter terminator: text to be printed ⎬ Parameters section 11 | /// at the end. ⎟ 12 | /// ⎭ 13 | /// - Note: To print without a trailing ⎫ 14 | /// newline, pass `terminator: ""` ⎟ 15 | /// ⎬ Symbol commands 16 | /// - SeeAlso: `CustomDebugStringConvertible`, ⎟ 17 | /// `CustomStringConvertible`, `debugPrint`. ⎭ 18 | public func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { 19 | // Empty 20 | } 21 | -------------------------------------------------------------------------------- /playground/Fundamentals.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/GeneralConventions.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Box { 4 | 5 | private let rawValue: Int 6 | 7 | init(_ rawValue: Int) { 8 | self.rawValue = rawValue 9 | } 10 | 11 | func value() -> Int? { 12 | rawValue 13 | } 14 | 15 | func value() -> String? { 16 | "\(rawValue)" 17 | } 18 | } 19 | 20 | let myBox = Box(100) 21 | 22 | myBox.value() as Int? 23 | myBox.value() as String? 24 | 25 | let intBoxValue: Int? = myBox.value() 26 | -------------------------------------------------------------------------------- /playground/GeneralConventions.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/NamingPromoteClearUsage.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | final class MyNotificationCenter { 4 | 5 | private var observers: [String: NSObject] = [:] 6 | 7 | func add(_ observer: NSObject, forKeyPath keyPath: String) { 8 | observers[keyPath] = observer 9 | } 10 | 11 | } 12 | 13 | let center = MyNotificationCenter() 14 | center.add(self, forKeyPath: "myKey") 15 | -------------------------------------------------------------------------------- /playground/NamingPromoteClearUsage.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/Parameters.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public func compare( 4 | _ other: String, 5 | options: CompareOptions = [], 6 | range: Range? = nil, 7 | locale: Locale? = nil 8 | ) { 9 | 10 | } 11 | 12 | compare("A") 13 | compare(locale: nil, "B") 14 | compare(options: [], locale: nil, "B") 15 | compare(options: [], range: nil, locale: nil, "B") 16 | 17 | compare("A") 18 | compare("B", locale: nil) 19 | compare("B", options: [], locale: nil) 20 | compare("B", options: [], range: nil, locale: nil) 21 | -------------------------------------------------------------------------------- /playground/Parameters.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/SpecialInstructions.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Storage { 4 | 5 | func doSomething() -> (reallocated: Bool, capacityChanged: Bool) { 6 | return (true, false) 7 | } 8 | 9 | } 10 | 11 | let storage = Storage() 12 | let result = storage.doSomething() 13 | 14 | result.reallocated 15 | result.capacityChanged 16 | 17 | var values: [Any] = [1, "a"] 18 | values.append(contentsOf: [2, 3, 4]) 19 | -------------------------------------------------------------------------------- /playground/SpecialInstructions.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/StriveForFluentUsage.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | var unsortedArray: [Int] = [5, 1, 3, 4] 4 | 5 | unsortedArray.sorted() 6 | unsortedArray 7 | 8 | unsortedArray.sort() // mutating 9 | unsortedArray // sorted array 10 | 11 | unsortedArray.append(100) 12 | unsortedArray 13 | 14 | print("hello") 15 | 16 | protocol ProgressReporting { 17 | 18 | } 19 | 20 | extension ProgressReporting { 21 | func reportProgress() { 22 | // 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /playground/StriveForFluentUsage.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/UseTerminologyWell.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | -------------------------------------------------------------------------------- /playground/UseTerminologyWell.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /presentation/test.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | theme: gaia 4 | _class: lead 5 | backgroundImage: url('https://marp.app/assets/hero-background.svg') 6 | --- 7 | 8 | # 🚀 Swift API Design Guidelines 9 | 10 | 홍성호 11 | 12 | --- 13 | 14 | # 목차 15 | 16 | - Fundamentals 17 | - Naming 18 | - Conventions 19 | - Special Instructions 20 | 21 | --- 22 | -------------------------------------------------------------------------------- /special-instructions.md: -------------------------------------------------------------------------------- 1 | # 👽 Special Instructions 2 | 3 | ## tuple members와 closure parameters에 Label을 붙이세요. 4 | 5 | 이러한 이름들은 설명력이 있고, 문서화된 주석에서 언급될 수 있으며, 튜플 멤버에 대한 expressive access를 제공합니다. 6 | 7 | ```swift 8 | /// Ensure that we hold uniquely-referenced storage for at least 9 | /// `requestedCapacity` elements. 10 | /// 11 | /// If more storage is needed, `allocate` is called with 12 | /// `byteCount` equal to the number of maximally-aligned 13 | /// bytes to allocate. 14 | /// 15 | /// - Returns: 16 | /// - reallocated: `true` iff a new block of memory 17 | /// was allocated. 18 | /// - capacityChanged: `true` iff `capacity` was updated. 19 | mutating func ensureUniqueStorage( 20 | minimumCapacity requestedCapacity: Int, 21 | allocate: (_ byteCount: Int) -> UnsafePointer 22 | ) -> (reallocated: Bool, capacityChanged: Bool) 23 | ``` 24 | 25 | 클로저 파라미터로 사용된 네이밍은 [📥 Parameters](conventions/parameters.md) 네이밍의 top-level functions와 동일한 방식으로 지어야 합니다. 호출하는 곳에서 볼 수 있는 closure argument label은 지원되지 않습니다. 26 | 27 | ## overload set에서의 모호함을 피하기 위해, 제약 없는 다형성에 각별히 주의하세요. 28 | 29 | eg. `Any`, `AnyObject`, unconstrained generic parameters 30 | 31 | 예를 들어, 이런 overload set이 있다고 생각해봅시다. 32 | 33 | ```swift 34 | // Bad 35 | struct Array { 36 | /// Inserts `newElement` at `self.endIndex`. 37 | public mutating func append(_ newElement: Element) 38 | 39 | /// Inserts the contents of `newElements`, in order, at 40 | /// `self.endIndex`. 41 | public mutating func append(_ newElements: S) 42 | where S.Generator.Element == Element 43 | } 44 | ``` 45 | 46 | 위의 메소드와 argument types는 처음에는 뚜렷하게 구분되는 것 처럼 보입니다. 그러나 `Element` 가 `Any` 인 경우 하나의 요소가 시퀀스와 동일한 유형을 가질 수 있습니다. (아래 예시) 47 | 48 | ```swift 49 | // Bad 50 | var values: [Any] = [1, "a"] 51 | values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]? 52 | ``` 53 | 54 | 모호함을 제거하기 위해, 두번째 overload의 이름을 더 명시적으로 지정합니다. 55 | 56 | ```swift 57 | // Good 58 | struct Array { 59 | /// Inserts `newElement` at `self.endIndex`. 60 | public mutating func append(_ newElement: Element) 61 | 62 | /// Inserts the contents of `newElements`, in order, at 63 | /// `self.endIndex`. 64 | public mutating func append(contentsOf newElements: S) 65 | where S.Generator.Element == Element 66 | } 67 | ``` 68 | --------------------------------------------------------------------------------