對比其他語言
其他語言比如Python用的是try Except finally
的方式來進行異常處理,執行邏輯是:嘗試執行一段代碼,如果發生異常則執行...無論是否發生異常都執行...;相比起來go語言的異常處理就簡單許多,因為程序中的異常基本上都是可預期的,所以GO語言處理異常的方式是返回這個異常,如果沒有發生異常則該值為nil,只要判斷這個預期的返回值是否是nil
便知道有沒有異常發生.go語言中還有一種panic
機制,panic
可以理解為致命的異常會中斷程序的運行,但是通過recover
函數可捕獲這個panic
讓程序繼續運行.
error
GO語言中一個普通的錯誤被稱為error,它本質是一個接口類型,可以在builtin.go
中看到其定義
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
error可以出現在很多地方,比如打開一個不存在的文件,還有數學運算錯誤等等.
例子:
package main
import (
"fmt"
"os"
)
type people interface {
name() string
}
func main() {
_, err := os.Open("不存在.go")
if err != nil {
fmt.Println(err) // open 不存在.go: no such file or directory
}
}
創建error
前面我們獲得error的方法是接受別人寫好函數的返回值,現在我們嘗試自己創建一個error,
在errors包中有多個創建error的方法,其中最常用的是errors.New()方法,該方法接收一個字符串用於描述這個錯誤.
其實現如下:
// 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方法返回了一個errorString結構體並將參數text穿進這個結構體中,這個結構體因為實現了Error方法所以他是一個error類型.
利用errors.New方法創建新error的例子:
package main
import (
"errors"
"fmt"
)
func main() {
err := errors.New("我自己創建的一個錯誤")
fmt.Println(err) // 我自己創建的一個錯誤
}
除了errors.New()方法創建error外,還可以用fmt.Errorf函數創建新的error,讓我們看看fmt.Errorf函數內部的實現:
func Errorf(format string, a ...interface{}) error {
p := newPrinter()
p.wrapErrs = true
p.doPrintf(format, a)
s := string(p.buf)
var err error
if p.wrappedErr == nil {
err = errors.New(s)
} else {
err = &wrapError{s, p.wrappedErr}
}
p.free()
return err
}
在var err error
之前的語句看不懂沒關系, 我簡單的說一下:創建了一個pp結構體指針,然后設置了wrapErrs為true,並調用doPrintf方法將我們的格式化輸入轉化成對應字符串,此時還存在p的緩沖區中,然后通過string進行類型轉化將得到的字符串村進變量s.關鍵看那個判斷語句,這說明,Errorf函數生成錯誤有兩種方式,要么調用errors.New要么返回一個wrapError類型實例.errors.New已經介紹過,下面看看wrapError結構體:
type wrapError struct {
msg string
err error
}
func (e *wrapError) Error() string {
return e.msg
}
相信大家都能明白了吧
舉個fmt.Errorf的例子
package main
import (
"fmt"
)
func main() {
err := fmt.Errorf("error error error")
fmt.Println(err) // error error error
}
番外篇 String() 和 Error()
在golang中如果直接打印一個普通對象,得到的結果就會向下面一樣,
package main
import "fmt"
type People struct {
name string
age int
}
func main(){
p := &People{
name: "horika",
age: 10,
}
fmt.Println(p) // &{horika 10}
fmt.Printf("%s\n", p) // &{horika %!s(int=10)}
}
也許有時候我們需要在打印或者轉換成字符串時想要讓他輸出自定義的一句話,這時我們可以給這個結構體增加一個String
方法,如下:
package main
import "fmt"
type People struct {
name string
age int
}
func (p *People) String() string{
return fmt.Sprintf("我叫%s, 我今年%d歲", p.name, p.age)
}
func main(){
p := &People{
name: "horika",
age: 10,
}
fmt.Println(p) // 我叫horika, 我今年10歲
fmt.Printf("%s\n", p) // 我叫horika, 我今年10歲
}
!!! 注意如果我們定義的String方法是指針調用的那么我們必須打印指針對象才有效果,也就是你定義什么類型,就打印什么類型,同學們可以自己去嘗試.
如果我們的結構體定義了一個Error
方法,那么打印的時候會優先調用Error
方法,如下
只定義Error方法的例子
:
package main
import "fmt"
type People struct {
name string
age int
}
func (p *People) Error() string{
return fmt.Sprintf("[Error] 我叫%s, 我今年%d歲", p.name, p.age)
}
func main(){
p := &People{
name: "horika",
age: 10,
}
fmt.Println(p) // [Error] 我叫horika, 我今年10歲
fmt.Printf("%s\n", p) // [Error] 我叫horika, 我今年10歲
}
即有String方法也有Error方法
package main
import "fmt"
type People struct {
name string
age int
}
func (p *People) String() string{
return fmt.Sprintf("[String] 我叫%s, 我今年%d歲", p.name, p.age)
}
func (p *People) Error() string{
return fmt.Sprintf("[Error] 我叫%s, 我今年%d歲", p.name, p.age)
}
func main(){
p := &People{
name: "horika",
age: 10,
}
fmt.Println(p) // [Error] 我叫horika, 我今年10歲
fmt.Printf("%s\n", p) // [Error] 我叫horika, 我今年10歲
}
可以看到如果Error和String方法同時存在,Error方法會覆蓋String方法,
看到這里我想你們就應該明白為什么我們之前打印一個error時只需要打印它本身而不用打印err.Error()了吧
定義自己的錯誤
看了前面的介紹我相信大家都能自己寫一個錯誤類型,無非分兩步,1. 定義一個結構體,2. 該結構體實現 Error() string
方法.然而事實真的就是這么簡單.其實在前面的番外篇里已經有了自定義錯誤的影子
舉個例子
package main
import "fmt"
type MyIntNegativeError struct {
msg string
val int
}
func (m *MyIntNegativeError)Error() string{
return fmt.Sprintf("[ERROR] reason %s; val: %d", m.msg, m.val)
}
func NewMyIntNegativeError(msg string, val int) *MyIntNegativeError{
return &MyIntNegativeError{
msg: msg,
val: val,
}
}
func Sub10(a int) (int, error){
ret := a - 10
if ret < 0 {
return 0, NewMyIntNegativeError("a必須大於10", a)
}
return ret, nil
}
func main(){
a := 9
ret, err := Sub10(a)
if err != nil {
fmt.Println("出錯啦", err) // 出錯啦 [ERROR] reason a必須大於10; val: 9
return
}
fmt.Println(ret)
}
除了必須實現Error
方法外,我一般習慣給自定義的錯誤實現一個構造函數.
panic和recover
panic
panic是一個內建函數,他會產生一個嚴重的錯誤使程序中斷執行,舉個例子
package main
import "fmt"
func main(){
for i:=1;i<10;i++{
fmt.Println(i)
if i%3 == 0{
panic("出現數字3的倍數,我不想繼續了")
}
}
}
輸出
1
2
3
panic: 出現數字3的倍數,我不想繼續了
goroutine 1 [running]:
main.main()
/home/kain/Documents/code/go_module/file_io/main.go:9 +0xf5
recover
recover可以捕獲一個panic使程序恢復運行,當然你也可以再次拋出異常,通常我們都是在defer語句中執行recover,這很容易理解,因為我們必須等所有程序都執行完才能保證整個過程不會發生panic,舉個例子
package main
import "fmt"
func main(){
f1()
}
func f1(){
defer func() {
pan := recover()
if pan != nil{
fmt.Println("我已經捕獲了錯誤,錯誤是:", pan)
fmt.Printf("錯誤類型是%T\n", pan)
}else{
fmt.Println("沒有錯誤")
}
}()
for i:=1;i<10;i++{
fmt.Println(i)
if i%3 == 0{
panic("出現數字3的倍數,我不想繼續了")
}
}
}
輸出
1
2
3
我已經捕獲了錯誤,錯誤是: 出現數字3的倍數,我不想繼續了
錯誤類型是string
以上就是golang的異常處理機制