Go 没有像 Java 和 .NET 那样的 try/catch
异常机制:不能执行抛异常操作。但是有一套 defer-panic-and-recover
机制
Go 的设计者觉得 try/catch
机制的使用太泛滥了,而且从底层向更高的层级抛异常太耗费资源。他们给 Go 设计的机制也可以“捕捉”异常,但是更轻量,并且只应该作为(处理错误的)最后的手段。
Go 是怎么处理普通错误的呢?通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil
,则没有错误发生——并且主调 (calling) 函数总是应该检查收到的错误。
永远不要忽略错误,否则可能会导致程序崩溃!!
panic()
和 recover()
是用来处理真正的异常(无法预测的错误)而不是普通的错误。
库函数通常必须返回某种错误提示给主调函数。
Go 检查和报告错误条件的惯有方式:
产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 nil
就是成功,非 nil
就是发生了错误。
为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。
if value, err := pack1.Func1(param1); err != nil {
fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
return // or: return err
} else {
// Process(value)
}
为了更清晰的代码,应该总是使用包含错误值变量的 if 复合语句
如果程序中止也没关系的话甚至可以使用 panic()
Go 有一个预先定义的 error 接口类型
type error interface {
Error() string
}
错误值用来表示异常状态;errors
包中有一个 errorString
结构体实现了 error
接口。当程序处于错误状态时可以用 os.Exit(1)
来中止运行。
任何时候当需要一个新的错误类型,都可以用 errors
包(必须先 import
)的 errors.New()
函数接收合适的错误信息来创建,像下面这样:
err := errors.New("math - square root of negative number")
通常(错误信息)都会有像 Error:...
这样的前缀,所以你的错误信息不要以大写字母开头(注:英文只有句首单词首字母大写,这里应当是考虑到这一点)。
在大部分情况下自定义错误结构类型很有意义的,可以包含除了(低层级的)错误信息以外的其它有用信息,例如,正在进行的操作(打开文件等),全路径或名字。看下面例子中 os.Open()
操作触发的 PathError
错误:
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Err error // Returned by the system call.
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": "+ e.Err.Error()
}
如果有不同错误条件可能发生,那么对实际的错误使用类型断言或类型判断(type-switch)是很有用的,并且可以根据错误场景做一些补救和恢复操作。
// err != nil
if e, ok := err.(*os.PathError); ok {
// remedy situation
}
或:
switch err := err.(type) {
case ParseError:
PrintParseError(err)
case PathError:
PrintPathError(err)
...
default:
fmt.Printf("Not a special error, just %s\n", err)
}
所有的例子都遵循同一种命名规范:错误类型以 ...Error
结尾,错误变量以 err...
或 Err...
开头或者直接叫 err
或 Err
。
syscall
是低阶外部包,用来提供系统基本调用的原始接口。它们返回封装整数类型错误码的 syscall.Errno
;类型 syscall.Errno
实现了 Error
接口。