C語言中文網 目錄
首頁 > Go語言教程 > Go語言函數 閱讀:2,701

Go語言處理運行時錯誤

Go 語言的錯誤處理思想及設計包含以下特征:
  • 一個可能造成錯誤的函數,需要返回值中返回一個錯誤接口(error)。如果調用是成功的,錯誤接口將返回 nil,否則返回錯誤。
  • 在函數調用后需要檢查錯誤,如果發生錯誤,進行必要的錯誤處理。

Go 語言沒有類似 Java 或 .NET 中的異常處理機制,雖然可以使用 defer、panic、recover 模擬,但官方并不主張這樣做。Go 語言的設計者認為其他語言的異常機制已被過度使用,上層邏輯需要為函數發生的異常付出太多的資源。同時,如果函數使用者覺得錯誤處理很麻煩而忽略錯誤,那么程序將在不可預知的時刻崩潰。

Go 語言希望開發者將錯誤處理視為正常開發必須實現的環節,正確地處理每一個可能發生錯誤的函數。同時,Go 語言使用返回值返回錯誤的機制,也能大幅降低編譯器、運行時處理錯誤的復雜度,讓開發者真正地掌握錯誤的處理。

net 包中的例子

net.Dial() 是 Go 語言系統包 net 即中的一個函數,一般用于創建一個 Socket 連接。

net.Dial 擁有兩個返回值,即 Conn 和 error。這個函數是阻塞的,因此在 Socket 操作后,會返回 Conn 連接對象和 error;如果發生錯誤,error 會告知錯誤的類型,Conn 會返回空。

根據 Go 語言的錯誤處理機制,Conn 是其重要的返回值。因此,為這個函數增加一個錯誤返回,類似為 error。參見下面的代碼:
func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}
在 io 包中的 Writer 接口也擁有錯誤返回,代碼如下:
type Writer interface {
    Write(p []byte) (n int, err error)
}
io 包中還有 Closer 接口,只有一個錯誤返回,代碼如下:
type Closer interface {
    Close() error
}

錯誤接口的定義格式

error 是 Go 系統聲明的接口類型,代碼如下:
type error interface {
    Error() string
}
所有符合 Error()string 格式的方法,都能實現錯誤接口。

Error() 方法返回錯誤的具體描述,使用者可以通過這個字符串知道發生了什么錯誤。

自定義一個錯誤

返回錯誤前,需要定義會產生哪些可能的錯誤。在 Go 語言中,使用 errors 包進行錯誤的定義,格式如下:
var err = errors.New("this is an error")
錯誤字符串由于相對固定,一般在包作用域聲明,應盡量減少在使用時直接使用 errors.New 返回。

1) errors 包

Go 語言的 errors 中對 New 的定義非常簡單,代碼如下:
// 創建錯誤對象
func New(text string) error {
    return &errorString{text}
}

// 錯誤字符串
type errorString struct {
    s string
}

// 返回發生何種錯誤
func (e *errorString) Error() string {
    return e.s
}
代碼說明如下:
  • 第 2 行,將 errorString 結構體實例化,并賦值錯誤描述的成員。
  • 第 7 行,聲明 errorString 結構體,擁有一個成員,描述錯誤內容。
  • 第 12 行,實現 error 接口的 Error() 方法,該方法返回成員中的錯誤描述。

2) 在代碼中使用錯誤定義

下面的代碼會定義一個除法函數,當除數為 0 時,返回一個預定義的除數為 0 的錯誤。
package main

import (
    "errors"
    "fmt"
)

// 定義除數為0的錯誤
var errDivisionByZero = errors.New("division by zero")

func div(dividend, divisor int) (int, error) {

    // 判斷除數為0的情況并返回
    if divisor == 0 {
        return 0, errDivisionByZero
    }

    // 正常計算,返回空錯誤
    return dividend / divisor, nil
}

func main() {

    fmt.Println(div(1, 0))
}
代碼輸出如下:
0 division by zero

代碼說明:
  • 第 9 行,預定義除數為 0 的錯誤。
  • 第 11 行,聲明除法函數,輸入被除數和除數,返回商和錯誤。
  • 第 14 行,在除法計算中,如果除數為 0,計算結果為無窮大。為了避免這種情況,對除數進行判斷,并返回商為 0 和除數為 0 的錯誤對象。
  • 第 19 行,進行正常的除法計算,沒有發生錯誤時,錯誤對象返回 nil。

示例:在解析中使用自定義錯誤

使用 errors.New 定義的錯誤字符串的錯誤類型是無法提供豐富的錯誤信息的。那么,如果需要攜帶錯誤信息返回,就需要借助自定義結構體實現錯誤接口。

下面代碼將實現一個解析錯誤(ParseError),這種錯誤包含兩個內容:文件名和行號。解析錯誤的結構還實現了 error 接口的 Error() 方法,返回錯誤描述時,就需要將文件名和行號返回。
package main

import (
    "fmt"
)

// 聲明一個解析錯誤
type ParseError struct {
    Filename string // 文件名
    Line     int    // 行號
}

// 實現error接口,返回錯誤描述
func (e *ParseError) Error() string {
    return fmt.Sprintf("%s:%d", e.Filename, e.Line)
}

// 創建一些解析錯誤
func newParseError(filename string, line int) error {
    return &ParseError{filename, line}
}

func main() {

    var e error
    // 創建一個錯誤實例,包含文件名和行號
    e = newParseError("main.go", 1)

    // 通過error接口查看錯誤描述
    fmt.Println(e.Error())

    // 根據錯誤接口具體的類型,獲取詳細錯誤信息
    switch detail := e.(type) {
    case *ParseError: // 這是一個解析錯誤
        fmt.Printf("Filename: %s Line: %d\n", detail.Filename, detail.Line)
    default: // 其他類型的錯誤
        fmt.Println("other error")
    }
}
代碼輸出如下:
main.go:1
Filename: main.go Line: 1

代碼說明如下:
  • 第 8 行,聲明了一個解析錯誤的結構體,解析錯誤包含有 2 個成員:文件名和行號。
  • 第 14 行,實現了錯誤接口,將成員的文件名和行號格式化為字符串返回。
  • 第 19 行,根據給定的文件名和行號創建一個錯誤實例。
  • 第 25 行,聲明一個錯誤接口類型。
  • 第 27 行,創建一個實例,這個錯誤接口內部是 *ParserError 類型,攜帶有文件名 main.go 和行號 1。
  • 第 30 行,調用 Error() 方法,通過第 15 行返回錯誤的詳細信息。
  • 第 33 行,通過錯誤斷言,取出發生錯誤的詳細類型。
  • 第 34 行,通過分析這個錯誤的類型,得知錯誤類型為 *ParserError,此時可以獲取到詳細的錯誤信息。
  • 第 36 行,如果不是我們能夠處理的錯誤類型,會打印出其他錯誤做出其他的處理。

錯誤對象都要實現 error 接口的 Error() 方法,這樣,所有的錯誤都可以獲得字符串的描述。如果想進一步知道錯誤的詳細信息,可以通過類型斷言,將錯誤對象轉為具體的錯誤類型進行錯誤詳細信息的獲取。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo