Error vs Exception
Error
Go error 就是普通的一个接口,普通的值。
我们经常使用 errors.New() 来返回一个 error 对象。
package errors
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
基础库中大量自定义的 error。
errors.New() 返回的是 内部 errorString 对象的指针。
Output: Named Type Error
Output: Error: EOF
Error vs Exception
各个语言的演进历史:
-
C
- 单返回值,一般通过传递指针作为入参,返回值为 int 表示成功还是失败。
- ngx_int_t ngx_create_path(ngx_file_t *file, ngx_path_t *path);
-
C++
- 引入了 exception,但是无法知道被调用方会抛出什么异常。
-
Java
- 引入了 checked exception,方法的所有者必须申明,调用者必须处理。在启动时抛出大量的异常是司空见惯的事情,并在它们的调用堆栈中尽职地记录下来。Java 异常不再是异常,而是变得司空见惯了。它们从良性到灾难性都有使用,异常的严重性由函数的调用者来区分。
- java.Error & java.RuntimeException
因此,java主要解决C++未检查异常问题,并引入了一系列自己的异常。然而,我认为java并没有解决C++的实际问题,也没有解决问题。如何向函数的调用者发出出错信号的问题。
Go 的处理异常逻辑是不引入 exception,支持多参数返回,所以你很容易的在函数签名中带上实现了 error interface 的对象,交由调用者来判定。
- 如果一个函数返回了 value, error,你不能对这个 value 做任何假设,必须先判定 error。唯一可以忽略 error 的是,如果你连 value 也不关心。
Go 中有 panic 的机制,如果你认为和其他语言的 exception 一样,那你就错了。当我们抛出异常的时候,相当于你把 exception 扔给了调用者来处理。
- 比如,你在 C++ 中,把 string 转为 int,如果转换失败,会抛出异常。或者在 java 中转换 string 为 date 失败时,会抛出异常。
- Go panic 意味着 fatal error(就是挂了)。不能假设调用者来解决 panic,意味着代码不能继续运行。
使用多个返回值和一个简单的约定,Go 解决了让程序员知道什么时候出了问题,并为真正的异常情况保留了 panic。
在一个互联网的世界里,网络的每一个输入都必须被认为是恶意的,把一个字符串解析成一个日期真的是个例外吗?当然不是。
必须检查它以确保它指向一个有效的地址。这是Java开发人员经常面临的情况,并导致对nil的深仇大恨
Error vs Exception
对于真正意外的情况,那些表示不可恢复的程序错误,例如索引越界、不可恢复的环境问题、栈溢出,我们才使用 panic。对于其他的错误情况,我们应该是期望使用 error 来进行判定。
you only need to check the error value if you care about the result. -- Dave This blog post from Microsoft’s engineering blog in 2005 still holds true today, namely: My point isn’t that exceptions are bad. My point is that exceptions are too hard and I’m not smart enough to handle them.
- 简单。
- 考虑失败,而不是成功(Plan for failure, not success)。
- 没有隐藏的控制流。
- 完全交给你来控制 error。
- Error are values。
- 哨兵模式 包级别的变量 公开的外部可以引用
- error.New返回指针
Error type
Sentinel Error
sentinel error定义
预定义的特定错误,我们叫为 sentinel error,这个名字来源于计算机编程中使用一个特定值来表示不可能进行进一步处理的做法。所以对于 Go,我们使用特定的值来表示错误。
if err == ErrSomething { … }
类似的 io.EOF,更底层的 syscall.ENOENT。
- 使用 sentinel 值是最不灵活的错误处理策略,因为调用方必须使用 == 将结果与预先声明的值进行比较。当您想要提供更多的上下文时,这就出现了一个问题,因为返回一个不同的错误将破坏相等性检查。
- 甚至是一些有意义的 fmt.Errorf 携带一些上下文,也会破坏调用者的 == ,调用者将被迫查看 error.Error() 方法的输出,以查看它是否与特定的字符串匹配。
不依赖检查 error.Error 的输出。
不应该依赖检测 error.Error 的输出,Error 方法存在于 error 接口主要用于方便程序员使用,但不是程序(编写测试可能会依赖这个返回)。这个输出的字符串用于记录日志、输出到 stdout 等。
- 日志打印原则
- 底层打包 一层一层传递上去 在拦截器的地方进行 统一记录
- 类似堆栈的实现 you should handel errors onece
- 使用pkg/error包
- error.Errorf error.New
-
如果是调用的是项目中的函数, 直接返回
-
如果是和其他库进行协作
-
直接返回错误 而不是到处大日志
-
goroutine 用%+v 打印堆栈详情
-