• Go 中的异常/错误处理


    即使是高质量的代码,也不能保证一定能够成功返回,因为有些因素并不受程序设计者掌控。例如任何 I/O 操作可能产生错误,事实上,这些地方便是程序员最需要关注的。

    因此错误处理是包的 API 设计或应用程序用户接口的重要部分,发生错误只是许多预料行为中的一种,这就是 Go 语言处理错误的方法。

    错误返回策略

    当函数调用发生错误时,我们习惯返回一个附加的结果作为错误值,且一般作为最后一个返回结果。

    1.如果错误只有一种情况,那么结果通常为「布尔类型」。例如下面的查询例子,只有在不存在对应键值的适合才返回错误:

    value, ok := cache.Lookup(key)
    if !ok {
        // chche[key] 不存在
    }
    

    2.但更多时候,尤其对于 I/O 操作,错误的原因可能多种多样,这时调用者需要一些详细信息,这种情况下,错误的类型往往为「error」。

    reso, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    

    和许多其他语言不同,Go 语言通过使用普通的值而非异常来报告错误;Go 语言中的异常通常只是针对程序 bug 导致的预料外错误,而不应作为常规的错误处理方法出现在程序中。

    如果用异常来报告错误,会出现下面这种情况:

    • 异常会陷入带有错误信息的控制流去处理它,通常导致预期外的结果:错误会以难以理解的栈跟踪信息报告给最终用户,这些信息大多关于程序结构方面而不是简单明了的错误信息

    因此,Go 使用通常的控制流机制(如 if 和 return)来应对错误,这种方式对错误处理逻辑方面有更高的要求。

    错误处理策略

    当函数调用返回一个错误时,调用者应该检查是否存在错误并采取合适的处理应对,下面我们讲讲 5 个常见的处理方式:

    将错误传递下去

    将错误传递之后,在子例程中发生的错误会变成主调例程的错误;这时我们希望传递的错误能够返回一个可读的错误描述

    error 中信息满足要求

    例如我们调用http.Get失败,我们可以直接返回这个 HTTP 错误:

    reso, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    

    它包含了失败的 url,也就是这里我们需要的信息。

    error 中信息不足

    但有时,error 中包含的信息并不清晰,例如我们对一个 response 的响应体调用http.Parse失败,这种情况下的 err 缺失两个关键信息:解析器的出错信息与被解析文档的 url。这种情况下我们会为它构建一个新的错误信息:

    doc, err := html.Parse(resp.Body)
    resp.Body.Close()
    if err != nil {
        return nil, fmt.Errorf("parsing %s as HTML: %v", usr, err)
    }
    

    fmt.Errorf会使用fmt.Sprintf格式化一条错误信息,并返回一个新的错误值。

    这样,我们便为原始的错误信息添加了额外的上下文信息,建立了一个可读的错误描述。当错误最终返回程序的 main 函数处理时,它应当提供了一个从最根本问题到总体故障的清晰因果链。

    例如 NASA 的事故调查例子:

    genesis: crashed: no parachute: G-switch failed: bad relay orientation
    

    构建错误信息的要求

    因为错误信息频繁地串联,因此消息字符串首字母应该小写,且避免换行。这样可能会让错误信息很长,但我们可以使用grep这样的工具找到需要的信息。

    一般地,一个函数f(x)的调用只报告函数的行为f参数值x,因为它们与错误上下文相关;再由「调用者」进一步添加信息。

    给定失败操作一定重试次数和限定时间,超出后再报错

    有些操作我们应该对它的失败有所容忍,它可能在短暂时间后便能成功:

    // 尝试连接 URL 对应服务器
    func WaitForServer(url string) error {
        const timeout = 1 * time.Minute
        deadline := time.Now().Add(timeout)
        for tries := 0; time.Now().Before(deadline); tries++ {
            _, err := http.Head(url)
            if err == nil {
                return nil // 成功
            }
            log.Printf("server not responding (%s); retrying...", err)
            time.Sleep(time.Second << uint(tries)) // 使用指数退避策略进行重试
        }
        return fmt.Errorf("server %s failed to respond after %s", url, timeout) // 失败
    }
    

    输出错误并停止程序

    一般来说,这个操作是留至主程序部分来处理的,其余函数应当将错误传递给调用者,除非这个错误是一个内部一致性错误(也就是该函数存在 bug)。

    // in function main
    if err := WaitForServer(url); err != nil {
        fmt.Fprintf(os.Stderr, "Site is down: %v
    ", err)
        os.Exit(1)
    }
    

    一个更方便的方式是调用log.Fatalf实现一样的效果,作为一个日志函数,它能够默认将时间和日期都作为前缀加到错误消息前面:

    if err := WaitForServer(url); err != nil {
        log.Fatalf("Site is down: %v
    ", err)
    }
    

    这样打印得出的格式有助于长期运行的服务器,它能够使我们方便的对错误定位。

    我们还可以自定义命令名称作为log包的前缀,并将日期和时间略去:

    log.SetPrefix("wait: ")
    log.SetFlags(0)
    

    仅记录错误信息然后程序继续运行

    有时错误并不会对程序当前运行产生很大的影响,我们可以将错误信息先进行记录待后续处理。

    我们可以用之前提到的log包来增加日志的常用前缀:

    if err := Ping(); err != nil {
        // 所有 log 函数都会为缺少换行符的日志填充换行符
        log.Printf("ping failed: %v; networking disabled", err)
    }
    

    或是直接输出到标准错误流:

    if err := Ping(); err != nil {
        fmt.Fprintf(os,Stderr, "ping failed: %v; networking disabled
    ", err)
    }
    

    直接忽略整个错误日志

    在一些罕见的情况下,错误日志并没有意义,这是我们可以直接安全地忽略掉整个日志:

    // 创建临时目录
    dir, err := iout.TempDir("", "scratch")
    if err != nil {
        return fmt.Errorf("failed to create temp dir: %v", err)
    }
    
    // 使用临时目录
    ...
    
    os.RemoveAll(dir) // 忽略这个语句可能产生的错误,$TMPDIR 会被周期性删除
    

    调用os.Remove可能会失败,但操作系统自己会周期性的删除这个目录,也就是这个语句的失败与否并无大碍,因此我们忽略了这个错误。

    在上例中,我们有意地抛弃了错误,但程序的逻辑看上去就像我们忘记处理了一样,因此如果我们需要有意地忽略一个错误,一定要在注释中清晰地写明理由。

    Go 语言中,对语句进行错误检查后;如果检测到的失败导致函数返回,成功的逻辑一般不会放在 else 块中,而是在外层的作用域中。

    一般来说,我们会在函数开头便进行一连串的检查用来返回错误,在之后再进行具体的函数体逻辑。

  • 相关阅读:
    个人破解汇总
    JavaScript学习中的挑战
    18个黑白配色网页设计
    正则表达式常用用法汇总 __西科大C语言
    JS正则表达式大全(整理详细且实用)
    印象最深的三个老师
    pbzip2
    集群接口机存储监控
    vue...扩展符报错
    框架mpvue创建一个小程序
  • 原文地址:https://www.cnblogs.com/Bylight/p/12089623.html
Copyright © 2020-2023  润新知