使用 Swift 進行軟件開發的最佳實踐.html
本文檔的英文版在這裏,感謝Swift社區(頻道爲 #bestpractices )爲咱們提供如此優質的文檔。ios
這個文檔的產生得益於我在創做Swift Graphics時作的一系列的手記。本指南中的大部分建議也考量了是否能夠爲其它的觀點和論點。固然,感受其餘的方法必須存在時除外。git
這些最佳實踐沒有規定或推薦 Swift 是否應該在一個程序上以面向對象的或者函數式的方式來使用。github
本文檔更多的是關注 Swift 語言及其標準庫。也就是說,以一個純粹的 Swift 的角度提供可提供的關於在 Mac OS, iOS, WatchOS 和 TVOS 上如何使用 Swift 的具體建議。 同時也會提供一些如何在 Xcode 和 LLDB中有效利用 Swift 的提示和技巧。swift
這項工做正在進行中,很是歡迎你們經過 Pull Request 或 Issues 的方式來貢獻內容。api
你也能夠在 Swift-Lang slack(位於 #bestpractices 頻道) 上參與討論。緩存
請確保全部的例程是可運行的 (這可能不適用於現有的例程)。這個 markdown 文件會轉化成一個 Mac OS X 的 playground.安全
Apple 一般是對的。應緊隨蘋果所推薦的或他的 Demo 中所展現的方式。您應該儘量地遵照 Apple 在 The Swift Programming Language 一書中所定義的代碼風格。但咱們仍是能夠看到他們的示例代碼中有不符合這些規則的地方,畢竟 Apple 是一家大公司嘛。markdown
不要僅僅爲了減小字符的鍵入數量而使用模棱兩可的簡短命名,較長的命名均可以依賴自動完成、自我暗示、複製粘貼來減低鍵入的難度。命名的詳細程度每每對代碼維護者頗有幫助。但過於冗長的命名卻會繞過Swift的主要特性之一: 類型推導,因此命名的原則應該是簡潔明瞭。閉包
按照 The Swift Programming Language 所推薦的命名法則,類型名稱應該使用首字母大寫的駝峯命名法 (例如: "VehicleController")。
變量與常量應該使用首字母小寫的駝峯命名法(例如: " vehicleName " )。
推薦使用 Swift 模塊來定義代碼的命名空間,而非在 Swift 代碼上使用 Objective-C 樣式的類前綴(除非接口要與 Objective-C 交互)。
不推薦使用任何形式的匈牙利命名法(好比:k 表明常量,m 表明方法),取代代之咱們應該使用短而簡潔的名字並使用 Xcode 的類型快速幫助 (⌥ + 左擊)。一樣咱們也不要使用相似 SNAKE_CASE
這樣的名字。
這些法則之上,惟一例外的狀況就是枚舉值了,枚舉值在這裏應該首字母大寫(這是 Apple 的 The Swift Programming Language 中的規範):
enum Planet {case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Nepture }
有必要的話命名不要縮寫,實際上在 Xcode 的"文本自動補全"功能下你能夠垂手可得地鍵入 相似 ViewController
的長命名。極爲常見的縮寫,例如: URL, 是很好的。縮寫應該是所有大寫 ( "URL" )或者酌情所有小寫( "url" )。URL 的類型和變量命名推薦的規則: 若是 url 是一個類型,它應該被大寫,若是是一個變量,那麼應該小寫。
不該該使用註釋來禁用代碼。被註釋掉的代碼會污染你的源代碼。若是你當前想要刪除一段代碼,但未來又可能會用到,推薦你依賴 git 或你的 bug 追蹤系統來管理。
(TODO: 追加一個關於文檔註釋的小節,使用 nshipster 的連接)
若是可能的話,使用 Swift 的類型推導,以免冗餘的類型信息。例如:
推薦:
var currentLocation = Location()
而非:
var currentLocation: Location = Location()
讓編譯器自動推斷全部的狀況,這是能夠作到的。在一些領域 self 應該被顯式地使用,包括在 init 中設置參數,或者 non-escaping
閉包。例如:
struct Example {let name: Stringinit(name: String) {self.name = name } }
在一個捕獲列表( capture list )中指定參數類型會致使代碼冗餘。若是須要的話,僅指定類型便可。
let people = [ ("Mary", 42), ("Susan", 27), ("Charlie", 18), ]let strings = people.map() { (name: String, age: Int) -> String inreturn "\(name) is \(age) years old"}
若是編譯器能夠推導出來的話,徹底能夠把類型刪掉:
let strings = people.map() { (name, age) inreturn "\(name) is \(age) years old"}
使用編號的參數名 ("$0") 進一步下降冗長,每每能完全消除捕獲列表的代碼冗餘。在閉包中當參數名沒有附帶任何更多信息時僅使用編號形式便可( 如很是簡單的映射和過濾器 )。
Apple 可以而且將會改變閉包的參數類型,經過他們的 Objective-C 框架的 Swift 變種提供出來。例如,optionals
被刪除或更改成 auto-unwrapping
等。故意 under-specifying 可選並依賴 Swift 來推導類型,能夠減小在這些狀況下代碼被破譯的風險。
你應該避免指定返回類型,例如這個捕獲列表( capture list )就是徹底多餘的:
dispatch_async(queue) { ()->Void inprint("Fired.") }
(以上內容也能夠參考:這裏)
類型定義中使用的常量應當被申明成靜態類型。例如:
struct PhysicsModel {static var speedOfLightInAVacuum = 299_792_458}class Spaceship {static let topSpeed = PhysicsModel.speedOfLightInAVacuumvar speed: Doublefunc fullSpeedAhead() { speed = Spaceship.topSpeed } }
將常量標示爲 static ,容許它們能夠被無類型的實例引用。
通常應該避免生成全局範圍的常量,單例除外。
若是你只是爲了實現一個 getter,請使用短版 (short version) 的計算屬性。例如:
推薦這樣:
class Example {var age: UInt32 {return arc4random() } }
不要:
class Example {var age: UInt32 {get {return arc4random() } } }
若是你在屬性裏添加一個 set
或者 didSet
, 那麼你須要顯示提供一個 get
。
class Person {var age: Int {get {return Int(arc4random()) }set { pint("That's not your age.") } } }
將一種類型的實例轉換爲到另外一種類型實例時的init()
方法:
extension NSColor {convenience init(_ mood: Mood) {super.init(color: NSColor.blueColor) } }
如今在 Swift 標準庫中實現這種轉換 init
方法彷佛是首選。
"to" 方法是另外一種合理的方法(雖然你應該遵循 Apple 的指引使用 init
方法)。
struct Mood {func toColor() -> NSColor {return NSColor.blueColor() } }
雖然你可能會使用 getter, 例如:
struct Mood {var color: NSColor {return NSColor.blueColor() } }
通常來講 getter 應該被限定返回接收類型的組件。例如,返回一個 Circle
實例的面積就很適合使用 getter,可是將一個Circle
轉換爲一個 CGPath
使用 "to" 函數或者一個 CGPath
的 init()
擴展會更好。
在 Swift 中,生成單例很簡單:
class ControversyManager {static let sharedInstance = ControversyManager() }
Swift 的 runtime 將確保以一種線程安全的方式來建立和訪問單例。
單例通常僅經過 sharedInstance
靜態屬性來訪問,除非你有一個使人信服的理由不把它命名爲 sharedInstance
。不要使用靜態函數或者全局函數來訪問單例。
( 由於在 Swift 中生成單例是如此簡單,而且由於統一的命名爲您節省了大量的時間,您將有更多的時間去抱怨單例如何如何 '反模式' 而且應該不惜一切代價避免使用。您的開發夥伴們會感激你的 <譯者注:反話?> 。)
擴展應該被用於幫助組織代碼。
當方法和屬性是一個實例的外圍延伸時,應該被遷移到某個擴展。注意,目前並非全部的屬性類型均可以被遷移到擴展 --- 在這個限制範圍內盡你所能。
你應該使用擴展來幫助組織你的實例的定義。這方面很好的一個例子就是:一個實現了表視圖數據源和委託協議的視圖控制器。它沒有將全部的表視圖代碼混成一個類,而是把數據源和委託方法放到了相關的協議擴展中。
能夠根據你的以爲最好的方式將一個源文件隨意分解並定義成任何的擴展,以從新組織這堆問題代碼。別擔憂主類中的方法或擴展裏指向方法和屬性的結構定義。只要它們在一個 Swift 文件中就沒事。
相反,主實例定義不該該指向在主 Swift 文件以外的主擴展中定義的元素。。。
不要爲了圖方便而使用鏈式 setter 來替代簡單的屬性 setter :
推薦:
instance.foo = 42instance.bar = "xyzzy"
不推薦:
instance.setFoo(42).setBar("xyzzy")
比起鏈式 setter 傳統的 setter 更簡單而且須要的樣板代碼更少。
Swift 2 的 do/ try/ catch
機制很是棒,推薦使用!(TODO: 擬定並提供示例)
try!
一般推薦:
do {try somethingThatMightThrow() }catch { fatalError("Something bad happened.") }
不推薦:
try! somethingThatMightThrow()
雖然這種形式顯得有點囉嗦,但它爲其餘的開發者進行代碼評審提供了清晰的上下文語境。
在沒有更好的錯誤處理策略進化出來以前,使用 try!
做爲一種臨時的錯誤處理也是 OK 的。可是建議你按期審查你的代碼是否錯誤的使用了 try!
,由於它極可能上次悄悄地躲過了代碼審查。
try?
try?
是用來屏蔽(譯者注:產生了錯誤但你不想作任何錯誤處理相關的事情)錯誤的,只有在你真的不關心錯誤的產生時,對你而言纔有用。一般狀況下你應該捕獲錯誤並至少打印錯誤日誌。
若是可能的話,使用 guard
申明來處理提早返回或退出 (例如: 致命錯誤(fatal errors) 或 拋出錯誤 (thrown 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!)
這讓代碼的邏輯扁平化,若是不符合則進入 if let
代碼塊,而且提早退出的語句放置 在接近他們相關條件的地方,而非下放到一個 else
代碼塊中。
即便你沒有捕捉值( guard let
),這種模式也會在編譯時強制執行提早退出。在第二個(不推薦的) if
例程中,雖然代碼扁平得跟 guard
同樣,但一個致命錯誤或其餘返回的一些非退出操做無心中的改變將致使崩潰 (亦或狀態無效,這取決於確切的狀況)。從一個 guard 申明的 else
block 中移除提早退出將會當即顯示錯誤(編譯器提示錯誤)。
即便你的代碼沒有分解爲各個獨立的模塊,你也應該老是考慮其訪問控制。標記一個定義爲 "private
" 或者 "internal
" 能夠輕量化代碼文檔。任何閱讀這代碼的人將知道這些元素是能夠暫時放一邊不予考慮的。相反,標記一個定義爲 "public
"就代表其餘的代碼能夠訪問這個標記元素。最好能有明確的指示而非依賴 Swift 的默認訪問權限 ("internal
")。
若是你的代碼庫慢慢膨脹,最終它可能被拆分爲 N 個子模塊。(而拆分爲 N 個子模塊這種工做若是)在一個已經分配了訪問控制權限信息的代碼庫上作會更快更容易。
在感受使用 "private
" 好過 "internal
" ,或使用 "internal
" 比 "public
" 更合適的時,給代碼添加更嚴格的訪問控制權限一般會更好。
這將使得以後放寬代碼的訪問控制權限變得更容易(權限從窄到寬: "private
" ---> "internal
" ---> "public
")。代碼的訪問控制權限若是放得太寬就有可能會被其餘的代碼不恰當地使用。給代碼更嚴格的訪問控制可以排除一些不恰當或不正確的調用從而提供出更好的接口。這是一種在天馬行空以後關上一扇穩定之門的風格嘗試。公開地暴露一個內部緩存正是這方面的一個例子。
此外,限制代碼的訪問權限能夠限制 "曝光面積" 而且代碼在重構時對其餘代碼的影響更少。另外的技術像 "協議驅動開發" 也能提供幫助。
未來可能會展開討論的一系列主題。
unowned
VS weak