<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 4.2 錯誤值檢查 我們先來看第一個問題:如何對一個傳播鏈條中的錯誤類型進行斷言? 在標準庫中,`errors`包中最為重要的一個`New`函數能夠從給定格式的字符串中創建一個錯誤, 它的內部實現僅僅是對`error`接口的一個實現`errorString`: ``` package errors type errorString struct { s string } func (e *errorString) Error() string { return e.s } func New(text string) error { return &amp;errorString{text} } ``` 當然,這遠遠不夠。為了能夠對錯誤進行格式化,在使用 Go 的過程中通常還會需要將`New`與`fmt.Sprintf`進行組合,達到格式化的目的: ``` func E(format string, a ...interface{}) error { return errors.New(fmt.Sprintf(format, a...)) } ``` 但這種依靠字符串進行錯誤定義的方式的可處理性幾乎為零,將會在調用上下文之間引入強依賴, 因為一個具體的錯誤值在`fmt`格式化封裝的過程中被轉移為了一個字符串類型,進而不能對 錯誤傳播過程中錯誤的來源進行斷言。 為此,Go 在`errors`包中引入了一系列 API 來增強錯誤檢查的手段。 ## 4.2.1 錯誤傳播鏈 首先,為了建立錯誤傳播鏈,`fmt.Errorf`函數允許使用`%w`動詞對一個錯誤進行包裝。 在`Errorf`的實現中,它會將需要包裝的`err`包裝為一個實現了`Error() string`和`Unwrap() error`兩個接口的`wrapError`結構,其包含需要封裝的新錯誤消息以及原始錯誤: ``` type wrapError struct { msg string err error } func (e *wrapError) Error() string { return e.msg } func (e *wrapError) Unwrap() error { return e.err } ``` `fmt`包本身對格式化的支持定義了`pp`結構,會將格式化后的內容存儲在`buf`中。 但在錯誤傳播鏈條的包裝上,為了不破壞原始錯誤值,額外使用了`wrapErrs`和`wrappedErr`兩個字段,其中`wrapErrs`用于格式化過程中判斷是否對錯誤進行了包裝,`wrappedErr`則用于存儲原始的錯誤: ``` type pp struct { buf buffer // 本質為 []byte 類型 ... wrapErrs bool wrappedErr error // wrappedErr 記錄了 %w 動詞的 err } ``` 方法`Errorf`會首先使用`newPrinter`和`doPrintf`對格式進行處理, 將帶有動詞的格式字符串和參數進行拼接。 具體而言,`Errorf`總是假設出現`%w`動詞,并`doPrintf`函數內部將對`error`類型的參數進行特殊處理。當有錯誤保存在`wrappedErr`時,說明需要對 錯誤進行一層包裝,否則說明是一個原始的錯誤構造: ``` package fmt import "errors" func Errorf(format string, a ...interface{}) error { p := newPrinter() p.wrapErrs = true // 假設格式化過程中可能包含 %w 動詞,設置為 true p.doPrintf(format, a) // 對 format 和實際的參數進行拼接,用于后續打印 s := string(p.buf) // 拼接好的內容保存在 buf 內 var err error if p.wrappedErr == nil { err = errors.New(s) // 構造原始錯誤 } else { err = &amp;wrapError{s, p.wrappedErr} // 對錯誤進行包裝 } p.free() return err } ``` `doPrintf`函數最終將調用`handleMethods`方法來對錯誤進行記錄。當遇到`%w`動詞時,會判斷`%w`對應的參數值是否為`error`類型,并將錯誤保存到`wrappedErr`內,并將后續處理退化為`%v`的后續拼接與格式化。 ``` // 調用鏈 doPrintf -&gt; printArg -&gt; handleMethods func (p *pp) handleMethods(verb rune) (handled bool) { ... if verb == 'w' { err, ok := p.arg.(error) // 判斷與 %w 對應的值是否為 error 類型,否則處理為錯誤的動詞組合 if !ok || !p.wrapErrs || p.wrappedErr != nil { ... return true } // 保存 err,并將其退化為 %v 動詞 p.wrappedErr = err verb = 'v' } ... } ``` 顯然,`%w`這個動詞的主要目的是將`err`記錄到`wrappedErr`這個同時實現了`Error() string`和`Unwrap() error`的錯誤中, 從而能安全的將`verb`轉化為`%v`動詞對參數進行后續的格式化拼接。 ## 4.2.2 錯誤值拆包 但形成錯誤鏈條后,使用`Unwrap`便能將一個已被`fmt`包裝過的`error`進行拆包, 其實現的核心思想是對錯誤值是否實現了`Unwrap() error`方法進行一次類型斷言: ``` func Unwrap(err error) error { // 斷言 err 實現了 Unwrap 方法 u, ok := err.(interface { Unwrap() error }) if !ok { return nil } return u.Unwrap() } ``` 在`fmt.Errorf`的實現中,已經看到,錯誤鏈條錯誤使用了`wrapError`進行包裝, 而這一類型恰好實現了`Unwrap() error`方法。 ## 4.2.3 錯誤斷言 `Is`用于檢查當前的兩個錯誤是否相等。之所以需要這個函數是因為一個錯誤可能被包裝了多層, 那么我們需要支持這個錯誤在包裝過多層后的判斷。 可想而知,在實現上需要一個`for`循環對其進行`Unwrap`操作: ``` func Is(err, target error) bool { if target == nil { return err == target } isComparable := reflect.TypeOf(target).Comparable() for { // 如果 target 錯誤是可比較的,則直接進行比較 if isComparable &amp;&amp; err == target { return true } // 如果 err 實現了 Is 方法,則調用其實現進行判斷 if x, ok := err.(interface{ Is(error) bool }); ok &amp;&amp; x.Is(target) { return true } // 否則,對 err 進行 Unwrap if err = Unwrap(err); err == nil { return false } // 如果 Unwrap 成功,則繼續判斷 } } ``` 可見`Is`方法的目的是替換使用`==`形式的錯誤斷言: ``` if err == io.ErrUnexpectedEOF { // ... 處理錯誤 } =&gt; if errors.Is(err, io.ErrUnexpectedEOF) { // ... 處理錯誤 } ``` 值得注意的是,`Is`方法要求自定義的錯誤值實現`Is(error) bool`方法來進行自定義的錯誤斷言, 否則錯誤的比較仍然只是使用`==`算符。 方法`As`的實現與`Is`基本類似,但不同之處在于`As`的目的是將某個錯誤給拆封 到具體的變量中,因此對于一個錯誤鏈而言,需要一個循環不斷對錯誤進行`Unwrap`, 當錯誤值實現了`As(interface{}) bool`方法時,則可完成拆封: ``` func As(err error, target interface{}) bool { if target == nil { panic("errors: target cannot be nil") } val := reflect.ValueOf(target) typ := val.Type() if typ.Kind() != reflect.Ptr || val.IsNil() { panic("errors: target must be a non-nil pointer") } if e := typ.Elem(); e.Kind() != reflect.Interface &amp;&amp; !e.Implements(errorType) { panic("errors: *target must be interface or implement error") } targetType := typ.Elem() for err != nil { // 若可直接將 err 拆封到 target if reflect.TypeOf(err).AssignableTo(targetType) { val.Elem().Set(reflect.ValueOf(err)) return true } // 判斷 err 是否實現 As 方法,若已實現則直接調用 if x, ok := err.(interface{ As(interface{}) bool }); ok &amp;&amp; x.As(target) { return true } // 否則對錯誤鏈進行 Unwrap err = Unwrap(err) } return false } var errorType = reflect.TypeOf((*error)(nil)).Elem() ``` 可見,由于錯誤鏈的存在,`errors.As`方法的目的是替換類型斷言式的錯誤斷言: ``` if e, ok := err.(*os.PathError); ok { // ... 處理錯誤 } =&gt; var e *os.PathError if errors.As(err, &amp;e) { // ... 處理錯誤 } ``` ## 4.2.4 小結 `errors`包中對錯誤檢查的設計通過暴露`New`、`Unwrap`、`Is`和`As`四個方法完成 在復雜函數調用鏈條中使用`fmt.Errorf`封裝的錯誤傳播鏈條的拆解。 其中`New`負責原始錯誤的創建,`Unwrap`允許對錯誤傳播鏈條進行一次拆包,`Is`則提供了在復雜錯誤鏈中,對錯誤類型進行斷言的能力; 而`As`解決了將錯誤從錯誤鏈拆解到某個目標錯誤類型的能力。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看