前言: \textcolor{Green}{前言:} 前言:
💞这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营
💞从这个专栏里面可以迅速获得Go的知识
今天的笔记是对编程规范的补充,对控制流程、错误和异常处理进行总结。通过这次的学习我们会对接下来的项目编写有很大的好处。
1.2.4 编码规范 - 控制流程
避免嵌套,保持正常流程清晰
如果两个分支中都包含 return 语句,则可以去除冗余的 else。方便后续的维护,else 一般是正常流程,如果需要在正常流程新增判断逻辑,则需要避免分支嵌套
// Bad
if foo {
return x
} else {
return nil
}
// Good
if foo {
return x
}
尽量保持正常代码路径为最小缩进
- 优先处理错误情况/特殊情况,尽早返回或继续循环来减少嵌套
// Bad
func OneFunc() error {
err := doSomething()
if err == nil {
err := doAnotherThing()
if err == nil {
return nil // normal case
}
}
}
通过上面的代码发现:
- 最常见的正常流程的路径被嵌套在两个 if 条件内
- 成功的退出条件是 return nil,必须仔细匹配大括号来发现
- 函数最后一行返回一个错误,需要追溯到匹配的左括号,才能了解何时会出发错误
- 如果后续正常流程需要增加一步操作,调用新的函数,则又会增加一层嵌套。
尽量保持正常代码路径为最小路径
调整后的代码:从上到下就是正常流程的执行过程。后续想排查问题可以针对具体问题进行错误详细分析。如果想正常流程新增操作,可以放心大胆的在函数中添加新的代码
// Good
func OneFunc() error {
if err := doSomething(); err != nil {
return err
}
if err := doAnotherThing(); err != nil {
return err;
}
return nil // normal case
}
下面是 go 仓库中的代码案例,也是优先处理 err 情况,保持正常流程的统一。
github中go仓库
编码规范 - 控制流程总结
- 线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支
- 正常流程代码沿着屏幕向下移动
go 代码不是成功的路径越来越深的嵌套到右边。
- 提供代码可维护性和可读性
一个功能可以通过多个功能的线性结合来实现,那么结构就会非常简单。反之用条件分支控制代码、毫无章法的增加状态数等行为会让代码变得难理解,要避免这种情况增加可读性。
正常流程应该自上而下,简单清晰地进行处理,代码可读性和可维护性都会提高,添加功能也会变得容易。 - 故障问题大多出现在复杂的条件语句和循环语句中
在维护这种逻辑时,添加功能就会变成高风险操作,容易遗漏部分条件导致问题。
1.2.5 编码规范 - 错误和异常处理
简单错误
- 简单地错误是指仅出现一次地错误,且在其他地方不需要捕获该错误
- 优先使用
errors.New
来创建匿名变量来直接表示简单错误 - 如果有格式的需求,使用
fmt.Errorf
错误的 Wrap 和 Unwrap
- 错误的 Wrap 实际上提供了一个 error 嵌套另一个 error 的能力,从而生成了一个 error 的跟踪链
- 在 fmt.Errorf 中使用:%w 关键字来将一个错误关连至错误链中
要注意:GO1.13在 errors 中新增了三个新API和一个新的 format 关键字,分别是 errors.is,errors.As,errors.Unwrap 以及 fmt.Errorf 的 %w。如果项目运行小于Go1.13的版本,需要导入 golang.org/x/xerrors 来使用。
错误判定
- 判定一个错误是否为特定错误,使用
errors.Is
- 不同于使用 == ,使用该方法可以判定错误链上的所有错误是否含有特定的错误。
错误判定
- 在错误链上获取特定种类的错误,使用 errors.As
panic
不建议在业务代码中使用 panic
。因为 panic 发生后,会向上传播至调用栈顶。- 调用函数不包含 recover 就会造成整个程序崩溃。
- 若问题可以屏蔽或解决,建议使用 error 代替 panic
- 特殊:当程序启动发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic。因为在这种情况下,服务启动起来也不会有意义。
例如:下图中的代码,启动消息队列监听器的逻辑,在创建消费组失败的时候会 panicf,实际打印日志,然后再抛出 panic。
recover
由于不能控制所有的代码,避免不了引入其他库,如果是引入库的 bug 导致 panic,影响到自身的逻辑应该如何处理。此时我们应该注意 recover 的生效条件
- recover 只能被 defer 的函数中使用
- 嵌套无法生效
- 只在当前 goroutine 生效
- defer 的语句是后进先出
- 如果需要更多的上下文信息,可以 recover 后在 log 中记录当前的调用栈
编码规范-错误和异常处理小结
- error 尽可能提供简明的上下文信息链,方便定位问题
- panic 用于真正异常的情况
- recover 生效范围,在当前 goroutine 的被 defer 的函数中生效
例子
哪种命名方式更好
package time
// A function returns the current local time
// which one is better
func Now() Time
// or
fun NowTime() Time
实际过程中:Now 和 NowTime 返回的是 time.Time类型,使用时我们没有必要协程 time.NowTime 来额外表示时间信息,使用 Now 更简洁
t := time.Now()
t := time.NowTime()
接下来再看
package time
// A function pares a duration string
// such as "300ms", "-1.5h" or "2h45m"
func Parse(s string) (Duration, error)
// or
func ParseDuration(s string) (Duration, error)
看到这里持续时间并不是 time 类型,使用 time.ParseDuration() 返回的是 time.Duration 类型。所以此时在函数命名中体现就是冗余了,所以我们使用 ParseDuration更好
duration := time.Parse(s)
duration := time.ParseDuration(s)
程序的输出是什么
func main() {
if true {
defer fmt.Printf("1")
} else {
defer fmt.Printf("2")
}
defer fmt.Printf("3")
}
- defer 语句在函数返回前调用
- 多个 defer 语句是后进先出
最终得到我们的答案:31