所有的标识符按照Go规定的标志符命名,即数字,字母下划线,应该以字母开头。

小写,尽量见名思义,看见文件名就可以知道这个文件下的大概内容,对于源代码里的文件,文件名要很好的代表了一个模块实现的功能。

包名

包名用小写,使用短命名,尽量和标准库不要冲突。

接口名

单个函数的接口名以”er”作为后缀,其函数去掉”er”如Reader,Writer。

  1. type Reader interface {
  2. Read(p []byte) (n int, err error)
  3. }

两个函数的接口名综合两个函数的名字,比如:

  1. type WriteFlusher interface {
  2. Write([]byte) (int, error)
  3. Flush() error
  4. }

三个函数及以上的接口名类似于结构体名,比如:

  1. type Car interface {
  2. Start([]byte)
  3. Stop() error
  4. Recover()
  5. }

变量名

  • 全局变量:采用驼峰命名法

  • 局部变量:驼峰式,小写字母开头

常量:大写,采用下划线

函数名

采用驼峰命名法,尽量不要使用下划线,首字母小写为包内可见,首字母大写则可以被包外引用。 函数的返回结果可以给定一个名字,该名字会被初始化,如果函数执行了没有参数的 return 语句,则结果参数的当前值便被作为要返回的值。比如:

  1. func ReadFull(r Reader, buf []byte) (n int, err error) {
  2. for len(buf) > 0 && err == nil {
  3. var nr int
  4. nr, err = r.Read(buf)
  5. n += nr
  6. buf = buf[nr:]
  7. }
  8. return
  9. }

Geter/Seter方法

Go不提供对Get方法和Set方法的自动支持,需要自己编写。

import在多行的情况下,Goimports会自动帮您格式化,在一个文件里面引入了一个package,建议采用如下格式:

  1. import (
  2. "fmt"
  3. )
  1. import (
  2. "encoding/json" //标准库包
  3. "strings"
  4. "myproject/models" //程序内部包
  5. "myproject/controller"
  6. "git.obc.im/obc/utils" //第三方包
  7. "git.obc.im/dep/beego"
  8. "git.obc.im/dep/mysql"
  9. )

导包时,应尽量使用绝对路径:

  1. import "xxxx.com/proj/net" // 正确
  2. import "../net" // 错误

格式

缩进: 代码对齐应该使用table对齐。

行长度: Go没有行长度限制。如果感觉一行太长,可以折成几行,并额外使用一个tab进行缩进。

括号: 控制结构(if/for/switch)的语法不需要括号。

go fmt :可以使用go fmt工具来处理格式问题,比如: 不需要花费时间对结构体中每个域的注释进行排列

  1. type T struct {
  2. value int// its value
  3. }

go fmt会做如下改动:

go fmt默认只对本目录下的.go文件进行格式化,使用go fmt ./…可以递归地对该目录下子文件都进行格式化。

Go有两种注释方式,块注释 / / 和 行注释 // 。 Godoc用来处理Go源文件,抽取有关程序包内容的文档。在顶层声明之前出现,若中间没有换行的注释,会随着声明一起被抽取,作为该项的解释性文本。 每个程序包都应该有一个包注释,位于包声明之前,比如:

  1. /*
  2. Package regexp implements a simple library for regular expressions.
  3. The syntax of the regular expressions accepted is:
  4. regexp:
  5. concatenation { '|' concatenation }
  6. concatenation:
  7. { closure }
  8. closure:
  9. term [ '*' | '+' | '?' ]
  10. '^'
  11. '$'
  12. '.'
  13. character
  14. '[' [ '^' ] character-ranges ']'
  15. '(' regexp ')'
  16. */
  17. package regexp

如果程序包很简单,则包注释可以非常简短:

  1. // Package path implements utility routines for
  2. // manipulating slash-separated filename paths.

函数的注释,第一条语句应该为一条概括语句,并且使用被声明的名字作为开头。比如:

  1. // Compile parses a regular expression and returns, if successful, a Regexp
  2. // object that can be used to match against text.
  3. func Compile(str string) (regexp *Regexp, err error) {}

变量的注释,可以对声明进行组合,比如:

  1. // Error codes returned by failures to parse an expression.
  2. var (
  3. ErrInternal = errors.New("regexp: internal error")
  4. ErrUnmatchedLpar = errors.New("regexp: unmatched '('")
  5. ErrUnmatchedRpar = errors.New("regexp: unmatched ')'")
  6. ...
  7. )

分号

在Go中,应该尽量只在for/if语句中使用分号,比如:

  1. for i := 0; i < 3 ; i++ {
  2. }
  3. if n, ok = info(); ok {
  4. }

控制结构

  1. if i < f() // 错误 词法分析器解析为--> if i < i f();{} 从而报错
  2. {
  3. }
  4. if i < f() { // 正确 词法分析器解析为--> if i < i f(){;} 立面的分号是合法的
  5. }

Go的if语句应该避免使用else语句,比如:

  1. f, err := os.Open(name)
  2. if err != nil {
  3. return err // 出错则不会往下走
  4. }
  5. d, err := f.Stat() // 这里的err不是重新声明,而是重新赋值
  6. if err != nil {
  7. f.Close()
  8. return err
  9. }
  10. codeUsing(f, d)

Go的for语句块统一了c的for和while语句:

  1. // Like a C for
  2. for init; condition; post { }
  3. // Like a C while
  4. for condition { }
  5. for { }
Go的自增/自减操作是语句而不是表达式,因此,当i++是正确的,而++i则会编译不通过。

Go的switch比c更加通用,表达式不需要为常量,甚至不需要为整数,case是按照从上到下的顺序进行求值,直到找到匹配的。如果 switch 没有表达式,则对true进行匹配。可以将大量的if-else-if-else串改写成一个switch,比如:

空白标识符

Go中以_下划线标识来接收空白的内容,相当于Unix系统的一个设备占用符null。当某个函数或者range返回多个值时,若只需要其中一个值,则可以用下划线占位,比如:

  1. if _, err := os.Stat(path); os.IsNotExist(err) {
  2. fmt.Printf("%s does not exist\n", path)
  3. }

禁止编译器对未使用导入包的错误报告,可以用空白标识符来引用一个被导入包中的符号。同样的,将未使用的变量fd赋值给一个空白标识符也可以禁止编译错误,但是一定要有注释:

  1. package main
  2. import (
  3. "io"
  4. "log"
  5. "os"
  6. )
  7. var _ = fmt.Printf // For debugging; delete when done.
  8. var _ io.Reader // For debugging; delete when done.
  9. func main() {
  10. fd, err := os.Open("test.go")
  11. if err != nil {
  12. log.Fatal(err)
  13. }
  14. // TODO: use fd.
  15. _ = fd
  16. }

若不需要使用这些API,为了实现仅为副作用而导入包的操作,可以在导入语句中,将包用空白标识符进行重命名:

  1. import _ "net/http/pprof"

error:error作为函数的值返回,必须尽快对error进行处理,采用独立的错误流进行处理,不要采用下面这种方式:

  1. if err != nil {
  2. // error handling
  3. } else {
  4. // normal code
  5. }

而是采用这种方式:

  1. if err != nil {
  2. // error handling
  3. return // or continue, etc.
  4. }
  5. // normal code

如果返回值需要初始化,则采用下面的方式:

  1. x , err := f()
  2. if err != nil {
  3. // error handling
  4. return // or continue, etc.
  5. }
  6. // use x

Panic:用来创建一个 RuntimeException 并结束当前程序。该函数接受一个任意类型的参数,并在程序挂掉之前打印该参数内容,通常选择一个字符串作为参数。比如:

  1. func init() {
  2. if user == "" {
  3. panic("no value for $USER")
  4. }
  5. }

应该在逻辑处理中禁用panic。在main包中只有当实在不可运行的情况采用panic,例如文件无法打开,数据库无法连接导致程序无法正常运行,但是对于其他的package对外的接口不能有panic,只能在包内采用。建议在main包中使用log.Fatal来记录错误,这样就可以由log来结束程序。

Recover:recover用于捕获runtime的异常,禁止滥用recover,在开发测试阶段尽量不要用recover,recover一般放在你认为会有不可预期的异常的地方。比如:

  1. func server(workChan <-chan *Work) {
  2. for work := range workChan {
  3. go safelyDo(work)
  4. }
  5. }
  6. func safelyDo(work *Work) {
  7. defer func() {
  8. if err := recover(); err != nil {
  9. log.Println("work failed:", err)
  10. }
  11. }()
  12. // do 函数可能会有不可预期的异常
  13. }