避免用 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)
// ... }