推薦一個能在線上用的 Go Debug 庫

語言: CN / TW / HK

在調式 Go 程式時,我們經常想知道物件的內部資料是什麼樣了,以便掌握程式的執行情況。

一般有兩種做法:對於簡單的程式碼測試,我們可以通過 fmt 包來列印一些物件資訊;在稍複雜場景下,可以利用調式器來完成,例如 GDB、LLDB 和 Delve 等。

但是,這兩種做法都有不足之處。 fmt 包能列印的資訊並不友好,尤其在結構體中含有指標物件時;通過調式器來調式程式也經常受限於各種因素,例如遠端訪問伺服器。

示例

對於 fmt 包的能力短板,我們來看一個例子。

定義 Instance 和 Inner 結構體,其中 Instance 的 C 屬性欄位是 Inner 型別指標。

type Instance struct {
 A string
 B int
 C *Inner
}

type Inner struct {
 D string
 E string
}

例項化一個 Instance 物件 ins

func main() {
 ins := Instance{
  A: "AAAA",
  B: 1000,
  C: &Inner{
   D: "DDDD",
   E: "EEEE",
  },
 }
 fmt.Println(ins)
}

此時,我們想知道 ins 的內部資料。通過 fmt.Println(ins) 語句得到的列印資訊如下

{AAAA 1000 0xc000054020}

由於 C 欄位是指標,所以打印出來的是一個地址0xc000054020,而地址背後的資料卻被隱藏了。顯然,這對程式排查非常不友好。

go-spew

go-spew 就是為了解決上述問題而生的,它為 Go 資料結構實現了一個深度印表機。

同樣以上文程式碼為例,這次使用 go-spew 進行列印。

下載

go get -u github.com/davecgh/go-spew/spew

導包

"github.com/davecgh/go-spew/spew"

列印

func main() {
 ins := Instance{
  A: "AAAA",
  B: 1000,
  C: &Inner{
   D: "DDDD",
   E: "EEEE",
  },
 }
 spew.Dump(ins)
}

得到列印結果

(main.Instance) {
 A: (string) (len=4) "AAAA",
 B: (int) 1000,
 C: (*main.Inner)(0xc0000ba0c0)({
  D: (string) (len=4) "DDDD",
  E: (string) (len=4) "EEEE"
 })
}

是不是非常詳細?

場景擴充套件

指標陣列

除了結構體中含有指標物件時列印 fmt 列印不夠清晰,如果陣列或者map中是指標物件時,傳統的列印同樣不友好。

type Demo struct {
 a int
 b string
}

func main() {
 arr := [...]*Demo{{100, "Python"}, {200, "Golang"}}
 fmt.Printf("%v\n-----------------分割線-----------\n", arr)
 spew.Dump(arr)
}

兩種列印的輸出結果對比

[0xc00011c018 0xc00011c030]
-----------------分割線-----------
([2]*main.Demo) (len=2 cap=2) {
 (*main.Demo)(0xc00011c018)({
  a: (int) 100,
  b: (string) (len=6) "Python"
 }),
 (*main.Demo)(0xc00011c030)({
  a: (int) 200,
  b: (string) (len=6) "Golang"
 })
}

孰強孰弱,一目瞭然。

迴圈結構

通過 spew.Dump 方法可以將指標地址和它指向的資料都打印出來,那如果 go-spew 需要列印迴圈資料結構怎麼辦,它能否正確處理(而不是陷入無限迴圈)?

定義迴圈結構體物件 Circular

type Circular struct {
 a    int
 next *Circular
}

例項化迴圈結構體物件,再分別通過 fmt 和 go-spew 進行列印對比

func main() {
 c := &Circular{1, nil}
 c.next = &Circular{2, c}

 fmt.Printf("%+v\n----------------分割線-------------------\n", c)
 spew.Dump(c)
}

得到結果

&{a:1 next:0xc0000962f0}
----------------分割線-------------------
(*main.Circular)(0xc0000962e0)({
 a: (int) 1,
 next: (*main.Circular)(0xc0000962f0)({
  a: (int) 2,
  next: (*main.Circular)(0xc0000962e0)(<already shown>)
 })
})

再次證明 go-spew 的強大。

總結

go-spew 藉助於 unsafe 包,為我們帶來了非常漂亮的列印功能。

當然,go-spew 不止 Dump 方法,它也提供了其他方法,例如轉換為字串的 Sdump 方法;輸出重定向的 Fdump 方法;與 fmt 類似的一套 Print 用法。

同時,可以通過 spew.Config 進行一些引數配置,例如設定 spew.Config.MaxDepth 用於控制列印深度。

調式 Go 程式時,go-spew 是一個非常好用的助手工具,推薦大家使用。

感謝你的 點贊在看 哦~