👨‍💻簡介

在軟體開發中,錯誤無所不在。無論是網路請求失敗、檔案不存在,還是數學計算錯誤,處理錯誤是任何開發者的日常工作,系統的穩定度基本取決於對於錯誤處理是否全面,好的錯誤處理也可以產生適當的錯誤訊息,讓 Debug 更加容易。在Go語言中,有一些獨特的功能在處理錯誤和資源管理方面非常有用。,其中包括errorpanicrecover。今天就來介紹這三個關鍵字與錯誤處理的簡單用法。

Error

什麼是Error?

在Go中,Error是一個擁有error interface的類型,可以看到他只有一個方法,該方法主要用來描述一個錯誤的字串。

type error interface {
    Error() string
}

如何產生錯誤訊息

  1. errors.New() 使用方式很簡單,在裡面放入要顯示的錯誤訊息
package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("This is a custom error message")
    fmt.Println(err.Error()) // output:This is a custom error message
}
  1. fmt.Errorf() 主要用在格式化錯誤訊息的情況下
package main

import (
    "fmt"
)

func main() {
    name := "John"
    err := fmt.Errorf("Hello, %s! This is a custom error message", name)
    fmt.Println(err.Error()) // output:Hello, John! This is a custom error message
}

Error用途

我們通常將Error用來當作函數的返回值,方便我們在調用函數時檢查錯誤:

func divide(x, y int) (int, error) {
    if y == 0 {
        return 0, errors.New("division by zero")
    }
    return x / y, nil
}

在範例中,我們透過errors.New創建一個新的Error實例,以表示除以零的錯誤情況。

panic與recover

什麼是panic?

在Go中,panic是一個內建函數,用於引發運行時異常或嚴重錯誤。通常情況下,我們應該避免使用panic,除非出現不可恢復的錯誤。

Panic是Go語言中的一種異常情況,表示程式遇到了一個無法處理的錯誤。當Panic發生時,程式會立即停止執行並打印Panic訊息,然後退出。Panic通常用於表示嚴重的錯誤,如空指標引用或陣列越界。

什麼是recover?

recover是Go語言中的一個內建函數,用於在發生panic時恢復程序的正常執行流程。它用於捕獲panic引發的錯誤,並允許程序繼續運行而不崩潰。

如何處理panic

為了處理panic,我們會使用recover。底下簡單使用兩者當作範例:

package main

import "fmt"

func main() {
    defer handlePanic()
    doSomething()
}

func doSomething() {
    panic("發生了一個嚴重錯誤")
}

func handlePanic() {
    if r := recover(); r != nil {
        fmt.Println("恢復了panic:", r)
    }
}

可以看到,doSomething函數引發了一個panic,但由於我們在main函數中使用了deferrecover,程序不會崩潰,而是執行了handlePanic函數,並輸出了錯誤訊息。需要注意的是,recover只能在defer中執行

常見用法

函數返回錯誤

當函數執行過程中遇到錯誤時,通常會將一個 error 類型的值作為返回值,以便呼叫方可以檢查和處理錯誤。

func Divide(a, b float64) (float64, error) {
if b == 0 {
    return 0, errors.New("division by zero")
}
return a / b, nil
}

錯誤處理

在呼叫函數時,通常會檢查返回的 error 是否為 nil,以判斷是否發生了錯誤。如果錯誤不為 nil,則需要採取適當的措施來處理錯誤,比如記錄錯誤日誌、返回錯誤訊息給使用者等。

result, err := Divide(10.0, 0.0)
if err != nil {
    log.Println("Error:", err)
    // 進一步處理錯誤,如返回錯誤響應給使用者
}

自定義錯誤類型

因為Error是一個 interface,所以我們也可以實作自己的 error struct,有時會需要自定義錯誤類型,方便我們更好描述特定類型的錯誤

// 自定義 error type
type MyError struct {
 Code    int
 Message string
}

// 實現 error interface的 Error 方法
func (e *MyError) Error() string {
 return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

func main() {
 // 簡單建立自己的error
 err := &MyError{
 	Code:    404,
 	Message: "Page not found",
 }

 // 調用自己的error 方法
 fmt.Println("Error:", err.Error())
}

錯誤類型判斷

還有一種常見的做法是將error搭配 type assertion進行錯誤類型判斷

package main

import (
	"errors"
	"fmt"
)

// 自定義 error type
type MyError struct {
	Code    int
	Message string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}

func main() {
	err := processRequest(0)

	switch e := err.(type) {
	case nil:
		fmt.Println("Request processed successfully.")
	case *MyError:
		fmt.Printf("Custom Error: %v\n", e)
		// 處理自定義錯誤
	case error:
		fmt.Printf("Generic Error: %v\n", e)
		// 處理其他錯誤
	default:
		fmt.Println("Unknown error.")
	}
}

func processRequest(param int) error {
	if param == 0 {
		return &MyError{Code: 500, Message: "Internal Server Error"}
	} else if param < 0 {
		return errors.New("Negative parameter value")
	}
	return nil
}

實際應用

1. 檔案操作

  • 場景:讀取檔案時檔案不存在的情況。
package main

import (
    "fmt"
    "os"
)

func main() {
    _, err := os.Open("non_existent_file.txt")
    if err != nil {
        if os.IsNotExist(err) {
            fmt.Println("檔案不存在。")
        } else {
            fmt.Println("錯誤:", err)
        }
    }
}

2. 網路通訊

  • 場景:嘗試建立網路連線時timeout的情況。
package main

import (
    "fmt"
    "net"
    "time"
)

func main() {
    _, err := net.DialTimeout("tcp", "example.com:8080", time.Second)
    if err != nil {
        fmt.Println("錯誤:", err)
    }
}

3. 資料庫操作

  • 場景:執行 SQL 查詢時發生語法錯誤的情況。
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/database")
    _, err := db.Exec("SELECT * FROM non_existent_table")
    if err != nil {
        fmt.Println("錯誤:", err)
    }
}

4. 使用者輸入驗證

  • 場景:驗證使用者輸入是否為數字。
package main

import (
    "fmt"
    "strconv"
)

func main() {
    userInput := "abc"
    _, err := strconv.Atoi(userInput)
    if err != nil {
        fmt.Println("無效的輸入:", err)
    }
}

5. 異常情況處理

  • 場景:處理不可預見的系統錯誤。
package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("/dev/zero")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    
    fmt.Println("File opened successfully.")
}

小結

錯誤處理三劍客,errorpanicrecover是強大的工具,它能讓你的程式碼更容易讀懂、容易維護,同時也能確保資源被妥善釋放,處理錯誤也更加方便。

📚Reference