Golang代码规范
1、各种边界条件是否考虑完备异常情况处理
2、稳定性保证
3、易读易维护
原则
简单性
消除“多余的复杂性”,以简单清晰的逻辑编写代码
不理解的代码无法修复改进
可读性
代码是写给人看的,而不是机器
编写可维护代码的第一步是确保代码可读
生产力
团队整体工作效率非常重要
代码格式
gofmt
Go 语言官方提供的工具,能自动格式化 Go语言代码为官方统一风格常见DDE都支持方便的配置
注释
1、注释应该解释代码作用
- 适合注释公共符号
2、注释应该解释代码如何做的
- 适合注释实现过程
3、注释应该解释代码实现的原因
-
适合解释代码的外部因素
-
提供额外上下文
4、注释应该解释代码什么情况会出错
- 适合解释代码的限制条件
5、公共符号始终要注释
-
包中声明的每个公共的符号变量、常量、函数以及结构都需要添加注释
-
任何既不明显也不简短的公共功能必须予以注释
-
无论长度或复杂程度如何对库中的任何函数都必须进行注释
-
对于公共符号都有注释说明
-
尽管 函数 本身没有注释,但它紧跟 函数 结构的声明,明确它的作用
命名规范
variable
1、简洁胜于冗长
2、缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
-
例如使用 ServeHTTP 而不是 ServeHttp
-
使用 XMLHTTPRequest 或者 xmlHTTPRequest
3、变量距离其被使用的地方越远,则需要携带越多的上下文信息
- 全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义
//i 和 index 的作用域范围仅限于 for 循环内部时index 的额外元长几乎没有增加对于程序的理解
// Bad
for index := 0; index < len(s); index++ {
// do something
}
// Goodfor
i := 0;i < len(s); i++ {
// do something
}
//将 deadline 替换成 t 降低了变量名的信息量
//t 常代指任意时间
//deadline 指截止时间,有特定的含义
// Good
func (c *Client) send( reg *Request, deadline time.Time)
// Bad
func (c *Client) send( reg *Request, t time.Time)
function
1、函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
2、函数名尽量简短
3、当名为 foo 的包某个函数返回类型 Foo 时,可以省略类型信息而不导致歧义
4、当名为 foo 的包某个函数返回类型 T 时(T 并不是 Fo),可以在函数名中加入类型信息
package
1、只由小写字母组成。不包含大写字母和下划线等字符
2、简短并包含一定的上下文信息。例如 schema、 task 等
3、不要与标准库同名。例如不要使用 sync 或者 strings
4、以下规则尽量满足,以标准库包名为例
-
不使用常用变量名作为包名。例如使用 bufio 而不是 buf
-
使用单数而不是复数。例如使用 encoding 而不是 encodings
-
谨慎地使用缩写。例如使用 fmt 在不破坏上下文的情况下比 format 更加简短
控制流程
线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
正常流程代码沿着屏幕向下移动
提升代码可维护性和可读性
故障问题大多出现在复杂的条件语句和循环语句中
1、避免嵌套,保持正常流程清晰(减少内容嵌套)
//如果两个分支中都包含return语句,则可以去除几余的else
//Bad
if foo {
return x
}else {
return nil
}
//Good
if foo {
return x
}
return nil
2、尽量保持正常代码路径为最小缩进(减少判断嵌套)
//最常见的正常流程的路径被嵌套在两个 if 条件内
//成功的退出条件是 return nil,必须仔细匹配大括号来发现
//函数最后一行返回一个错误,需要追溯到匹配的左括号,才能了解何时会触发错误
//如果后续正常流程需要增加一步操作,调用新的函数,则又会增加一层嵌套
//Bad
func OneFunc() error {
err := doSomething()
if err == nil {
err := doAnotherThing()
if err == nil {
return nil
// normal case
}
return err
}
return err
}
// Good
func OneFunc() error {
if err := doSomething(); err != nil {
return err
}
if err := doAnotherThing(); err != nil {
return err
}
return nil
// normal case
}
错误和异常处理
1、尽可能提供简明的上下文信息链,方便定位问题error
2、panic 用于真正异常的情况
3、recover 生效范围,在当前 goroutine 的被 defer 的函数中生效
简单错误
1、简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
2、优先使用 errors.New 来创建匿名变量来直接表示简单错误
3、如果有格式化的需求,使用 fmt.Errorf
func defaultCheckRedirect(reg xRequest, via []*Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
}
错误的 Wrap 和 Unwrap
1、实际上是提供了一个error 嵌套另一个错误的 Wraperror 的能力,从而生成一个 error 的跟踪链
2、在 fmt.Errorf 中使用: % w 关键字来将一个错误关联至错误链中
list, _, err := c.GetBytes(cache.Subkey(a.actionID,"srcfiles" ))
if err != nil {
return fmt.Errorf("reading srcfiles list: %w", err)
}
错误判定
判定一个错误是否为特定错误,使用 errors.Is
不同于使用 ==,使用该方法可以判定错误链上的所有错误是否含有特定的错误
data, err = lockedfile.Read(targ)
if errors.Is(err,fs.ErrNotExist) {
// Treat non-existent as empty, to bootstrap othe "latest" file
// the first time we connect to a given database
return []byte{}, nil
}
return data,err
在错误链上获取特定种类的错误,使用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.PrintIn(err)
}
}
panic
1、不建议在业务代码中使用 panic
2、调用函数不包含 recover 会造成程序崩溃
3、若问题可以被屏蔽或解决,建议使用error 代替 panic
4、当程序启动阶段发生不可逆转的错误时,可以在 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 ...interfacet}) {
s := fmt.Sprintf( format, v...)
std.Output(2, s)
panic(s)
}
recover
1、recover 只能在被 defer 的函数中使用
2、嵌套无法生效
3、只在当前 goroutine 生效
4、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)
}
}
}()
//....
}