尽可能使用错误
仅当您的代码可能最终处于容易崩溃的不良状态时才使用恐慌;真正出乎意料的事情。上面带有ForEach() 的示例是一个接受接口的导出函数,因此它应该预期有人会不正确地调用它。如果调用不当,您就会知道为什么不能继续,并且知道如何处理该错误。 isNotIterable 实际上是二进制的,易于控制。
但错误不像 try/catch
即使您试图通过查看其他语言的 throw/catch 来证明恐慌/恢复的合理性,您仍然会使用错误。我们知道您正在尝试该函数,因为您正在调用它,我们知道有一个错误是因为 err != nil,就像检查抛出的异常类型一样,您可以检查返回的错误类型 errors.Is(err, ErrNotIterable)
那么你应该对并发错误使用恐慌吗?
答案很可能仍然是否定的。错误仍然是 Go 中的首选方式,您可以使用等待组来关闭 goroutine:
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
// automatically cancel in 5 min
defer cancel()
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
// do crazy stuff; you can still check for errors
if ... {
return fmt.Errorf("critical error, stopping all goroutines now")
}
// code completed without issues
return nil
})
err = errGroup.Wait()
即使使用原始示例的结构,您仍然可以更好地控制错误而不是恐慌:
func ForEach(iterable interface{}, f interface{}) error {
if isNotIterable(iterable) {
return fmt.Errorf("expected something iterable but got %v", reflect.ValueOf(iterable).String())
}
switch v.Kind() {
case reflect.Map:
...
case reflect.Array, reflect.Slice:
...
default:
return fmt.Errorf("isNotIterable is false but I do not know how to iterate through %v", reflect.ValueOf(iterable).String())
}
但是错误感觉很冗长
是的,这就是重点。当返回一个错误时,就是在那个时候对它做一些事情。您正在提供调用代码选项,而不是决定开始关闭并杀死应用程序除非您recover()。如果您只是在调用堆栈中一直返回相同的错误,那么错误似乎不如恐慌,但这是由于在问题发生时没有解决问题。
那么什么时候使用panic呢?
当您的代码在碰撞过程中崩溃并且您无法假设自己的出路时。另一个是当代码假设某些不再正确的东西并且必须从这里开始检查每个函数的完整性将是乏味的(并且可能会影响性能)。尽管如此,您仍会使用panic() 来摆脱不确定性的层...然后仍然处理错误:
func ForEach(iterable interface{}, f interface{}) error {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("cannot iterate due to unexpected runtime error %v", r)
return
}
}()
...
// perhaps a broken pipe in a global var
// or an included module threw a panic at you!
}
但如果你还是不服气...Here is the Go FAQ
我们认为,将异常耦合到控制结构(如 try-catch-finally 习惯用法)会导致代码复杂化。它还倾向于鼓励程序员将过多的普通错误(例如无法打开文件)标记为异常。
Go 采用了不同的方法。对于简单的错误处理,Go 的多值返回可以很容易地报告错误而不会重载返回值。规范的错误类型与 Go 的其他特性相结合,使错误处理变得愉快,但与其他语言中的处理完全不同。