Go反模式之越俎代庖
按《AntiPatterns》作者的说法,可以用至少两个关键因素来把反面模式和不良习惯、错误的实践或糟糕的想法区分开来:
- 行动、过程和结构中的一些重复出现的乍一看是有益的,但最终得不偿失的模式
- 在实践中证明且可重复的清晰记录的重构方案
维基百科上列出了一些反模式列表: 反面模式
, 我开了☝系列,用来记录Go语言开发中的一些反模式。
这是第一篇,介绍 越俎代庖
反模式,或者叫做 画蛇添足
反模式,或者叫做 镀金
反模式( Gold plating
)。 意思是指项目已经达到了设计的最高价值,结果还添加额外的功能,反而使项目变得很差。
当然,本文以及后续文章中的实例可能会引起争议,欢迎在评论中讨论。你如果也发现了一些Go的反模式,也欢迎留言。
golang/glog
,这是Google开源的一个log库,可以实现多级的log日志输出。它实现了 google/glog
相同行为的日志输出。
项目介绍说这个项目的源代码master在Google内部。github上的目前处于不维护的状态,最新同步都是四年前了。
首先,我们说一下这个库的好处,简单好用,可以根据日志级别进行设置,而且带文件输出功能。
你可以写一个简单的程序测试一下:
package main import ( "flag" "github.com/golang/glog" ) func main() { flag.Parse() defer glog.Flush() glog.Infof("hello %s", "glog") }
然后运行 go run main.go
看看效果。
什么?没有任何日志输出,再尝试 go run main.go -stderrthreshold INFO
试试:
➜ go run main.go -stderrthreshold INFO I0526 19:46:05.793886 11860 main.go:14] hello glog
这次终于看到日志了。
如果你运行 go run main.go
,你会看到你的程序莫名其妙的加了几个参数:
➜ abc go run main.go -h -alsologtostderr log to standard error as well as files -log_backtrace_at value when logging hits line file:N, emit a stack trace -log_dir string If non-empty, write log files in this directory -logtostderr log to standard error instead of files -stderrthreshold value logs at or above this threshold go to stderr -v value log level for V logs -vmodule value comma-separated list of pattern=N settings for file-filtered logging
这就是我们介绍的反模式。本来glog作为一个库提供给其它人使用,但是却额外偷偷的在命令行参数中注入了几个参数,这种强迫并且非显式的方式并不是作为库的好的行为。
这种方式并没有在库的使用者允许的情况下就注入额参数,一是污染了使用者的命令行解析方式,二是给使用者一个风险提示,这个库是否是安全的,会不会偷偷注入恶意代码?
更大的问题是命令行参数冲突。 假设你要为你的程序提供一个查看版本的功能,使用者可以使用 main -v
显示版本号:
var ( v = flag.Bool("v", false, "show version") ) func main() { flag.Parse() if *v { fmt.Println("1.0.0") } defer glog.Flush() glog.Infof("hello %s", "glog") }
如果你运行上面的程序,会panic失败:
➜ abc go run main.go /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/go-build692968448/b001/exe/main flag redefined: v panic: /var/folders/gq/jd9v5dd95p570hkztblb8ht40000gn/T/go-build692968448/b001/exe/main flag redefined: v goroutine 1 [running]: flag.(*FlagSet).Var(0xc00005a180, 0x1113000, 0xc00001c11c, 0x10f15d8, 0x1, 0x10f28a3, 0xc) /usr/local/go/src/flag/flag.go:851 +0x4b8 flag.(*FlagSet).BoolVar(...) /usr/local/go/src/flag/flag.go:624 flag.(*FlagSet).Bool(0xc00005a180, 0x10f15d8, 0x1, 0x0, 0x10f28a3, 0xc, 0x6) /usr/local/go/src/flag/flag.go:637 +0x8a flag.Bool(0x10f15d8, 0x1, 0x11a6200, 0x10f28a3, 0xc, 0xe) /usr/local/go/src/flag/flag.go:644 +0x5e main.init() /Users/abc/go/src/github.com/abc/abc/main.go:11 +0x50 exit status 2
原因在于glog定义了一个 v
参数,而你也定义了一个 v
参数,导致冲突。可是 v
是很通用的一个查看版本的参数,这也意味着你不得不改个参数名称。
很显然, glog
库把一些本来使用者需要决定的事情给实现了,本来移除掉这些代码,或者单独抽取出独立的函数,或者使用者可以定制参数,都是比这种私自决定的方式好很多。
同样的, vitess
也有同样的问题。
当然,vitess作为一个独立的工具,而不是库来说,问题不大,因为代码不会作为库使用。但是实际上很多项目也使用vitness的代码,这也会导致问题。