Indented flow is for erroes

无错误正常流程代码,将成为一条直线,而不是缩进代码:

package main// 建议style
f, err := os.Open(path)
if err != nil {// handle error  将错误的代码在这里提前处理掉
}
// do stuff  做一些事情// 不建议的style
f, err := os.Open(path)
if err == nil {// do stuff
}
// handle error

下面代码有什么问题?

func AuthenticeRequest(r *Request) error {err := authentication(r.User) // 验证用户是否合法if err != nil {return err}return nil
}
// 改进版  减少了if err判断
func AuthenticeRequest(r *Request) error {return authentication(r.User)
}

Eliminate error handling by eliminating errors

统计io.Reader读取内容的行数

func CountLines(r io.Reader) (int, error) {var (br = bufio.NewReader(r)lines interr error)for {_, err  = br.ReadString('\n')lines++        // 先lines++,因为如果有io.EOF,跳过就会少记录一行,所以先lines++再处理errif err != nil {break}}if err != io.EOF {return 0, err}return lines, nil
}

精简版:

func CountLines(r io.Reader) (int, error) {sc := bufio.NewScanner(r)        // 创建一个扫描器lines := 0for sc.Scan() {        // 如果还有下一行就返回true,否则返回falselines++}return lines, sc.Err()
}

例子

type Header struct {Key, Value string
}type Status struct {Code intReason string
}// 返回HTTP请求,需要将其写到io.Wreiter对象
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {// 将code和msg拼写进去_, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)   if err != nil {return err}// 将自定义的headers写进去for _, h := range headers {_, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)if err != nil {return err}}// 需要加一个换行符if _,err := fmt.Fprintf(w, "\r\n");err != nil {return err}// 将body内容拷贝进去 _, err = io.Copy(w, body)return err
}

通过errWriter包装方法改进版本

type Header struct {Key, Value string
}type Status struct {Code intReason string
}// 定义一个errWriter对象
type errWriter struct {io.Writererr error       // 类似Scanner和Rows的暂存error
}// 提供一个errWriter方法,将buf写入
func (e *errWriter) Write(buf []byte) (int, error)  {// 若之前暂存的err不为nil,则直接返回,什么都不用输出,并将暂存的err抛出去if e.err != nil {return 0, e.err}// 否则就正常的write输出var n intn, e.err = e.Writer.Write(buf)return n, nil
}// 通过errWriter包装方法改进版本
func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {ew := &errWriter{Writer: w}/* 将要写的http、code、状态码放入ew对象中若之前的errwriter已经出错了,不管传什么内容进去,errWriter都会判断它之前已经报错了不会做任何的处理,不会污染已经写入buf的内容所以不需要做任何的err判定*/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.Fprintf(ew,"\r\n")io.Copy(ew, body)return ew.err
}

Wrap errors

刚开始的auth代码,如果authentication返回错误,则AuthenticeRequest会将错误返回给调用方,调用者可能也会同样处理,以此类推。在程序顶部,程序的主体将把错误打印到屏幕或者日志中,打印出来的知识:没有这样的文件或目录。

没有生成错误的file.line信息,没有导致错误的调用堆栈的堆栈跟踪。这段代码的作者将被迫进行长时间的代码分割,以此发现是哪个代码路径触发了文件未找到错误。

func AuthenticeRequest(r *Request) error {err := authentication(r.User)  // 验证用户是否合法if err != nil {return fmt.Errorf("authentication failed: %v", err)}return nil
}

但是正如我们前面看到的,这种模式与sentinel errorstype assertions的使用不兼容,因为将错误值转换为字符串,将其与另一个字符串合并,然后将其转换回fmt.Errorf破坏了原始错误,导致等值判定失败。

you should handle errors once. Handle an error means inspecting the error value, and making a single decsion

我们经常习惯性的在错误处理中,带了两个任务(到处打日志):记录日志并再次返回错误。

func WriteAll(w io.Writer, buf []byte) error {_, err := w.Write(buf)if err != nil {log.Println("unable to write:", err)      // 先是打印了日志return err                        // 往上抛}return nil
}

在下面例子中,如果在w.Write过程中发生了一个错误,那么一行代码将被写入日志文件中,记录发生的文件和行,并且错误也会返回给调用者,调用者可能会记录它并返回他,一直到程序的顶部。

func WriteConfig(w io.Writer, conf *Config) error {buf, err := json.Marshal(conf)if err != nil {log.Println("could net marshal config: %v", err)return err}if err := WriteAll(w, buf);err != nil {log.Println("could not write config: %v", err)return err}return nil
}

Output:

unable to write: io.EOF
could not write config:io.EOF

Go中的错误处理契约规定,在出现错误的情况下,不能对其他返回值的内容作出任何假设。由于JSON序列化失败,buf的内容是未知的,可能它不包含任何内容,但更糟糕的是,它可能包含一个板鞋的JSON片段。

由于程序员在检查并记录错误后忘记return,损坏的缓冲区将被传递给WriteAll,这可能会成功,因此配置文件将被错误的写入。但是,该函数返回的结果是正确的。

func WriteConfig(w io.Writer, conf *Config) error {buf, err := json.Marshal(conf)if err != nil {log.Println("could net marshal config: %v", err)// oops, forgot to return}if err := WriteAll(w, buf);err != nil {log.Println("could not write config: %v", err)return err}return nil
}

日志记录与错误无关且对调试没有帮助的信息应被视为噪音,应予以质疑。记录的原因是因为某些东西失败了,而日志包含了答案。

  • 错误要被日志记录,报错一定要记录日志
  • 应用程序处理错误,保证100%完整性
  • 之后不再报告当前错误,在产生错误的地方产生了日志,往上抛就都不应该记录日志

github.com/pkg/errors

例子:

package mainimport ("fmt""os""github.com/pkg/errors""path/filepath"
)// 读文件
func ReadFile(path string) ([]byte, error) {f, err := os.Open(path)if err != nil {// 返回一个nil字节流,一个Wrap方法:原始错误保留了(包含当前错误的堆栈信息),还能捎带一些扩展的信息"open failed"return nil, errors.Wrap(err,"open failed")}defer f.Close()return nil, nil
}func ReadConfig()([]byte, error) {home := os.Getenv("HOME")config, err := ReadFile(filepath.Join(home, ".setting.xml"))  // 若这儿出错,则这个为最底层的错误// WithMessage方法:原始错误保留,但不会保留堆栈信息,因为ReadFile已经保存了堆栈信息了return config, errors.WithMessage(err, "could not  read config")
}
func main() {_, err := ReadConfig()if err != nil {// Cause方法可以获取原始错误fmt.Printf("original eror: %T %v\n", errors.Cause(err), errors.Cause(err))// 堆栈的追踪fmt.Printf("stack trace:\n%+v\n", err)     // %+v  将堆栈信息打印出来os.Exit(1)}
}

使用技巧

通过使用pkg/errors包,我们可以向错误值添加上下文,这种方式可以由使用者和机器检查。

func Write(w io.Writer, buf []byte) error {_, err := w.Write(buf)if err != nil {log.Println("unable to write:", err)return err}return nil
}

在err中处理了两次:log.Println("unable to write:", err)return err,改进之后:

func Write(w io.Writer, buf []byte) error {_, err := w.Write(buf)return errors.Wrap(err, "write failed")
}
  • 在我们的应用代码中,使用errors.New或者errors.Errorf返回错误。这两个方法都是由pkg/errors包提供的,这两个方法都会携带原始的堆栈信息,相对于标准的errors.New能拿到堆栈信息。是因为自己的逻辑而主动产生的错误需要将堆栈信息保存起来。
func parseArgs(args []string) error {if len(args) < 3 {return errors.Errorf("not enough arguments, expected at least..")}// ...
}
  • 如果调用其他包内的函数,通常简单的直接返回。
if err != nil {return err
}
  • 如果和其他库进行协作,考虑使用errors.Wrap或者errors.Wrapf保存堆栈信息。同样适用于标准库协作的时候。因为调用别人代码,别人报错了,可以使用Wrap将其包装起来保存堆栈信息,可以继续往下走或往上抛。
 f ,err := os.Open(path)if err != nil {return errors.Wrapf(err, "failed to open %q", path)
}
  • 直接返回错误,而不是每个错误产生的地方到处打印日志。
  • 在程序的顶部或者是工作的goroutine顶部(请求入口),使用%+v把堆栈信息详情记录。
func main() {err := app.Run()if err != nil {fmt.Printf("FATAL: %+v\n", err)os.Exit(1)}
}
  • 使用errors.Cause获取 root error ,再进行和 sentinel error 判定。

总结

  • Packages that are reusable across many projects only up the call stack.

    选择 wrap error 是只有 applications 可以选择应用的策略。具有最高可重用性的包只能返回根错误值。此机制与 Go 标准库中使用的相同(kit 库的 sql.ErrNoRows)。就是自己的业务应用去使用 wrap ,而第三方库或者kit库不应该使用 wrap ,第三方库应抛sentinel errors 的原始错误或自定义错误。

  • If the error is not going to be handled, wrap and return up the call stack.

    这是关于函数/方法调用返回的每个错误的基本问题。如果函数/方法不打算处理错误,那么用足够的上下文 wrap errors 并将其返回到调用堆栈中。例如,额外的上下文可以是使用输入参数或失败的查询语句。认证我们记录的上下文是合适还是太多的一个方法是检查日志并验证它们在开发期间是否为您“工作”或帮助。

  • Once an error is handled , it is not allowed to be passed up the call stack any longer.

    一旦确定函数/方法将处理错误,错误就不再是错误了。如果函数/方法扔需要发出返回,则它不能返回错误值。它应该只返回零(比如降级处理中,你反悔了降级数据,然后需要 return nil)。

05 Go处理错误--Handing Error相关推荐

  1. Spring Boot项目错误:Error parsing lifecycle processing instructions

    pom.xml文件错误:Error parsing lifecycle processing instructions 解决方法:清空.m2/repository下的所有依赖文件,重新下载即可解决该问 ...

  2. 错误:Error #2032解决方案

    问题: Error #2032错误要访问外部数据,必须信任此文件. 现象: 要访问外部数据,必须信任此文件. 对于 PDF 文件,在 Adobe Reader 中,单击"Edit" ...

  3. R语言ggplot2可视化在散点图中的每个点上绘制两个错误条:常见的是垂直错误条,它对应于Y值点上的错误(error bar),添加与X轴(水平)相关的错误条(error bar)

    R语言ggplot2可视化在散点图中的每个点上绘制两个错误条:常见的是垂直错误条,它对应于Y值点上的错误(error bar),添加与X轴(水平)相关的错误条(error bar) 目录

  4. R语言使用tryCatch函数调试R代码实战:tryCatch函数运行正常R代码、tryCatch函数运行有错误(error)的R代码示例/tryCatch函数运行有警告(warning)的R代码示例

    R语言使用tryCatch函数调试R代码实战:tryCatch函数运行正常R代码.tryCatch函数运行有错误(error)的R代码示例/tryCatch函数运行有警告(warning)的R代码示例 ...

  5. Xamarin+vs2010部署错误:error MSB6004: 指定的任务可执行文件位置\sdk\\tools\zipalign.exe”无效...

    好不容易配好了Xamarin和vs2010,也搞好了GenyMotion的虚拟机配置,开始调试的时候又报出了这样的错误: error MSB6004: 指定的任务可执行文件位置"C:\Use ...

  6. MDK:assert_param函数未定义的错误:Error: L6218E

    今天使用奋斗stm32开发板,编译程序时 出现了一下错误,网上有很多解决方案,可是一直没解决, 在链接过程中出现assert_param函数未定义的错误:Error: L6218E: Undefine ...

  7. 安装vs2008中文时出现错误Write error in the file

    安装vs2008中文时出现错误Write error in the file VS2008TeamSuite90DayTrialCHSX1429243.iso. Probably the disk i ...

  8. 编译错误 fatal error C1010: unexpected end of file while looking for precompiled header directive

    VC6.0在编译的时候出现这种错误 fatal error C1010: unexpected end of file while looking for precompiled header dir ...

  9. yarn install 遇到的错误消息 - Error EPERM operation not permitted, open .yarnrc

    我在某个 Angular 项目文件下执行 npm install 命令时,遇到如下错误: Error: EPERM: operation not permitted, open C:\Users\i0 ...

最新文章

  1. 【effective c++读书笔记】【第7章】模板和泛型编程(3)
  2. 单机redis 主从实例
  3. 华为8c系统语言切换,华为WS331C怎么设置 华为WS331C设置教程(使用方法)-192路由网...
  4. [二叉树] 二叉树中的最大路径和---leetcode124
  5. 全国计算机等级考试题库二级C操作题100套(第85套)
  6. DHCP服务(dhcpd)
  7. 坑爹的 Lombok,把我害惨了!
  8. netty源码阅读之UnpooledByteBufAllocator
  9. go语言linux下开发工具,LiteIDE 开发工具指南 (Go语言开发工具)
  10. 织梦ajax加载文章列表,织梦dedecms首页列表页ajax点击下拉加载更多文章瀑布流效果...
  11. 关于Xshell的使用和网络攻防原理
  12. MacOS Mojave 安装 AI 東北きりたん 东北切蒲英 NEUTRINO 教程
  13. 如何降低和开发人员的bug沟通成本?
  14. BZOJ3505 CQOI2014数三角形(组合数学)
  15. Python数据处理二
  16. 电源平面Z阻抗参数的提取是否需要设置VRM,以及Port 参考阻抗对仿真结果的影响
  17. 分享淘宝的IP地址库查询接口
  18. node的fs读取html文件报错,node.js使用fs读取文件出错的解决方案
  19. 如何开发一个酷炫的mdx
  20. 学习笔记(十八):MoRe-Fi用深度学习网络从非线性信号中恢复呼吸波形

热门文章

  1. 【CSS】CSS 层叠样式表 ① ( 简介 | CSS 引入方式 - 内联样式 | 内联样式语法 | 内联样式缺点 )
  2. Daily English Dictation
  3. Linux 安装 SVN
  4. 她,从种蘑菇卖煤球,到开源 Zadig
  5. eMMC读写速度与什么有关 宏旺半导体来解答
  6. pygame的应用——python版飞机大战
  7. 2018 完美世界校招笔试编程题(Java)
  8. React的setTimeout定时任务,和setTimeout的定时无效
  9. 对话清华教授孙茂松:第三代人工智能要处理“可解释性”问题
  10. c++创建对象的几种方式