看透 Go 對象內部細節的神器
本文轉載自微信公眾號「Golang技術分享」,作者機器鈴砍菜刀。轉載本文請聯系Golang技術分享公眾號。
在調式 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 是一個非常好用的助手工具,推薦大家使用。