本文内容摘录于 知名 Go 语言贡献者与布道师 Dave Cheney 发表的名为《The Zen of Go》里面的内容,针对如何编写简单、可读、可维护的 Go 代码给出了11条工程要点。

原文地址: https://the-zen-of-go.netlify.app/

每个包实现单一目标 (Each package fulfils a single purpose)

良好的包设计应该是职责单一,包内相关的行为都所属同一目录。 同时包名应该简洁,明确。 就像使用电梯法则一样, 以最短的时间介绍清楚目标。

A well designed Go package provides a single idea, a set of related behaviours. A good Go package starts by choosing a good name. Think of your package’s name as an elevator pitch to describe what it provides, using just one word.

任何错误都要立即并显示的处理(Handle errors explicitly)

所有的err返回都应该立即,明确的处理,这也是保障程序健壮性的关键手段之一。虽然下面的处理语句 if err != nil { return err } 看起来会很冗余,但也比出了故障再去处理更有价值。 panic 和 recover 处理原则也一样。

Robust programs are composed from pieces that handle the failure cases before they pat themselves on the back. The verbosity of if err != nil { return err } is outweighed by the value of deliberately handling each failure condition at the point at which they occur. Panic and recover are not exceptions, they aren’t intended to be used that way.

尽早return,不要加深嵌套(Return early rather than nesting deeply)

每次缩进都会增加程序员的短期记忆的要求, 所以应该尽可能的避免需要深层缩进的控制流。 与其深入嵌套,不如把明确成功的路径保持在浅层次缩进里返回。

Every time you indent you add another precondition to the programmer’s stack consuming one of the 7 ±2 slots in their short term memory. Avoid control flow that requires deep indentation. Rather than nesting deeply, keep the success path to the left using guard clauses.

并发控制权留给调用者(Leave concurrency to the caller)

让调用者选择是否要异步运行你的库或函数,不要强制他们使用异步。

Let the caller choose if they want to run your library or function asynchronously, don’t force it on them. If your library uses concurrency it should do so transparently.

在启动 goroutine 之前,要知道它什么时候会停止(Before you launch a goroutine, know when it will stop)

goroutines 拥有资源、锁、变量与内存等,释放这些资源的可靠方法是停止 goroutine。

Before you launch a goroutine, know when it will stop Goroutines own resources; locks, variables, memory, etc. The sure fire way to free those resources is to stop the owning goroutine.

避免包级别的状态(Avoid package level state)

要完成明确和减少耦合的操作,需要通过提供类型需要的依赖项作为该类型上的字段,而不是使用包变量。

Seek to be explicit, reduce coupling, and spooky action at a distance by providing the dependencies a type needs as fields on that type rather than using package variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// bad
package counter

var count int

func Increment(n int) int {
count += n
return count
}
// good
package counter

type Counter struct {
count int
}

func (c *Counter) Increment(n int) int {
c.count += n
return c.count
}

简单性很重要(Simplicity matters)

简单性不是老练的代名词。简单并不意味着粗糙,它意味着可读性和可维护性。如果可以选择,请遵循较简单的解决方案。

Simplicity is not a synonym for unsophisticated. Simple doesn’t mean crude, it means readable and maintainable. When it is possible to choose, defer to the simpler solution.

养成编写测试确认API行为的习惯(Write tests to lock in the behaviour of your package’s API)

在引入API功能开发时,不管先后,不管多少,一定要进行测试。测试是确定合约的保证。通过测试来确保功能是预期的,可信任的。

Test first or test later, if you shoot for 100% test coverage or are happy with less, regardless your package’s API is your contract with its users. Tests are the guarantees that those contracts are written in. Make sure you test for the behaviour that users can observe and rely on.

基准测试是验证性能的关键手段(If you think it’s slow, first prove it with a benchmark)

我们总是为了性能问题,承担很多的质疑与成本,而且性能的优化往往会引入破坏抽象、暴露内部和紧密耦合等设计问题,所以先证明是否是性能问题很重要,先证明再做一下步。

So many crimes against maintainability are committed in the name of performance. Optimisation tears down abstractions, exposes internals, and couples tightly. If you’re choosing to shoulder that cost, ensure it is done for good reason.

节制是一种美德(Moderation is a virtue)

用最简单,明了的方式实现才是最好的。 所以请适度使用 goroutine、通道、锁、接口与嵌套。

Use goroutines, channels, locks, interfaces, embedding, in moderation.

可维护性很重要(Maintainability counts)

清晰、可读性、简单性,都是提升可维护性的很重要方面。你辛辛苦苦建造的东西在你离开后还能维持吗?你今天能做些什么来让那些追随你的人更容易?

Clarity, readability, simplicity, are all aspects of maintainability. Can the thing you worked hard to build be maintained after you’re gone? What can you do today to make it easier for those that come after you?