# Coding Rule & Coding Style ###### tags: `tutorial` > [name=Kevin Chiang] > [time=Wed, Jun 23, 2021 4:48 PM] - Coding Style 的目的就是為了讓大家的程式碼統一風格,減少閱讀他人程式碼時的心智轉換。 - 已經存在的 codebase 在重構時更新。新增的程式碼請開始 follow 本規則。 ## Coding Rule ### 基本準則 - 善用 `?` 避免使用 `!` - 常數建議使用 大駱駝 或 大寫底線大寫,勿用小駱駝 - `mutable` 變數命名一律用小駱駝, 1. scope 越小的變數名字可以短 2. 檔案內的就要寫清楚點 3. 全域的變數名字要很精準 - 程式碼沒有用到就刪除,減少以註解方式保留。 > 唯一沒有 Bug 的程式碼,就是沒有程式碼。 - var, func 盡量私有化 `private`,除非必要再將 `private` 移除換成預設的 `internal` 供外部使用,實施 Open/close principle(OCP) 開放/封閉原則。 - 變數要用了才宣告,swift 不是 K&R C 不用考慮 stack 空間配置問題。變數宣告越臨近使用處,能減少閱讀上的難度。 - 記得在 capture list 裡明確的宣告它們,該 `weak` 就 `weak`,避免使用 `unowned`,會增加後續維護成本。 - 在實作自定義 model, manager 時,函式皆以 `cmd`+`opt`+`/` 方式寫註解,提升易讀性。 - 寫註解時,中文為主,英文其次,以清楚描述為原則。 ## Coding Style ### 空格 - 在 `,` 後一定要有 `空格` 或是直接換行。 - 在 `:` 後一定要有 `空格`,前面不用。 ```swift let abc: Bool = someFunction(a: a, b: b) ``` - 在運算子 `+` `-` `*` `/` `&` `&&` `|` `||` `==` `===` 前後一定要有 `空格`。 ```swift if a == b - c { // do something } ``` - 左括和前面的變數要有一個 `空格` ```swift if condition { } ``` - 在函式後,接一個 `空格` ```swift func functionA() { // do something } ``` ### 空行 無論如何,最多三個空行。 `註解` 都不算空行,視為對下方 class / variable / method 的一部分 - 左括 class / function 左括後可有一個空行。 ```swift // ok class A { func xxx() { // do something } } // 這樣也 ok class A { func xxx() { // do something } } ``` - 右括 右括和下一個右括不留空行 (非硬性) ```swift // Preferred: class A { func xxx() { } } // Not Preferred: class A { func xxx() { } } ``` - class 間 可以有 1~3 個空行 - function 間 一個空行 ```swift func a() { } func b() { } ``` - function 內 ```swift func a() { // 邏輯相關性高 doA() doSomethingRelatedToA() // 另一個邏輯段 doB() doSomethingRelatedToB() } ``` ### 換行 (非硬性) 一行程式碼最多到 120 個字,所以在 `,` 後是最佳換行時機, ```swift // ok func short(a: Int, b: Int) { } // ok func short(a: Int, b: Int) { } // Not Preferred: func a(a: Int, b: Int, c: Int, somethingVeryLongLongLong: SomeTypeVeryLongLongLong) { } // Preferred: func a(a: Int, b: Int, c: Int, somethingVeryLongLongLong: SomeTypeVeryLongLongLong) { } ``` ### self (非硬性) 盡量不要使用 *self* ,除非被 shadow 了 ```swift init(valA: Int) { self.valA = valA // 不得不 } func xxx(valB: Int) { if self.valB > valB { // 不得不 // do something } } func doAsync() { doSomethingAsync { [weak self] in guard let self = self else { return } self.xxx() // 不得不 } } ``` 在上面的 closure 例子中,你可以不 strongify self,一旦你做了,不要用: ```swift let weakSelf = self // 因為self 已經被 strongify 了,不再 weak ``` 在 swift 4.2 之前有 bug,所以會有 ```swift let `self` = self // 4.2 之前才需要加 ` ``` ### 離開判斷 判斷離開的程式碼可以有以下型式,基本上 condition 短的建議一行完成 ```swift // Preferred: func functionA() { guard condition else { return } } // ok func functionB() { guard conditionA else { return } guard conditionB, let a = aa, let b = bb else { return } } ``` ### 流程 主要採用 **先判斷離開函式的條件** ,一來重構方便(extract method),一來真正要處理的 code 才不會被縮排到很後面 - Not Preferred: ```swift if conditionA { if conditionB { if conditionC { // conditionC } } else { // !conditionB } } else { // !conditionA } ``` - Preferred: ```swift guard conditionA else { // !conditionA return } guard conditionB else { // !conditionB return } if conditionC { // conditionC } ``` ### chain - 單行 chain ```swift func xxx() { let names = childCoordinators.map { $0.className } // do something with names } ``` - 多行 chain ```swift func bind(viewModel: UpdateDialogViewModelProtype) { cancelButton.rx.tap .subscribe(onNext: { viewModel.updateNextTime() }) .disposed(by: disposeBag) updateNowButton.rx.tap .subscribe(onNext: { viewModel.updateNow() }) .disposed(by: disposeBag) } ``` ### 結構 - Apple 的 class 在做 Extension 時盡量集中放在同一個 file 中 `UIView_Extension.swift`, `Color_Extionsion.swift` - 自定義 class 避免使用 `MyViewController_ext.swift` 的型式。該重構就重構,該拉出來就拉出來。 ### Delegates 自定義 delegate 內 function 時,命名皆以 delegate 名稱作為開頭,且傳入呼叫者主體 (UIKit 內有許多範例可作參考) ```swift protocol namePickerViewDelegate { // Preferred: func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String) func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool // 或者 func namePickerView(_ namePickerView: NamePickerView, shouldReload: Void) -> Bool // Not Preferred: func didSelectName(namePicker: NamePickerViewController, name: String) func namePickerShouldReload() -> Bool } ``` ### import 先 import iOS SDK 再 import 第三方函式庫 ### 順序 (非硬性) > 常數, enum, struct, typealias > IBOutlet > *公開的* 變數 (public, internal) > *私有的* 變數 (private) > init > override > *公開的* 函式 (public, internal) > IBAction > > << 以下使用 extension 繼續 >> > > *私有的* 函式(private) > @obj private func > 實作各種 *protocol* 註: *公開的* 以 internal 優先,真的有需要再使用 public ```swift class A: UIViewController { static let STATIC_CONST = 1 @IBOutlet var nextButton: UIButton! public let var1 = true var var2 = 123 private var var3 = "var3" init() {} deinit() {} override viewDidLoad() {} func publicFunc() {} @IBAction func nextButtonPressed(_ sender: Any) {} } ``` 私有函式請使用 *extention* 分組 ```swift extension A { private func privateFunc() {} @objc private func objcFunc() {} } ``` 實作 protocol 在可行的狀況下請用 *extention* ```swift extension A: UICollectionViewDelegate { // implementation } extension A: MyClassProtocol { // implementation } ``` > 更多規則請參考:<https://github.com/raywenderlich/swift-style-guide>