17條 Swift 最佳實(shí)踐規(guī)范

這篇文章講述的范圍主要針對 Swift 語言以及 Swift 標(biāo)準(zhǔn)庫。即便如此,如果能提出一個獨(dú)特的 Swift 的視角和見解,我們依然會提供諸如 Swift 在 Mac OS、iOS、 WatchOS 以及 TV OS 上使用的特別建議。而如何在 Xcode 和 LLDB 上有效地使用 Swift,這樣的建議也會以 Hints & tips 的風(fēng)格提供。
這個過程需要付出很多的努力,非常感謝為本文做出貢獻(xiàn)的那些人。

1.命名

正如 Swift Programming Language 中的類型名稱都是以大駝峰命名法命名的(例如:VehicleController)。
變量和常量則以小駝峰命名法命名(例如:vehicleName)。
你應(yīng)該使用 Swift 模板去命名你的代碼而不是使用 Objective-C 類前綴的風(fēng)格(除非和 Objective-C 接連)。
不要使用任何匈牙利標(biāo)識法( Hungarian notation )命名(例如:k為常量,m為方法),應(yīng)使用簡短的命名并且使用 Xcode 的類型 Quick Help (01.png+ click) 去查明變量的類型。同樣地,不要使用小寫字母+下劃線( SNAKE_CASE )的命名方式。
唯一比較特別的是 enum 值的命名,這里需要使用大駝峰命名法(這個也是遵循 Apple 的 Swift Programming Language 風(fēng)格):

enum Planet {
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}

在所有可能的情況里,名稱的不必要減少和縮寫都應(yīng)該避免,將來你應(yīng)該能在沒有任何損害和依賴 Xcode 的自動補(bǔ)全功能的情況下,確切地指出類型特征" ViewController "。非常普遍的縮寫如 URL 是允許的??s寫應(yīng)該用所有字母大寫( URL )或者所有字母小寫( url )表示。對類型和變量使用相同的規(guī)則。如果 url 是個類型,則應(yīng)該為大寫,如果是個變量,則應(yīng)該為小寫。

2.注釋

注釋不應(yīng)該用來使代碼無效,注釋代碼會使代碼無效且影響代碼的整潔。如果你想要移除代碼,但是仍想保留以防代碼在以后會用到,你應(yīng)該依賴 git 或者 bug tracker 。

3.類型推斷

在可能的地方,使用Swift的類型推斷以減少多余的類型信息。例如,正確的寫法:

var currentLocation = Location()

而不是:

var currentLocation: Location = Location()

4.Self 推斷

讓編譯器在所有允許的地方推斷 self 。在 init 中設(shè)置參數(shù)以及 non-escaping closures 中應(yīng)該顯性地使用 self 。例如:

struct Example {
    let name: String
     
    init(name: String) {
        self.name = name
    }
}

5.參數(shù)列表類型推斷

在一個閉包表達(dá)式( closure expression )中指定參數(shù)類型可能導(dǎo)致代碼更加冗長。只有當(dāng)需要指定類型時。

let people = [
    ("Mary", 42),
    ("Susan", 27),
    ("Charlie", 18),
]
 
let strings = people.map() {
    (name: String, age: Int) -> String in
    return "\(name) is \(age) years old"
}

如果編譯器能夠推斷類型,則應(yīng)該去掉類型定義。

let strings = people.map() {
    (name, age) in
    return "\(name) is \(age) years old"
}

使用排序好的參數(shù)編號命名("$0","$1","$2")能更好地減少冗余,這經(jīng)常能夠完整匹配參數(shù)列表。只有當(dāng)closure的參數(shù)名稱中沒有過多的信息時,使用編號命名。(例如特別簡單的 maps 和 filters )。
Apple 能夠并將會改變由 Objective-C frameworks 轉(zhuǎn)換過來的 Swift 的參數(shù)類型。例如,選項(xiàng)被移除或者變?yōu)樽詣诱归_等。我們應(yīng)有意地指定你的選項(xiàng)并依賴 Swift 去推斷類型,減少在這種情況下程序中斷的風(fēng)險(xiǎn)。
你總是應(yīng)該有節(jié)制地指定返回類型。例如,這個參數(shù)列表明顯過分冗余:

dispatch_async(queue) {
    () -> Void in
    print("Fired.")
}

6.常量

在類型定義的時候,常量應(yīng)該在類型里聲明為 static 。例如:

struct PhysicsModel {
    static var speedOfLightInAVacuum = 299_792_458
}
 
class Spaceship {
    static let topSpeed = PhysicsModel.speedOfLightInAVacuum
    var speed: Double
     
    func fullSpeedAhead() {
        speed = Spaceship.topSpeed
    }
}

使用 static 修飾常量可以允許他們在被引用的時候不需要實(shí)例化類型。
除了單例以外,應(yīng)盡量避免生成全局常量。

7.計(jì)算型類型屬性(Computed Properties)

當(dāng)你只需要繼承 getter 方法時,返回簡單的 Computed 屬性即可。例如,應(yīng)該這樣做:

class Example {
    var age: UInt32 {
        return arc4random()
    }
}

而不是:

class Example {
    var age: UInt32 {
        get {
            return arc4random()
        }
    }
}

如果你在屬性中添加了 set 或者 didSet ,那么你應(yīng)該顯示地提供 get 方法。

class Person {
    var age: Int {
        get {
            return Int(arc4random())
        }
        set {
            print("That's not your age.")
        }
    }
}

8.實(shí)例轉(zhuǎn)換(Converting Instances)

當(dāng)創(chuàng)建代碼去從一個類型轉(zhuǎn)換到另外的 init() 方法:

extension NSColor {

    convenience init(_ mood: Mood) {

        super.init(color: NSColor.blueColor)

    }

}

在 Swift 標(biāo)準(zhǔn)庫中,對于把一個類型的實(shí)例轉(zhuǎn)換為另外一種,現(xiàn)在看來 init 方法是比較喜歡用的一種方式。

"to" 方法是另外一種比較合理的技術(shù)(盡管你應(yīng)該遵循 Apple 的引導(dǎo)去使用 init 方法):

struct Mood {
    func toColor() -> NSColor {
        return NSColor.blueColor()
  }
}

而你可能試圖去使用一個getter,例如:

struct Mood {
    var color: NSColor {
        return NSColor.blueColor()
    }
}

getters 通常由于應(yīng)該返回可接受類型的組件而受到限制。例如,返回了 Circle 的實(shí)例是非常適合使用 getter 的,但是轉(zhuǎn)換一個 Circle 為 CGPath 最好在 CGPath 上使用"to"函數(shù)或者 init() 擴(kuò)展。

9.單例(Singletons)

在Swift中單例是很簡單的:

class ControversyManager {
    static let sharedInstance = ControversyManager()
}

Swift 的 runtime 會保證單例的創(chuàng)建并且采用線程安全的方式訪問。
單例通常只需要訪問"sharedInstance"的靜態(tài)屬性,除非你有不得已的原因去重命名它。注意,不要用靜態(tài)函數(shù)或者全局函數(shù)去訪問你的單例。
(因?yàn)樵?Swift 中單例太簡單了,并且持續(xù)的命名已經(jīng)耗費(fèi)了你太多的時間,你應(yīng)該有更多的時間去抱怨為什么單例是一個反模式的設(shè)計(jì),但是避免花費(fèi)太多時間,你的同伴會感謝你的。)

10.使用擴(kuò)展來組織代碼

擴(kuò)展應(yīng)該被用于組織代碼。
一個實(shí)例的次要方法和屬性應(yīng)該移動到擴(kuò)展中。注意,現(xiàn)在并不是所有的屬性類型都支持移動到擴(kuò)展中,為了做到最好,你應(yīng)該在這個限制中使用擴(kuò)展。
你應(yīng)該使用擴(kuò)展去幫助組織你的實(shí)例定義。一個比較好的例子是,一個 view controller 繼承了 table view data source 和 delegate protocols 。為了使table view中的代碼最小化,把 data source 和 delegate 方法整合到擴(kuò)展中以適應(yīng)相應(yīng)的 protocol 。
在一個單一的源文件中,在你覺得能夠最好地組織代碼的時候,把一些定義加入到擴(kuò)展中。不要擔(dān)心把 main class 的方法或者 struct 中指向方法和屬性定義的方法加入擴(kuò)展。只要所有文件都包涵在一個 Swift 文件中,那就是沒問題的。
反之,main 的實(shí)例定義不應(yīng)該指向定義在超出 main Swift 文件范圍的擴(kuò)展的元素。

11.鏈?zhǔn)?Setters

對于簡單的 setters 屬性,不要使用鏈?zhǔn)?setters 方法當(dāng)做便利的替代方法。
正確的做法:

instance.foo = 42
instance.bar = "xyzzy"

錯誤的做法:

instance.setFoo(42).setBar("xyzzy")

相較于鏈?zhǔn)絪etters,傳統(tǒng)的setters更為簡單和不需要過多的公式化。

12.錯誤處理

Swift 2.0 的 do/try/catch 機(jī)制非常棒。

13.避免使用try!

一般來說,使用如下寫法:

do {
    try somethingThatMightThrow()
}
catch {
    fatalError("Something bad happened.")
}

而不是:

try! somethingThatMightThrow()

即使這種形式特別冗長,但是它提供了context讓其他開發(fā)者可以檢查這個代碼。
在更詳盡的錯誤處理策略出來之前,如果把 try! 當(dāng)做一個臨時的錯誤處理是沒問題的。但是建議你最好周期性地檢查你代碼,找出其中任何有可能逃出你代碼檢查的非法try!。

14.避免使用try?

try?是用來“壓制”錯誤,而且只有當(dāng)你確信對錯誤的生成不關(guān)心時,try?才是有用的。一般來說,你應(yīng)該捕獲錯誤并至少打印出錯誤。

15.過早返回&Guards

可能的話,使用guard聲明去處理過早的返回或者其他退出的情況(例如,fatal errors 或者 thorwn errors)。

正確的寫法:

guard let safeValue = criticalValue else {
    fatalError("criticalValue cannot be nil here")
}
someNecessaryOperation(safeValue)

錯誤的寫法:

if let safeValue = criticalValue {
    someNecessaryOperation(safeValue)
} else {
    fatalError("criticalValue cannot be nil here")
}

或者:

if criticalValue == nil {
    fatalError("criticalValue cannot be nil here")
}
someNecessaryOperation(criticalValue!)

這個flatten code以其他方式進(jìn)入一個if let 代碼塊,并且在靠近相關(guān)的環(huán)境中過早地退出了,而不是進(jìn)入else代碼塊。

甚至當(dāng)你沒有捕獲一個值(guard let),這個模式在編譯期間也會強(qiáng)制過早退出。在第二個if的例子里,盡管代碼flattend得像guard一樣,但是一個毀滅性的錯誤或者其他返回一些無法退出的進(jìn)程(或者基于確切實(shí)例的非法態(tài))將會導(dǎo)致crash。一個過早的退出發(fā)生時,guard聲明將會及時發(fā)現(xiàn)錯誤,并將其從else block中移除。

16."Early"訪問控制

即使你的代碼沒有分離成獨(dú)立的模塊,你也應(yīng)該經(jīng)??紤]訪問控制。把一個定義標(biāo)記為 private 或者 internal 對于代碼來說相當(dāng)于一個輕量級的文檔。每一個閱讀代碼的人都會知道這個元素是不能“觸碰”的。反之,把一個定義為 public 就相當(dāng)于邀請其他代碼去訪問這個元素。我們最好顯示地指明而不是依賴 Swift 的默認(rèn)訪問控制等級。( internal )
如果你的代碼庫在將來不斷擴(kuò)張,它可能會被分解成子模塊.這樣做,會使一個已經(jīng)裝飾著訪問控制信息的代碼庫更加方便、快捷。

17.限制性的訪問控制

一般來來說,當(dāng)添加訪問控制到你的代碼時,最好有詳盡的限制。這里,使用 private 比 internal 更有意義,而使用 internal 顯然比 public 更好。(注意: internal 是默認(rèn)的)。
如有需要,把代碼的訪問控制變得更加開放是非常容易的(沿著這樣的途徑: "private" to "internal" to "public") 。過于開放的訪問控制代碼被其他代碼使用可能不是很合適。有足夠限制的代碼能夠發(fā)現(xiàn)不合適和錯誤的使用,并且能提供更好的接口。一個例子就是一個類型公開地暴露了一個internal cache。
而且,代碼的限制訪問限制了“暴露的表面積”,并且允許代碼在更小影響其他代碼的情況下重構(gòu)。其他的技術(shù)如:Protocol Driven Development 也能起到同樣的作用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容