产品环境中 Go 语言的最佳实践(2)

避免用 make 和 new,除非他们是必要的(new(int),或 make(Chan int)),或者我们能提前知道要分配的东西的尺寸( make(map[int]string,n),或 make([]int,0,256))。

使用 struct{} 作为标记值,而不是布尔或接口{}。例如,集合是 map[string]struct{};信道是 chan struct{}。它明确标明了信息的明确缺乏。

打断长行的参数也很好。那更象是Java的风格:

// 不要这样。 func process(dst io.Writer, readTimeout,

    writeTimeout time.Duration, allowInvalid bool,

        max int, src <-chan util.Job) {

    // ... }

这样会更好:

func process(

    dst io.Writer,

    readTimeout, writeTimeout time.Duration,

    allowInvalid bool,

    max int,

    src <-chan util.Job,

) {

    // ... }

当构造对象时也同样分为多行:

f := foo.New(foo.Config{


    Site: "zombo.com",


    Out:  os.Stdout,


    Dest: conference.KeyPair{


        Key:  "gophercon",

        Value: 2014,

    },

})

另外,当分配新的对象时,在初始化部分传递成员值(如上面)比下面这样过后设置要好。

// 不要这样。 f := &Foo{} // or, even worse: new(Foo) f.Site = "zombo.com"

f.Out = os.Stdout

f.Dest.Key = "gophercon"

f.Dest.Value = 2014

配置

我们尝试了通过多种方式向Go程序传递配置:解析配置文件,用 os.Getenv 直接从环境中提取配置,各种增值flag解析包。最后,最合乎经济原则的就是普通的package flag,它的严格类型和简单语义对我们所需的一切都绝对够用而且够好。

我们主要部署12-Factor 的应用,12-Factor 应用程序通过环境传递配置。但即使这样,我们也使用一个启动脚本来把环境变量转换为flags。Flags作为程序及其运行环境之间的一个明确和全文档化的表面区域。他们对于了解和操作程序来说是非常宝贵的。

一个关于flags的不错的习惯是把他们定义到你的main函数中。这样就能防止你在代码中随意的将他们作为全局变量使用,这使你严格的遵守依赖注入从而方便测试。

func main() {

    var (

        payload = flag.String("payload", "abc", "payload data")

        delay  = flag.Duration("delay", 1*time.Second, "write delay")

    )

    flag.Parse()

    // ... }

日志和遥测

我们尝试过几个日志框架,他们提供像日志级别,调试,路由输出,自定义格式化等等功能。最终我们选定package log。因为我们只记录可操作信息。 这意味着需要人工处理的 serious, panic级别的错误,或者结构化数据会被其他机器消耗。 举个例子,搜索转发器发送每一个它使用上下文信息处理的请求,因此我们的分析工作流可以看到新西兰的人们经常搜索 Lorde, 或者随便什么。

我们考虑到遥测,在一个运行过程中释放出的任何其他量:请求响应时间,QPS,运行错误,队列深度等等。并且遥测基本上包括两种模式:push和pull。

push意味着释放指标到一个已知的系统。例如Graphite, Statsd, and AirBrake

pull意味着在一些已知的位置暴露指标,并允许已知的系统去擦除它们。例如,expvar和Prometheus(或许还有其他的)

当然两种方式都有自己的存在性。当你开始使用时,push是直观和简单的。但是推送指标的增长却有悖常理:你得到的越大,成本越高。我们过去发现在特定规模大小的基础设施上,pull是该尺度下的唯一模型。那也有许多值能反映一个运行的系统。所以,最好的实践是:expvar或者类似风格的。

测试和验证

在一年的过程中我们尝试了许多的测试库和框架,但是很快放弃了他们中的大部分,今天我们所有的测试通过数据驱动(表驱动)测试,用普通的包测试。我们没有强烈或者明确的抱怨测试/检查包,除此之外,他们根本没有提供巨大的价值。有一件事情是有帮助的:让你更简单的对任意值进行比较(例如expected对got)。

包测试是面向单元测试的,对于集成测试,就会有点麻烦。运行的外部服务依赖于你的集成环境,但是我们找到了一个好的方式集成他们。写一个integration_test.go,给它一个integration的构建标签。定义(全局)标志,比如服务地址和连接字符串,用他们在你的测试中。

// +build integration

var fooAddr = flag.String(...)

 

func TestToo(t *testing.T) {

    f, err := foo.Connect(*fooAddr)

    // ... }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/f83da06d239a59aa25b6f62cdc715281.html