2

Go反模式之越俎代庖

 4 years ago
source link: https://colobu.com/2020/05/26/golang-anti-pattern-yuezudaipao/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

反模式(anti-pattern或antipattern)又叫做反面模式,指的是在实践中经常出现但又低效或是有待优化的设计模式,是用来解决问题的带有共同性的不良方法。Andrew Koenig在1995年造了anti-pattern这个词,灵感来自于GoF的《设计模式》一书。

按《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的代码,这也会导致问题。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK