文章目錄
-
- 導言
- 錯誤處理
-
- 錯誤是什麼?
- 例子
- 錯誤的類型表示
- 從錯誤中提取更多資訊
-
- 方法 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
行,我們将比對的檔案名輸出。
程式輸出如下:
從輸出中可以看出,此時沒有
與模式串比對的檔案名。但實際上,這是因為模式串
格式錯誤導緻的。
由于忽略了錯誤處理,對于上面的輸出,我們不能确定:到底是沒有
與模式串比對的的檔案名,還是
出現了錯誤。
是以,請不要忽略錯誤處理!!!這就是全部内容了~
祝你每天都開心~
原作者留言
優質内容來之不易,您可以通過該 連結 為我捐贈。
最後
感謝原作者的優質内容。
歡迎指出文中的任何錯誤。