Go 错误处理机制有两种:
- 绝大多数函数返回
errors
- 部分无法恢复的情况,比如访问数组不存在的 index,会抛出一个运行时异常,被称为 panic
Errors
Go 中有一种 pattern,一个函数返回两个值,分别是操作结果和 err 对象,比如 os.Open()
:
func Open(name string) (file *File, err error)
使用时:
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
自定义错误
基于字符串、不带其他数据,同时不使用类型来区分错误时:
// simple string-based error
err1 := errors.New("math: square root of negative number")
// with formatting
err2 := fmt.Errorf("math: square root of negative number %g", x)
带其他数据或者希望使用类型来区分错误时,可以自定义一个 struct,只要实现了 Go 预先定义的 error
接口:
type error interface {
Error() string
}
比如:
type SyntaxError struct {
Line int
Col int
}
func (e *SyntaxError) Error() string {
return fmt.Sprintf("%d:%d: syntax error", e.Line, e.Col)
}
type InternalError struct {
Path string
}
func (e *InternalError) Error() string {
return fmt.Sprintf("parse %v: internal error", e.Path)
}
假如不带其他数据,可以定义一个 int
型错误,比如:
type keyIncorrectError int
func (ki keyIncorrectError) Error() string {
return "openpgp: incorrect key"
}
// 因为每次的 Error() string 都是一样的,出现这种错误时,该包返回一个固定的
// error 对象(ErrKeyIncorrect)即可。该包函数的调用方判断 err 是否等于
// ErrKeyIncorrect 即可,而不用 switch / case 来判断 error 类型。
var ErrKeyIncorrect error = keyIncorrectError(0)
使用时用 switch / case 语句来判断是哪种错误:
if err := Foo(); err != nil {
switch e := err.(type) {
case *SyntaxError:
// Do something interesting with e.Line and e.Col.
case *InternalError:
// Abort and file an issue.
default:
log.Println(e)
}
}
Panic
Panic 类似于 C++、Java 或 Python 的异常。抛出一个异常,用 panic()
函数:
func foo() int {
panic("something wrong happened")
return 0
}
panic 是针对 goroutine 的。一旦抛出异常,正常的执行流被中断。如果没有调用 recover()
,程序会 crash 并且打印出堆栈,像这样:
goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
/usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
/usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
/usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:789 +0x2de
GOTRACEBACK
变量可以控制输出信息的详细程度。
子协程的 panic 并不会被父协程 recover
这个需要特别注意:
func say(s string) {
fmt.Println(s)
time.Sleep(1000 * time.Millisecond)
panic("saying panic!")
}
func main() {
defer func(){
if p:= recover();p!=nil{
err := fmt.Errorf("internal error:%v", p)
fmt.Println(err)
}
}()
go say("world")
say("hello")
}
虽然 main()
中有 recover()
,但是程序仍然会 panic 退出。
panic 后的恢复
Go 中唯一能停止 panic 将程序 crash 的方式是,在 defer 语句 中调用 recover()
函数:
func main() {
n := foo()
fmt.Println("main received", n)
}
func foo() int {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
m := 1
panic("foo: fail")
m = 2
return m
}
foo: fail
main received 0
也可以使用命名返回值(named return value),使得 defer 语句中可以指定返回值。如果没有指定返回值,程序继续执行时就像当前函数返回了空的返回值一样返回。例子:
func main() {
n := foo()
fmt.Println("main received", n)
}
func foo() (m int) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
m = 2
}
}()
m = 1
panic("foo: fail")
m = 3
return m
}
foo: fail
main received 2