编程规范-控制流程、错误和异常处理

前言: \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
        }
    }
}

通过上面的代码发现:

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

尽量保持正常代码路径为最小路径

调整后的代码:从上到下就是正常流程的执行过程。后续想排查问题可以针对具体问题进行错误详细分析。如果想正常流程新增操作,可以放心大胆的在函数中添加新的代码

// 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦 羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值