【Go】錯誤和異常處理

Go語言中沒有try catch語句,而是通過函數返回值逐層往上拋。

錯誤指的是可能出現問題的地方出現了問題。例如:打開一個文件時失敗了,這種情況在人們的意料之中。
異常指的是不應該出現問題的地方出現了問題。例如:引用了空指針,這種情況是在人們的意料之外。
錯誤是業務過程中的一部分,而異常不是。

Go語言引入了內置的error類型表示錯誤,錯誤值可以存儲在變量中,從函數中返回,error也可以看成一種數據類型。

package main

import (
	"fmt"
	"os"
)

func main(){
	/*
	如果一個函數或方法返回一個錯誤,那麼按照慣例,它必須是函數返回的最後一個值
	 */
	f, err := os.Open("test.txt")
	if err != nil{
		fmt.Println(err)
		return
	}
	// 根據f進行文件的讀或寫
	fmt.Println(f.Name(), "opened successfully")
}

error是一種內置數據類型,內置的接口:

type error interface {
	Error() string
}

其中定義了一個Error的方法。使用Go語言提供的包構建一個error:

  • errors包下的函數:New(), 創建一個error對象
  • fmt包下的Errorf()函數:func Errorf(format string, a …interface{}) error
    創建實例如下:
// 1. 創建一個error數據
	err1  := errors.New("自己創建的一個錯誤")
	fmt.Println(err1)
	fmt.Printf("%T\n", err1)
	// 2.另一個創建error的方法
	err2 := fmt.Errorf("錯誤的信息碼%d", 404)
	fmt.Println(err2)
	fmt.Printf("%T\n", err2)

例如:

// 設計一個函數:驗證年齡是否合法,如果爲負數,就返回一個error
func checkAge(age int) error{
	if age < 0{
		// 返回error
		// err := errors.New("年齡不合法")
		err := fmt.Errorf("給定的年齡是:%d, 是不合法的", age)
		return err
	}
	fmt.Println("年齡是:", age)
	return nil
}

根據斷言查看錯誤類型相關數據:

package main

import (
	"fmt"
	"os"
)

func main(){
	/*
	如果一個函數或方法返回一個錯誤,那麼按照慣例,它必須是函數返回的最後一個值
	 */
	f, err := os.Open("test.txt")
	if err != nil{
		fmt.Println(err)
		// 使用斷言 查看錯誤信息
		if ins, ok := err.(*os.PathError); ok{
			fmt.Println("1.Op:", ins.Op)
			fmt.Println("2.Path:", ins.Path)
			fmt.Println("3.Err:", ins.Err)
		}
		return
	}
	// 根據f進行文件的讀或寫
	fmt.Println(f.Name(), "opened successfully")
}

還有可以這個例子:


package main

import (
	"fmt"
	"net"
)

func main() {
	// 正常訪問一個網址
	addr, err := net.LookupHost("www.baidu.com")
	fmt.Println(err)
	fmt.Println(addr)
	// 不能訪問一個網址
	addr2, err2 := net.LookupHost("www.baidu2.com")
	fmt.Println(err2)
	fmt.Println(addr2)
	// 使用斷言查看斷言相關錯誤信息
	if ins, ok := err.(*net.DNSError);ok{
		if ins.Timeout(){
			fmt.Println("操作超時")
		}else if ins.Temporary(){
			fmt.Println("臨時性錯誤")
		}else{
			fmt.Println("通常錯誤")
		}
	}

}

如果返回的錯誤是我們關心的錯誤,我們可以直接比較,然後具體情況具體分析。例如:


package main

import (
	"fmt"
	"path/filepath"
)

func main() {
	// filepath包的Glob函數用於返回與模式匹配的所有文件的名稱,當模式出現錯誤時,該函數返回一個錯誤ErrBadPattern
	files, err := filepath.Glob("[")
	if err != nil && err == filepath.ErrBadPattern{
		fmt.Println(err)
		return
	}
	fmt.Println("matched files", files)
}

不要忽略一個錯誤,忽視錯誤會招致麻煩。

自定義error。 error是一個接口,實現接口方法即可。例如:

package main

import (
	"fmt"
	"math"
)

/**
自定義錯誤
 */

func main()  {
	radius := 4.0
	area1, err1 := circleArea(radius)
	if err1 != nil{
		fmt.Println(err1)
		// 斷言判斷錯誤類型爲areaError,獲取錯誤類型中的屬性
		if err, ok:=err1.(*areaError);ok{
			fmt.Printf("半徑是:%.2f", err.radius)
		}
		return
	}
	fmt.Println("圓形的面積:", area1)
	radius = - 4.0
	area2, err2 := circleArea(radius)
	if err2 != nil{
		fmt.Println(err2)
		// 斷言判斷錯誤類型爲areaError,獲取錯誤類型中的屬性
		if err, ok:=err2.(*areaError);ok{
			fmt.Printf("半徑是:%.2f", err.radius)
		}
		return
	}
	fmt.Println("圓形的面積:", area2)
}
// 定義一個結構體
type areaError struct {
	msg string
	radius float64
}
// 實現error接口
func (e *areaError) Error() string{
	return fmt.Sprintf("error:半徑,%.2f,%s", e.radius, e.msg)
}
func circleArea(radius float64)(float64, error)  {
	if radius < 0{
		return 0, &areaError{msg:"半徑非法", radius:radius}
	}
	return math.Pi*radius*radius, nil

}

進階:自定義錯誤以及對錯誤的處理方法。

package main

import "fmt"

func main()  {
	length, width := 5.6, 10.2
	area, err := rectArea(length, width)
	if err!=nil {
		fmt.Println(err)
		return
	}
	fmt.Println("矩形的面積爲:", area)
	length, width = -5.6, -10.2  // 這是賦值,不是定義
	area, err = rectArea(length, width)
	if err!=nil {
		fmt.Println(err)
		// 使用斷言判斷error類型
		if err, ok:= err.(*areaError); ok{
			if err.lengthNegative(){
				fmt.Printf("error:長度,%.2f小於0", err.length)
			}
			if err.widthNegative(){
				fmt.Printf("error:寬度,%.2f小於0", err.width)
			}
		}
		return
	}
	fmt.Println("矩形的面積爲:", area)
}
// 結構體
type areaError struct {
	msg string  // 錯誤的描述
	length float64  // 發生錯誤的時候,矩形的長度
	width float64  // 發生錯誤的時候,矩形的寬度
}

func (e *areaError)Error() string  {
  return e.msg
}
func (e *areaError) lengthNegative() bool  {
	return e.length < 0
}
func (e *areaError) widthNegative() bool {
	return e.width < 0
}
// 計算矩形的面積
func rectArea(length, width float64) (float64, error)  {
	msg := ""
	if length < 0{
		msg = "矩形長度小於零"
	}
	if width < 0{
		if msg == ""{
			msg = "矩形寬度小於零"
		}else{
			msg += ", 矩形寬度小於零"
		}
	}
	if msg != ""{
		// 有錯誤,創建對應的錯誤結構體(對象)
		return 0, &areaError{msg:msg, length:length,width:width}
	}
	return length*width, nil
}

panic()和recover()
golang(go)中引入兩個內置函數panic和recover來觸發和終止異常處理流程,同時引入關鍵字defer來延遲defer後面的內容,一直等到defer語句的函數執行完畢是,延遲函數defer候命的內容纔會被執行,而不管defer語句是通過return正常結束,還是由於panic導致已成結束。

package main

import "fmt"

func main()  {
	defer func() {
		// recover()  返回panic中的參數類型
		// 這時說明恐慌了
		if msg:= recover() ;msg != nil{
			fmt.Println(msg, "程序恢復了") // 執行recover後,程序就可以恢復執行(不報錯),類似於catch
			// 但是異常後面的程序是不會再運行了
		}
	}()
	funA()
	defer myPrint("defer main 3...")
	funB()
	defer myPrint("defer main 4...")
	fmt.Println("main over")

}
func myPrint(s string)  {
	fmt.Println(s)
}

func funA()  {
	fmt.Println("一個很普通的函數A")
	
}
func funB()  { // defer 爲外圍函數

	fmt.Println("一個很普通的函數B")
	defer myPrint("defer funcB 1...")
	for i:=1; i<=10;i++{
		fmt.Println("i:", i)
		if i == 5{
			// 讓程序中斷
			panic("funB恐慌了")  // 參數爲一個接口,可以接受任意值
			// 這時程序後面的內容就不能再執行了
			// 注已經被defer的函數還會執行
		}
	}
	defer myPrint("defer funcB 2...")

}

什麼時候使用錯誤,什麼時候使用異常需要根據實際情況而定。例如:

  • 空指針引用
  • 下標越界
  • 除數爲0
  • 不應該出現的分支,比如default
  • 輸入不應該引起函數錯誤

等,可使用異常處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章