错误
1. type error interface { 2. Error() string 3. }
内建error接口类型是约定用于表示错误信息,nil值表示无错误。
获取error信息
_, err := os.Create("./.??") if err != nil{ fmt.Printf("%v %T\n",err,err) fmt.Println(err.Error()) }
结果
open ./.??: The filename, directory name, or volume label syntax is incorrect. *fs.PathError
open ./.??: The filename, directory name, or volume label syntax is incorrect.
源代码
src->os->error.go
type PathError = fs.PathError
src->io->fs->fs.go
type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } func (e *PathError) Unwrap() error { return e.Err } func (e *PathError) Timeout() bool { t, ok := e.Err.(interface{ Timeout() bool }) return ok && t.Timeout() }
PathError是结构体实现了error接口,可以看到结果和源代码的格式一样
error统一定义
errors包有
func New(text string) error
使用字符串创建一个错误
部分朋友一样的错误,每次都New,例如,errors.New("b can not be zero")、errors.New("division by zero")、errors.New("division by zero!!!")
我们应该学习go的开发者们的写法,将error统一定义,如果比较多,可以单独写一个文件(例如,errors.go)放在包中,比较少的话可以写在本文件
var divisionByZeroError = errors.New("division by zero")
error放在返回值类型列表的最后
约定的写法,一般都是放在最后,建议大家这样写
func division(a,b int) (int,error) { if b == 0{ return 0,divisionByZeroError } return a/b,nil }
多次尝试可避免失败,不必立即返回error
这个常见的情况就是写爬虫,由于网络原因,导致连接失败,一般情况下都是用户传一个重试次数retries,或者超时时间timeout,达到条件时才会返回错误或抛出异常。
多层嵌套,给error添加日志/出错位置
go的标准库的日志不是很强大,这里就不展示日志了,可以标出出错位置
func positionError(file,line string,err error) error { return errors.New(file+" "+ line + ":"+err.Error()) }
我们写一个正整数除法,调用前面的函数(有修改,查看全部代码)
func cal(a,b int) (int,error) { if a<=0 || b<0{ return 0,positionError("err.go","28",negativeError) } return division(a,b) }1.
_, err = cal(3, 0) fmt.Println(err) _,err = cal(-3,3) fmt.Println(err)
结果
err.go 20:division by zero
err.go 28:calculate negative number
异常
有时程序出错是不可控的或很难判断的,如数组越界、空指针,这是就需要用到panic
panic
func panic(v interface
内建函数panic停止当前goroutine的正常执行。当函数F调用panic时,F的正常执行就会立刻停止。F中defer的所有函数先入后出执行后,F返回给其调用者G。G如同F一样行动,层层返回,直到该Go程中所有函数都按相反的顺序停止执行。之后,程序被终止,而错误情况会被报告,包括引发该panic的实参值,此终止序列称为panic过程。
// 使用panic func cal2(a,b int) (int,error) { if a<=0 || b<0{ panic("cannot use negative number") } return division(a,b) }
结果
panic: cannot use negative number
goroutine 1 [running]:
main.cal2(0x1, 0xffffffffffffffff, 0xc0000d5f28, 0x1, 0x1)
E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:36 +0x8a
main.main()
E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:52 +0x2af
exit status 2
recover
有时异常是我们意料之外的,需要进行恢复,不影响后序程序的执行,这时,就需要recover
func recover() interface{}
内建函数recover允许程序管理panic过程中的goroutine。在defer的函数中,执行recover调用会取回传至panic调用的错误值,恢复正常执行,停止恐慌过程。若recover在defer的函数之外被调用,它将不会停止panic过程序列。在此情况下,或当该goroutine不在panic过程中时,或提供给panic的实参为nil时,recover就会返回nil。
func cal2(a,b int) (int,error) { if a<=0 || b<0{ panic("cannot use negative number") } err := recover() fmt.Println(err) if err != nil{ return 0,err.(error) } return division(a,b) }
res,err := cal2(1,-1) fmt.Println(res)
结果
panic: cannot use negative number
goroutine 1 [running]:
main.cal2(0x1, 0xffffffffffffffff, 0xc0000d5f28, 0x1, 0x1)
E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:37 +0x185
main.main()
E:/Workspace/Go_workspace/learn_go/src/learnerr/main/err.go:59 +0x2bb
exit status 2
可见,不在defer 的函数中使用recover是没有意义的
修改到defer的函数中
func cal2(a,b int) (int,error) { defer func() { if err := recover();err!=nil{ fmt.Println(err) } }() if a<=0 || b<0{ panic("cannot use negative number") } return division(a,b) }
结果:
cannot use negative number
0
可以看到,虽然输出了panic函数中的话,但是没有panic,程序继续执行,输出了res
更详细的使用和细节查看:Go-关键字defer、panic、recover详解
全部代码
package main import ( "errors" "fmt" "os" ) // error统一定义 var divisionByZeroError = errors.New("division by zero") var negativeError = errors.New("calculate negative number") // 记录错误发生的文件和函数 func positionError(file,line string,err error) error { return errors.New(file+" "+ line + ":"+err.Error()) } // 除法函数 func division(a,b int) (int,error) { if b == 0{ return 0,positionError("err.go","20",divisionByZeroError) } return a/b,nil } // 计算正整数除法 func cal(a,b int) (int,error) { if a<=0 || b<0{ return 0,positionError("err.go","28",negativeError) } return division(a,b) } // 使用panic func cal2(a,b int) (int,error) { defer func() { if err := recover();err!=nil{ fmt.Println(err) } }() if a<=0 || b<0{ panic("cannot use negative number") } //不在defer中使用recover是没有意义的 //err := recover() //fmt.Println(err) //if err != nil{ // return 0,err.(error) //} return division(a,b) } func main() { //-----------获取err信息------------ _, err := os.Create("./.??") if err != nil{ fmt.Printf("%v %T\n",err,err) fmt.Println(err.Error()) } //----------嵌套error,标明位置 _, err = cal(3, 0) fmt.Println(err) _,err = cal(-3,3) fmt.Println(err) res,err := cal2(1,2) fmt.Println(res) fmt.Println(fmt.Errorf("line %d, error:%s",69,negativeError)) }
运行截图
总结
- 错误是可控的,是程序定义的
- 异常是未意料到的
- 尽量使用error显式返回错误,而不是panic
- error应该统一定义,添加位置等信息
参考
-------------------------2021年5月29日 更新-----------------------------
fmt中有生成error的函数,
func Errorf(format string, a ...interface{}) error
Errorf根据format参数生成格式化字符串并返回一个包含该字符串的错误。
测试代码
fmt.Println(fmt.Errorf("line %d, error:%s",69,negativeError))
结果
line 69, error:calculate negative number
已更新至全部代码
-------------------------2021年5月29日 更新结束-----------------------------
更多Go相关内容:Go-Golang学习总结笔记
有问题请下方评论,转载请注明出处,并附有原文链接,谢谢!如有侵权,请及时联系。