Manjusaka

Manjusaka

Swift 3 中的函數參數命名規範指北

昨天,我開始將這個 Jayme 遷移到 Swift 3。這是我第一次將一個項目從 Swift 2.2 遷移至 Swift 3。說實話這個過程十分的繁瑣,由於 Swift 3 在舊版本基礎上發生了很多比較大的改變,我不得不承認眼前這樣一個事實,除了花費較多的時間以外,沒有其餘的捷徑可走。不過這樣的經歷也帶來一點好處:我對 Swift 3 的理解變得更為深入,對我來講,這可能是最好的消息了。😃

在遷移代碼的過程中,我需要做出很多的選擇。更為蛋疼的是,整個遷移過程並不是修改代碼那麼簡單,你還需要用耐心去一點點適應 Swift 3 中帶來的新變化。某種意義上來講,修改代碼只是整個遷移過程的開始而已。

如果你已經決定將你的代碼遷移到 Swift 3,我建議你去看看這篇文章來作為你萬里長征的第一步。

如果一切順利的話,在不久以後,我將回去寫一篇博客來記錄下整個遷移過程中的點點滴滴,包括我所作出的決定等等。但是眼前,我將會把注意力集中在一個非常非常重要的問題上:怎樣正確的編寫函數簽名.

開篇#

首先,讓我們來看看在 Swift 3 與 Swift 2 相比函數命名方式的差異吧。

在 Swift 2 中,函數中的第一個參數的標籤在調用時可以省略,這是為了遵循這樣一個 good ol' Objective-C conventions 標準。比如我們可以這樣寫代碼:

    // Swift 2
    func handleError(error: NSError) { }
    let error = NSError()
    handleError(error) // Looks like Objective-C

在 Swift 3 中調用函數時,其實也是有辦法省略第一個參數的標籤的,但默認情況下不是這樣:

    // Swift 3
    func handleError(error: NSError) { }
    let error = NSError()
    handleError(error)  // Does not compile!
    // ⛔ Missing argument label 'error:' in call

當遇到這樣的情況時,我們第一反應可能是下面這樣的:

    // Swift 3
    func handleError(error: NSError) { }
    let error = NSError()
    handleError(error: error)    
    // Had to write 'error' three times in a row!
    // My eyes already hurt 🙈

當然如果這樣做,你肯定會很快意識到你的代碼將會變得有多坑爹。

如同前面所說的一樣,在 Swift 3 中,我們是可以在調用函數時,將第一個參數的標籤省略的,但是記住,你要去明確的告訴編譯器這一點:

    // Swift 3
    func handleError(_ error: NSError) { }
    // 🖐 Notice the underscore!
    let error = NSError()
    handleError(error)  // Same as in Swift 2

你可能在使用 Xcode 自帶的遷移工具進行遷移時遇到這樣的情況。

注意,在函數簽名中的下劃線的意思是:告訴編譯器,我們在調用函數時第一個參數不需要外帶標籤。這樣,我們可以按照 Swift 2 中的方式去調用函數。

此外,你需要意識到,Swift 3 之所以修改了函數編寫方式,是為了保證其一致性與可讀性:我們不再需要對不同的參數區別對待。我想這可能是你遇到的第一個問題。

好了,現在代碼可以編譯運行了,但是你必須知道,你需要反復的去閱讀 Swift 3 API design guidelines 一文。

☝️ 一點微小的人生經驗:你需要隨時去誦讀 Swift 3 API design guidelines 一文,這會為你解鎖 Swift 開發的新體位。

第二步,精簡你的代碼#

Pruning

讓我們再來看看之前的代碼:

為了精簡我們的代碼,你可以將你的代碼進行修剪一番,比如去除函數名裡的類型信息等。

    // Swift 3
    func handle(_ error: NSError) { /* ... */ }
    let error = NSError()
    handle(error)   // Type name has been pruned
    // from function name, since it was redundant

如果你想讓你的代碼變得更短,更精悍,更明瞭的話,我給你們講,作為一個欽定的開發者,一定要去反復誦讀這篇 Swift 3 API design guidelines 文章到可以默寫為止。

要注意讓函數的調用過程是清晰、明確的,我們根據以下兩點來確定函數的的命名和參數:

  • 我們知道函數的返回類型
  • 我們知道參數所對應的類型(比如在上面這個例子中,我們毫無疑問的知道其參數所屬的類型是 NSError)。

更多的一些問題#

現在請睜大眼睛看清楚我們下面所討論的東西。 ⚠️

上面我們所講的東西並沒有包括所有可能出現的情況,換句話說,你可能遇到這樣一種特殊情況,即,一個參數的類型沒有辦法直觀的體現其作用。

讓我們考慮下面這樣一種情況:

    // Swift 2
    func requestForPath(path: String) -> URLRequest {  }
    let request = requestForPath("local:80/users")

如果你想將代碼遷移到 Swift 3,那麼根據已有的知識,你可能會這麼做:

    // 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")

OK,現在看起來舒服多了,但是並沒有解決我上面提到的問題。

在我們調用這個函數的時候,我們怎樣很直觀的知道我們需要給這個參數傳遞一個 Web Url 呢?你所能提前知道的是你需要傳遞一個 String 類型的變量進去,但是你並不清楚你需要傳遞一個 Web Url 進去。

同理,我們在一個大型項目中,我們需要很清楚的明白每個參數的作用所在,但是很明顯,目前我們還沒有解決這個大問題,比如:

  • 你怎麼知道一個 String 類型的變量代表著 Web Url。
  • 你怎麼知道一個 Int 類型的變量代表著 Http 狀態碼。[String: String]
  • 你怎麼知道一個 [String: String] 類型的變量代表著 Http Header。
  • 等等...。

⚠️ 综上,我給你們一點微小的人生經驗吧: 謹慎精簡你的代碼

回到代碼上,我們可以給參數添加上相對應的標籤來解決這個問題,好了看看下面這個代碼:

    func request(forPath path: String) -> URLRequest {  }
    let request = request(forPath: "local:80/users")

好了,現在代碼看起來是不是更清楚可讀性更強了呢? 🎉 恭喜~

Hooray

講真,看到這裡其實你可以關閉瀏覽器了,但是事實上,下面才是最精華的部分。

好了,讓我們來看看關於函數參命名的用詞問題:

    func request(forPath path: String) -> URLRequest {  }
    // The word 'path' appears twice

這段代碼看起來不錯,但是如果你想讓其變得更好,那麼請看接下來的部分。

你所不知道的小技巧#

這個小技巧很簡單:在上下文中反映參數的類型及作用,這樣你就可以無腦的精簡你的代碼了。

Prune with no mercy

呐,我們來看看下面這段代碼。

    typealias Path = String      // To the rescue!

    func request(for path: Path) -> URLRequest {  }
    let request = request(for: "local:80/users")

在這個例子中,參數的類型和參數的作用表達達成了完美的統一,因為你在上下文中為 String 賦予了一個別名叫做 Path

現在,你的函數看起來還是依舊的精簡,可讀性較高,但是卻不重複。

以此類推,你可以使用同樣的方式來書寫一些優美的代碼,比如:

    typealias Path = String
    typealias StatusCode = Int
    typealias HTTPHeader = [String: String]
    // etc...

如你所見,你可以盡情的寫精簡而優美的代碼了。

不過,請記住,凡事走向極端便變了味了:這個小技巧會為你的代碼添加額外的負擔,特別是你們代碼存在多重嵌套的情況。因此請記住,如果你無腦的使用這樣的小技巧的話,那麼你可能會付出一些慘痛的代價。

結論#

很多時候,你在使用 Swift 3 時,命名函數的時候你會遇到很多困難。

積累一些代碼片段可能會幫助你很多:

    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"])
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。