高质量编程简介及编码规范 | 青训营

68 阅读4分钟

一、简介

(一)什么是高质量:

正确:边界条件是否考虑完备

可靠:异常情况处理,稳定性保证

简洁:易读易维护

(二)编程原则

简单性、可读性、生产力

(三)如何编写高质量代码

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( ))

}

}()

// ...

}