相关文章推荐
英俊的紫菜  ·  XACT_STATE ...·  9 月前    · 
博学的豌豆  ·  rxjava介绍_flowable.bloc ...·  11 月前    · 

当在 GO 中遇到错误时你会怎么做?

处理错误并不简单。在讨论功能性需求时,很少考虑错误处理需求,但是错误处理是软件开发的一个重要部分。

在 GO 中,错误条件以方法返回值的形式返回。在我看来,将错误条件作为主流程的一部分是很有用的 -- 它让开发人员在编写功能代码时承担处理错误的责任。这种范例与其他编程语言(如 Java )所提供的非常不同 -- 其中异常是完全不同的流程。虽然这种不同的风格使代码更具可读性,但也带来了新的挑战。

本文讨论了六种处理错误、重试和可服务性的技术。虽然很少有想法是琐碎的,但其他想法并不那么受欢迎。

因此,让我们从列表开始!

1 向左对齐

处理错误的最佳策略是检查错误并立即从函数返回。在一个函数中有多个错误返回语句是可以的 -- 事实上,这是明智的选择。[1]

例如,下面的代码片段展示了如何使用 if err == nil 来处理一个愉快的场景,从而导致嵌套 if 检查。

// Handling Happy case first - leading to nested if checks...
func example() error {
     err := somethingThatReturnsError()
     if err == nil {
        //Happy processing
        err = somethingElseThatReturnsError()
        if err == nil {
           //More Happy processing
        } else {
           return err
        }
     } else {
        return err
     }
}

上述逻辑可以通过向左对齐逻辑来处理:

func ABetterExample() error {
     err := somethingThatReturnsError()
     if err != nil {
        return err
     }
     
     // Happy processing
     err = somethingElseThatReturnsError()
     if err != nil {
        return err
     }
     // More Happy processing
}

2 重试可恢复错误

很少有可恢复的错误值得重试 -- 网络故障、IO 操作等都可以通过简单的重试恢复。

下面的包可以帮助解决重试带来的麻烦。package backoff

// An operation that may fail.
operation := func() error {
    return nil // or an error
}

err := Retry(operation, NewExponentialBackOff())
if err != nil {
    // Handle error.
    return
}

指数回退意味着重试间隔呈指数增长 -- 对于大多数网络 /IO 故障来说,这是一个明智的选择。

3 包装错误

默认的错误包是有限的 -- 错误上下文的详细信息经常会丢失。例如:

func testingError2() error {
   return errors.New("New Error")
}
func testingError(accountNumber string) error {
   err := testingError2()
   if err != nil {
      return err
   }
   return nil
}
func main() {
   err := testingError("Acct1")
   logrus.Error("Error occurred", fmt.Sprintf("%+v", err))
}

在这种情况下,主函数收到的错误实例没有发生在帐户 Acct1 上的信息。可以在函数 testingErrror 中记录 accountNumber ,但是由于当前包错误,无法将该信息传递给主函数。

这就是 github.Com/pkg/errors 的来源。该库与 errors 兼容并带来了一些很酷的功能。

func testingError2() error {
   return errors.New("New Error")
}
func testingError(accountNumber string) error {
   err := testingError2()
   if err != nil {
        return errors.Wrap(err, "Error occurred while processing Card Number "+accoutNumber)
   }
   return nil
}
func main() {
   err := testingError("Acct1")
   logrus.Error("Error occurred", fmt.Sprintf("%+v", err))
}

github.com/pkg/errors 中,您还可以使用一些额外的有用功能 -- errors.Unwrap errors.Is

4 日志策略

Golang 的默认包日志不提供使用日志记录级别进行日志记录功能。这里有一些其他的选择:

  • Glog : https://github.com/golang/glog
  • Logrus : https://github.com/sirupsen/logrus
  • Zap : https://github.com/uber-go/zap

Logrus Zap 还提供了结构化日志输出的功能 -- 这是一个非常方便的功能,因为它为开发人员提供了向错误日志消息添加上下文的能力。

func example(accountNumber string) error {
 logrus.SetFormatter(&logrus.JSONFormatter{})
ctxFields := logrus.Fields{
  "accountNumber": accountNumber,
  "appname":       "my-app",
 }
//Happy processing
 err := errors.New("Some test error while doing happy processing")
if err != nil {
  logrus.WithFields(ctxFields).WithError(err).Error("ErrMsg")
  return err
 }
 return nil
}

结构化日志输出如下:

{"accountNumber":"ABC","appname":"my-app","error":"Some test error while doing happy processing","level":"error","msg":"ErrMsg","time":"2009-11-10T23:00:00Z"}

日志的另一个关键方面是获得日志堆栈跟踪的能力。如果你使用 github.com/pkg/errors ,你就可以

logrus.Error("Error occurred", fmt.Sprintf("%+v", err))

你会得到一个错误堆栈跟踪如下:

main.testingError2
        /home/nayars/go/src/github.com/nayarsn/temp.go:12
main.testingError
        /home/nayars/go/src/github.com/nayarsn/temp.go:25
main.main
        /home/nayars/go/src/github.com/nayarsn/temp.go:39
runtime.main
        /usr/lib/go-1.15/src/runtime/proc.go:204
runtime.goexit
        /usr/lib/go-1.15/src/runtime/asm_amd64.s:1374

Zap 为性能进行了缓冲和优化。[2]

5 错误检查

将错误视为值是好的 -- 它是明确的,而明确的有很多意义。但它也可以为开发人员提供跳过的机会。例如:

func testingError(accoutNumber string) error {
    var err error
    _ = errors.New("errors.New with _"
    errors.New("errors.New not capturing return")
    return err
}

上面的示例显示应用程序程序员是由 errors.New 语句返回的两个错误。这可能是有意或无意发生的。

幸运的是,有一个 linter 实用程序可以帮助您。kisielk/errcheck

一旦你安装了 linter,你可以简单地做以下事情:

errcheck -blank ./...

你会得到这样的输出:

temp.go:16:2:   _ = errors.New("Error capturing return using _")
temp.go:18:12:  errors.New("Error not capturing return")

这可以作为 CI/CD 流程的一部分,以确保应用程序开发人员不会错过这一部分。

errcheck 是 Go linters 聚合器实用程序的一部分 -- https://golangci-lint.run/

6 多个错误

你有多个错误的场景 -- 它们是同一个 go routine 的一部分,你不想停止处理 -- 而是继续处理并记录所有错误。这里有一个专门的库:hashicorp/go-multierror

这里有一个简单的例子:

func step1() error {
    return errors.New("Step1")
}
func step2() error {
    return errors.New("Step2")
}
func main() {
    var result error
    if err := step1(); err != nil {
        result = multierror.Append(result, err)
    }
    if err := step2(); err != nil {
        result = multierror.Append(result, err)
    }
    
    fmt.Println(multierror.Flatten(result))
}

同样,对于多个 go routines ,可以使用以下库:errgroup

我知道上述列表并非全部。对于你们中的一些人来说,这可能是微不足道的 -- 但希望对你们中的一些人来说,这有助于你们掌握错误处理技术。

  • [1] https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88
  • [2] https://medium.com/a-journey-with-go/go-how-zap-package-is-optimized-dbf72ef48f2d

译自:https://medium.com/higher-order-functions/golang-six-error-handling-techniques-to-help-you-write-elegant-code-8e6363e6d2b

b7752e8388f14e5a3018614968e518ba.png

来都来了,点个“再看”再走叭~~              点上方蓝色“云原生领域”关注我,设个星标,不会让你失望概述当在 GO 中遇到错误时你会怎么做?处理错误并不简单。在讨论功能性需求时,很少考虑错误处理需求,但是错误处理是软件开发的一个重要部分。在 GO 中,错误条件以方法返回值的形式返回。在我看来,将错误条件作为主流程的一部分是很有用的 -- 它让开发人员在编写功能代码时承担处理错误的责任。这种范例与其他编程语言(如 Java )所提供的...
有时候 golang 打印了 错误 日志,却发现很多地方都有打印这种日志,定位起来有点难度,有没有一些方法,在打印 错误 日志的时候把 代码 堆栈 也打印出来呢? 方法如下: func WrapError(wrapMsg string, err error) error { pc, file, line, ok := runtime.Caller(1) f := runtime.FuncForPC(pc) if !ok { return errors.New("WrapError 方法获取 堆栈 失败") 在开发过程中 会使用到日志库去 记录 错误 的日志,尤其是 golang 中 有无穷无尽的error 如果不 记录 ,当你的 代码 出错,就无从排错了。 zap 是开源的 Go 高性能日志库 主要有以下特点: 支持不同的日志级别 能够打印基本信息等但不支持日志的分割 但是可以使用 lumberjack 也是 zap 官方推荐用于日志分割 官网:https://github.com/uber-go/zap...
个人博客:https://jian1098.github.io CSDN博客:https://b log .csdn.net/c_jian 简书:https://www.jianshu.com/u/8ba9ac5706b6 联系方式:jian1098@qq.com zap是Uber开发的非常快的、结构化的,分日志级别的Go日志库。根据Uber-go Zap的文档,它的性能比类似的结构化日志包更好,也比标准库更快。具体的性能测试可以去github上看到。 github地址:https:.
开发服务器程序时如果未经过充分测试, 服务稳定运行一段时间后会突然崩溃退出。一般是因为程序中出现了某个未捕获的异常。 这类问题属于偶现的,且需要服务器运行一段时间之后才会出现,难以定位有问题的 代码 段。 这中情况下应该将服务进程的stderr重定向至某个文件,这样当进程因未捕获的异常导致崩溃时,go运行时会将异常发生时各个协程的调用栈信息 记录 下来,便于定位问题。 log File, err := os.OpenFile("./ log /fatal. log ", os.O_CREATE|os.O_APPEND|
Golang 中,可以使用 MySQL 驱动程序(如 `github.com/go-sql-driver/mysql`)连接 MySQL 数据库,并执行插入操作并返回自增长ID。以下是一个示例: ```go import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" func main() { // 连接 MySQL 数据库 db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database") if err != nil { panic(err.Error()) defer db.Close() // 执行插入操作并返回自增长ID result, err := db.Exec("INSERT INTO user(name, age) VALUES(?, ?)", "John", 25) if err != nil { panic(err.Error()) id, err := result.LastInsertId() if err != nil { panic(err.Error()) fmt.Println("Inserted row id:", id) 在上面的示例中,首先使用 `sql.Open()` 函数连接 MySQL 数据库,并在插入数据时使用 `db.Exec()` 函数执行插入操作。然后,使用 `result.LastInsertId()` 函数返回最后插入的行的自增长ID。