3

Golang 六种错误处理技术,可帮助您编写优雅的代码

 2 years ago
source link: http://team.jiunile.com//blog/2020/11/go-handle-error.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

当在 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.Unwraperrors.Is

4 日志策略

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

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

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 流程的一部分,以确保应用程序开发人员不会错过这一部分。

errchec 是 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

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

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

微信订阅号


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK