一、简介
(一)什么是高质量:
正确:边界条件是否考虑完备
可靠:异常情况处理,稳定性保证
简洁:易读易维护
(二)编程原则
简单性、可读性、生产力
(三)如何编写高质量代码
1、代码格式
(1)gofmt(自动格式化代码)
不修改源文件只是输出在终端:gofmt 文件名
修改源文件:gofmt -w文件名
(2)goimports(= Gofmt +依赖包管理)
自动增删依赖包的引用
将依赖包按照字母排序分类
2、注释
(1)注释应该解释的内容:作用、如何实现、为什么这样实现、什么情况会出错(限制条件)
(2)注释公共符号:包中声明的每个变量、常量、函数、结构
例外:实现接口方法时,不需要注释,例如可以删除如下注释
// Read implements the io.Reader interfacefunc ( r *FileReader ) Read( buf [ ] byte ) ( int, error )
(3)注释库中提供的函数
3、命名规范
(1)变量
A.缩略词全大写:ServeHTTP而不是ServeHttp
B.缩略词位于变量开头且不需要导出时,全小写:xmlHTTPRequest而不是XMLHTTPRequest
C.变量距离被使用的地方越远,越应该携带更多的上下文信息:全局变量应该命名复杂,而for循环内部的循环变量简洁即可
//例:截止时间命名:deadline time比、t time要更清晰(t可代表任意时间)
(2)函数
A.不携带包名的上下文信息,因为包名和函数名总数成对出现
//例:当包名为http时,函数名为Serve比ServeHTTP更好
B.尽量简短
C.当某函数的返回类型与其包名相同时,可以省略类型信息,如果不相同,可以在函数名中加入类型信息
(3)包
A.只由小写字母组成
B.不与标准库同名
C.使用单数而非复数
D.谨慎使用缩写
4、控制流程
(1)尽量使用线性结构
if foo {return x} else {return nil} //复杂,应该删除else
(2)优先处理错误情况:在一个嵌套的判断中,应该尽早判断错误情况return,而不是一步步判断正确
5、错误和异常处理
(1)简单错误
(2)错误的Wrap和Unwrap
A.错误的Wrap实际上是提供了一个error嵌套另一个error的能力,从而生成一个error的跟踪链
B.在fmt.Errorf中使用: %w关键字来将一个错误关联至错误链中(Go1.13在errors中新增了新API和一个新的format关键字,分别是errors.ls errors.As,errors.Unwrap以及fmt.Errorf的%w。如果项目运行在小于Go113的版本中,导入golang.org/x/xerrors使用。
//示例代码:list,_, err := c.GetBytes(cache.Subkey(a.actionID, “srcfiles"))if err != nil {return fmt.Errorf("reading srcfiles list: 9w", err)
}
(3)错误判定
A.判定一个错误是否为特定错误,使用errors.ls(不同于使用==,使用该方法可以判定错误的所有错误是否含有特定的错误)
//示例代码:
data, err = lockedfile.Read(targ)
if errors.Is(err, fs.ErrNotExist){
// Treat non-existent as empty, to bootstrap the "latest" file
// the first time we connect to a given database.
return []byte{}, nil
}
return data, err
B.在错误链上获取特定种类的错误,使用
errors.As
//示例代码:
if _, err := os.Open( non-existing"); err != nil {
var pathError *fs.PathError
if errors.As(err, &pathError) {
fmt.Println("Failed at path:", pathError.Path)
} else {
fmt.Println(err)
}
}
(4)panic
A.不建议在业务代码中使用panic
B.调用函数不包含recover会造成程序崩溃
C.若问题可以被屏蔽或解决,建议使用error代替panic
D.当程序启动阶段发生不可逆转的错误时,可以在init或main函数中使用panic
//示例代码:
func main(){
//...
ctx, cancel :m context.WithCancel(context.Background())
client,err := sarama.NewConsumerGroup(strings.Split(brokers,",")
,
group, config)
if err im nil {
log.Panicf("Error creating consumer group client: *v", err)
}
//...
}
//Pantcf is equivalent to printf() followed ay a call to panic().
func Panicf(format string, v ...interface{}) {
s :=fmt.Sprintf(format, v...)
std.Output(2,s)panic(s)
}
(5)recover
A.recover只能在被defer的函数中使用嵌套无法生效
B.只在当前goroutine生效
C.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)
}
}
}( )
//
...
}
D.如果需要更多的上下文信息,可以recover后在log中记录当前的调用栈
//示例代码:
func (t *treeFS) Open(name string) (f fs.File, err error) {
defer func(){
if e := recover(); e != nil {
f = nil
err = fmt.Errorf("gitfs panic: v\n’s",e, debug.Stack( ))
}
}()
// ...
}