Swift 3 中的函數參數命名規范指北
昨天,我開始將這個 Jayme 遷移到 Swift 3。這是我第一次將一個項目從 Swift 2.2 遷移至 Swift 3。說實話這個過程十分的繁瑣,由于 Swift 3 在老版本基礎上發生了很多比較大的改變,我不得不承認眼前這樣一個事實,除了花費較多的時間以外,沒有其余的捷徑可走。不過這樣的經歷也帶來一點好處:我對 Swift 3 的理解變得更為深入,對我來講,這可能是最好的消息了。:smiley:
在遷移代碼的過程中,我需要做出很多的選擇。更為蛋疼的是,整個遷移過程并不是修改代碼那么簡單,你還需要用耐心去一點點適應 Swift 3 中帶來的新變化。某種意義上來講,修改代碼只是整個遷移過程的開始而已。
如果一切順利的話,在不久以后,我將回去寫一篇博客來記錄下整個遷移過程中的點點滴滴,包括我所作出的決定等等。但是眼前,我將會把注意力集中在一個非常非常重要的問題上: 怎樣正確的編寫函數簽名 .
開篇
首先,讓我們來看看在 Swift 3 與 Swift 2 相比函數命名方式的差異吧。
在 Swift 2 中,函數中的第一個參數的標簽在調用時可以省略,這是為了遵循這樣一個 good ol’ Objective-C conventions 標準。比如我們可以這樣寫代碼:
// Swift 2
funchandleError(error: NSError){ }
leterror =NSError()
handleError(error) // Looks like Objective-C
在 Swift 3 中調用函數時,其實也是有辦法省略第一個參數的標簽的,但默認情況下不是這樣:
// Swift 3
funchandleError(error: NSError){ }
leterror =NSError()
handleError(error) // Does not compile!
// :no_entry: Missing argument label 'error:' in call
當遇到這樣的情況時,我們第一反應可能是下面這樣的:
// Swift 3
funchandleError(error: NSError){ }
leterror =NSError()
handleError(error: error)
// Had to write 'error' three times in a row!
// My eyes already hurt :see_no_evil:
當然如果這樣做,你肯定會很快意識到你的代碼將將會變得有多坑爹。
如同前面所說的一樣,在 Swift 3 中,我們是可以在調用函數時,將第一個參數的標簽省略的,但是記住,你要去明確的告訴編譯器這一點:
// Swift 3
funchandleError(_error: NSError){ }
// ?? Notice the underscore!
leterror =NSError()
handleError(error) // Same as in Swift 2
你可能在使用 Xcode 自帶的遷移工具進行遷移時遇到這樣的情況。
注意,在函數簽名中的下劃線的意思是:告訴編譯器,我們在調用函數時第一個參數不需要外帶標簽。這樣,我們可以按照 Swift 2 中的方式去調用函數。
此外,你需要意識到,Swift 3 之所以修改了函數編寫方式,是為了保證其一致性與可讀性:我們不在需要對不同的參數區別對待。我想這可能是你遇到的第一個問題。
好了,現在代碼可以編譯運行了,但是你必須知道,你需要反復的去閱讀 Swift 3 API design guidelines 一文。
:point_up: 一點微小的人生經驗:你需要隨時去誦讀 Swift 3 API design guidelines 一文,這會為你解鎖 Swift 開發的新體位。
第二步,精簡你的代碼
讓我們再來看看之前的代碼:
為了精簡我們的代碼,你可以將你的代碼進行修剪一番,比如去除函數名里的類型信息等。
// Swift 3
funchandle(_error: NSError){/* ... */}
leterror =NSError()
handle(error) // Type name has been pruned
// from function name, since it was redundant
如果你想讓你的代碼變得更短,更精悍,更明了的話,我給你們講,作為一個欽定的開發者,一定要去反復誦讀這篇 Swift 3 API design guidelines 文章到可以默寫為止。
要注意讓函數的調用過程是清晰、明確的,我們根據以下兩點來確定函數的的命名和參數:
- 我們知道函數的返回 類型
- 我們知道參數所對應的類型(比如在上面這個例子中,我們毫無疑問的知道其參數所屬的類型是 NSError )。
更多的一些問題
現在請睜大眼睛看清楚我們下面所討論的東西。 :warning:
上面我們所講的東西并沒有包括所有可能出現的情況,換句話說,你可能遇到這樣一種特殊情況,即,一個參數的類型沒有辦法直觀的體現其作用。
讓我們考慮下面這樣一種情況:
// Swift 2
funcrequestForPath(path: String)->URLRequest{ }
letrequest = requestForPath("local:80/users")
如果你想將代碼遷移到 Swift 3 ,那么根據已有的知識,你可能會這么做:
// Swift 3
funcrequest(_path: String)->URLRequest{ }
letrequest = request("local:80/users")
講真,這段代碼看起來可讀性很差,讓我們稍微修改下:
// Swift 3
funcrequest(forpath: String)->URLRequest{ }
letrequest = request(for:"local:80/users")
OK,現在看起來舒服多了,但是并沒有解決我上面提到的問題。
在我們調用這個函數的時候,我們怎樣很直觀的知道我們需要給這個參數傳遞一個 Web Url 呢?你所能提前知道的是你需要傳遞一個 String 類型的變量進去,但是你并不清楚你需要傳遞一個 Web Url 進去。
同理,我們在一個大型項目中,我們需要很清楚的明白每個參數的作用所在,但是很明顯,目前我們還沒有解決這個大問題,比如:
- 你怎么知道一個 String 類型的變量代表著 Web Url。
- 你怎么知道一個 Int 類型的變量代表著 Http 狀態碼。 [String: String]
- 你怎么知道一個 [String: String] 類型的變量代表著 Http Header。
- 等等…。
:warning: 綜上,我給你們一點微小的人生經驗吧: 謹慎精簡你的代碼 ?
回到代碼上,我們可以給參數添加上相對應的標簽來解決這個問題,好了看看下面這個代碼:
funcrequest(forPath path: String)->URLRequest{ }
letrequest = request(forPath:"local:80/users")
好了,現在代碼看起來是不是 更清楚 , 可讀性 更強了呢? :tada: 恭喜~
講真,看到這里其實你可以關閉瀏覽器了,但是事實上,下面才是最精華的部分。
好了,讓我們來看看關于函數參命名的用詞問題:
funcrequest(forPath path: String)->URLRequest{ }
// The word 'path' appears twice
這段代碼看起來不錯,但是如果你想讓其變得更好,那么請看接下來的部分。
你所不知道的小技巧
這個小技巧很簡單:在上下文中反映參數的類型及作用,這樣你就可以無腦的精簡你的代碼了。
吶,我們來看看下面這段代碼。
typealiasPath=String// To the rescue!
funcrequest(forpath: Path)->URLRequest{ }
letrequest = request(for:"local:80/users")
在這個例子中,參數的類型和參數的作用表達達成了一個完美的統一,因為你在上下文中為 String 賦予了一個別名叫做 Path 。
現在,你的函數看起來還是依舊的精簡,可讀性較高,但是卻不重復。
以此類推,你可以使用同樣的方式來書寫一些優美的代碼,比如:
typealiasPath=String
typealiasStatusCode=Int
typealiasHTTPHeader= [String:String]
// etc...
如你所見,你可以盡情的寫精簡而優美的代碼了。
不過,請記住,凡事走向極端便變了味了:這個小技巧會為你的代碼添加額外的負擔,特別是你們代碼存在多重嵌套的情況下。因此請記住,如果你無腦的使用這樣的小技巧的話,那么你可能會付出一些慘痛的代價。
結論
很多時候,你在使用 Swift 3 時,命名函數的時候你會遇到很多困難。
積累一些代碼片段可能會幫助你很多:
funcremove(at position: Index)->Element{ }
employees.remove(at: x)
funcremove(_member: Element)->Element? { }
allViews.remove(cancelButton)
funcurl(forPath path: String)->URL{ }
leturl = url(forPath:"local:80/users")
typealiasPath=String// Alternative
funcurl(forpath: Path)->URL{ }
leturl = url(for:"local:80/users")
funcentity(from dictionary: [String: Any])->Entity{/* ... */}
letentity = entity(from: ["id":"1","name":"John"])
來自:http://manjusaka.itscoder.com/2016/10/09/Function-Naming-In-Swift-3/