程序员社区

Go错误处理

Go错误处理插图

【译文】原文地址

摘要

对于来自其他面向对象语言比如C#(Go并不是你所认知典型面向对象语言)的读者来说,错误和异常几乎等同的概念。在其他语言中,因为你不确定函数是否会抛异常,因此总是使用try...catch来封装函数。取而代之的是,Go函数支持多返回值,这种能力通常用于将错误和函数结果一起返回。

func add(a,b int) (int, error){

如果你的函数会因为某些原因执行失败,你应该将提前定义的error类型返回。该函数的调用者就知道在使用返回结果前对错误进行检查。如果错误不是nil,就有责任对错误进行处理(打日志、返回、触发重试/清理等处理)。

result,err := add(a,b)
if err != nil {
    // handle error
}
// continue

这是Go错误工作方式的特点。错误处理作为代码主要的一部分,并不是放在一个单独的异常模块中来处理的。某些情况下,在写代码时错误处理无法忽视。

底层逻辑

实际上error是一个内建interface类型,全局可用。实现其函数Error就可以返回一个错误消息。

type error interface {
  Error() string
}

因为interface类型变量的零值是nil,任何实现error接口类型都可作为error值。我们来通过实现error接口来创建自定义错误类型。

Go错误处理插图1

上面的例子,我们创建了CustomError结构体类型,并实现了Error方法。该方法返回一个字符串。因此CustomError结构体实现了error接口。这样,err就是一个error类型的实现了。如果值是error类型,Println函数能自动调用其Error方法,这样就会打印Error!。

现在你可能要问就打印这么简单消息,为什么要定义错误类型这么麻烦。以上代码只是仅仅详述了其中的原理。Go提供了内置的errors包,支持可导出的New函数。该函数需要传入字符串,并返回一个error类型值。

Go错误处理插图2

通过上面的例子,可以看到err是*errors.errorString类型,是一个指向errors.errorString类型的指针。当使用%#v读取值,myErr是一个指向包含一个属性值的结构体指针。

因此New函数返回的是一个指向errorString结构体的指针,并包含传入的错误信息字符串。errors包中的errorString结构体实现了error接口。

type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}

New函数创建并返回指向errorString结构体的指针。

func New(text string) error {
    return &errorString{text}
}

不同的错误处理方法

有些错误是可以通过重试来解决的-网络故障、IO操作等。
以下链接中的包实现了重试解决错误. https://godoc.org/github.com/cenkalti/backoff#example-Retry

Go错误处理插图3

ExponentialBackOff指数级增加重试时间。

添加上下文信息到错误中

在实际开发中,你碰到的错误不仅仅是一个简单的字符串。它将需要更多的上下文信息,例如错误发生在哪里。基于我们前面自定义的错误,来定义一个HttpError结构体,包含status和methon属性值。然后实现Error方法,这样就实现了error接口了。因此,Error方法通过使用status和method值返回更多有用信息。

Go错误处理插图4

我们来一点点地过下上面的例子。首先我们创建了HttpError结构体,包含status和method属性。通过实现Error方法,就实现了error接口。因此,Error方法返回详细信息包含status和method值。

GetServerResponse是一个用于模拟发送http请求并返回的模拟函数。在该例子当中,返回值为空和一个错误。这里错误是一个指向HttpError的指针,包含401状态值和GET方法。

在main函数中,我们调用GetServerResponse函数会返回字符串和错误err。然后通过类型断言得到errval值是*HttpError。因此errval包含所有错误信息。

封装错误

如果存在调用链,使用当前errors包是不可能将错误信息传递到main函数中的,因此错误的上下文信息可能会丢失。这就是github.com/pkg/errors 的用武之地了。这个包和errors包兼容的但包含一些很酷的特性。

Go错误处理插图5

使用github.com/pkg/errors包,还包含更多的有用函数-errors.Unwrap和errors.Is。

日志策略

golang的默认日志包并没有提供日志级别功能。这也是Logrus的优势所在。Logrus还提供了构造日志输出的功能—这是一个非常方便的功能,因为它为开发人员提供了向错误日志消息添加上下文的能力。

Go错误处理插图6

栈跟踪

另一个使用日志的重要方面是栈跟踪。如果你使用github.com/pkg/errors包的话,你可以这么用:

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

是否要panic

简而言之,panic将导致应用程序崩溃。当一些意想不到的问题发生时,可以使用panic。大多数情况下,在出现任何中断应用程序正常运行的问题时,都会使用panic使应用程序失败。数据库事务我们可以认为一个完美的例子。通常,应用程序在初始化时会尝试与数据库建立连接。但是如果应用程序无法与数据库建立连接,应用程序将无法继续正常运行。所以在这种情况下,应用程序应该panic。
panic将导致堆栈跟踪,这将允许我们跟踪错误。

什么时候不要panic

考虑一个允许用户登录的应用程序。如果用户尝试使用数据库中不存在的帐户登录怎么办。在这种情况下,我们不能panic。我们必须优雅地处理这个错误。我们可以用用户输入的登录详细信息记录错误,并将错误响应返回给用户。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » Go错误处理

一个分享Java & Python知识的社区