前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言中常见100问题-#53-54 Not handling an error & defer errors

Go语言中常见100问题-#53-54 Not handling an error & defer errors

作者头像
数据小冰
发布2022-08-15 15:20:27
5530
发布2022-08-15 15:20:27
举报
文章被收录于专栏:数据小冰
不处理错误

在某些时候,我们需要忽略函数的返回值。在Go语言中,应该只有一种处理方法。下面开始分析原因。

下面的notify函数返回一个错误值,我们对返回值不感兴趣,所以直接忽略掉不进行任何处理。

代码语言:javascript
复制
func f() {
        // ...
        notify()
}

func notify() error {
        // ...
}

上面f函数中调用notify函数后,没有将返回值赋值给任何error变量,从语法层面来说,没有任何问题,这段代码是可以通过编译并且是按预期的效果执行。

然而从代码可维护性的角度,这将会导致一些问题。假如一个新程序员在读到这段代码的时候,他会猜测是作者忘记处理notify返回值了呢还是特意忽略它?

所以,在Go语言中,当想忽略函数的返回值时,只有如下的一种写法,将返回的错误值赋值给_,虽然对于编译器来说,这种写法与前面的没有区别,但它显示的告诉程序员不需要处理返回值。

代码语言:javascript
复制
_ = notify()

我们可以在代码的旁边添加注释说明,像下面的注释说明应该避免,因为它没有说明代码不处理返回值的原因,而只是在重复说明代码显示忽略返回值。

代码语言:javascript
复制
// Ignore the error
_ = notify()

合理的注释应该是像下面这样,指明要忽略原因。

代码语言:javascript
复制
// Notifications are sent in best effort.
// Hence, it's accepted to miss some of them in case of errors.
_ = notify()

忽略Go语言中的错误返回值是一种例外的情况,大部分情况下,可以采用日志记录错误的方式处理,即使在较低的日志级别。然而,如果我们确定一个错误可以并且应该被忽略,我们必须通过将它分配给空白标识符来显示处理。这样,将来的读者就会明白这是特意这样处理的。

不处理defer语句错误

不处理defer语句中的错误是Go开发人员经常犯的问题。下面开始讨论原因以及解决方法。

下面的函数是实现一个给定账号ID从数据库中查询余额的功能,我们将使用database/sql中的query方法。具体实现如下,这里只关注查询本身,对结果转换处理不在这里讨论。

代码语言:javascript
复制
const query = "..."

func getBalance(db *sql.DB, clientID string) (
        float32, error) {
        rows, err := db.Query(query, clientID)
        if err != nil {
                return 0, err
        }
        defer rows.Close()

        // Use rows
}

rows是一个*sql.Rows类型,它实现了Closer接口方法。

代码语言:javascript
复制
 type Closer interface {
        Close() error
}

上面的接口包含一个Close方法,该方法返回一个error参数。前面讨论了函数的返回errors值总是应该被处理。然而,本例中defer调用返回的错误值却被忽略了。

代码语言:javascript
复制
defer rows.Close()

根据前面讨论的结果,如果我们不想对返回错误值进行处理,需要将它赋值给一个_. 像下面这样。

代码语言:javascript
复制
defer func() { _ = rows.Close() }()

上面这个版本有点冗长,但从可维护的角度来看更好,它准确的反映了我们期望忽略返回值的想法。

然而,在这种情况下与其盲目地忽略defer调用中的返回值,需要问问这是不是最好的处理方法。调用Close()将在无法释放数据库连接时返回错误,因此,忽略这个错误并不是我们想要的,更好的处理方法是记录错误日志。下面的代码,在rows执行Close失败时,会将错误信息记录在日志中,方便我们排查问题。

代码语言:javascript
复制
defer func() {
        err := rows.Close()
        if err != nil {
                log.Printf("failed to close rows: %v", err)
        }
}()

如果,换一种处理方式,现在不处理错误,将错误值返回给getBalance,以便该函数的调用方决定如何处理。代码实现如下:

代码语言:javascript
复制
defer func() {
        err := rows.Close()
        if err != nil {
                return err
        }
}()

上面的这段代码是无法通过编译的,因为匿名函数是没有返回值的,现在返回一个错误是不行的。如何将defer func中的error与getBalance中的返回error建立联系呢,可以采用命名结果参数。代码如下,一旦rows.Close被调用,它的返回值将被赋值给外层的getBalance函数的返回值。

代码语言:javascript
复制
func getBalance(db *sql.DB, clientID string) (
        balance float32, err error) {
        rows, err := db.Query(query, clientID)
        if err != nil {
                return 0, err
        }
        defer func() {
                err = rows.Close()
        }()

        if rows.Next() {
                err := rows.Scan(&balance)
                if err != nil {
                        return 0, err
                }
                return balance, nil
        }
        // ...
}

上面这段代码初看是可以的,实际是存在问题的。如果rows.Scan执行失败,rows.Close调用总是被执行。这将导致rows.Close的返回值会覆盖掉rows.Scan返回值。可能会出现,rows.Scan执行失败但rows.Close执行成功,最后返回的错误值为nil, 这并不是我们期望的效果。

上述实现的逻辑并不简单,预期的效果是

rows.Scan

rows.Close

返回值

执行成功

执行成功

返回nil

执行成功

执行失败

返回rows.Close的错误

执行失败

执行成功

期望返回rows.Scan的错误

执行失败

执行失败

到底返回哪个错误?

如果rows.Scanrows.Close都执行失败,如何处理呢?有两种不同的处理方法, 方法一:自定义的一个错误类型,包含这种两种错误。方法二:返回rows.Scan错误值,并记录rows.Close错误信息到日志中。方法二实现代码如下

代码语言:javascript
复制
defer func() {
        closeErr := rows.Close()
        if err != nil {
                if closeErr != nil {
                        log.Printf("failed to close rows: %v", closeErr)
                }
                return
        }
        err = closeErr
}()

上述代码将rows.Close的返回值赋值给一个临时变量closeErr,在将closeErr赋值给err之前,检查err值是否是为非nil, 如果err非nil,说明rows.Scan已经出现了错误。这时,不将closeErr赋值给err,直接返回它,并将closeErr的错误信息记录到日志中。

如前面所述,应始终处理错误。对于defer调用返回的错误,我们至少应该明确地忽略它。如果这还不够,我们可以决定直接通过记录错误或将错误传递给调用者来处理错误。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据小冰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不处理错误
  • 不处理defer语句错误
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档