《Effective Go》--空白識別符號

衣舞晨風發表於2017-11-14

未使用的匯入和變數

    如果你在程式中匯入了一個包或宣告瞭一個變數卻沒有使用的話,會引起編譯錯誤。因為,匯入未使用的包不僅會使程式變得臃腫,同時也降低了編譯效率;初始化 一個變數卻不使用,輕則造成對計算的浪費,重則可能會引起更加嚴重BUG。當一個程式處於開發階段時,會存在一些暫時沒有被使用的匯入包和變數,如果為了 使程式編譯通過而將它們刪除,那麼後續開發需要使用時,又得重新新增,這非常麻煩。空白識別符號為上述場景提供瞭解決方案。

    以下一段程式碼包含了兩個未使用的匯入包(fmt和io) 以及一個未使用的變數(fd),因此無法編譯通過。我們可能希望這個程式現在就可以正確編譯。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: use fd.
}

    為了禁止編譯器對未使用匯入包的錯誤報告,我們可以用空白識別符號來引用一個被匯入包中的符號。同樣的,將未使用的變數fd賦值給一個空白識別符號也可以禁止編譯錯誤。這個版本的程式就可以編譯通過了。

package main

import (
    "fmt"
    "io"
    "log"
    "os"
)

var _ = fmt.Printf // For debugging; delete when done.
var _ io.Reader    // For debugging; delete when done.

func main() {
    fd, err := os.Open("test.go")
    if err != nil {
        log.Fatal(err)
    }
    // TODO: use fd.
    _ = fd
}

    按照約定,用來臨時禁止未使用匯入錯誤的全域性宣告語句必須緊隨匯入語句塊之後,並且需要提供相應的註釋資訊 —— 這些規定使得將來很容易找並刪除這些語句。

副作用式匯入

    像上面例子中的匯入的包,fmt或io,最終要麼被使用,要麼被刪除:使用空白識別符號只是一種臨時性的舉措。但有時,匯入一個包僅僅是為了引入一些副作用,而不是為了真正使用它們。例如,net/http/pprof包會在其匯入階段呼叫init函式,該函式註冊HTTP處理程式以提供除錯資訊。這個包中確實也包含一些匯出的API,但大多數客戶端只會通過註冊處理函式的方式訪問web頁面的資料,而不需要使用這些API。為了實現僅為副作用而匯入包的操作,可以在匯入語句中,將包用空白識別符號進行重新命名:

import _ "net/http/pprof"

    這一種非常乾淨的匯入包的方式,由於在當前檔案中,被匯入的包是匿名的,因此你無法訪問包內的任何符號。(如果匯入的包不是匿名的,而在程式中又沒有使用到其內部的符號,那麼編譯器將報錯。)

介面檢查

    一個型別不需要明確的宣告它實現了某個介面。一個型別要實現某個介面,只需要實現該介面對應的方法就可以了。在實際中,多數介面的型別轉換和檢查都是在編譯階段靜態完成的。例如,將一個*os.File型別傳入一個接受io.Reader型別引數的函式時,只有在*os.File實現了io.Reader介面時,才能編譯通過。

    但是,也有一些介面檢查是發生在執行時的。其中一個例子來自encoding/json包內定義的Marshaler介面。當JSON編碼器接收到一個實現了Marshaler介面的引數時,就呼叫該引數的marshaling方法來代替標準方法處理JSON編碼。編碼器利用型別斷言機制在執行時進行型別檢查:

m, ok := val.(json.Marshaler)

    假設我們只是想知道某個型別是否實現了某個介面,而實際上並不需要使用這個介面本身 —— 例如在一段錯誤檢查程式碼中 —— 那麼可以使用空白識別符號來忽略型別斷言的返回值:

if _, ok := val.(json.Marshaler); ok {
    fmt.Printf("value %v of type %T implements json.Marshaler\n", val, val)
}

    在某些情況下,我們必須在包的內部確保某個型別確實滿足某個介面的定義。例如型別json.RawMessage,如果它要提供一種定製的JSON格式,就必須實現json.Marshaler介面,但是編譯器不會自動對其進行靜態型別驗證。如果該型別在實現上沒有充分滿足介面定義,JSON編碼器仍然會工作,只不過不是用定製的方式。為了確保介面實現的正確性,可以在包內部,利用空白識別符號進行一個全域性宣告:

var _ json.Marshaler = (*RawMessage)(nil)

    在該宣告中,賦值語句導致了從*RawMessage到Marshaler的型別轉換,這要求*RawMessage必須正確實現了Marshaler介面,該屬性將在編譯期間被檢查。當json.Marshaler介面被修改後,上面的程式碼將無法正確編譯,因而很容易發現錯誤並及時修改程式碼。

    在這個結構中出現的空白識別符號,表示了該宣告語句僅僅是為了觸發編譯器進行型別檢查,而非建立任何新的變數。但是,也不需要對所有滿足某介面的型別都進行這樣的處理。按照約定,這類宣告僅當程式碼中沒有其他靜態轉換時才需要使用,這類情況通常很少出現。

本文整理自:https://www.kancloud.cn/kancloud/effective/72211

個人微信公眾號:
這裡寫圖片描述

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章