├── .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 |
--------------------------------------------------------------------------------