程序運行過程當中不可避免的發生各類錯誤,要想讓本身的程序保持較高的健壯性,那麼異常,錯誤處理是須要考慮周全的,每一個編程語言提供了一套本身的異常錯誤處理機制,在Go中,你知道了嗎?接下來咱們一塊兒看看Go的異常錯誤機制。java
func sum (x,y int) (int,int,int){ z := x+y return z,x,y }
// The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. //翻譯下來就是: //錯誤的內置接口類型是 error,沒有錯誤用 nil 表示 type error interface { Error() string }
func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. 翻譯 // 把error轉換成String是錯誤的簡單實現 type errorString struct { s string } func (e *errorString) Error() string { return e.s }
咱們能夠看到errorString結構體實現了 Error()string 接口,經過New()方法返回一個errorString指針類型的對象。golang
看到這裏不知道你們想到沒,Go對錯誤的處理就是顯示的經過方法返回值告訴你須要對錯誤進行判斷和處理。也就是錯誤對你是可見的,這也須要開發人員在方法中儘量的考慮到各類發生的錯誤,並返回給方法調用者。編程
咱們知道程序在運行時會發生各類各樣的運行時錯誤,好比數組下標越界異常,除數爲0的異常等等,而這些異常若是不被處理會致使go程序的崩潰,那麼如何捕獲這些運行時異常轉化爲錯誤返回給上層調用鏈,就讓我一塊兒看看這幾個關鍵字:panic, defer recover,此處咱們不討論原理。數組
咱們把運行時發生異常稱爲發生了一個恐慌,咱們也能夠手動拋出一個恐慌,以下微信
func TestPanic(){ panic("發生恐慌了") } //截取一部分結果,咱們看到程序終止了,打印了堆棧信息 anic: 發生恐慌了 [recovered] panic: 發生恐慌了 goroutine 19 [running]: testing.tRunner.func1(0xc0000b6100) D:/sdk/go12/src/testing/testing.go:830 +0x399 panic(0x521da0, 0x57bb10) D:/sdk/go12/src/runtime/panic.go:522 +0x1c3 gome_tools/basic.TestPanic(...) D:/gome_space/gome_tools/basic/array_slice.go:101
恐慌發生了怎麼處理呢,這時須要defer和recover一塊兒協做,defer什麼意思呢,是表示這個方法最後執行的一段代碼,不管這個方法發生錯誤,異常等,defer裏面的裏代碼必定會被執行,而咱們能夠在defer中經過recover關鍵字恢復咱們的恐慌,將之處理,轉化爲一個錯誤並打印,以下代碼:編程語言
func TestDeferAndRecover(){ defer func(){ if err:=recover(); err != nil{ fmt.Println("異常信息爲:",err) } }() panic("發生恐慌了") } //結果 異常信息爲: 發生恐慌了
func division(x,y int) (int,error){ //若是除數爲0,則返回一個錯誤信息給上游 if y == 0{ return 0,errors.New("y is not zero") } z := x / y return z ,nil } result, err := division(1,0) if err != nil { //處理錯誤邏輯 } //處理正常邏輯
如上,division函數裏面判斷y等於0時,給調用者返回一個錯誤信息,調用者經過兩個變量來接受division的返回值,判斷 err是否爲空作出不一樣的錯誤處理邏輯ide
仍是上面的
division(x,y)(z,error)
函數,假設咱們入參傳(4,2)進去,這時咱們是清楚的知道不可能發生錯誤,咱們能夠按以下處理,經過下劃線 _ 忽略這個返回值。函數
//經過_忽略第二個返回值 result, _ := division(1,0) //打印結果 fmt.Println(result)
仍是
division(x,y)(z,error)
函數,假設小明忘記了或者沒想到要判斷除數爲0的狀況,寫出來的代碼以下:ui
func division(x,y int) (int,error){ z := x / y return z ,nil }
小紅在調用上面的方法時寫成了
result,_ := division(1,0)
,很明顯division方法是會發生錯誤的,錯誤信息以下,integer divide by zero ,被除數爲0,咱們知道程序出錯了,而且整個程序終止了spa
tips: Go語言中,一旦某一個協程發生了panic而沒有被捕獲,那麼致使整個go程序都會終止,確實有點坑,但確實如此(瞭解java的人都知道,在java中一個線程發生發生了異常,只要其主線程未曾終止,那麼整個程序仍是運行的) ,但go不是這樣的,文章最後我會寫一個例子,你們能夠看看。
經過上面的tips,咱們知道,咱們不能讓咱們的方法發生panic,在不確保方法不會發生panic時必定要捕獲,謹記。
panic: runtime error: integer divide by zero [recovered] panic: runtime error: integer divide by zero
func division(x,y int) (result int,err error){ defer func(){ if e := recover(); e != nil{ err = e.(error) } }() result = x / y return result ,nil }
這段代碼什麼意思呢?當咱們
division(1,0)
時,必定會報除0異常,division函數聲明瞭返回值result(int型),err(error型),當x / y
發生異常時,在defer函數中,咱們經過recover()函數來捕獲發生的異常,若是不爲空,將這個異常賦值給返回結果的變量 err,咱們再來調用這個函數division(1,0)
看看輸出什麼,以下,是否是將堆棧信息轉化爲了一段字符串描述。
0 runtime error: integer divide by zero
咱們知道go中關於錯誤定義了一個接口,若是想要自定義本身的錯誤類型,咱們只須要實現這個接口就能夠了,仍是這個函數,咱們爲其定義一個除數爲0的錯誤
type DivideByZero struct{ //錯誤信息 e string //入參信息(除數和被除數) param string } //實現接口中的Error()string方法,組裝錯誤信息爲字符串 func (e *DivideByZero) Error() string { buffer := bytes.Buffer{} buffer.WriteString("錯誤信息:") buffer.WriteString(divideByZero.e) buffer.WriteString(",入參信息:") buffer.WriteString(divideByZero.param) return buffer.String() } func division(x,y int) (int,error){ //若是除數爲0,則返回一個錯誤信息給上游 if y == 0{ //這個時候咱們返回以下錯誤 return 0, &DivideByZero{ e:"除數不能0", param:strings.Join([]string{strconv.Itoa(x),strconv.Itoa(y)},","), } } z := x / y return z ,nil } //最終結果 0 錯誤信息:除數不能爲0,入參信息:1,0
上文提到,go中一旦某一個協程發生了panic而沒被recover,那麼整個go程序會終止,而Java中,某一線程發生了異常,即使沒被catche,那麼只是這個線程終止了,Java程序是不會終止的,只有主線程完成Java程序纔會結束,看下面兩段代碼
public static void main(String []args){ new Thread(new Runnable() { @Override public void run() { throw new RuntimeException("拋出異常了"); } }).start(); try { Thread.sleep(10 * 1000); }catch (InterruptedException e) { } }
func main(){ go func() { panic("發生恐慌了") }() time.Sleep(10 * time.Second) }
上面兩端代碼含義都是同樣的,啓動後各開一個線程和協程,在線程和協程內分別主動拋出異常,但結果不同,java的主線程會休眠10秒鐘後結束,而go主協程會當即結束。
歡迎你們關注微信公衆號:「golang那點事」,更多精彩期待你的到來