说明
Golang中引入error接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含error;Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。
错误/异常处理的一些原则
错误处理的正确姿势
- 失败的原因只有一个时,不使用error
- 没有失败时,不使用error
- error应放在返回值类型列表的最后
- 错误值统一定义,而不是跟着感觉走
- 错误逐层传递时,层层都加日志
- 错误处理使用defer
- 当尝试几次可以避免失败时,不要立即返回错误
- 当上层函数不关心错误时,建议不返回error
- 当发生错误时,不忽略有用的返回值
异常处理的正确姿势
- 在程序开发阶段,坚持速错
- 在程序部署后,应恢复异常避免程序终止
- 对于不应该出现的分支,使用异常处理
- 针对入参不应该有问题的函数,使用panic设计
详见https://studygolang.com/articles/11753
error类型
// 此处返回值是error接口
func xxx() error {}
// error是一个接口
type error interface {
Error() string
}
通用error
// errors.New
func New(text string) error {
return &errorString{text}
}
//
type errorString struct {
s string
}
// 实现了Error方法,因此是error接口
func (e *errorString) Error() string {
return e.s
}
err := errors.New("this is error")
自定义error
type myError struct {
msg string
other string
}
// 实现Error方法
func (self *myError) Error() string {
return fmt.Sprintf("%v %v", self.msg, self.other)
}
例子
package main
import "fmt"
type myError struct {
msg string
other string
}
func (self *myError) Error() string {
return fmt.Sprintf("%v %v", self.msg, self.other)
}
var (
MYERROR1 = &myError{"myError1", "other thing1"}
MYERROR2 = &myError{"myError222222", "other thing222222"}
)
func test1() (err error) {
return MYERROR1
}
func test2() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
panic(MYERROR2)
}
func main() {
fmt.Println("######### test1 start ##############")
err := test1()
if err != nil {
fmt.Println(err)
}
fmt.Println("######### test1 end ##############")
// ######### test1 start ##############
// myError1 other thing1
// ######### test1 end ##############
fmt.Println()
fmt.Println("######### test2 start ##############")
test2()
fmt.Println("######### test2 end ##############")
// ######### test2 start ##############
// myError222222 other thing222222
// ######### test2 end ##############
}
一些自定义的error
var (
ErrSuccess = StandardError{0, "成功"}
ErrUnrecognized = StandardError{-1, "未知错误"}
ErrAccessForbid = StandardError{1000, "没有访问权限"}
ErrNamePwdIncorrect = StandardError{1001, "用户名或密码错误"}
ErrAuthExpired = StandardError{1002, "证书过期"}
ErrAuthInvalid = StandardError{1003, "无效签名"}
ErrClientInnerError = StandardError{4000, "客户端内部错误"}
ErrParamError = StandardError{4001, "参数错误"}
ErrReqForbidden = StandardError{4003, "请求被拒绝"}
ErrPathNotFount = StandardError{4004, "请求路径不存在"}
ErrMethodIncorrect = StandardError{4005, "请求方法错误"}
ErrTimeout = StandardError{4006, "服务超时"}
ErrServerUnavailable = StandardError{5000, "服务不可用"}
ErrDbQueryError = StandardError{5001, "数据库查询错误"}
)
//StandardError 标准错误,包含错误码和错误信息
type StandardError struct {
ErrorCode int `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
// Error 实现了 Error接口
func (err StandardError) Error() string {
return fmt.Sprintf("errorCode: %d, errorMsg %s", err.ErrorCode, err.ErrorMsg)
}
Panic和recover
Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在Go语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为 0了)。才使用Go中引入的Exception处理:defer, panic, recover。
这几个异常的使用场景可以这么简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
语法
panic
// 手动抛出异常
panic(v interface{})
// recover捕捉异常
if err:=recover();err!=nil{
fmt.Println(err) // 这里的err其实就是panic传入的内容
}
例子
package main
import "fmt"
func testPanic(){
// 使用了recover,则本func会panic掉,后面的代码会继续执行
// 如果不使用recover,则整个程序都会panic掉
defer func(){
if err := recover();err != nil{
fmt.Println(err)
}
}()
panic("haha")
// 上面panic,这里就不会执行了
fmt.Println("end")
}
func main() {
// 由于使用了recover捕捉panic,所以该func执行后还会执行后面代码
testPanic()
fmt.Println("main end")
}