最詳盡的 Swift 程式碼規範指南

2016-07-22    分類:iOS開發、推薦閱讀、程式設計開發、首頁精華0人評論發表於2016-07-22

本文由碼農網 – 豆照建原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

1. 程式碼格式

  • 1.1 使用四個空格進行縮排。
  • 1.2 每行最多160個字元,這樣可以避免一行過長。 (Xcode->Preferences->Text Editing->Page guide at column: 設定成160即可)
  • 1.3 確保每個檔案結尾都有空白行。
  • 1.4 確保每行都不以空白字元作為結尾 (Xcode->Preferences->Text Editing->Automatically trim trailing whitespace + Including whitespace-only lines).
  • 1.5 左大括號不用另起一行。
class SomeClass {
    func someMethod() {
        if x == y {
            /* ... */
        } else if x == z {
            /* ... */
        } else {
            /* ... */
        }
    }

    /* ... */
}
  • 1.6 當在寫一個變數型別,一個字典裡的主鍵,一個函式的引數,遵從一個協議,或一個父類,不用在分號前新增空格。
// 指定型別
let pirateViewController: PirateViewController

// 字典語法(注意這裡是向左對齊而不是分號對齊)
let ninjaDictionary: [String: AnyObject] = [
    "fightLikeDairyFarmer": false,
    "disgusting": true
]

// 宣告函式
func myFunction<T, U: SomeProtocol where T.RelatedType == U>(firstArgument: U, secondArgument: T) {
    /* ... */
}

// 呼叫函式
someFunction(someArgument: "Kitten")

// 父類
class PirateViewController: UIViewController {
    /* ... */
}

// 協議
extension PirateViewController: UITableViewDataSource {
    /* ... */
}
  • 1.7 基本來說,要在逗號後面加空格。
let myArray = [1, 2, 3, 4, 5]
  • 1.8 二元運算子(+, ==, 或->)的前後都需要新增空格,左小括號後面和右小括號前面不需要空格。
let myValue = 20 + (30 / 2) * 3
if 1 + 1 == 3 {
    fatalError("The universe is broken.")
}
func pancake() -> Pancake {
    /* ... */
}
  • 1.9  遵守Xcode內建的縮排格式( 如果已經遵守,按下CTRL-i 組合鍵檔案格式沒有變化)。當宣告的一個函式需要跨多行時,推薦使用Xcode預設的格式,目前Xcode 版本是 7.3。
// Xcode針對跨多行函式宣告縮排
func myFunctionWithManyParameters(parameterOne: String,
                                  parameterTwo: String,
                                  parameterThree: String) {
    // Xcode會自動縮排
    print("\(parameterOne) \(parameterTwo) \(parameterThree)")
}

// Xcode針對多行 if 語句的縮排
if myFirstVariable > (mySecondVariable + myThirdVariable)
    && myFourthVariable == .SomeEnumValue {

    // Xcode會自動縮排
    print("Hello, World!")
}
  • 1.10 當呼叫的函式有多個引數時,每一個引數另起一行,並比函式名多一個縮排。
someFunctionWithManyArguments(
    firstArgument: "Hello, I am a string",
    secondArgument: resultFromSomeFunction()
    thirdArgument: someOtherLocalVariable)
  • 1.11 當遇到需要處理的陣列或字典內容較多需要多行顯示時,需把 [ 和 ] 類似於方法體裡的括號, 方法體裡的閉包也要做類似處理。
someFunctionWithABunchOfArguments(
    someStringArgument: "hello I am a string",
    someArrayArgument: [
        "dadada daaaa daaaa dadada daaaa daaaa dadada daaaa daaaa",
        "string one is crazy - what is it thinking?"
    ],
    someDictionaryArgument: [
        "dictionary key 1": "some value 1, but also some more text here",
        "dictionary key 2": "some value 2"
    ],
    someClosure: { parameter1 in
        print(parameter1)
    })
  • 1.12 應儘量避免出現多行斷言,可使用本地變數或其他策略。
// 推薦
let firstCondition = x == firstReallyReallyLongPredicateFunction()
let secondCondition = y == secondReallyReallyLongPredicateFunction()
let thirdCondition = z == thirdReallyReallyLongPredicateFunction()
if firstCondition && secondCondition && thirdCondition {
    // 你要幹什麼
}

// 不推薦
if x == firstReallyReallyLongPredicateFunction()
    && y == secondReallyReallyLongPredicateFunction()
    && z == thirdReallyReallyLongPredicateFunction() {
    // 你要幹什麼
}

2. 命名

  • 2.1 在Swift中不用如Objective-C式 一樣新增字首 (如使用 GuybrushThreepwoode 而不是 LIGuybrushThreepwood)。
  • 2.2 使用帕斯卡拼寫法(又名大駱駝拼寫法,首字母大寫)為型別命名 (如 structenumclasstypedefassociatedtype 等)。
  • 2.3 使用小駱駝拼寫法 (首字母小寫) 為函式,方法,變數,常量,引數等命名。
  • 2.4 首字母縮略詞在命名中一般來說都是全部大寫,例外的情形是如果首字母縮略詞是一個命名的開始部分,而這個命名需要小寫字母作為開頭,這種情形下首字母縮略詞全部小寫。
// "HTML" 是變數名的開頭, 需要全部小寫 "html"
let htmlBodyContent: String = "<p>Hello, World!</p>"
// 推薦使用 ID 而不是 Id
let profileID: Int = 1
// 推薦使用 URLFinder 而不是 UrlFinder
class URLFinder {
    /* ... */
}
  • 2.5 使用字首 k + 大駱駝命名法 為所有非單例的靜態常量命名。
class MyClassName {
    // 基元常量使用 k 作為字首
    static let kSomeConstantHeight: CGFloat = 80.0

    // 非基元常量也是用 k 作為字首
    static let kDeleteButtonColor = UIColor.redColor()

    // 對於單例不要使用k作為字首
    static let sharedInstance = MyClassName()

    /* ... */
}
  • 2.6 對於泛型和關聯型別,可以使用單個大寫字母,也可是遵從大駱駝命名方式並能描述泛型的單詞。如果這個單詞和要實現的協議或繼承的父類有衝突,可以為相關型別或泛型名字新增 Type 作為字尾。
class SomeClass<T> { /* ... */ }
class SomeClass<Model> { /* ... */ }
protocol Modelable {
    associatedtype Model
}
protocol Sequence {
    associatedtype IteratorType: Iterator
}
  • 2.7 命名應該具有描述性 和 清晰的。
// 推薦
class RoundAnimatingButton: UIButton { /* ... */ }

// 不推薦
class CustomButton: UIButton { /* ... */ }
  • 2.8 不要縮寫,簡寫命名,或用單個字母命名。
// 推薦
class RoundAnimatingButton: UIButton {
    let animationDuration: NSTimeInterval

    func startAnimating() {
        let firstSubview = subviews.first
    }

}

// 不推薦
class RoundAnimating: UIButton {
    let aniDur: NSTimeInterval

    func srtAnmating() {
        let v = subviews.first
    }
}
  • 2.9 如果原有命名不能明顯表明型別,則屬性命名內要包括型別資訊。
// 推薦
class ConnectionTableViewCell: UITableViewCell {
    let personImageView: UIImageView

    let animationDuration: NSTimeInterval

    // 作為屬性名的firstName,很明顯是字串型別,所以不用在命名裡不用包含String
    let firstName: String

    // 雖然不推薦, 這裡用 Controller 代替 ViewController 也可以。
    let popupController: UIViewController
    let popupViewController: UIViewController

    // 如果需要使用UIViewController的子類,如TableViewController, CollectionViewController, SplitViewController, 等,需要在命名裡標名型別。
    let popupTableViewController: UITableViewController

    // 當使用outlets時, 確保命名中標註型別。
    @IBOutlet weak var submitButton: UIButton!
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var nameLabel: UILabel!

}

// 不推薦
class ConnectionTableViewCell: UITableViewCell {
    // 這個不是 UIImage, 不應該以Image 為結尾命名。
    // 建議使用 personImageView
    let personImage: UIImageView

    // 這個不是String,應該命名為 textLabel
    let text: UILabel

    // animation 不能清晰表達出時間間隔
    // 建議使用 animationDuration 或 animationTimeInterval
    let animation: NSTimeInterval

    // transition 不能清晰表達出是String
    // 建議使用 transitionText 或 transitionString
    let transition: String

    // 這個是ViewController,不是View
    let popupView: UIViewController

    // 由於不建議使用縮寫,這裡建議使用 ViewController替換 VC
    let popupVC: UIViewController

    // 技術上講這個變數是 UIViewController, 但應該表達出這個變數是TableViewController
    let popupViewController: UITableViewController

    // 為了保持一致性,建議把型別放到變數的結尾,而不是開始,如submitButton
    @IBOutlet weak var btnSubmit: UIButton!
    @IBOutlet weak var buttonSubmit: UIButton!

    // 在使用outlets 時,變數名內應包含型別名。
    // 這裡建議使用 firstNameLabel
    @IBOutlet weak var firstName: UILabel!
}
  • 2.10 當給函式引數命名時,要確保函式能理解每個引數的目的。
  • 2.11 根據蘋果介面設計指導文件, 如果協議描述的是協議做的事應該命名為名詞(如Collection) ,如果描述的是行為,需新增字尾 able 或 ing (如Equatable 和 ProgressReporting)。 如果上述兩者都不能滿足需求,可以新增Protocol作為字尾,例子見下面。
// 這個協議描述的是協議能做的事,應該命名為名詞。
protocol TableViewSectionProvider {
    func rowHeight(atRow row: Int) -> CGFloat
    var numberOfRows: Int { get }
    /* ... */
}

// 這個協議表達的是行為, 以able最為字尾
protocol Loggable {
    func logCurrentState()
    /* ... */
}

// 因為已經定義類InputTextView,如果依然需要定義相關協議,可以新增Protocol作為字尾。
protocol InputTextViewProtocol {
    func sendTrackingEvent()
    func inputText() -> String
    /* ... */
}

3. 程式碼風格

3.1 綜合

  • 3.1.1 儘可能的多使用let,少使用var。
  • 3.1.2 當需要遍歷一個集合並變形成另一個集合時,推薦使用函式 mapfilter 和 reduce。
// 推薦
let stringOfInts = [1, 2, 3].flatMap { String($0) }
// ["1", "2", "3"]

// 不推薦
var stringOfInts: [String] = []
for integer in [1, 2, 3] {
    stringOfInts.append(String(integer))
}

// 推薦
let evenNumbers = [4, 8, 15, 16, 23, 42].filter { $0 % 2 == 0 }
// [4, 8, 16, 42]

// 不推薦
var evenNumbers: [Int] = []
for integer in [4, 8, 15, 16, 23, 42] {
    if integer % 2 == 0 {
        evenNumbers(integer)
    }
}
  • 3.1.3 如果變數型別可以依靠推斷得出,不建議宣告變數時指明型別。
  • 3.1.4 如果一個函式有多個返回值,推薦使用 元組 而不是 inout 引數, 如果你見到一個元組多次,建議使用typealias ,而如果返回的元組有三個或多於三個以上的元素,建議使用結構體或類。
func pirateName() -> (firstName: String, lastName: String) {
    return ("Guybrush", "Threepwood")
}

let name = pirateName()
let firstName = name.firstName
let lastName = name.lastName
  • 3.1.5 當使用委託和協議時,請注意避免出現迴圈引用,基本上是在定義屬性的時候使用 weak 修飾。
  • 3.1.6 在閉包裡使用 self 的時候要注意出現迴圈引用,使用捕獲列表可以避免這一點。
myFunctionWithClosure() { [weak self] (error) -> Void in
    // 方案 1

    self?.doSomething()

    // 或方案 2

    guard let strongSelf = self else {
        return
    }

    strongSelf.doSomething()
}
  • 3.1.7 Switch 模組中不用顯式使用break。
  • 3.1.8 斷言流程控制的時候不要使用小括號。
// 推薦
if x == y {
    /* ... */
}

// 不推薦
if (x == y) {
    /* ... */
}
  • 3.1.9 在寫列舉型別的時候,儘量簡寫。
// 推薦
imageView.setImageWithURL(url, type: .person)

// 不推薦
imageView.setImageWithURL(url, type: AsyncImageView.Type.person)
3.1.10 在使用類方法的時候不用簡寫,因為類方法不如 列舉 型別一樣,可以根據輕易地推匯出上下文。
// 推薦
imageView.backgroundColor = UIColor.whiteColor()

// 不推薦
imageView.backgroundColor = .whiteColor()
  • 3.1.11 不建議使用用self.修飾除非需要。
  • 3.1.12 在新寫一個方法的時候,需要衡量這個方法是否將來會被重寫,如果不是,請用 final 關鍵詞修飾,這樣阻止方法被重寫。一般來說,final 方法可以優化編譯速度,在合適的時候可以大膽使用它。但需要注意的是,在一個公開發布的程式碼庫中使用 final 和本地專案中使用 final 的影響差別很大的。
  • 3.1.13 在使用一些語句如 else,catch等緊隨程式碼塊的關鍵詞的時候,確保程式碼塊和關鍵詞在同一行。下面 if/else 和 do/catch 的例子.
if someBoolean {
    // 你想要什麼
} else {
    // 你不想做什麼
}

do {
    let fileContents = try readFile("filename.txt")
} catch {
    print(error)
}

3.2 訪問控制修飾符

  • 3.2.1 如果需要把訪問修飾符放到第一個位置。
// 推薦
private static let kMyPrivateNumber: Int

// 不推薦
static private let kMyPrivateNumber: Int
  • 3.2.2 訪問修飾符不應單獨另起一行,應和訪問修飾符描述的物件保持在同一行。
// 推薦
public class Pirate {
    /* ... */
}

// 不推薦
public
class Pirate {
    /* ... */
}
  • 3.2.3  預設的訪問控制修飾符是 internal, 如果需要使用internal 可以省略不寫。
  • 3.2.4 當一個變數需要被單元測試 訪問時,需要宣告為 internal 型別來使用@testable import {ModuleName}。 如果一個變數實際上是private 型別,而因為單元測試需要被宣告為 internal 型別,確定新增合適的註釋文件來解釋為什麼這麼做。這裡新增註釋推薦使用 - warning: 標記語法。
/**
 這個變數是private 名字
 - warning: 定義為 internal 而不是 private 為了 `@testable`.
 */
let pirateName = "LeChuck"

3.3 自定義操作符

不推薦使用自定義操作符,如果需要建立函式來替代。

在重寫操作符之前,請慎重考慮是否有充分的理由一定要在全域性範圍內建立新的操作符,而不是使用其他策略。

你可以過載現有的操作符來支援新的型別(特別是 ==),但是新定義的必須保留操作符的原來含義,比如 == 必須用來測試是否相等並返回布林值。

3.4 Switch 語句 和 列舉

  • 3.4.1 在使用 Switch 語句時,如果選項是有限集合時,不要使用default,相反地,把一些不用的選項放到底部,並用 break 關鍵詞 阻止其執行。
  • 3.4.2 因為Swift 中的 switch 選項預設是包含break的, 如果不需要不用使用 break 關鍵詞。
  • 3.4.3 case 語句 應和 switch 語句左對齊,並在 標準的 default 上面。
  • 3.4.4 當定義的選項有關聯值時,確保關聯值有恰當的名稱,而不只是型別。(如. 使用 case Hunger(hungerLevel: Int) 而不是 case Hunger(Int)).
enum Problem {
    case attitude
    case hair
    case hunger(hungerLevel: Int)
}

func handleProblem(problem: Problem) {
    switch problem {
    case .attitude:
        print("At least I don't have a hair problem.")
    case .hair:
        print("Your barber didn't know when to stop.")
    case .hunger(let hungerLevel):
        print("The hunger level is \(hungerLevel).")
    }
}
  • 3.4.5 推薦儘可能使用fall through。
  • 3.4.6 如果default 的選項不應該觸發,可以丟擲錯誤 或 斷言類似的做法。
func handleDigit(digit: Int) throws {
    case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
        print("Yes, \(digit) is a digit!")
    default:
        throw Error(message: "The given number was not a digit.")
}

3.5 可選型別

  • 3.5.1 唯一使用隱式拆包可選型(implicitly unwrapped optionals)的場景是結合@IBOutlets,在其他場景使用 非可選型別 和 常規可選型別,即使有的場景你確定有的變數使用的時候永遠不會為 nil, 但這樣做可以保持一致性和程式更加健壯。
  • 3.5.2 不要使用 as! 或 try!。
  • 3.5.3 如果對於一個變數你不打算宣告為可選型別,但當需要檢查變數值是否為 nil,推薦用當前值和 nil 直接比較,而不推薦使用 if let 語法。
// 推薦
if someOptional != nil {
    // 你要做什麼
}

// 不推薦
if let _ = someOptional {
    // 你要做什麼
}
  • 3.5.4 不要使用 unowned,unowned 和 weak 變數基本上等價,並且都是隱式拆包( unowned 在引用計數上有少許效能優化),由於不推薦使用隱式拆包,也不推薦使用unowned 變數。
// 推薦
weak var parentViewController: UIViewController?

// 不推薦
weak var parentViewController: UIViewController!
unowned var parentViewController: UIViewController
  • 3.5.5 當拆包取值時,使用和被拆包取值變數相同的名稱。
guard let myVariable = myVariable else {
    return
}

3.6 協議

在實現協議的時候,有兩種方式來組織你的程式碼:

  1. 使用 // MARK: 註釋來分割協議實現和其他程式碼。
  2. 使用 extension 在 類/結構體已有程式碼外,但在同一個檔案內。

請注意 extension 內的程式碼不能被子類重寫,這也意味著測試很難進行。 如果這是經常發生的情況,為了程式碼一致性最好統一使用第一種辦法。否則使用第二種辦法,其可以程式碼分割更清晰。

使用而第二種方法的時候,使用  // MARK:  依然可以讓程式碼在 Xcode 可讀性更強。

3.7 屬性

  • 3.7.1 對於只讀屬性,計算後(Computed)屬性, 提供 getter 而不是 get {}。
var computedProperty: String {
    if someBool {
        return "I'm a mighty pirate!"
    }
    return "I'm selling these fine leather jackets."
}
  • 3.7.2 對於屬性相關方法 get {}set {}willSet, 和 didSet, 確保縮排相關程式碼塊。
  • 3.7.3 對於willSet/didSet 和 set 中的舊值和新值雖然可以自定義名稱,但推薦使用預設標準名稱 newValue/oldValue。
var computedProperty: String {
    get {
        if someBool {
            return "I'm a mighty pirate!"
        }
        return "I'm selling these fine leather jackets."
    }
    set {
        computedProperty = newValue
    }
    willSet {
        print("will set to \(newValue)")
    }
    didSet {
        print("did set from \(oldValue) to \(newValue)")
    }
}
  • 3.7.4 在建立類常量的時候,使用 static 關鍵詞修飾。
class MyTableViewCell: UITableViewCell {
    static let kReuseIdentifier = String(MyTableViewCell)
    static let kCellHeight: CGFloat = 80.0
}
  • 3.7.5 宣告單例屬性可以通過下面方式進行:
class PirateManager {
    static let sharedInstance = PirateManager()

    /* ... */
}

3.8 閉包

  • 3.8.1 如果引數的型別很明顯,可以在函式名裡可以省略引數型別, 但明確宣告型別也是允許的。 程式碼的可讀性有時候是新增詳細的資訊,而有時候部分重複,根據你的判斷力做出選擇吧,但前後要保持一致性。
// 省略型別
doSomethingWithClosure() { response in
    print(response)
}

// 明確指出型別
doSomethingWithClosure() { response: NSURLResponse in
    print(response)
}

// map 語句使用簡寫
[1, 2, 3].flatMap { String($0) }
  • 3.8.2 如果使用捕捉列表 或 有具體的非 Void返回型別,引數列表應該在小括號內, 否則小括號可以省略。
// 因為使用捕捉列表,小括號不能省略。
doSomethingWithClosure() { [weak self] (response: NSURLResponse) in
    self?.handleResponse(response)
}

// 因為返回型別,小括號不能省略。
doSomethingWithClosure() { (response: NSURLResponse) -> String in
    return String(response)
}
  • 3.8.3 如果閉包是變數型別,不需把變數值放在括號中,除非需要,如變數型別是可選型別(Optional?), 或當前閉包在另一個閉包內。確保閉包裡的所以引數放在小括號中,這樣()表示沒有引數,Void 表示不需要返回值。
let completionBlock: (success: Bool) -> Void = {
    print("Success? \(success)")
}

let completionBlock: () -> Void = {
    print("Completed!")
}

let completionBlock: (() -> Void)? = nil

3.9 陣列

  • 3.9.1 基本上不要通過下標直接訪問陣列內容,如果可能使用如 .first 或 .last, 因為這些方法是非強制型別並不會崩潰。 推薦儘可能使用 for item in items 而不是 for i in 0..<items.count 遍歷陣列。 如果需要通過下標訪問陣列內容,在使用前要做邊界檢查。
  • 3.9.2 不要使用 += 或 + 操作符給陣列新增新元素,使用效能較好的.append() 或.appendContentsOf()  ,如果需要宣告陣列基於其他的陣列並保持不可變型別, 使用 let myNewArray = [arr1, arr2].flatten(),而不是let myNewArray = arr1 + arr2 。

3.10 錯誤處理

假設一個函式 myFunction 返回型別宣告為 String,但是總有可能函式會遇到error,有一種解決方案是返回型別宣告為 String?, 當遇到錯誤的時候返回 nil。

例子:

func readFile(withFilename filename: String) -> String? {
    guard let file = openFile(filename) else {
        return nil
    }

    let fileContents = file.read()
    file.close()
    return fileContents
}

func printSomeFile() {
    let filename = "somefile.txt"
    guard let fileContents = readFile(filename) else {
        print("不能開啟 \(filename).")
        return
    }
    print(fileContents)
}

實際上如果預知失敗的原因,我們應該使用Swift 中的 try/catch 。

定義 錯誤物件 結構體如下:

struct Error: ErrorType {
    public let file: StaticString
    public let function: StaticString
    public let line: UInt
    public let message: String

    public init(message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
        self.file = file
        self.function = function
        self.line = line
        self.message = message
    }
}

使用案例:

func readFile(withFilename filename: String) throws -> String {
    guard let file = openFile(filename) else {
        throw Error(message: “打不開的檔名稱 \(filename).")
    }

    let fileContents = file.read()
    file.close()
    return fileContents
}

func printSomeFile() {
    do {
        let fileContents = try readFile(filename)
        print(fileContents)
    } catch {
        print(error)
    }
}

其實專案中還是有一些場景更適合宣告為可選型別,而不是錯誤捕捉和處理,比如在獲取遠端資料過程中遇到錯誤,nil作為返回結果是合理的,也就是宣告返回可選型別比錯誤處理更合理。

整體上說,如果一個方法有可能失敗,並且使用可選型別作為返回型別會導致錯誤原因湮沒,不妨考慮丟擲錯誤而不是吃掉它。

3.11 使用 guard 語句

  • 3.11.1 總體上,我們推薦使用提前返回的策略,而不是 if 語句的巢狀。使用 guard 語句可以改善程式碼的可讀性。
// 推薦
func eatDoughnut(atIndex index: Int) {
    guard index >= 0 && index < doughnuts else {
        // 如果 index 超出允許範圍,提前返回。
        return
    }

    let doughnut = doughnuts[index]
    eat(doughnut)
}

// 不推薦
func eatDoughnuts(atIndex index: Int) {
    if index >= 0 && index < donuts.count {
        let doughnut = doughnuts[index]
        eat(doughnut)
    }
}
  • 3.11.2 在解析可選型別時,推薦使用 guard 語句,而不是 if 語句,因為 guard 語句可以減少不必要的巢狀縮排。
// 推薦
guard let monkeyIsland = monkeyIsland else {
    return
}
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)

// 不推薦
if let monkeyIsland = monkeyIsland {
    bookVacation(onIsland: monkeyIsland)
    bragAboutVacation(onIsland: monkeyIsland)
}

// 禁止
if monkeyIsland == nil {
    return
}
bookVacation(onIsland: monkeyIsland!)
bragAboutVacation(onIsland: monkeyIsland!)
  • 3.11.3 當解析可選型別需要決定在 if 語句 和 guard 語句之間做選擇時,最重要的判斷標準是是否讓程式碼可讀性更強,實際專案中會面臨更多的情景,如依賴 2 個不同的布林值,複雜的邏輯語句會涉及多次比較等,大體上說,根據你的判斷力讓程式碼保持一致性和更強可讀性, 如果你不確定 if 語句 和 guard 語句哪一個可讀性更強,建議使用 guard 。
// if 語句更有可讀性
if operationFailed {
    return
}

// guard 語句這裡有更好的可讀性
guard isSuccessful else {
    return
}

// 雙重否定不易被理解 - 不要這麼做
guard !operationFailed else {
    return
}
  • 3.11.4  如果需要在2個狀態間做出選擇,建議使用if 語句,而不是使用 guard 語句。
// 推薦
if isFriendly {
    print("你好, 遠路來的朋友!")
} else {
    print(“窮小子,哪兒來的?")
}

// 不推薦
guard isFriendly else {
    print("窮小子,哪兒來的?")
    return
}

print("你好, 遠路來的朋友!")
  • 3.11.5  你只應該在在失敗情形下退出當前上下文的場景下使用 guard 語句,下面的例子可以解釋 if 語句有時候比 guard 語句更合適 – 我們有兩個不相關的條件,不應該相互阻塞。
if let monkeyIsland = monkeyIsland {
    bookVacation(onIsland: monkeyIsland)
}

if let woodchuck = woodchuck where canChuckWood(woodchuck) {
    woodchuck.chuckWood()
}
  • 3.11.6 我們會經常遇到使用 guard 語句拆包多個可選值,如果所有拆包失敗的錯誤處理都一致可以把拆包組合到一起 (如 returnbreakcontinue,throw 等).
// 組合在一起因為可能立即返回
guard let thingOne = thingOne,
    let thingTwo = thingTwo,
    let thingThree = thingThree else {
    return
}

// 使用獨立的語句 因為每個場景返回不同的錯誤
guard let thingOne = thingOne else {
    throw Error(message: "Unwrapping thingOne failed.")
}

guard let thingTwo = thingTwo else {
    throw Error(message: "Unwrapping thingTwo failed.")
}

guard let thingThree = thingThree else {
    throw Error(message: "Unwrapping thingThree failed.")
}

4. 文件/註釋

4.1 文件

如果一個函式比 O(1) 複雜度高,你需要考慮為函式新增註釋,因為函式簽名(方法名和引數列表) 並不是那麼的一目瞭然,這裡推薦比較流行的外掛 VVDocumenter. 不論出於何種原因,如果有任何奇淫巧計不易理解的程式碼,都需要新增註釋,對於複雜的 類/結構體/列舉/協議/屬性 都需要新增註釋。所有公開的 函式/類/變數/列舉/協議/屬性/常數 也都需要新增文件,特別是 函式宣告(包括名稱和引數列表) 不是那麼清晰的時候。

寫文件時,確保參照蘋果文件中提及的標記語法合集。

在註釋文件完成後,你應檢查格式是否正確。

規則:

  • 4.1.1 一行不要超過160個字元 (和程式碼長度限制雷同).
  • 4.1.2 即使文件註釋只有一行,也要使用模組化格式 (/** */).
  • 4.1.3 註釋模組中的空行不要使用 * 來佔位。
  • 4.1.4 確定使用新的 – parameter 格式,而不是就得 Use the new -:param: 格式,另外注意 parameter 是小寫的。
  • 4.1.5 如果需要給一個方法的 引數/返回值/丟擲異常 新增註釋,務必給所有的新增註釋,即使會看起來有部分重複,否則註釋會看起來不完整,有時候如果只有一個引數值得新增註釋,可以在方法註釋裡重點描述。
  • 4.1.6 對於負責的類,在描述類的使用方法時可以新增一些合適的例子,請注意Swift註釋是支援 MarkDown 語法的。
/**
 ## 功能列表

 這個類提供下一下很讚的功能,如下:

 - 功能 1
 - 功能 2
 - 功能 3

 ## 例子

 這是一個程式碼塊使用四個空格作為縮排的例子。

     let myAwesomeThing = MyAwesomeClass()
     myAwesomeThing.makeMoney()

 ## 警告

 使用的時候總注意以下幾點

 1. 第一點
 2. 第二點
 3. 第三點
 */
class MyAwesomeClass {
    /* ... */
}
  • 4.1.8 在寫文件註釋時,儘量保持簡潔。

4.2 其他註釋原則

  • 4.2.1  // 後面要保留空格。
  • 4.2.2 註釋必須要另起一行。
  • 4.2.3 使用註釋 // MARK: - xoxo 時, 下面一行保留為空行。
class Pirate {

    // MARK: - 例項屬性

    private let pirateName: String

    // MARK: - 初始化

    init() {
        /* ... */
    }

}

譯文連結:http://www.codeceo.com/article/swift-style-guide.html
英文原文:Swift Style Guide
翻譯作者:碼農網 – 豆照建
轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]

相關文章