Swift3中的函數命名
昨天我開始將 Jayme 遷移到 Swift3,這是我第一次嘗試將 Swift2.2的代碼遷移到3.0.整個過程非常繁瑣,我不得不承認; Swift3跟之前的版本相比是有很大的不同,而且其中大部分的變化都是突然地,而且得花時間去思考這些變化.但是從我們自己的角度來說: 我寫 Swift3,越寫越開心.
在遷移過程中,有很多很多需要去思考的變化,而且,這不僅僅是翻譯代碼,而是把整個遷移過程分成很多步驟,然后一點一點,有耐心的遷移.當然,代碼的變化僅僅是整個遷移工作的一步.
如果你已經決定開始遷移你的代碼庫,那么 文字鏈接 是一個很好的幫助.
我希望在不久的將來,我會把我遷移經驗寫一篇博文,包括了整個遷移過程并且總結我在遷移過程中做出的大部分決定.但到目前為止,我只能關注我認為最重要的一個:如何正確地寫函數簽名。
基礎:
讓我們開始理解一個觀點:Swift3函數命名跟 Swift2的不同.
在 Swift2中,當你調用一個函數時,對一個函數的第一個參數標簽是忽略的,這樣寫是為了遵守 Cocoa框架的命名規范 ,所以我們一直都是這樣寫:
// Swift 2
func handleError(error: NSError) { }
let error = NSError()
handleError(error) // 很像 OC
在 Swift3中,相反,,函數的第一個參數標簽可以在函數調用中省略,但在默認情況下,并不能省略:
// Swift 3
func handleError(error: NSError) { }
let error = NSError()
handleError(error) // Does not compile! 編譯過不了
// :no_entry: Missing argument label 'error:' in call
所以,這樣改一下:
// Swift 3
func handleError(error: NSError) { }
let error = NSError()
handleError(error: error)
// 但是不得不把 'error' 寫了三遍
// 辣眼睛:see_no_evil:
在這一點上,你可以知道你的代碼就會變得無聊和重復…
也就是說,如果你想在函數調用中省略第一個參數標簽.但是在 Swift3中,你必須明確的寫成:
// Swift 3
func handleError(_ error: NSError) { }
// ?? 注意這個下劃線!
let error = NSError()
handleError(error) // 跟 Swift2一樣
當你運行 Xcode 的遷移工具時,這是個默認的變化
注意函數聲明中的下劃線,它的意思是當你調用函數是第一個參數不需要標簽.這樣,我們就可以像 Swift2中那樣調用了.
此外,你可以意識到, Swift3中的函數命名更具有一致性也更容易理解: 所有的參數標簽都是一樣的,再也沒有像第一個參數標簽隱藏這樣的了.
這樣代碼可以通過編譯,但是你想寫的更 Swifty 的話,你得看看 Swift 3 API design guidelines .
一點點建議:反復閱讀 Swift 3 API design guidelines .最好每天早上都讀,直到你懂得了用一個新的方式去寫 Swift 代碼.
Furthermore, you can realize that Swift 3 is more consistent and easier to understand when it comes to functions naming: All parameter labels are treated the same; there is no such thing as treating the first one differently.
此外,你可以意識到,3是更一致,更容易理解,當涉及到函數命名:所有的參數標簽都是相同的,沒有這樣的東西作為治療的第一個不同的。
更進一步: 減代碼
觀察這行代碼是怎么重復的
handleError(error)
為了使它不重復性和更簡潔,您可以從函數命名中刪除冗余類型的名稱:
// Swift 3
func handle(_ error: NSError) { /* ... */ }
let error = NSError()
handle(error) // 類型名稱減掉了
// 從函數名看,它是多余的
這更短,更清晰,更簡潔,并鼓勵開發人員按照 Swift 3 API design guidelines 來開發(是的,讀一遍,再次!)
注意函數調用是清晰的。我們可以知道函數參數是什么,因為有兩個事實:
1.我們知道它的類型。
2.此外,該類型表示的參數正是預期的代表(在這種情況下毫無疑問是一個錯誤類型)。
減代碼并不總是一個問題
現在該打起精神,開始小心一點了!
在很多情況下,上面的例子并沒有發生.換言說,參數的類型不能反映這個參數本身.
來看看下面的例子:
// Swift 2
func requestForPath(path: String) -> URLRequest { }
let request = requestForPath("local:80/users")
如果你試著將這個代碼遷移到 Swift3,根據我們之前學的,你可能會寫出這樣的代碼:
// Swift 3
func request(_ path: String) -> URLRequest { }
let request = request("local:80/users")
但是這樣寫還是有點小問題而且可讀性不高,讓我們在修改一下:
// Swift 3
func request(for path: String) -> URLRequest { }
let request = request(for: "local:80/users")
現在,雖然這是更具可讀性,它并沒有解決我上面提到的問題。
在調用這個函數的那一刻,你怎么能知道你需要傳遞的for是需要什么?因為Swift是靜態類型的,所有你可以事先知道是該參數預期是字符串類型,但是你需要傳入的 path在調用的時候并不知道for 后面跟的是什么參數.
有很多這些場景中的參數類型是沒有意義的,應該代表什么時,例如:
1.一個String有時候并不總是代表 path
2.一個int并不總是代表一個狀態碼。
3.[String: String]不總是代表一個 http 標頭
:warning: 我的建議: 減代碼的時候一定要小心!?
回到代碼,來解決這個問題第一種方法可以追加參數的名稱為參數的標簽,使得它在結果明確:
func request(forPath path: String) -> URLRequest { }
let request = request(forPath: "local:80/users")
// for改成了forPath
此代碼是準確的,可編譯并遵循了設計規范。:tada:萬歲!
你可以讀到這里,但等等,最好的部分是尚未到來...
現在,請注意在函數聲明這一措辭:
func request(forPath path: String) -> URLRequest { }
// The word 'path' appears twice
雖然這并不是丑代碼,在大多數情況下,這仍然是正確的,而且編譯得很順利,但有一種方法,以避免它。
更多關于這在下面的部分......
一些你可能不知道的小技巧
這個想法很簡單:讓參數類型反映了它的內容,目的就是能夠盡量的減少代碼。
如果我告訴你
typealias Path = String
func request(for path: Path) -> URLRequest { }
let request = request(for: "local:80/users")
在這種情況下,你的參數的類型和入參是具有一致性,因為參數的類型被定義了一個typealias, 把 Path的別名設為 String.
這樣,你的函數仍然是直觀,可讀的,明確的,但不重復。
同樣的,你能想到的,可能在其他常見的場景利用typealias的其他一些例子:
typealias Path = String
typealias StatusCode = Int
typealias HTTPHeader = [String: String]
// etc...
這將使你寫更清晰的代碼,就像我們剛才看到的。
然而,極端都不好:輸入別名增加了代碼的復雜性,你的代碼,甚至會變得更多,如果它們嵌套......所以,不要虐待自己.??雖然在某些情況下,他們可以幫你很好,因為我們只看到有時它們實際上是不必要的,只會使你的代碼難以效仿。
結論
在很多情況下,你會在 Swift3 的函數命名中栽個跟頭.
但這些代碼片段抵過再多的博文:
func remove(at position: Index) -> Element { }
employees.remove(at: x)
func remove(_ member: Element) -> Element? { }
allViews.remove(cancelButton)
func url(forPath path: String) -> URL { }
let url = url(forPath: "local:80/users")
typealias Path = String // Alternative
func url(for path: Path) -> URL { }
let url = url(for: "local:80/users")
func entity(from dictionary: [String: Any]) -> Entity { /* ... */ }
let entity = entity(from: ["id": "1", "name": "John"])
(就好好讀系統的 API)
來自:http://www.jianshu.com/p/11f4fa4a7f7d