目录
error
Go
语言内置了一个简单的错误接口作为一种错误处理机制,接口定义如下:http://golang.org/pkg/builtin/#error
type error interface {
Error() string
}
它包含一个 Error()
方法,返回值为string
Go
的error
构造有两种方式,分别是
第一种:errors.New()
http://golang.org/src/pkg/errors/errors.go
err := errors.New("This is an error")
if err != nil {
fmt.Print(err)
}
errors.New()
返回的是内部errorString
对象的指针。
第二种:fmt.Errorf()
:
http://golang.org/src/pkg/errors/errors.go
err := fmt.Errorf("This is an error")
if err != nil {
fmt.Print(err)
}
除了直接使用Go
自带的方法,还可以自定义错误。下面以自然数函数作为例子:
type NotNature float64
func (err NotNature) Error() string {
return fmt.Sprintf("自然数为大于或等于0的数: %v", float64(err))
}
func Nature(x float64) (float64,error) {
if x<0 {
return 0,NotNature(x)
} else {
return x,nil
}
}
func main() {
fmt.Println(Nature(1))
fmt.Println(Nature(-1))
}
需要注意一下几点:
-
如果函数需要处理异常,通常将
error
作为多值返回的最后一个值,返回的error
值为nil
则表示无异常,非nil
则是有异常。 -
一般先用
if
语句处理error!=nil
,正常逻辑放if
后面。
Go
语言的error
代表的并不是真“异常”,只是通过返回error
来表示错误信息,换句话说,不是运行时错误范围预定义的错误,某种不符合期望的行为并不会导致程序无法运行(自然数函数例子),都应使用error
进行异常处理。当程序出现重大错误,如数组越界,才会将其当成真正的异常,并用panic
来处理。
定义自己的错误
实现error
接口
package main
import (
"fmt"
"time"
)
// MyError is an error implementation that includes a time and message.
type MyError struct {
When time.Time
What string
}
func (e MyError) Error() string {
return fmt.Sprintf("%v: %v", e.When, e.What)
}
func oops() error {
return MyError{
time.Date(1989, 3, 15, 22, 30, 0, 0, time.UTC),
"the file system has gone away",
}
}
func main() {
if err := oops(); err != nil {
fmt.Println(err)
}
}
panic
Go
不使用try...catch
方法来处理异常,而是使用panic
和recover
先上代码举一个简单的例子
func main() {
fmt.Println("Hello,Go!")
panic(errors.New(" i am a error"))
fmt.Println("hello,again!")
}
输出:
Hello,Go!
panic: i am a error
goroutine 1 [running]:
main.main()
~/error.go:12 +0xb5
exit status 2
可以看到,panic
后面的程序不会被执行了。但是我们捕捉异常并不是为了停止程序(一般情况),而是为了让程序能正常运行下去,这时候就到recover
出场了。
package main
import "fmt"
func main(){
defer func(){
fmt.Println("我是defer里面第一个打印函数")
if err:=recover();err!=nil{
fmt.Println(err)
}
fmt.Println("我是defer里面第二个打印函数")
}()
f()
}
func f(){
fmt.Println("1")
panic("我是panic")
fmt.Println("2")
}
输出:
1
我是defer里面第一个打印函数
我是panic
我是defer里面第二个打印函数
可以看到,f函数一开始正常打印,当遇到panic
,就跳到defer
函数,执行defer
函数里的内容
需要注意的是,defer
函数里打印的err
其实就是panic
里面的内容。
下面详细介绍一下panic
和recover
的原理。首先来看一下panic
和recove
r的官方定义
func panic(v interface{})
内置函数panic
会停止当前goroutine
的正常执行。当函数F
调用panic
时,F
的正常执行立即停止。任何被F
延迟执行的函数都将以正常的方式运行,然后F
返回其调用者。对调用方G
来说,对F
的调用就像调用panic
一样,终止G
的执行并运行任何延迟的函数。直到执行goroutine
中的所有函数都按逆序停止。此时,程序将以非0
退出代码终止。此终止序列称为panicking
,可由内置函数recover
控制。
func recover() interface{}
recover
内置函数允许程序管理panicking
的goroutine
的行为。在defer
函数(但不是它调用的任何函数)内执行恢复调用,通过恢复正常执行来停止panicking
序列,并检索传递给panic
调用的错误值。如果在defer
函数之外调用recover
,则不会停止panicking
的序列。在这种情况下,或者当goroutine
不panicking
时,或者提供给panic
的参数是nil
,recover
返回nil
。因此,recover
的返回值报告goroutine
是否panicking
注意:defer
和recove
r必须在panic
之前定义,否则无效。
源码分析
errors.New
的定义如下
// src/errors/errors.go
// 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
}
-
New
函数返回格式为给定文本的错误 -
即使文本是相同的,每次对
New
的调用都会返回一个不同的错误值。
Error vs Exception
各个语言的演进历史:
C
单返回值,一般通过传递指针作为指针为入参,返回值为int
表示成功还是失败C++
引入了exception
,但是无法知道被调用方法会抛出什么异常。Java
引入了checked exception
,方法的所有者必须申明,调用者必须处理。在启动时抛出大量的异常是司空见惯的事情,并在它们的调用堆栈中尽职地记录下来。Java
异常不再是异常,而是变得司空见惯了。它们从良性到灾难性都有使用,异常的严重性由函数的调用者来区分。
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