译|Don’t just check errors, handle them gracefully(一)

简介: 译|Don’t just check errors, handle them gracefully

本文摘自我最近在日本东京举行的GoCon春季会议上的演讲。

Errors are just values

我花了很多时间考虑Go程序中错误处理的最佳方法。我真希望存在单一的错误处理方式,可以通过死记硬背教给所有Go程序员,就像教数学或英文字母表一样。

但是,我得出结论,不存在单一的错误处理方式。 相反,我认为Go的错误处理可以分为三个核心策略。

Sentinel errors

第一类错误处理就是我所说的_sentinel errors_。

if err == ErrSomething { … }

该名称源于计算机编程中使用特定值的实践,表示不可能进一步处理。 因此,对于Go,我们使用特定值来表示错误。

例子包括 io.EOF 类的值,或低层级的错误,如 syscall 包中的常 syscall.ENOENT

甚至还有 sentinel errors 表示_没有_发生错误,比如 go/build.NoGoError , 和 path/filepath.Walkpath/filepath.SkipDir

使用 sentinel 值是灵活性最小的错误处理策略,因为调用者必须使用等于运算符,将结果与预先声明的值进行比较。 当您想要提供更多上下文时就会出现问题,因为返回一个不同的错误会破坏相等检查。

即使是用心良苦的使用 fmt.Errorf 为错误添加一些上下文,将使调用者的相等测试失败。 调用者转而被迫查看 errorError 方法的输出,以查看它是否与特定字符串匹配。

Never inspect the output of error.Error

另外,我认为永远不应该检查 error.Error 方法的输出。error 接口上的 Error 方法是为人类,而不是代码。

该字符串的内容属于日志文件,或显示在屏幕上。 您不应该尝试通过检查它以更改程序的行为。

我知道有时候这是不可能的,正如有人在推特上指出的那样,此建议并不适用于编写测试。 更重要的是,在我看来,比较错误的字符串形式是一种代码气味,你应该尽量避免它。

Sentinel errors become part of your public API

如果您的 public 函数或方法返回特定值的错误,那么该值必须是 public 的,当然还要有文档记录。 这会增加API的面积。

如果您的API定义了一个返回特定错误的接口,则该接口的所有实现都将被限制为仅返回该错误,即使它们可能提供更具描述性的错误。

通过 io.Reader 看到这一点 。 像 io.Copy 这样的函数,需要一个 reader 实现来_精确_地返回 io.EOF,以便向调用者发出不再有数据的信号,但这不是错误 。

Sentinel errors create a dependency between two packages

到目前为止,sentinel error values 的最大问题是它们在两个包之间创建源代码依赖性。 例如,要检查错误是否等于 io.EOF,您的代码必 import io 包。

这个具体示例听起来并不那么糟糕,因为它很常见,但想象一下,当项目中的许多包导出 error values,项目中的其他包必须 import 以检查特定的错误条件时存在的耦合。

在一个玩弄这种模式的大型项目中工作过,我可以告诉你,以 import 循环的形式出现的糟糕设计的幽灵从未远离我们的脑海。

Conclusion: avoid sentinel errors

所以,我的建议是在你编写的代码中避免使用 sentinel error values。 在某些情况下,它们会在标准库中使用,但你不应该模仿这种模式。

如果有人要求您从包中导出错误值,您应该礼貌地拒绝,而是建议一种替代方法,例如我将在下面讨论的方法。

Error types

Error types 是我想讨论的Go错误处理的第二种形式。

if err, ok := err.(SomeType); ok { … }

错误类型是您创建的实现错误接口的类型。 在此示例中,MyError 类型跟踪文件和行,以及解释所发生情况的消息。

type MyError struct {
  Msg string
  File string
  Line int
}
func (e *MyError) Error() string {
  return fmt.Sprintf("%s:%d: %s”, e.File, e.Line, e.Msg)
}
return &MyError{"Something happened", “server.go", 42}

由于 MyError error 是一种类型,因此调用者可以使用类型断言从错误中提取额外的上下文。

err := something()
switch err := err.(type) {
case nil:
// call succeeded, nothing to do
case *MyError:
fmt.Println(“error occurred on line:”, err.Line)
default:
// unknown error
}

error types 相对于 error values 的重大改进是,它们能够包装底层错误以提供更多上下文。

一个很好的例子是 os.PathError 类型,它通过它试图执行的操作和它试图使用的文件来注释底层错误。

// PathError records an error and the operation
// and file path that caused it.
type PathError struct {
  Op string
  Path string
  Err error // the cause
}
func (e *PathError) Error() string
Problems with error types

调用者可以使用类型断言或类型 switch,error types 必须是 public。

如果您的代码实现了一个接口,其契约需要特定的错误类型,则该接口的所有实现者都需要依赖于定义错误类型的包。

对包类型的深入了解,会建立与调用者很强耦合,从而形成一个脆弱的API。

Conclusion: avoid error types

虽然 error typessentinel error values 更好,因为它们可以捕获更多关于错误的上下文,错误类型同样拥有许多 error values 的问题。

所以我的建议是避免 error types,或者至少避免使它们成为公共API的一部分。

目录
相关文章
|
4月前
|
安全
Warning: Don’t paste code into the DevTools Console that you don’t understand or haven’t reviewed yo
Warning: Don’t paste code into the DevTools Console that you don’t understand or haven’t reviewed yo
|
SQL Java 数据库连接
sql injection violation, syntax error: syntax error, error in :‘**‘expect IDENTIFIER, actual IDENTIF
sql injection violation, syntax error: syntax error, error in :‘**‘expect IDENTIFIER, actual IDENTIF
203 0
|
Java Go API
译|Don’t just check errors, handle them gracefully(二)
译|Don’t just check errors, handle them gracefully(二)
96 0
error: ‘PRIO_PROCESS’ undeclared
error: ‘PRIO_PROCESS’ undeclared
96 0
|
缓存 开发工具 iOS开发
Xcode The operation couldn’t be completed. (LaunchServicesError error 0.)问题的解决
Xcode The operation couldn’t be completed. (LaunchServicesError error 0.)问题的解决
Xcode The operation couldn’t be completed. (LaunchServicesError error 0.)问题的解决
|
安全 iOS开发 MacOS
“XXXXX” is damaged and can’t be opened. You should move it to the Trash 解决方案
“XXXXX” is damaged and can’t be opened. You should move it to the Trash 解决方案
586 0
|
Go iOS开发
The operation couldn’t be completed. Unable to log in with account 'myappleid'. An unexpected failure occurred while logging in (Underlying error code 1100).解决方法
The operation couldn’t be completed. Unable to log in with account 'myappleid'. An unexpected failure occurred while logging in (Underlying error code 1100).解决方法
452 0
|
Java Apache
Failed to place enough replicas
如果DataNode的dfs.datanode.data.dir全配置成SSD类型,则执行“hdfs dfs -put /etc/hosts hdfs:///tmp/”时会报如下错误: 2017-05-04 16:08:22,545 WARN org.
3296 0
|
程序员 PHP 区块链
Error: the tx doesn't have the correct nonce.TestRPC/Ganache无法获取nonce
做一个truffle相关的项目,每次尝试创建交易时,总会有以下的一个错误提示: the tx doesn’t have the correct nonce 完整的一般是这样: Error: the tx doesn't have the correct nonce.
2647 0