1. <output id="hzk7v"><pre id="hzk7v"><address id="hzk7v"></address></pre></output>
      <output id="hzk7v"></output>
    2. <nav id="hzk7v"><i id="hzk7v"><em id="hzk7v"></em></i></nav>
    3. <listing id="hzk7v"><delect id="hzk7v"><em id="hzk7v"></em></delect></listing>

      Golang中重复错误处理的优化方法

       更新时间£º2019年04月12日 09:54:31   作者£º老王   我要评论

      这篇文章主要给大家介绍了关于Golang中重复错误处理优化的相关资料£¬文中通过示例代码介绍的非常详细£¬对大家学习或者使用Golang具有一定的参考学习价值£¬需要的朋友们下面来一起学习学习吧

      Golang 错误处理最让人头疼的问题就是代码里充斥着¡¸if err != nil¡¹£¬它们破坏了代码的可读性£¬本文收集了几个例子£¬让大家明白如何优化此类问题¡£

      让我们看看 Errors are values 中提到的一个 io.Writer 例子£º

      _, err = fd.Write(p0[a:b])
      if err != nil {
       return err
      }
      _, err = fd.Write(p1[c:d])
      if err != nil {
       return err
      }
      _, err = fd.Write(p2[e:f])
      if err != nil {
       return err
      }

      如上代码乍一看无法直观的看出其本来的意图是什么£¬改进版£º

      type errWriter struct {
       w io.Writer
       err error
      }
      
      func (ew *errWriter) write(buf []byte) {
       if ew.err != nil {
       return
       }
       _, ew.err = ew.w.Write(buf)
      }
      
      ew := &errWriter{w: fd}
      ew.write(p0[a:b])
      ew.write(p1[c:d])
      ew.write(p2[e:f])
      if ew.err != nil {
       return ew.err
      }

      通过自定义类型 errWriter 来封装 io.Writer£¬并且封装了 error£¬新类型有一个 write 方法£¬不过其方法签名并没有返回 error£¬而是在方法内部判断一旦有问题就立刻返回£¬有了这些准备工作£¬我们就可以?#35328;?#26412;穿插在业务逻辑中间的错误判断提出来放到最后来统一调用£¬从而在视觉上保证让人可以直观的看出代码本来的意图是什?#30784;?/p>

      让我们再看看 Eliminate error handling by eliminating errors 中提到的另一个 io.Writer 例子£º

      type Header struct {
       Key, Value string
      }
      
      type Status struct {
       Code int
       Reason string
      }
      
      func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
       _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
       if err != nil {
       return err
       }
      
       for _, h := range headers {
       _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
       if err != nil {
       return err
       }
       }
      
       if _, err := fmt.Fprint(w, "\r\n"); err != nil {
       return err
       }
      
       _, err = io.Copy(w, body)
       return err
      }

      第一感觉既然错误是 fmt.Fprint 和 io.Copy 返回的£¬是不是我们要重新封装一下它们£¿?#23548;?#19978;真正的源头是它们的参数 io.Writer£¬因为直接调用 io.Writer 的 Writer 方法的话£¬方法签名中有返回值 error£¬所以每一步 fmt.Fprint 和 io.Copy 操作都?#22351;?#19981;进行重复的错误处理£¬看上去是坏味道£¬改进版£º

      type errWriter struct {
       io.Writer
       err error
      }
      
      func (e *errWriter) Write(buf []byte) (int, error) {
       if e.err != nil {
       return 0, e.err
       }
      
       var n int
       n, e.err = e.Writer.Write(buf)
       return n, nil
      }
      
      func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
       ew := &errWriter{Writer: w}
       fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
      
       for _, h := range headers {
       fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
       }
      
       fmt.Fprint(ew, "\r\n")
       io.Copy(ew, body)
      
       return ew.err
      }

      通过自定义类型 errWriter 来封装 io.Writer£¬并且封装了 error£¬同时重写了 Writer 方法£¬虽然方法签名中仍然有返回值 error£¬但是我们单独保存了一份 error£¬并且在方法内部判断一旦有问题就立刻返回£¬有了这些准备工作£¬新版的 WriteResponse 不再有重复的错误判断£¬只需要在最后检查一下 error 即可¡£

      类似的做法在 Golang 标准库中屡见不鲜£¬让我们继续看看 Eliminate error handling by eliminating errors 中提到的一个关于 bufio.Reader 和 bufio.Scanner 的例子£º

      func CountLines(r io.Reader) (int, error) {
       var (
       br = bufio.NewReader(r)
       lines int
       err error
       )
      
       for {
       _, err = br.ReadString('\n')
       lines++
       if err != nil {
       break
       }
       }
      
       if err != io.EOF {
       return 0, err
       }
      
       return lines, nil
      }

      我们构造一个 bufio.Reader£¬然后在一个循环中调用 ReadString 方法£¬如果读到文件结尾£¬那么 ReadString 会返回一个错误£¨io.EOF£©£¬为了判断此类情况£¬我们?#22351;?#19981;在?#30475;?#24490;环时判断¡¸if err != nil¡¹£¬看上去这是坏味道£¬改进版£º

      func CountLines(r io.Reader) (int, error) {
       sc := bufio.NewScanner(r)
       lines := 0
      
       for sc.Scan() {
       lines++
       }
      
       return lines, sc.Err()
      }

      ?#23548;?#19978;£¬和 bufio.Reader 相比£¬bufio.Scanner 是一个更高阶的类型£¬换句话简单点来说的话£¬相当于是 bufio.Scanner 抽象了 bufio.Reader£¬通过把低阶的 bufio.Reader 换成高阶的 bufio.Scanner£¬循环中不再需要判断¡¸if err != nil¡¹£¬因为 Scan 方法签名不再返回 error£¬而是返回 bool£¬当在循环里读到了文件结尾的时候£¬循环直接结束£¬如此一来£¬我们就可以统一在最后调用 Err 方法来判断成功还是失败£¬看看 Scanner 的定义£º

      type Scanner struct {
       r   io.Reader // The reader provided by the client.
       split  SplitFunc // The function to split the tokens.
       maxTokenSize int  // Maximum size of a token; modified by tests.
       token  []byte // Last token returned by split.
       buf   []byte // Buffer used as argument to split.
       start  int  // First non-processed byte in buf.
       end   int  // End of data in buf.
       err   error  // Sticky error.
       empties  int  // Count of successive empty tokens.
       scanCalled bool  // Scan has been called; buffer is in use.
       done   bool  // Scan has finished.
      }

      可见 Scanner 封装了 io.Reader£¬并且封装了 error£¬和我们之前讨论的做法一致¡£有一点说明一下£¬?#23548;?#19978;查看 Scan 源代码的话£¬你会发现它不是通过 err 来判断是否结束的£¬而是通过 done 来判断是否结束£¬这是因为 Scan 只有遇到文件结束的错误才退出£¬其它错误会继续执行£¬当然£¬这只是具体的细节问题£¬不影响我们的结论¡£

      通过对以上几个例子的分析£¬我们可以得出优化重复错误处理的大概套路£º通过创建新的类型来封装原本干脏活累活的旧类型£¬同时在新类型中封装 error£¬新旧类型的方法签名可以保持兼容£¬也可以不兼容£¬这个不是关键的£¬视客观情况而定£¬至于具体的逻辑实现£¬先判断有没有 error£¬如果有就直接退出£¬如果没有就继续执行£¬并且在执行过程中保存可能出现的 error 以便后面操作使用£¬最后通过统一调用新类型的 error 来完成错误处理¡£提醒一下£¬此方案的缺点是要到最后才能知道有没有错误£¬好在如此的控制粒度在多数时候并无大碍¡£

      总结

      以上就是这篇文章的全部内容了£¬希望本文的内容对大家的学习或者工作具有一定的参考学习价值£¬谢谢大家对脚本之家的支持¡£

    4. Go语言 channel如?#38382;?#29616;归并?#21028;?#20013;的merge函数详解

      Go语言 channel如?#38382;?#29616;归并?#21028;?#20013;的merge函数详解

      这篇文章主要给大家介绍了关于Go语言 channel如?#38382;?#29616;归并?#21028;?#20013;merge函数的相关资料£¬文中通过示例代码介绍的非常详细£¬对大家的学习或者工作具有一定的参考学习价值£¬需要的朋友们下面随着小编来一起学习学习吧¡£
      2018-02-02
    5. 深入理解Go语言中的Dispatcher

      深入理解Go语言中的Dispatcher

      最近看到了Go语言中的Dispatcher£¬但是在网上发现资料非常少£¬所以想着总结处理分享给大家£¬下面这篇文章主要给大家深入的介绍Go语言中Dispatcher的相关资料£¬需要的朋友可以参考借鉴£¬下面来一起看看吧¡£
      2017-03-03
    6. go语言net包rpc远程调用的使用示例

      go语言net包rpc远程调用的使用示例

      本篇文章主要介绍了go语言net包rpc远程调用的使用示例£¬小编觉得挺不错的£¬现在分享给大家£¬也给大家做个参考¡£一起跟随小编过来看看吧
      2017-11-11
    7. GO语言中的常量

      GO语言中的常量

      go语言支持的常量有字符型£¬字符串型£¬?#32423;?#22411;和数?#20013;Í¡?#26412;文实例讲述了Go语言中常量定义方法¡£分享给大家供大家参考¡£
      2015-04-04
    8. golang的httpserver优雅重启方法详解

      golang的httpserver优雅重启方法详解

      这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料£¬文中通过示例代码介绍的非常详细£¬对大家的学习或者工作具有一定的参考学习价值£¬需要的朋友们下面随着小编来一起学习学习吧¡£
      2018-03-03
    9. 如何使用Go语言实现远程执行命令

      如何使用Go语言实现远程执行命令

      远程执行命令最常用的方法就是利用SSH协议,将命令发送到远程机器上执行,并获取返回结果¡£本文将介绍如何使用Go语言实现远程执行命令¡£下面一起来看?#30784;?/div> 2016-08-08
    10. golang守护进程用法示例

      golang守护进程用法示例

      这篇文章主要介绍了golang守护进程用法,结合实例形式分析了Go语言守护进程的具体实现与使用技巧,需要的朋友可以参考下
      2016-07-07
    11. Go语言string£¬int£¬int64 ,float之间类型转换方法

      Go语言string£¬int£¬int64 ,float之间类型转换方法

      Go语言中int类型和string类?#25237;?#26159;属于基本数据类型,两种类型的转化都非常简单¡£下面通过本文给大家分享Go语言string£¬int£¬int64 ,float之间类型转换方法£¬感兴趣的朋友一起看看吧
      2017-07-07
    12. 最新评论

      常用在线小工具

      3dÊÔ»úºÅÖвÊÍø

        1. <output id="hzk7v"><pre id="hzk7v"><address id="hzk7v"></address></pre></output>
          <output id="hzk7v"></output>
        2. <nav id="hzk7v"><i id="hzk7v"><em id="hzk7v"></em></i></nav>
        3. <listing id="hzk7v"><delect id="hzk7v"><em id="hzk7v"></em></delect></listing>

            1. <output id="hzk7v"><pre id="hzk7v"><address id="hzk7v"></address></pre></output>
              <output id="hzk7v"></output>
            2. <nav id="hzk7v"><i id="hzk7v"><em id="hzk7v"></em></i></nav>
            3. <listing id="hzk7v"><delect id="hzk7v"><em id="hzk7v"></em></delect></listing>