gopl-zh.github.com/ch7/ch7-11.md

67 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## 7.11. 基於類型斷言區别錯誤類型
思考在os包中文件操作返迴的錯誤集合。I/O可以因爲任何數量的原因失敗但是有三種經常的錯誤必須進行不同的處理文件已經存在對於創建操作找不到文件對於讀取操作和權限拒絶。os包中提供了這三個幫助函數來對給定的錯誤值表示的失敗進行分類
```go
package os
func IsExist(err error) bool
func IsNotExist(err error) bool
func IsPermission(err error) bool
```
對這些判斷的一個缺乏經驗的實現可能會去檢査錯誤消息是否包含了特定的子字符串,
```go
func IsNotExist(err error) bool {
// NOTE: not robust!
return strings.Contains(err.Error(), "file does not exist")
}
```
但是處理I/O錯誤的邏輯可能一個和另一個平台非常的不同所以這種方案併不健壯併且對相同的失敗可能會報出各種不同的錯誤消息。在測試的過程中通過檢査錯誤消息的子字符串來保證特定的函數以期望的方式失敗是非常有用的但對於線上的代碼是不夠的。
一個更可靠的方式是使用一個專門的類型來描述結構化的錯誤。os包中定義了一個PathError類型來描述在文件路徑操作中涉及到的失敗像Open或者Delete操作,併且定義了一個叫LinkError的變體來描述涉及到兩個文件路徑的操作像Symlink和Rename。這下面是os.PathError
```go
package os
// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
```
大多數調用方都不知道PathError併且通過調用錯誤本身的Error方法來統一處理所有的錯誤。盡管PathError的Error方法簡單地把這些字段連接起來生成錯誤消息PathError的結構保護了內部的錯誤組件。調用方需要使用類型斷言來檢測錯誤的具體類型以便將一種失敗和另一種區分開具體的類型比字符串可以提供更多的細節。
```go
_, err := os.Open("/no/such/file")
fmt.Println(err) // "open /no/such/file: No such file or directory"
fmt.Printf("%#v\n", err)
// Output:
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
```
這就是三個幫助函數是怎麽工作的。例如下面展示的IsNotExist它會報出是否一個錯誤和syscall.ENOENT(§7.8)或者和有名的錯誤os.ErrNotExist相等(可以在§5.4.2中找到io.EOF或者是一個*PathError它內部的錯誤是syscall.ENOENT和os.ErrNotExist其中之一。
```go
import (
"errors"
"syscall"
)
var ErrNotExist = errors.New("file does not exist")
// IsNotExist returns a boolean indicating whether the error is known to
// report that a file or directory does not exist. It is satisfied by
// ErrNotExist as well as some syscall errors.
func IsNotExist(err error) bool {
if pe, ok := err.(*PathError); ok {
err = pe.Err
}
return err == syscall.ENOENT || err == ErrNotExist
}
```
下面這里是它的實際使用:
```go
_, err := os.Open("/no/such/file")
fmt.Println(os.IsNotExist(err)) // "true"
```
如果錯誤消息結合成一個更大的字符串當然PathError的結構就不再爲人所知例如通過一個對fmt.Errorf函數的調用。區别錯誤通常必須在失敗操作後錯誤傳迴調用者前進行。