What do you do when things go wrong?
出现问题时您该怎么办?
Dealing with errors is not trivially simple. Error handling requirements are rarely deliberated on while discussing functional requirements, yet error handling is a vital part of software development.
处理错误并非易事。 在讨论功能需求时很少讨论错误处理要求,但是错误处理是软件开发的重要组成部分。
In GO, error conditions are returned as a method return value(s). In my opinion, it is useful to consider error conditions as part of the main flow — it puts the onus on developers to address error handling while writing functional code. This paradigm is very different from what other programming languages (such as Java) offer — where exceptions are a totally different flow. While this different style makes the code more readable, it also brings out new challenges.
在GO中,错误条件作为方法返回值返回。 在我看来,将错误条件作为主要流程的一部分是有用的–它使开发人员有责任在编写功能代码时解决错误处理问题。 这种范例与其他编程语言(例如Java)提供的范例非常不同,在后者中,异常是完全不同的流程。 虽然这种不同的样式使代码更具可读性,但也带来了新的挑战。
This blog entry discusses six techniques to handle errors, retries, and serviceability. While few of the ideas are trivial, others are not as popular.
该博客文章讨论了六种处理错误,重试和可维护性的技术。 尽管其中一些想法微不足道,但其他想法却不那么受欢迎。
So, let’s get started with the list:
因此,让我们从列表开始:
Align to the left
向左对齐
The best strategy to handle the error is to check the error and return from the function immediately. It is okay to have multiple error return statements in a function — in fact, it is the sensible choice. [1]
处理错误的最佳策略是检查错误并立即从函数返回。 一个函数中可以有多个错误返回语句是可以的-实际上,这是明智的选择。 [1]
For example, the following code snippet shows how handling a happy scenario using if err == nil, leads to nested if checks.
例如,以下代码片段显示了如何使用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
}
}
The above logic can be aligned to left by aligning logic to the left:
通过将逻辑左对齐,可以将上述逻辑左对齐:
func ABetterExample() error {
err := somethingThatReturnsError()
if err != nil {
return err
}
// Happy processing
err = somethingElseThatReturnsError()
if err != nil {
return err
}
// More Happy processing
}
Retry recoverable Errors
重试可恢复的错误
Few recoverable errors deserve retries — network glitches, IO operations, etc. can be recovered with simple retries.
几乎没有可恢复的错误值得重试-通过简单的重试就可以恢复网络故障,IO操作等。
The following package can help resolve the pain with retries.
以下软件包可帮助解决重试带来的痛苦。
// An operation that may fail.
operation := func() error {
return nil // or an error
}
err := Retry(operation, NewExponentialBackOff())
if err != nil {
// Handle error.
return
}
Exponential Backoff means that the retry interval is increased exponentially — a sensible choice for most of the network/IO failures.
指数退避意味着重试间隔成倍增加-对于大多数网络/ IO故障,这是明智的选择。
Wrapping Errors
包装错误
The default error package is limited — error context details can often be lost. For example,
默认错误包是受限制的-错误上下文详细信息经常会丢失。 例如,
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))
}
In this case, the error instance received by the main
function doesn’t have information that it happened for account Acct1
. It is possible to log accountNumber
in function testingErrror
but with the current package errors
it is not possible to pass on that information to the main
function.
在这种情况下, main
函数接收到的错误实例不具有针对帐户Acct1
发生的信息。 可以在功能testingErrror
记录accountNumber
,但是由于当前包errors
,无法将该信息传递给main
功能。
That is where github.com/pkg/errors
comes in. This library is compatible with errors
but brings in some cool capabilities.
这就是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))
}
With github.com/pkg/errors
you also some additional useful functions — errors.Unwrap
and errors.Is
随着github.com/pkg/errors
还附加一些有用的功能- errors.Unwrap
和errors.Is
Logging Strategies
记录策略
Golang’s default package log doesn’t provide the ability to log with the logging level. There are many other choices:
Golang的默认软件包日志不提供使用日志记录级别进行记录的功能。 还有许多其他选择:
Glog: https://github.com/golang/glog
Glog: https : //github.com/golang/glog
Logrus: https://github.com/sirupsen/logrus
Logrus: https : //github.com/sirupsen/logrus
Zap: https://github.com/uber-go/zap
Zap: https : //github.com/uber-go/zap
Logrus and Zap also provide the capability to structure log output — this is a very handy capability as it provides developers the ability to add context to the error log message.
Logru和Zap lso提供了结构日志输出的功能-这是非常方便的功能,因为它为开发人员提供了将上下文添加到错误日志消息的功能。
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
}
Structured log output will look like:
结构化日志输出如下所示:
{"accountNumber":"ABC","appname":"my-app","error":"Some test error while doing happy processing","level":"error","msg":"ErrMsg","time":"2009-11-10T23:00:00Z"}
Another key aspect of logging is the ability to get log stack trace. If you use github.com/pkg/errors
, you could
日志记录的另一个关键方面是能够获取日志堆栈跟踪。 如果您使用github.com/pkg/errors
,则可以
logrus.Error("Error occurred", fmt.Sprintf("%+v", err))
And you will get an error stack trace like below:
并且您将得到如下所示的错误堆栈跟踪:
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 is buffered and optimized for performance. [2]
Zap已进行缓冲并针对性能进行了优化。 [2]
Error Checks
错误检查
Treating error
as value is good — it is explicit and explicit makes lots of sense. But it can also provide opportunities for developers to skip. For example,
将error
视为价值是件好事-明确和明确是很有意义的。 但它也可以为开发人员提供跳过的机会。 例如,
func testingError(accoutNumber string) error {
var err error
_ = errors.New("errors.New with _"
errors.New("errors.New not capturing return")
return err
}
The above example shows that the application programmer is two errors returned by errors.New
statements. And this can happen intentionally or unintentionally.
上面的例子表明,应用程序程序员是由errors.New
语句返回的两个错误。 这可以有意或无意地发生。
Luckily, there is a linter utility that can help you.
幸运的是,有一个linter实用程序可以为您提供帮助。
Once you have the linter installed, you can simply do the following:
一旦安装了lint,就可以简单地执行以下操作:
errcheck -blank ./...
And you will get output like:
您将得到如下输出:
temp.go:16:2: _ = errors.New("Error capturing return using _")
temp.go:18:12: errors.New("Error not capturing return")
This can be added as part of tne CD CI pipeline to ensure that the application developers don’t miss this part.
这可以作为CD CI管道的一部分添加,以确保应用程序开发人员不会错过这一部分。
errchec
is a part of Go linters aggregator utility — https://golangci-lint.run/
errchec
是Go linters聚合器实用程序的一部分— https://golangci-lint.run/
Multiple Errors
多个错误
You have scenarios where you have multiple errors — they are part of then same go routine and you don’t want to stop processing — but rather continue processing and record all the errors. There is a library just for that:
在某些情况下,您会遇到多个错误-它们是同一go例程的一部分,并且您不想停止处理-而是继续处理并记录所有错误。 有一个库专门用于:
Here is a simple example:
这是一个简单的示例:
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))
}
Similarly, for multiple go routines, following library can be used:
同样,对于多个go例程,可以使用以下库:
Conclusion
结论
I know that the above list is not exhaustive. And for a few of you, it would be all trivial — but hopefully, for some of you, it has contributed to your repertoire of error handling techniques. Please comment in the “Comments Section” if you have any other useful ideas.
我知道上面的列表并不详尽。 对于你们中的一些人来说,这将是微不足道的-但希望对于你们中的某些人,它有助于您掌握各种错误处理技术。 如果您还有其他有用的想法,请在“评论部分”中评论。
[1] https://medium.com/@matryer/line-of-sight-in-code-186dd7cdea88
[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
[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