Go语言高质量编码规范|青训营

247 阅读6分钟

一.简介

​ 高质量的编码是指能够达到正确可靠、简洁清晰的目标。首先各种边界条件是否考虑完备,异常情况处理和稳定性保证,易读易维护。遵循的编程原则有简单性,消除"多余的复杂性”,以简单清晰的逻辑编写代码,不理解的代码无法修复改进;可读性,代码是写给人看的,而不是机器,编写可维护代码的第一步是确保代码可读;生产力,团队整体工作效率非常重要。

二.编码规范

1.代码格式

​ 推荐使用gofmt编码工具,因为这是Go语言官方提供的工具,能自动格式化Go语言代码为官方统一风木常见IDE都支持方便的配置。推荐使用goimports,这也是Go语言官方提供的工具,能够自动增删依赖的包引用、将依赖包按字母序排序并分类。

2.注释

​ 为什么要编写注释?首先注释起到解释代码作用,其次注释可以解释代码如何做的,注释可以解释代码实现的原因,注释可以解释代码什么情况会出错。下列列举了一些写注释的作用。

注释的解释代码作用

// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O RDONLY.
// If there is an error, it will be of type *PathError.
func Open(name string) (*File,error) {
    return OpenFile(name,  0_RDONLY,0)
}

注释代码的实现过程

// Add the Referer header from the most recent
// request URL to the new one,if it's not https->http:
if ref :=refererForURL(reqs[len(reqs)-1].URL, req.URL);ref!="" {
    ref != req .Header .Set("Referer", ref )
}

注释解释代码实现的原因

switch resp.StatusCode{
    //...
}case 307308:
   redirectMethod = regMethod
   shouldRedirect = true
   includeBody = true
if ireq.GetBody == nil && ireq.outgoingLength() != 0 {
    // We had a request body,and 307/308 require
    // re-sending it, but GetBody is not defined. So just
    //return this response to theuser instead of an
    // error,like we did in Go 1.7 and earlier.
    shouldRedirect = false
    }
}

注释解释代码什么情况会出错

// parseTimeZone parses a time zone string and returns its length. Time zones
// are human-generated and unpredictable. We can't do precise error checking.
// On the other hand, for a correct parse there must be a time zone at the
// beginning of the string, so it's almost always true that there's one
// there. We look at the beginning of the string for a run of upper-case letter
// If there are more than 5,it's an error.
// If there are 4 or 5 and the last is a T,it's a time zone.
//If there are 3,it's a time zone.
// 0therwise,other than special cases, it's not a time zone
// GMT is special because it can have an hour offset.
func parseTimeZone(value string) (length int, ok bool)

公共符号始终要注释

​ 包中声明的每个公共的符号:变量、常量、函数以及结构都需要添加注释。任何既不明显也不简短的公共功能必须予以注释。无论长度或复杂程度如何,对库中的任何函数都必须进行注释。

// ReadAll reads from r until an error or EOF and returns thedata it read.
// A successful call returns err == nil,not err == EOF. BecauseReadAll is
// defined to read from src until EOF,it does not treat an EOFfrom Read
// as an error to be reported
func ReadAll(r Reader) ([]byte, error)

例外:不需要注释实现接口的方法,具体如下

// Read implements the io.Reader interface
func (r *FileReader) Read( buf []byte) (int, error)
3.命名规范

​ 变量: 简洁胜于冗长;缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写 例如使用 ServeHTTP 而不是 ServeHttp,使用 XMLHTTPRequest 或者 xmIHTTPRequest;变量距离其被使用的地方越远,则需要携带越多的上下文信息;全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义。

函数:函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的;函数名尽量简短;当名为 foo 的包某个函数返回类型 Foo 时,可以省略类型信息而不导致歧义;当名为 foo 的包某个函数返回类型 T 时 (T 并不是 Foo),可以在函数名中加入类型信息。

​ 包:只由小写字母组成;不包含大写字母和下划线等字符简短并包含一定的上下文信息,例如 schema、task 等;不要与标准库同名,例如不要使用 sync 或者 strings。

4.控制流程

避免嵌套,保持正常流程清晰。

//错误写法
if foo {
   return x
} else {
   return nil
}
//正确写法
if foo {
   return x
}
return nil

​ 尽量保持正常代码路径为最小缩进。最常见的正常流程的路径被嵌套在两个 if 条件内;成功的退出条件是 return nil,必须仔细匹配大括号来发现;函数最后一行返回一个错误,需要追溯到匹配的左括号,才能了解何时会触发错误;如果后续正常流程需要增加一步操作,调用新的函数,则又会增加一层嵌套。

//错误写法
func OneFunc() error {
    err := doSomething()
    if err == nil {
		err := doAnotherThing( )
        if err == nil{
            return nil // normal case
        }
        return err
    }
	return err
}

//正确写法
func OneFunc() error {
    if err := doSomething(); err != nil {
        return err
    }
	if err := doAnotherThing(); err != nil {
        return err
    }
    return nil // normal case
}
5.错误和异常处理

​ 简单错误。简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错。优先使用 errors.New 来创建匿名变量来直接表示简单错误。如果有格式化的需求,使用 fmt.Errorf。

func defaultCheckRedirect(req *Request, via []xRequest) error {
    if len(via) >= 10{
        return errors.New("stopped after 10 redirects")
    }
    return nil
}

​ 错误的Wrap和Unwrap。错误的 Wrap 实际上是提供了一个 error 嵌套另一个error 的能力,从而生成一个 error 的跟踪链。在 fmt.Errorf 中使用: % w 关键字来将一个错误关联至错误链中。

list, -, err := c.GetBytes( cache.Subkey(a.actionID,"srcfiles"))
if err != nil {
    return fmt.Errorf("reading srcfiles list: %w", err)
}

​ 真正异常。不建议在业务代码中使用 真正异常。调用函数不包含 recover :会造成程序崩溃。若问题可以被屏蔽或解决,建议使用error 代替 panic。当程序启动阶段发生不可逆转的错误时可以在 init 或 main 函数中使用 panic。

func main() {
    ctx cancel := context.withCancel(context.Background())
    client, err := sarama.NewConsumerGroup(strings.Split(brokers,,group, config)
    if err != nil {
        log.Panicf("Error creating consumer group client: %v" err)
    }
    //...
}
// Panicf is equivalent to Printf() followed by a call to panic().
func Panicf(format string, v ...interfacef}) {
    s := fmt.Sprintf( format, v...)
    std.Output(2,s )
    panic(s)
}

生效范围。生效范围 只能在被 defer 的函数中使用。嵌套无法生效。只在当前 goroutine 生效。defer 的语句是后进先出。

func (s *ss) Token( skipSpace bool, f func(rune) bool)
(tok []byte, err error) {
    defer func() {
        if e := recover(); e != nil {
            if se, ok := e.(scanError); ok {
                err = se.err
            }else {
				panic(e)
            }
        }
    }()
    //...
)