设为首页 收藏本站
查看: 973|回复: 0

[经验分享] 【GoLang】GoLang 官方 对 error 处理的意见

[复制链接]

尚未签到

发表于 2018-9-20 10:05:03 | 显示全部楼层 |阅读模式
Errors are values
  12 January 2015
  A common point of discussion among Go programmers, especially those new to the language, is how to handle errors. The conversation often turns into a lament at the number of times the sequence
  

if err != nil {  return err
  
}
  

  shows up. We recently scanned all the open source projects we could find and discovered that this snippet occurs only once per page or two, less often than some would have you believe. Still, if the perception persists that one must type
  

if err != nil  

  all the time, something must be wrong, and the obvious target is Go itself.
  This is unfortunate, misleading, and easily corrected. Perhaps what is happening is that programmers new to Go ask, "How does one handle errors?", learn this pattern, and stop there. In other languages, one might use a try-catch block or other such mechanism to handle errors. Therefore, the programmer thinks, when I would have used a try-catch in my old language, I will just type if err != nil in Go. Over time the Go code collects many such snippets, and the result feels clumsy.
  Regardless of whether this explanation fits, it is clear that these Go programmers miss a fundamental point about errors: Errors are values.
  Values can be programmed, and since errors are values, errors can be programmed.
  Of course a common statement involving an error value is to test whether it is nil, but there are countless other things one can do with an error value, and application of some of those other things can make your program better, eliminating much of the boilerplate that arises if every error is checked with a rote if statement.
  Here's a simple example from the bufio package's Scanner type. Its Scan method performs the underlying I/O, which can of course lead to an error. Yet the Scan method does not expose an error at all. Instead, it returns a boolean, and a separate method, to be run at the end of the scan, reports whether an error occurred. Client code looks like this:
  

scanner := bufio.NewScanner(input)  
for scanner.Scan() {
  token := scanner.Text()
  // process token
  
}
  
if err := scanner.Err(); err != nil {
  // process the error
  
}
  

  Sure, there is a nil check for an error, but it appears and executes only once. The Scan method could instead have been defined as
  

func (s *Scanner) Scan() (token []byte, error)  

  and then the example user code might be (depending on how the token is retrieved),
  

scanner := bufio.NewScanner(input)  
for {
  token, err := scanner.Scan()
  if err != nil {
  return err // or maybe break
  }
  // process token
  
}
  

  This isn't very different, but there is one important distinction. In this code, the client must check for an error on every iteration, but in the real Scanner API, the error handling is abstracted away from the key API element, which is iterating over tokens. With the real API, the client's code therefore feels more natural: loop until done, then worry about errors. Error handling does not obscure the flow of control.
  Under the covers what's happening, of course, is that as soon as Scan encounters an I/O error, it records it and returns false. A separate method, Err, reports the error value when the client asks. Trivial though this is, it's not the same as putting
  

if err != nil  

  everywhere or asking the client to check for an error after every token. It's programming with error values. Simple programming, yes, but programming nonetheless.
  It's worth stressing that whatever the design, it's critical that the program check the errors however they are exposed. The discussion here is not about how to avoid checking errors, it's about using the language to handle errors with grace.
  The topic of repetitive error-checking code arose when I attended the autumn 2014 GoCon in Tokyo. An enthusiastic gopher, who goes by @jxck_ on Twitter, echoed the familiar lament about error checking. He had some code that looked schematically like this:
  

_, 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
  
}
  
// and so on
  


  It is very repetitive. In the real code, which was longer, there is more going on so it's not easy to just refactor this using a helper function, but in this>  

var err error  
write := func(buf []byte) {
  if err != nil {
  return
  }
  _, err = w.Write(buf)
  
}
  
write(p0[a:b])
  
write(p1[c:d])
  
write(p2[e:f])
  
// and so on
  
if err != nil {
  return err
  
}
  

  This pattern works well, but requires a closure in each function doing the writes; a separate helper function is clumsier to use because the err variable needs to be maintained across calls (try it).
  We can make this cleaner, more general, and reusable by borrowing the>Scan method above. I mentioned this technique in our discussion but @jxck_ didn't see how to apply it. After a long exchange, hampered somewhat by a language barrier, I asked if I could just borrow his laptop and show him by typing some code.
  I defined an object called an errWriter, something like this:
  

type errWriter struct {  w   io.Writer
  err error
  
}
  

  and gave it one method, write. It doesn't need to have the standard Write signature, and it's lower-cased in part to highlight the distinction. The write method calls the Write method of the underlying Writer and records the first error for future reference:
  

func (ew *errWriter) write(buf []byte) {  if ew.err != nil {
  return
  }
  _, ew.err = ew.w.Write(buf)
  
}
  

  As soon as an error occurs, the write method becomes a no-op but the error value is saved.
  Given the errWriter type and its write method, the code above can be refactored:
  

ew := &errWriter{w: fd}  
ew.write(p0[a:b])
  
ew.write(p1[c:d])
  
ew.write(p2[e:f])
  
// and so on
  
if ew.err != nil {
  return ew.err
  
}
  

  This is cleaner, even compared to the use of a closure, and also makes the actual sequence of writes being done easier to see on the page. There is no clutter any more. Programming with error values (and interfaces) has made the code nicer.
  It's likely that some other piece of code in the same package can build on this>errWriterdirectly.
  Also, once errWriter exists, there's more it could do to help, especially in less artificial examples. It could accumulate the byte count. It could coalesce writes into a single buffer that can then be transmitted atomically. And much more.
  In fact, this pattern appears often in the standard library. The archive/zip and net/http packages use it. More salient to this discussion, the bufio package's Writer is actually an implementation of the errWriter idea.>bufio.Writer.Write returns an error, that is mostly about honoring the io.Writer interface. The Write method of bufio.Writer behaves just like our errWriter.write method above, with Flush reporting the error, so our example could be written like this:
  

b := bufio.NewWriter(fd)  
b.Write(p0[a:b])
  
b.Write(p1[c:d])
  
b.Write(p2[e:f])
  
// and so on
  
if b.Flush() != nil {
  return b.Flush()
  
}
  

  There is one significant drawback to this approach, at least for some applications: there is no way to know how much of the processing completed before the error occurred. If that information is important, a more fine-grained approach is necessary. Often, though, an all-or-nothing check at the end is sufficient.
  We've looked at just one technique for avoiding repetitive error handling code. Keep in mind that the use of errWriter or bufio.Writer isn't the only way to simplify error handling, and this approach is not suitable for all situations. The key lesson, however, is that errors are values and the full power of the Go programming language is available for processing them.
  Use the language to simplify your error handling.
  But remember: Whatever you do, always check your errors!
  Finally, for the full story of my interaction with @jxck_, including a little video he recorded, visit his blog.
  By Rob Pike
  参考资料:
  https://blog.golang.org/errors-are-values



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598704-1-1.html 上篇帖子: Simple Web API Server in Golang (1) 下篇帖子: golang导出excel(excel格式)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表