看透 Go 对象内部细节的神器

开发 后端
在调式 Go 程序时,我们经常想知道对象的内部数据是什么样了,以便掌握程序的运行情况。

[[437722]]

本文转载自微信公众号「Golang技术分享」,作者机器铃砍菜刀。转载本文请联系Golang技术分享公众号。

在调式 Go 程序时,我们经常想知道对象的内部数据是什么样了,以便掌握程序的运行情况。

一般有两种做法:对于简单的代码测试,我们可以通过fmt包来打印一些对象信息;在稍复杂场景下,可以利用调式器来完成,例如 GDB、LLDB 和 Delve 等。

但是,这两种做法都有不足之处。fmt包能打印的信息并不友好,尤其在结构体中含有指针对象时;通过调式器来调式程序也经常受限于各种因素,例如远程访问服务器。

示例

对于 fmt 包的能力短板,我们来看一个例子。

定义 instance 和 Inner 结构体,其中 instance 的C属性字段是 Inner 类型指针。

  1. type instance struct { 
  2.  A string 
  3.  B int 
  4.  C *Inner 
  5.  
  6. type Inner struct { 
  7.  D string 
  8.  E string 

实例化一个 instance 对象ins

  1. func main() { 
  2.  ins := instance{ 
  3.   A: "AAAA"
  4.   B: 1000, 
  5.   C: &Inner
  6.    D: "DDDD"
  7.    E: "EEEE"
  8.   }, 
  9.  } 
  10.  fmt.Println(ins) 

此时,我们想知道ins的内部数据。通过fmt.Println(ins)语句得到的打印信息如下

  1. {AAAA 1000 0xc000054020} 

由于 C 字段是指针,所以打印出来的是一个地址0xc000054020,而地址背后的数据却被隐藏了。显然,这对程序排查非常不友好。

go-spew

go-spew 就是为了解决上述问题而生的,它为 Go 数据结构实现了一个深度打印机。

同样以上文代码为例,这次使用 go-spew 进行打印。

下载

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

导包

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

打印

  1. func main() { 
  2.  ins := instance{ 
  3.   A: "AAAA"
  4.   B: 1000, 
  5.   C: &Inner
  6.    D: "DDDD"
  7.    E: "EEEE"
  8.   }, 
  9.  } 
  10.  spew.Dump(ins) 

得到打印结果

  1. (main.instance) { 
  2.  A: (string) (len=4) "AAAA"
  3.  B: (int) 1000, 
  4.  C: (*main.Inner)(0xc0000ba0c0)({ 
  5.   D: (string) (len=4) "DDDD"
  6.   E: (string) (len=4) "EEEE" 
  7.  }) 

是不是非常详细?

场景扩展

指针数组

除了结构体中含有指针对象时打印 fmt 打印不够清晰,如果数组或者map中是指针对象时,传统的打印同样不友好。

  1. type Demo struct { 
  2.  a int 
  3.  b string 
  4.  
  5. func main() { 
  6.  arr := [...]*Demo{{100, "Python"}, {200, "Golang"}} 
  7.  fmt.Printf("%v\n-----------------分割线-----------\n", arr) 
  8.  spew.Dump(arr) 

两种打印的输出结果对比

  1. [0xc00011c018 0xc00011c030] 
  2. -----------------分割线----------- 
  3. ([2]*main.Demo) (len=2 cap=2) { 
  4.  (*main.Demo)(0xc00011c018)({ 
  5.   a: (int) 100, 
  6.   b: (string) (len=6) "Python" 
  7.  }), 
  8.  (*main.Demo)(0xc00011c030)({ 
  9.   a: (int) 200, 
  10.   b: (string) (len=6) "Golang" 
  11.  }) 

孰强孰弱,一目了然。

循环结构

通过 spew.Dump 方法可以将指针地址和它指向的数据都打印出来,那如果 go-spew 需要打印循环数据结构怎么办,它能否正确处理(而不是陷入无限循环)?

定义循环结构体对象 Circular

  1. type Circular struct { 
  2.  a    int 
  3.  next *Circular 

实例化循环结构体对象,再分别通过 fmt 和 go-spew 进行打印对比

  1. func main() { 
  2.  c := &Circular{1, nil} 
  3.  c.next = &Circular{2, c} 
  4.  
  5.  fmt.Printf("%+v\n----------------分割线-------------------\n", c) 
  6.  spew.Dump(c) 

得到结果

  1. &{a:1 next:0xc0000962f0} 
  2. ----------------分割线------------------- 
  3. (*main.Circular)(0xc0000962e0)({ 
  4.  a: (int) 1, 
  5.  next: (*main.Circular)(0xc0000962f0)({ 
  6.   a: (int) 2, 
  7.   next: (*main.Circular)(0xc0000962e0)(<already shown>) 
  8.  }) 
  9. }) 

再次证明 go-spew 的强大。

总结

go-spew 借助于 unsafe 包,为我们带来了非常漂亮的打印功能。

当然,go-spew 不止 Dump 方法,它也提供了其他方法,例如转换为字符串的 Sdump 方法;输出重定向的 Fdump 方法;与 fmt 类似的一套 Print 用法。

同时,可以通过 spew.Config 进行一些参数配置,例如设置 spew.Config.MaxDepth 用于控制打印深度。

 

调式 Go 程序时,go-spew 是一个非常好用的助手工具,推荐大家使用。

 

责任编辑:武晓燕 来源: Golang技术分享
相关推荐

2011-05-25 19:31:07

Stratus信息化

2023-08-14 08:34:14

GolangHttp

2010-10-08 10:26:45

JavaScript内

2013-03-11 09:23:22

Go语言面向对象

2021-06-02 09:23:57

Go开发内存

2024-05-14 08:47:01

JavaPAC++

2023-03-06 07:50:19

内存回收Go

2021-04-25 08:58:00

Go拍照云盘

2011-12-06 11:12:59

Java

2013-07-30 09:27:32

Go云计算语言

2013-07-30 09:23:43

VMwareGoogle云平台

2024-10-28 11:39:30

2022-09-02 08:17:40

MapStruct代码工具

2024-10-29 10:30:57

2011-10-08 14:09:27

JavaScript

2009-07-06 15:47:11

Servlet API

2024-11-11 10:46:05

2024-01-08 07:02:48

数据设计模式

2023-02-24 07:48:20

PHPGo服务

2018-07-27 10:39:13

对象存储Git
点赞
收藏

51CTO技术栈公众号