Golang Error Handling 是好的设计吗?

栏目: Go · 发布时间: 5年前

内容简介:从Java、C++、PHP转过来的Gopher在遇到错误处理时都会很苦恼,与前者们的类似try/catch模式相比,Golang的检查返回值判断错误的写法显得特别繁琐。本文试图去探究下Golang中Error Handling设计的背景与思维过程,力求还原一个真实的设计权衡。所以自定义一个error类型很简单,只要实现Error方法即可:

Java 、C++、 PHP 转过来的Gopher在遇到错误处理时都会很苦恼,与前者们的类似try/catch模式相比,Golang的检查返回值判断错误的写法显得特别繁琐。本文试图去探究下Golang中Error Handling设计的背景与思维过程,力求还原一个真实的设计权衡。

1、标准包中的Error Handling

Golang标准包 提供的Error Handling功能是通过error这个interface实现的。

type error interface {
    Error() string
}

所以自定义一个error类型很简单,只要实现Error方法即可:

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

那么如何处理错误呢?

官方推荐使用类似于 C语言 的,检查返回值的方式( if err != nil ),例如:

func readfile(path string) error {
    err := openfile(path)
    if err != nil {
        return fmt.Errorf("cannot open file: %v", err)
    }
    //...
}

以上可能还看不出问题,比如下面这样:

func DoSomething() (*C, error) {
   var err error
   var a string
   a, err =DoA()
   if err == nil {
      var b string
      b, err = DoB(a)
      if err == nil {
         var c string
         c, err = DoC(b)
         if err == nil {
            return c, nil
         }
      }
   }
   return nil, err
}

每次调用一个函数都需要做一次错误检查,这使得代码写起来十分繁琐,而不像其他编程语言中的try/catch来的简洁:错误统一处理,开发者专注当前业务流程。

2、为什么是这个样子?

Golang的错误处理模式受到了许多批评的声音,于是Rob Pike不得不在官网 刊文 解释,并声明『Errors are values』: Values can be programmed, and since errors are values, errors can be programmed. ,即开发者可以自定义error类型,优雅地设计符合自身业务流程的错误处理方式。

但同时他再次强调到:不管怎么设计,在程序中检查暴露出来的错误都是至关重要的。

一直到文章最后,Rob Pike都没有解释为什么要这么设计,而是在阐述如何优雅地使用Error Handling。

继续搜索之,官方的 FAQ 道出了设计背后考虑: We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. 即官方认为将异常耦合到控制结构(就像尝试try-catch-finally习惯用法一样)会导致复杂的代码,并鼓励开发者去显示地检查错误,这样程序流程不会被打断。这也是为什么Golang会提供多值返回的原因之一。虽然与其他编程语言不同, 但规范化的错误类型,加上Golang的其他特性(如error可编程),使错误处理变得方便。

总结起来,Error Handling的设计初衷是 拿编码的冗余性换取了逻辑的简单性 ,这便是其中的tradeoff,正如 Go proverbs所言: Clear is better than clever.

3、pkg包中的Error Handling

使用错误处理的姿势往往不止一种,标准库里通过的Error接口只够满足一般简单的场景,对于某些需要了解错误细节(如判断错误是网络中断还是返回值格式问题)以及当前执行栈的场景,则需要对Error接口的进一步封装,目前比较推崇的是 github.com/pkg/errors 包。

它除了输出调用层级上每一级错误内容之外,还可以打印出发生错误的文件名与所在行号,这对于定位Bug非常有帮助。

使用方法也很简单,可以看下面一个例子:

package main

import (
    "fmt"

    "github.com/pkg/errors"
)

func A() error {
    return errors.New("NullPointerException")
}

func B() error {
    return A()
}

func main() {
    fmt.Printf("Error: %+v", B())
}

运行输出:

Golang Error Handling 是好的设计吗?

4、Golang 2 草案

似乎开发者们已经习惯了try-catch-finally的用法,对于官方给出的Error Handling的设计解释并不买账,于是在 Golang 2 草案 中, Error Handling 作为一个重大改变被提了出来,并给出了优化方案:增加 checkhandle 两个新关键字。

这里 有两个对比示例:

Go 1

type Parsed struct { ... }

func ParseJson(name string) (Parsed, error) {

    // Open the file
    f, err := os.Open(name)
    if err != nil {
        return fmt.Errorf("parsing json: %s %v", name, err)
    }
    defer f.Close()

    // Parse json into p
    var p Parsed
    err = json.NewDecoder(f).Decode(&p)
    if err != nil {
        return fmt.Errorf("parsing json: %s %v", name, err)
    }

    return p
}

Go 2

type Parsed struct { ... }

func ParseJson(name string) (Parsed, error) {
    handle err {
        return fmt.Errorf("parsing json: %s %v", name, err)
    }

    // Open the file
    f := check os.Open(name)
    defer f.Close()

    // Parse json into p
    var p Parsed
    check json.NewDecoder(f).Decode(&p)

    return p
}

对比可以发现:增加了check与hanle关键字之后,整体的代码逻辑变得更加简洁,错误可以统一在handle处得到处理,类似于try/catch.

  • check:负责显示地检查错误(标记)
  • handle:定义错误处理逻辑,一旦check到指定错误,便会进入相应的错误处理逻辑。

当然,作为2.0的草案,并不一定会最终纳入Golang2的正式标准中,但如此好的设计值得期待。

5、References


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编程之道

编程之道

杰弗雷﹒詹姆斯 / 清华大学出版社 / 1999-05 / 18.00元

本书出自美国一位善于进行哲学思考、有十多年工作经验的程序设计师——杰弗雷·詹姆斯之手,他以一种敏锐的眼光审视着发生在程序设计室里的各种各样的小故事,并利用古老的道家思想对其进行分析。简单的故事蕴含深奥的道理,是本书的最大特色。本书语言优美,比喻生动,可读性极强。一起来看看 《编程之道》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码