目录

Examples

在添加新包时,包括预期使用的示例:

  • 一个可运行的示例
  • 或一个演示完整调用序列的简单测试

Goroutine Lifetimes

goroutines
goroutinegoroutine
goroutinepanicgoroutines
goroutinegoroutines
go
// GoSafe runs the given fn using another goroutine, recovers if fn panics.
func GoSafe(log *log.Logger, fn func()) {
   go RunSafe(log, fn)
}
// RunSafe runs the given fn, recovers if fn panics.
func RunSafe(log *log.Logger, fn func()) {
   defer Recover(log)
   fn()
}
// Recover is used with defer catch panics.
func Recover(log *log.Logger) {
   if p := recover(); p != nil {
      log.Error("%s\n%s", p, debug.Stack())
   }
}

Handle Errors

_panic

可以参考官方文档中优雅的做法:Effective Go

import

避免重命名导入,除非是为了避免名称冲突

好的包名不需要重命名。如果发生冲突,最好重命名、本地的或特定于项目的导入。

导入按组区分,组与组之间有空白行。标准库包总是在第一组中。

package main
import (
	"fmt"
	"hash/adler32"
	"os"
	"appengine/foo"
	"appengine/user"
	"github.com/foo/bar"
	"rsc.io/goversion/version"
)

使用 goimport 能帮助到您,所以在项目中配置 goimport 是必要的。

Import Blank

仅为其副作用而导入的包(使用import _ "pkg"语法)应该只在程序的主包中导入,或在需要它们的测试中导入。

init

Import Dot

import .
package foo_test
import (
	"bar/testutil" // also imports "foo"
	. "foo"
)
foobar/testutilfooimport .fooimport .Quux

In-Band Errors

C
// Lookup returns the value for key or "" if there is no mapping for key.
func Lookup(key string) string
// Failing to check for an in-band error value can lead to bugs:
Parse(Lookup(key))  // returns "parse failure for value" instead of "no value for key"
Go
// Lookup returns the value for key or ok=false if there is no mapping for key.
func Lookup(key string) (value string, ok bool)

这可以防止调用者错误地使用返回值:

Parse(Lookup(key))  // compile-time error

并鼓励更健壮和可读的代码:

value, ok := Lookup(key)
if !ok {
	return fmt.Errorf("no value for %q", key)
}
return Parse(value)

该规则适用于导出函数,但也适用于私有函数。

当它们是函数的有效结果时,像 nil、""、0 和 -1 这样的值是可以的,也就是说,当调用者不需要以不同于其他值的方式处理它们时。

strings

Indent Error Flow

尽量将正常的代码路径缩进到最小,并缩进错误处理,首先处理它。这通过允许快速视觉扫描正常路径提高了代码的可读性。例如,不要写:

if err != nil {
	// error handling
} else {
	// normal code
}

相反,写:

if err != nil {
	// error handling
	return // or continue, etc.
}
// normal code
if
if x, err := f(); err != nil {
	// error handling
	return
} else {
	// use x
}

然后,这可能需要将短变量声明移动到它自己的行:

x, err := f()
if err != nil {
	// error handling
	return
}
// use x

Initialisms

"URL""NATO""URL""URL""url""urlPony""URLPony""Url"ServeHTTPServeHttp"xmlHTTPRequest""XMLHTTPRequest"
"ID""identifier""ID""ego""superego""ID""appID""appId"
protobuf

Interfaces

Go
"mock"

不要在使用之前定义接口:没有一个实际的使用示例,很难判断接口是否必要,更不用说它应该包含哪些方法了。(有点重构的意思,将已有的方法抽象起来)

package consumer  // consumer.go
type Thinger interface { Thing() bool }
func Foo(t Thinger) string { … }
package consumer // consumer_test.go
type fakeThinger struct{ … }
func (t fakeThinger) Thing() bool { … }
…
if Foo(fakeThinger{…}) == "x" { … }
// DO NOT DO IT!!!
package producer
type Thinger interface { Thing() bool }
type defaultThinger struct{ … }
func (t defaultThinger) Thing() bool { … }
func NewThinger() Thinger { return defaultThinger{ … } }

相反,返回一个具体类型,并让使用者模拟生产者实现。

package producer
type Thinger struct{ … }
func (t Thinger) Thing() bool { … }
func NewThinger() Thinger { return Thinger{ … } }

Line Length

在Go代码中没有严格的行长限制,但是要避免令人不舒服的长行。类似地,当行更长的时候,不要添加换行符以保持行短——例如,如果它们是重复的。

大多数情况下,当人们“不自然地”换行时(或多或少地在函数调用或函数声明中换行,尽管存在一些异常),如果它们有合理数量的参数和合理较短的变量名,换行将是不必要的。较长的行似乎和较长的名字联系在一起,去掉长名字会有很大帮助。

换句话说,断行是因为你所写的内容的语义(作为一般规则),而不是因为行长。如果您发现这产生了太长行,那么更改名称或语义,您可能会得到一个好的结果。

实际上,这与函数的长度是完全相同的。没有“函数永远不会超过N行的规则”,但确实存在过长的函数,以及重复过小的函数,解决方法是改变函数的边界,而不是开始计数行数。

Mixed Caps

Named Result Parameters

godoc
func (n *Node) Parent1() (node *Node) {}
func (n *Node) Parent2() (node *Node, err error) {}
godoc
func (n *Node) Parent1() *Node {}
func (n *Node) Parent2() (*Node, error) {}

另一方面,如果函数返回两个或三个相同类型的参数,或者如果从上下文不清楚结果的含义,添加名称在某些上下文中可能是有用的。不要为了避免在函数内部声明 var 而命名结果参数;以不必要的API冗长为代价来换取较小的实现简便性。

func (f *Foo) Location() (float64, float64, error)

不如:

// Location returns f's latitude and longitude.
// Negative values mean south and west, respectively.
func (f *Foo) Location() (lat, long float64, err error)

如果函数只有几行,裸返回是可以的。一旦它是一个中等大小的函数,就要显式地显示返回值。推论:仅仅因为结果参数允许使用裸返回而命名结果参数是不值得的。清晰的文档总是比在函数中节省一两行更重要。

最后,在某些情况下,您需要命名结果参数,以便在延迟闭包中更改它。这总是可以的。

Naked Returns

没有参数的语句返回指定的返回值。这就是所谓的“裸返回”。

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

Package Comments

godocpackage
// Package math provides basic constants and mathematical functions.
package math
/*
Package template implements data-driven templates for generating textual
output such as HTML.
....
*/
package template

对于 "package main" 注释,其他风格的注释在二进制名称之后也可以(如果它在前面,则可以大写),例如,对于您可以编写的目录中的

// Binary seedgen ...
package main
// Command seedgen ...
package main
// Program seedgen ...
package main
// The seedgen command ...
package main
// The seedgen program ...
package main
// Seedgen ..
package main

这些都是例子,合理的变体也是可以接受的。

注意,以小写单词开头的句子不属于包注释的可接受选项,因为它们是公开可见的,应该用正确的英语编写,包括句子的第一个单词大写。当二进制名称是第一个单词时,即使它与命令行调用的拼写不严格匹配,也需要将其大写。

struct

每个结构体必须有自己的构造函数,并且使用 options 模式来构建新的参数。

您可能感兴趣的文章: