推荐一个能在线上用的 Go Debug 库
在调式 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 是一个非常好用的助手工具,推荐大家使用。
感谢你的 点赞 和 在看 哦~
- Go社区大佬,谈怎么避免技术内卷化
- Go 切片面试真题八连问
- Go 的切片支持并发吗?
- Go单元测试-- 全能打桩工具Monkey的使用介绍
- 世界读书日,推荐几本我读过的好书
- Go 单元测试--Mock接口实现和对接口打桩
- Go单测测试 — 数据库 CRUD 的 Mock 测试
- 春天里,一场关于技术人成长的直播
- Go单元测试--模拟服务请求和接口返回
- Go1.18 新特性TryLock一出,面试题库里少了一道题,好慌。
- 绝对零门槛,IDEA两步搭建好Java开发环境
- Ballast,一种精准控制 Go GC 提高性能的方法
- かいわ面试官 の 两个事务并发写,能保证数据唯一吗?
- 想在研发群里装?先学会这几个排查K8s问题的办法
- Go单元测试从入门到放弃—0.单元测试基础
- 想不到吧,这些都是 Go 语言的语法糖
- 整洁架构之道--三种经典的编程范式
- 读书分享|高效能人士要培养哪些习惯
- 这道 Go 题目外网超过 80% 的人都答错了,你来试试...
- 图解算法基础--快速排序,附 Go 代码实现