钱文翔的博客

(翻译)go-styleguide

原文在这里: https://github.com/bahlo/go-styleguide/blob/master/README.md

Go Styleguide Go的风格指南

This serves as a supplement to
Effective Go, based on years of
experience and inspiration/ideas from conference talks.

这篇文章作为Effective Go的补充,这是根据多年的经验和来自会议讨论的灵感/想法来写的。

Table of contents 目录

Add context to errors 给errors添加上下文

Don’t:

1
2
3
4
file, err := os.Open("foo.txt")
if err != nil {
return err
}

Using the approach above can lead to unclear error messages because of missing
context.

使用上面的方法会因为缺少上下文而产生不明确的错误消息。

Do:

1
2
3
4
5
6
7
8
import "github.com/pkg/errors" // for example

// ...

file, err := os.Open("foo.txt")
if err != nil {
return errors.Wrap(err, "open foo.txt failed")
}

Wrapping errors with a custom message provides context as it gets propagated up
the stack.

用通用的消息包裹错误信息从而提供上下文信息,可以在堆栈返回时传递此错误。

This does not always make sense.

这不总是有意义

If you’re unsure if the context of a returned error is at all times sufficient,
wrap it.
Make sure the root error is still accessible somehow for type checking.

如果您不确定返回的错误的上下文是否足够,那就加上上下文信息。
确保根错误通过某种方式进行类型检查时仍然有意义。

Dependency management 依赖管理

Use dep 使用dep

Use dep, since it’s production ready and will
soon become part of the toolchain.

使用dep,因为它已经是生成级工具并且很快就会成为工具链的一部分。
Sam Boyer at GopherCon 2017

Use Semantic Versioning 使用Semantic Versioning

Since dep can handle versions, tag your packages using
Semantic Versioning.

因为dep可以处理版本,所以使用Semantic Versioning给你的包加上
标签

Avoid gopkg.in 避免使用gopkg.in

While gopkg.in is a great tool and was really
useful, it tags one version and is not meant to work with dep.
Prefer direct import and specify version in Gopkg.toml.

尽管gopkg.in是非常好的工具并且真的很好用,但是它只标注一个版本
并且使用dep。应该直接import并在Gopkg.toml指定版本

Structured logging 结构化logging

Don’t:

1
2
3
log.Printf("Listening on :%d", port)
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
// 2017/07/29 13:05:50 Listening on :80

Do:

1
2
3
4
5
6
7
8
9
10
11
12
import "github.com/uber-go/zap" // for example

// ...

logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Server started",
zap.Int("port", port),
zap.String("env", env),
)
http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
// {"level":"info","ts":1501326297.511464,"caller":"Desktop/structured.go:17","msg":"Server started","port":80,"env":"production"}

This is a harmless example, but using structured logging makes debugging and log
parsing easier.

这是没有损害的例子,但是使用结构化的日志可以使调试和解析log更简单

Avoid global variables 避免使用全局变量

Don’t:

1
2
3
4
5
6
7
8
9
10
11
var db *sql.DB

func main() {
db = // ...
http.HandleFunc("/drop", DropHandler)
// ...
}

func DropHandler(w http.ResponseWriter, r *http.Request) {
db.Exec("DROP DATABASE prod")
}

Global variables make testing and readability hard and every method has access
to them (even those, that don’t need it).

全局变量使得测试和可读性变得更加困难。每一个方法都能访问他(即使那些不需要它的方法)

Do:

1
2
3
4
5
6
7
8
9
10
11
func main() {
db := // ...
http.HandleFunc("/drop", DropHandler(db))
// ...
}

func DropHandler(db *sql.DB) http.HandleFunc {
return func (w http.ResponseWriter, r *http.Request) {
db.Exec("DROP DATABASE prod")
}
}

Use higher-order functions instead of global variables to inject dependencies
accordingly.

因此使用高阶函数取代全局变量来加入依赖性。

Testing 测试

Use assert-libraries 使用assert-libraries

Don’t:

1
2
3
4
5
6
7
func TestAdd(t *testing.T) {
actual := 2 + 2
expected := 4
if (actual != expected) {
t.Errorf("Expected %d, but got %d", expected, actual)
}
}

Do:

1
2
3
4
5
6
7
import "github.com/stretchr/testify/assert" // for example

func TestAdd(t *testing.T) {
actual := 2 + 2
expected := 4
assert.Equal(t, expected, actual)
}

Using assert libraries makes your tests more readable, requires less code and
provides consistent error output.

使用断言库让你的测试更有可读性,代码量更少并且提供一致的错误输出。

Use table driven tests 使用表驱动测试

Don’t:

1
2
3
4
5
6
func TestAdd(t *testing.T) {
assert.Equal(t, 1+1, 2)
assert.Equal(t, 1+-1, 0)
assert.Equal(t, 1, 0, 1)
assert.Equal(t, 0, 0, 0)
}

The above approach looks simpler, but it’s much harder to find a failing case,
especially when having hundreds of cases.

以上的方法看起来更简单,但是它更难找到错误样例,尤其是在有几百个样例的时候。

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TestAdd(t *testing.T) {
cases := []struct {
A, B, Expected int
}{
{1, 1, 2},
{1, -1, 0},
{1, 0, 1},
{0, 0, 0},
}

for _, tc := range cases {
t.Run(fmt.Sprintf("%d + %d", tc.A, tc.B), func(t *testing.T) {
assert.Equal(t, t.Expected, tc.A+tc.B)
})
}
}

Using table driven tests in combination with subtests gives you direct insight about which case is failing and which cases are tested.
Mitchell Hashimoto at GopherCon 2017

使用表驱动测试结合子测试让你直观的看到哪一个册书样例失败了,哪个测试样例正在执行

Avoid mocks

Don’t:

1
2
3
4
func TestRun(t *testing.T) {
mockConn := new(MockConn)
run(mockConn)
}

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TestRun(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
t.AssertNil(t, err)

var server net.Conn
go func() {
defer ln.Close()
server, err := ln.Accept()
t.AssertNil(t, err)
}()

client, err := net.Dial("tcp", ln.Addr().String())
t.AssertNil(err)

run(client)
}

Only use mocks if not otherwise possible, favor real implementations. – Mitchell Hashimoto at GopherCon 2017

只有在没有其他机会的时候使用mock,否则尽量使用真实实现。

Avoid DeepEqual 避免DeepEqual

Don’t:

1
2
3
4
5
6
7
8
9
10
11
type myType struct {
id int
name string
irrelevant []byte
}

func TestSomething(t *testing.T) {
actual := &myType{/* ... */}
expected := &myType{/* ... */}
assert.True(t, reflect.DeepEqual(expected, actual))
}

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type myType struct {
id int
name string
irrelevant []byte
}

func (m *myType) testString() string {
return fmt.Sprintf("%d.%s", m.id, m.name)
}

func TestSomething(t *testing.T) {
actual := &myType{/* ... */}
expected := &myType{/* ... */}
if actual.testString() != expected.testString() {
t.Errorf("Expected '%s', got '%s'", expected.testString(), actual.testString())
}
// or assert.Equal(t, actual.testString(), expected.testString())
}

Using testString() for comparing structs helps on complex structs with many fields that are not relevant for the equality check.
This approach only makes sense for very big or tree-like structs.
Mitchell Hashimoto at GopherCon 2017

使用 testing()来比较结构体,在结构体很复杂且结构体中的许多字段不用于比较相等的时候很有用。这个方法对于大结构体或者树状结构体很有用。

Avoid testing unexported funcs 不要测试不可导出的函数

Only test unexported funcs if you can’t access a path via exported funcs. Since they are unexported, they are prone to change.

只有当你不能通过导出的函数访问不可导出函数的时候,测试不可导出的函数。因为他们不能被导出,所有会改变。

Use linters 使用linters

Use linters (e.g. gometalinter) to
lint your projects before committing.

在你的项目提交之前使用linters检查代码

Use gofmt 使用gofmt

Only commit gofmt’d files, use -s to simplify code.

只提交gofmt后的文件,使用 -s 参数来简化代码。

Avoid side-effects 避免side-effects

Don’t:

1
2
3
func init() {
someStruct.Load()
}

Side effects are only okay in special cases (e.g. parsing flags in a cmd). If you find no other way, rethink and refactor.
Side effect只有在特殊情况下可以(例如在cmd中解析flags)。如果你想不到其他方法,重新想或者重构。

Favour pure funcs 尽量使用纯函数

In computer programming, a function may be considered a pure function if both of the following statements about the function hold:

在计算机编程中,如果函数包含以下的描述,则应该当做是一个纯函数:

  1. The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices.

函数总是在传递相同的参数时得出相同的值。函数的结果不依赖于程序执行过程中的任何隐藏的信息或者状态,也不依赖来自I/O设备的外部的输入。

  1. Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.

结果的计算不会导致任何明显的语义上的副作用或者输出,例如可变对象的变化或输出到输入/输出设备。

Wikipedia

Don’t:

1
2
3
4
5
6
7
8
func MarshalAndWrite(some *Thing) error {
b, err := json.Marshal(some)
if err != nil {
return err
}

return ioutil.WriteFile("some.thing", b, 0644)
}

Do:

1
2
3
4
5
6
// Marshal is a pure func (even though useless)
func Marshal(some *Thing) ([]bytes, error) {
return json.Marshal(some)
}

// ...

This is obviously not possible at all times, but trying to make every possible func pure makes code more understandable and improves debugging.

显然不能任何时候都这么写,但是尽量使每一个函数都是纯函数,让代码更加易懂且易调试。

Don’t over-interface 不要过度接口化

Don’t:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Server interface {
Serve() error
Some() int
Fields() float64
That() string
Are([]byte) error
Not() []string
Necessary() error
}

func debug(srv Server) {
fmt.Println(srv.String())
}

func run(srv Server) {
srv.Serve()
}

Do:

1
2
3
4
5
6
7
8
9
10
11
type Server interface {
Serve() error
}

func debug(v fmt.Stringer) {
fmt.Println(v.String())
}

func run(srv Server) {
srv.Serve()
}

Favour small interfaces and only expect the interfaces you need in your funcs.

使用更小的接口且在你的函数中(的形参中)只要求你需要的接口.

Don’t under-package

Deleting or merging packages is fare more easier than splitting big ones up. When unsure if a package can be split, do it.

删除或者合并包比从中分离包简单的多。当你不确定一个包是否能被分离,那你可以去去试试看

Handle signals 处理信号

Don’t:

1
2
3
4
5
6
func main() {
for {
time.Sleep(1 * time.Second)
ioutil.WriteFile("foo", []byte("bar"), 0644)
}
}

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
logger := // ...
sc := make(chan os.Signal)
done := make(chan bool)

go func() {
for {
select {
case s := <-sc:
logger.Info("Received signal, stopping application",
zap.String("signal", s.String()))
break
default:
time.Sleep(1 * time.Second)
ioutil.WriteFile("foo", []byte("bar"), 0644)
}
}
done <- true
}()

signal.Notify(sc, os.Interrupt, os.Kill)
<-done // Wait for go-routine
}

Handling signals allows us to gracefully stop our server, close open files and connections and therefore prevent file corruption among other things.

处理信号可以使用安全的退出服务,关闭打开的文件和连接。防止因为其他事情损坏文件。

Divide imports 分开 imports

Don’t:

1
2
3
4
5
6
7
import (
"encoding/json"
"github.com/some/external/pkg"
"fmt"
"github.com/this-project/pkg/some-lib"
"os"
)

Do:

1
2
3
4
5
6
7
8
9
import (
"encoding/json"
"fmt"
"os"

"github.com/some/external/pkg"

"github.com/this-project/pkg/some-lib"
)

Dividing std, external and internal imports improves readability.

将std,外部和内部imports分开,提高可读性

Avoid unadorned return 避免不加修饰的return

Don’t:

1
2
3
4
func run() (n int, err error) {
// ...
return
}

Do:

1
2
3
4
func run() (n int, err error) {
// ...
return n, err
}

Named returns are good for documentation, unadorned returns are bad for readability and error-prone.

命名的返回值对于文档很有用,不加修饰的返回不利于可读性且易错

Use package comment 使用包注释

Don’t:

1
package sub

Do:

1
package sub // import "github.com/my-package/pkg/sth/else/sub"

Adding the package comment adds context to the package and makes importing easy.

包注释给包添加了上下文,并使用导入更加容易。

Avoid empty interface 避免空的接口

Don’t:

1
2
3
func run(foo interface{}) {
// ...
}

Empty interfaces make code more complex and unclear, avoid them where you can.

空接口是代码更加的复杂,能不使用就不使用。

Main first

Don’t:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main // import "github.com/me/my-project"

func someHelper() int {
// ...
}

func someOtherHelper() string {
// ...
}

func Handler(w http.ResponseWriter, r *http.Reqeust) {
// ...
}

func main() {
// ...
}

Do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main // import "github.com/me/my-project"

func main() {
// ...
}

func Handler(w http.ResponseWriter, r *http.Reqeust) {
// ...
}

func someHelper() int {
// ...
}

func someOtherHelper() string {
// ...
}

Putting main() first makes reading the file a lot more easier. Only the init() function should be above it.

将main函数放在最开始,可以使你阅读这个文件更加简单。只有init()函数应该放在main上面。

Use internal packages 使用内部包

If you’re creating a cmd, consider moving libraries to internal/ to prevent import of unstable, changing packages.

如果你创建一个cmd,尝试将包放到internal/中,避免这个不稳定且经常改动的包被其他地方import

Avoid helper/util

Use clear names and try to avoid creating a helper.go, utils.go or even package.

使用清楚的名字,尽量避免创建helper.goutils.go这样的文件或者package

Embed binary data 内置二进制数据

To enable single-binary deployments, use tools to add templates and other static
assets to your binary
(e.g. github.com/jteeuwen/go-bindata).

为了能够有单二进制文件部署,使用工具添加模板和其他静态文件资源到你的二进制文件中。

Use decorator pattern 使用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
type Config struct {
port int
timeout time.Duration
}

type ServerOpt func(*Config)

func WithPort(port int) ServerOpt {
return func(cfg *Config) {
cfg.port = port
}
}

func WithTimeout(timeout time.Duration) ServerOpt {
return func(cfg *Config) {
cfg.timeout = timeout
}
}

func startServer(opts ...ServerOpt) {
cfg := new(Config)
for _, fn := range opts {
fn(cfg)
}

// ...
}

func main() {
// ...
startServer(
WithPort(8080),
WithTimeout(1 * time.Second),
)
}