我在新工作中写了很多 Go 代码,并且努力理解一个新的代码库。
当我阅读不熟悉的代码时,我喜欢使用打印调试来追踪代码的运行情况。我会打印出当前所在的循环分支、不同变量的值、正在调用的函数等等。有些人喜欢调试器或类似的工具,但对于学习一门新语言来说,它们是需要学习的另一项内容——而打印“Hello World”却是所有语言教程的第一步。
Go 语言内置的打印调试方法是使用fmt.Printf或log.Printf 。这当然没问题,但是我的调试信息会混杂在现有的日志中,因此很难找到,而且这些调试语句很容易在代码审查中被忽略。
相反,我从Ping Yee 的 Python 模块“q”中汲取了灵感。如果你还不熟悉这个模块,我推荐你观看他的闪电演讲,他在演讲中解释了在海量日志中查找单个变量的挫败感。他的模块提供了一个名为qq()函数,可以将任何表达式记录到一个独立的文件中。这个函数编写起来既快捷又简单,而且输出结果与你其他的日志记录完全分离。
我用 Go 语言也做了类似的东西:一个导出单个函数Q()的模块,并将接收到的所有内容记录到/tmp/q.txt中。以下是一个示例:
package main import ( "github.com/alexwlchan/q" "os" ) func printShapeInfo ( name string , sides int ) { qQ ( "a %s has %d sides" , name , sides ) } func main () { qQ ( "hello world" ) qQ ( 2 + 2 ) _ , err := os.Stat ( "does_not_exist.txt" ) qQ ( err ) printShapeInfo ( "triangle" , 3 ) }日志输出package main import ( "github.com/alexwlchan/q" "os" ) func printShapeInfo ( name string , sides int ) { qQ ( "a %s has %d sides" , name , sides ) } func main () { qQ ( "hello world" ) qQ ( 2 + 2 ) _ , err := os.Stat ( "does_not_exist.txt" ) qQ ( err ) printShapeInfo ( "triangle" , 3 ) }
/tmp/q.txt包含了函数名以及传递给Q()表达式:
main : "hello world" main : 2 + 2 = 4 main : err = stat does_not_exist.txt: no such file or directory printShapeInfo : a triangle has 3 sides
我通常打开一个终端窗口运行tail -f /tmp/q.txt来查看q记录的内容。
这个模块只有 120 行 Go 代码,可以在 GitHub 上找到。你可以把它复制到你的项目中,或者它足够简单,你也可以自己编写一个版本。它有两个有趣的想法,或许可以有更广泛的应用。
获取runtime包的上下文
调用Q()时,它会接收最终值——例如,如果调用Q(2 + 2) ,它会接收4但我希望记录原始表达式和函数名。这是 Ping 的 Python 包的一个特性,也是 q 函数如此易用的原因。它为日志消息提供了上下文,省去了您手动输入上下文的麻烦。
我从 Go 的runtime包中获取此信息,特别是runtime.Caller函数,该函数提供有关当前正在运行的函数的信息。
我调用runtime.Caller(1)将调用栈向上移动 1 行,找到我在代码中输入Q().它会告诉我“程序计数器”、文件名和行号。我可以使用runtime.FuncForPC将程序计数器解析为函数名,然后打开文件并查找该行以读取表达式。(这假设源代码自编译以来没有更改,而当我进行本地调试时,这总是成立的。)
不会通过本地 .gitignore 文件影响我的同事
为了使用这个文件,我将q.go复制到我的工作仓库中,并将其添加到.git/info/exclude文件中。后者是一个仅本地生效的忽略文件,与会被提交到仓库的.gitignore文件不同。这意味着我不会意外地将q.go提交到仓库或推送到 GitHub。
这也意味着我不能忘记删除我的调试代码,因为如果我这样做,CI 中的测试将找不到q.go从而导致测试失败。
这样就避免了其他更具破坏性或更令人恼火的方法,例如将其作为项目依赖项或将其添加到共享的.gitignore文件中。
[如果您的RSS阅读器中此文章的格式显示异常,请访问原文]