go的错误处理机制跟php的完全不一样,go讲究的是:
go的错误分为3种:
1:编译时错误,在编译时抛出的错误,例如有变量未出现使用,变量未声明直接使用等,此错误是在运行之前被编译器找出报错的
2:运行时业务自行抛出的错误(Error),此错误可以直接通过函数返回值返回,由调用栈接收处理,如果不处理则不会影响程序运行
3:通过panic 函数抛出的错误,此错误无法被调用栈拦截,但可以在之后由recover接收并忽略,但是无法恢复原来的调用栈
自行抛出的error错误
例如:
package main import ( "errors" "fmt" ) func main() { var a, b float64 a = 10 b = 0 result, err := division(a, b) if err != nil { fmt.Printf("出错了:%s", err) } fmt.Printf("%f 除于 %f 等于 %f", a, b, result) } /** 除法 */ func division(num1, num2 float64) (float64, error) { if num2 == 0 { return 0, errors.New("0不能作为除数") } return num1 / num2, nil }
输出:
出错了:0不能作为除数10.000000 除于 0.000000 等于 0.000000
在示例中,如果函数出现业务异常,只能通过errors.New进行return错误数据,
再由调用函数接收error并做判断处理
也可以声明错误结构体,并声明函数方法实现error接口,使其抛出异常:
package main import ( "fmt" ) type DivideError struct { num1 float64 num2 float64 } func (receiver DivideError) Error() string { strFormat := ` Cannot proceed, the divider is zero. dividee: %d divider: 0 ` return fmt.Sprintf(strFormat, receiver. num2) } func main() { var a, b float64 a = 10 b = 0 result,err := division(a, b) if err!=nil { fmt.Printf("出错了:%s",err) return } fmt.Printf("%f 除于 %f 等于 %f",a,b,result) } /** 除法 */ func division(num1, num2 float64) (float64, error) { //if num2 == 0 { // return 0, errors.New("0不能作为被除数") //} fmt.Println(num2) if num2 == 0 { divisionError := DivideError{num1: num1 ,num2: num2} return 0, divisionError } return num1 / num2,nil }
输出
GOROOT=/Users/tioncico/sdk/go1.17.6 #gosetup GOPATH=null #gosetup /Users/tioncico/sdk/go1.17.6/bin/go build -o /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_LearnGoProject\_ . #gosetup /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_LearnGoProject\_ 0 出错了: Cannot proceed, the divider is zero. dividee: %!d(float64=0) divider: 0
panic函数抛出错误
如果你访问数组时越界,go将自动检查并调用panic抛出错误:
package main import ( "fmt" ) func main() { arr := make(\[\]int,3) fmt.Println(arr\[4\]) fmt.Println("结束") //此行不会运行 }
将输出:
GOROOT=/Users/tioncico/sdk/go1.17.6 #gosetup GOPATH=null #gosetup /Users/tioncico/sdk/go1.17.6/bin/go build -o /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go /Users/tioncico/GolandProjects/LearnGoProject/main.go #gosetup /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go panic: runtime error: index out of range \[4\] with length 3 goroutine 1 \[running\]: main.main() /Users/tioncico/GolandProjects/LearnGoProject/main.go:10 +0x1d Process finished with exit code 2
在panic函数调用后,并不是立即结束进程,而是会调用该协程中的defer函数,例如:
package main import ( "fmt" ) func main() { defer func() { fmt.Println("defer 1") }() defer func() { fmt.Println("defer 2") }() defer func() { fmt.Println("defer 3") }() arr := make(\[\]int,3) fmt.Println(arr\[4\]) fmt.Println("结束") //此行不会被运行 }
在结束后,会依次通过栈 先入后出的形式调用defer:
GOROOT=/Users/tioncico/sdk/go1.17.6 #gosetup GOPATH=null #gosetup /Users/tioncico/sdk/go1.17.6/bin/go build -o /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go /Users/tioncico/GolandProjects/LearnGoProject/main.go #gosetup /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go defer 3 defer 2 defer 1 panic: runtime error: index out of range \[4\] with length 3 goroutine 1 \[running\]: main.main() /Users/tioncico/GolandProjects/LearnGoProject/main.go:18 +0x67
可通过 recover接收错误
package main import ( "fmt" ) func main() { defer func() { err := recover() fmt.Println("defer 1") fmt.Println("出现了错误:") fmt.Println(err) }() arr := make(\[\]int,3) fmt.Println(arr\[4\]) fmt.Println("结束") }
输出:
GOROOT=/Users/tioncico/sdk/go1.17.6 #gosetup GOPATH=null #gosetup /Users/tioncico/sdk/go1.17.6/bin/go build -o /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go /Users/tioncico/GolandProjects/LearnGoProject/main.go #gosetup /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go defer 1 出现了错误: runtime error: index out of range \[4\] with length 3 Process finished with exit code 0
通过recover函数可以获取到错误信息,并且可以通过此信息做一些其他操作(例如记录日志等)
但是可以看出,在recover之后,程序并没有继续运行,("结束"依旧没打印)
如果是在其他调用栈调用的情况:
package main import ( "fmt" ) func main() { test() fmt.Println("结束") } func test() { defer func() { err := recover() fmt.Println("defer 1") fmt.Println("出现了错误:") fmt.Println(err) }() arr := make(\[\]int,3) fmt.Println(arr\[4\]) }
将输出:
GOROOT=/Users/tioncico/sdk/go1.17.6 #gosetup GOPATH=null #gosetup /Users/tioncico/sdk/go1.17.6/bin/go build -o /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go /Users/tioncico/GolandProjects/LearnGoProject/main.go #gosetup /private/var/folders/y7/b5kdfsqx12vchqdhjh4b0mth0000gn/T/\_\_\_go\_build\_main\_go defer 1 出现了错误: runtime error: index out of range \[4\] with length 3 结束
可以看到,在其他调用栈调用panic+recover之后,只会影响运行panic的调用栈,而不是影响上层调用
在服务器场景中,可能需要同时处理多个请求,每个请求的操作互不影响,可以通过此操作,使得请求错误之后,不会影响其他请求栈