# 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>