<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>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 4.5 錯誤處理的未來 TODO: 討論社區里的一些優秀的方案、以及未來可能的設計 ## 4.5.1 來自社區的方案 在錯誤處理這件事情上,其實社區提供了許多非常優秀的方案, 其中一個非常出色的工作來自 Dave Cheney 和他的錯誤原語。 ### 錯誤原語 `pkg/errors`與標準庫中`errors`包不同,它首先提供了`Wrap`: ``` func Wrap(err error, message string) error { if err == nil { return nil } // 首先將錯誤產生的上下文進行保存 err = &amp;withMessage{ cause: err, msg: message, } // 再將 withMessage 錯誤的調用堆棧保存為 withStack 錯誤 return &amp;withStack{ err, callers(), } } type withMessage struct { cause error msg string } func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } type withStack struct { error *stack // 攜帶 stack 的信息 } func (w *withStack) Cause() error { return w.error } func callers() *stack { const depth = 32 var pcs [depth]uintptr n := runtime.Callers(3, pcs[:]) var st stack = pcs[0:n] return &amp;st } ``` 這是一種依賴運行時接口的解決方案,通過`runtime.Caller`來獲取錯誤出現時的堆棧信息。通過`Wrap()`產生的錯誤類型`withMessage`還實現了`causer`接口: ``` type causer interface { Cause() error } ``` 當我們需要對一個錯誤進行檢查時,則可以通過`errors.Cause(err error)`來返回一個錯誤產生的原因,進而獲得了錯誤產生的上下文信息: ``` func Cause(err error) error { type causer interface { Cause() error } for err != nil { cause, ok := err.(causer) if !ok { break } err = cause.Cause() } return err } ``` 進而可以做到: ``` switch err := errors.Cause(err).(type) { case *CustomError: // ... } ``` 得益于`fmt.Formatter`接口,`pkg/errors`還實現了`Fomat(fmt.State, rune)`方法, 進而在使用`%+v`進行錯誤打印時,能攜帶堆棧信息: ``` func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': if s.Flag('+') { // %+v 支持攜帶堆棧信息的輸出 fmt.Fprintf(s, "%+v", w.Cause()) w.stack.Format(s, verb) // 將 runtime.Caller 獲得的信息進行打印 return } fallthrough case 's': io.WriteString(s, w.Error()) case 'q': fmt.Fprintf(s, "%q", w.Error()) } } ``` 得到形如下面格式的錯誤輸出: ``` current message: causer message main.causer /path/to/caller/main.go:5 main.caller /path/to/caller/main.go:12 main.main /path/to/caller/main.go:27 ``` ### 基于錯誤鏈的高層抽象 我們再來看另一種錯誤處理的哲學,現在我們來考慮下面這個例子: ``` conn, err := net.Dial("tcp", "localhost:1234") if err != nil { panic(err) } _, err := conn.Write(command1) if err != nil { panic(err) } r := bufio.NewReader(conn) status, err := r.ReadString('\n') if err != nil { panic(err) } if status == "ok" { _, err := conn.Write(command2) if err != nil { panic(err) } } ``` 我們很明確的能夠觀察到錯誤處理帶來的問題:為清晰的閱讀代碼的整體邏輯帶來了障礙。我們希望上面的代碼能夠清晰的展現最重要的代碼邏輯: ``` conn := net.Dial("tcp", "localhost:1234") conn.Write(command1) r := bufio.NewReader(conn) status := r.ReadString('\n') if status == "ok" { conn.Write(command2) } ``` 如果我們進一步觀察這個問題的現象,可以將整段代碼抽象為圖 1 所示的邏輯結構。 ![](https://golang.design/under-the-hood/assets/errors-branch.png)**圖 1: 產生分支的錯誤處理手段** 如果我們嘗試將這段充滿分支的邏輯進行高層抽象,將其轉化為一個單一鏈條,則能夠得到 圖 2 所示的隱式錯誤鏈條。 ![](https://golang.design/under-the-hood/assets/errors-chan.png)**圖 2: 消除分支的鏈式錯誤處理手段** 則能夠得到下面的代碼: ``` type SafeConn struct { conn net.Conn r *bufio.Reader status string err error } func safeDial(n, addr string) SafeConn { conn, err := net.Dial(n, addr) r := bufio.NewReader(conn) return SafeConn{conn, r, "ok", err} } func (c *SafeConn) write(b []byte) { if c.err != nil &amp;&amp; status == "ok" { return } _, c.err = c.conn.Write(b) } func (c *SafeConn) read() { if err != nil { return } c.status, c.err = c.r.ReadString('\n') } ``` 則當建立連接時候: ``` c := safeDial("tcp", "localhost:1234") // 如果此條指令出錯 c.write(command1) // 不會發生任何事情 c.read() // 不會發生任何事情 c.write(command2) // 不會發生任何事情 // 最后對進行整個流程的錯誤處理 if c.err != nil || c.status != "ok" { panic("bad connection") } ``` 這種將錯誤進行高層抽象的方法通常包含以下四個一般性的步驟: 1. 建立一種新的類型 2. 將原始值進行封裝 3. 將原始行為進行封裝 4. 將分支條件進行封裝 ## 4.5.2 其他可能的設計 TODO: Generics + Error handling? Either Coproduct [https://www.ituring.com.cn/article/508191](https://www.ituring.com.cn/article/508191)[https://www.bookstack.cn/read/mostly-adequate-guide-chinese/ch8.4](https://www.bookstack.cn/read/mostly-adequate-guide-chinese/ch8.4) ## 4.5.3 歷史性評述
                  <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>

                              哎呀哎呀视频在线观看