天天看點

Go語言 錯誤處理

文章目錄

    • 導言
    • 錯誤處理
      • 錯誤是什麼?
      • 例子
      • 錯誤的類型表示
      • 從錯誤中提取更多資訊
        • 方法 1
        • 方法 2
        • 方法 3
      • 不要忽略錯誤
    • 原作者留言
    • 最後

導言

  • 原文連結: Part 30: Error Handling
  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.

錯誤處理

錯誤是什麼?

錯誤就是程式中存在的異常情況。打開一個不存在的檔案,這就是一個異常情況,也就是錯誤。

Go

中,錯誤也是一個值,它的類型為

error

正如其它的内建類型,如

int

float64

… 我們可以把錯誤存儲在變量中,也可以讓函數傳回錯誤。

例子

接下來,我将寫一個程式,這個程式試圖打開一個不存在的檔案。

package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}
           

Open

函數 位于

os

包,它的定義如下:

Open

函數存在

2

種情況:

  • 如果檔案被成功打開,

    Open

    函數 會傳回檔案句柄,此時錯誤為

    nil

  • 如果在打開檔案過程中遇到錯誤,

    Open

    函數 會傳回

    nil

    檔案句柄,此時錯誤不為

    nil

在一般情況下,如果函數需要傳回錯誤,錯誤必須位于最後一個傳回值。根據這個規則,

Open

函數 将

err

作為最後一個傳回值。

在第

9

行,我們試圖打開路徑為

/test.txt

的檔案。

在第

10

行,我們将

err

nil

進行比較。如果

err

不等于

nil

,我們就輸出錯誤。

運作程式,輸出如下:

open /test.txt: No such file or directory  
           

如我們所想,我們得到了一個錯誤,該錯誤告訴我們:檔案不存在。

錯誤的類型表示

我們深入一下,看看

error

類型 是如何定義的。

type error interface {  
    Error() string
}
           

從上面可以看出:

error

是一個接口類型,它擁有一個

Error

方法。

任何實作了

error

接口 的類型,都可以作為錯誤。

在上面的樣例程式中,為了擷取錯誤的描述資訊,

fmt.Println

函數 内部會調用

Error() string

方法。

從錯誤中提取更多資訊

接下來,我們看看怎麼從錯誤中提取更多資訊。

在上面的例子中,我們隻是輸出了錯誤的描述資訊。假如我們需要錯誤中的檔案路徑,我們應該怎麼做呢?

上面是什麼意思呢?

意思就是:之前的錯誤的描述資訊是

open /test.txt: No such file or directory

,而此時我們想要的是

/test.txt

其中一種方式就是:從錯誤的描述資訊中 (描述資訊是一個字元串),提取出檔案路徑。

但這是一種很爛的方式,因為随着版本更新,錯誤的描述資訊可能會發生改變。

有沒有方法能可靠地擷取檔案名呢?答案是肯定的,

通過

Go

的标準庫,我們能擷取到錯誤的更多資訊。

接下來,我将一個一個地列舉。

方法 1

獲得錯誤更多資訊的第

1

種方法是:先斷言錯誤的底層類型,再從該底層類型的字段中獲得更多的資訊。

如果你仔細閱讀

Open

函數 的文檔,你會發現:

Open

函數 會傳回一個類型為

*PathError

的錯誤。

PathError

是一個結構體,它在标準庫的實作如下:

type PathError struct {  
    Op   string
    Path string
    Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } 
           

通過聲明

Error

方法,

*PathError

類型 實作了

error

接口。

PathError

結構 的

Path

字段 就是檔案路徑。

接下來,我們改寫下之前的程式。在新的程式中,我們列印出錯誤資訊的檔案路徑:

package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")
    if err, ok := err.(*os.PathError); ok {
        fmt.Println("File at path", err.Path, "failed to open")
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}
           

在第

10

行,通過類型斷言,我們得到了錯誤接口的底層對象。

程式輸出如下:

File at path /test.txt failed to open  
           

使用類型斷言,我們成功地從錯誤中提取出檔案路徑。

方法 2

獲得錯誤更多資訊的第

2

種方法是:先斷言錯誤的底層類型,再調用該底層類型的方法,進而擷取更多資訊。

通過例子了解吧~

在标準庫中,

DNSError

結構 的定義如下:

type DNSError struct {  
    ...
}

func (e *DNSError) Error() string {  
    ...
}
func (e *DNSError) Timeout() bool {  
    ... 
}
func (e *DNSError) Temporary() bool {  
    ... 
}
           

DNSError

Timeout() bool

Temporary() bool

方法,分别會告訴我們:錯誤是否是逾時産生的、錯誤是否是臨時的。

接下來,我們來寫個程式。在程式中,我們會先将錯誤斷言為

*DNSError

類型,之後通過調用

DNSError

結構 的

Timeout() bool

Temporary() bool

方法,判斷錯誤是否是逾時産生的,以及是否是臨時。

package main

import (  
    "fmt"
    "net"
)

func main() {  
    addr, err := net.LookupHost("golangbot123.com")
    if err, ok := err.(*net.DNSError); ok {
        if err.Timeout() {
            fmt.Println("operation timed out")
        } else if err.Temporary() {
            fmt.Println("temporary error")
        } else {
            fmt.Println("generic error: ", err)
        }
        return
    }
    fmt.Println(addr)
}
           

在上面程式的第

9

行,我們試着去擷取一個非法域名 (

golangbot123.com

) 的

IP

位址。

在第

10

行,通過将錯誤接口斷言為

*net.DNSError

,我們擷取到了錯誤接口的底層對象。

在第

11

13

行,我們分别判斷錯誤是否是逾時産生的,以及是否是臨時。

程式輸出如下:

generic error:  lookup golangbot123.com: no such host  
           

上面的例子也告訴我們:我們可以根據錯誤的産生原因,對症下藥。

方法 3

獲得錯誤更多資訊的第

3

種方法是:直接比較。

讓我們通過例子來了解這種方法。

filepath

包 有一個

Glob

函數,這個函數能傳回一些

與模式串比對的

檔案名。當模式串格式錯誤時,

Glob

函數 會傳回一個錯誤變量 —

ErrBadPattern

ErrBadPattern

變量 被定義在

filepath

包 中。

errors.New

會建立一個新的錯誤。

當模式串格式錯誤時,

Glob

函數 會傳回

ErrBadPattern

變量。通過這個特點,我們來寫個小程式。

package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    files, error := filepath.Glob("[")
    if error != nil && error == filepath.ErrBadPattern {
        fmt.Println(error)
        return
    }
    fmt.Println("matched files", files)
}
           

在上面的程式中,模式串

[

的格式是錯誤的。

在第

10

行,為了擷取更多錯誤資訊,我們直接将

Glob

函數傳回的錯誤 與

filepath.ErrBadPattern

變量 進行比較。如果它們相等,則表示

Glob

函數傳回的錯誤 是 格式錯誤。

程式輸出如下:

syntax error in pattern  
           

标準庫會通過以上提及的方法,為我們提供更多錯誤資訊。當自定義錯誤時,我們也會用到這些方法。

不要忽略錯誤

不要忽略錯誤!忽略錯誤會帶來麻煩!

接下來,我重寫下上面的例子 (就是

Glob

函數 的那個例子)。這次,我們不進行錯誤處理。

package main

import (  
    "fmt"
    "path/filepath"
)

func main() {  
    files, _ := filepath.Glob("[")
    fmt.Println("matched files", files)
}
           

如前所述,模式串

[

是非法的。

在第

9

行,通過使用 空白辨別符

_

,我們忽略了

Glob

函數傳回的錯誤。

在第

10

行,我們将比對的檔案名輸出。

程式輸出如下:

從輸出中可以看出,此時沒有

與模式串比對的

檔案名。但實際上,這是因為模式串

格式錯誤

導緻的。

由于忽略了錯誤處理,對于上面的輸出,我們不能确定:到底是沒有

與模式串比對的

的檔案名,還是

出現了錯誤

是以,請不要忽略錯誤處理!!!

這就是全部内容了~

祝你每天都開心~

原作者留言

優質内容來之不易,您可以通過該 連結 為我捐贈。

最後

感謝原作者的優質内容。

歡迎指出文中的任何錯誤。