设为首页 加入收藏

TOP

Golang error 的突围(一)
2019-09-18 11:10:41 】 浏览:149
Tags:Golang error 突围

写过 C 的同学知道,C 语言中常常返回整数错误码(errno)来表示函数处理出错,通常用 -1 来表示错误,用 0 表示正确。

而在 Go 中,我们使用 error 类型来表示错误,不过它不再是一个整数类型,是一个接口类型:

type error interface {
    Error() string
}

它表示那些能用一个字符串就能说清的错误。

我们最常用的就是 errors.New() 函数,非常简单:

// src/errors/errors.go

func New(text string) error {
    return &errorString{text}
}

type errorString struct {
    s string
}

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

使用 New 函数创建出来的 error 类型实际上是 errors 包里未导出的 errorString 类型,它包含唯一的一个字段 s,并且实现了唯一的方法:Error() string

通常这就够了,它能反映当时“出错了”,但是有些时候我们需要更加具体的信息,例如:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // implementation
}

当调用者发现出错的时候,只知道传入了一个负数进来,并不清楚到底传的是什么值。在 Go 里:

It is the error implementation's responsibility to summarize the context.

它要求返回这个错误的函数要给出具体的“上下文”信息,也就是说,在 Sqrt 函数里,要给出这个负数到底是什么。

所以,如果发现 f 小于 0,应该这样返回错误:

if f < 0 {
    return 0, fmt.Errorf("math: square root of negative number %g", f)
}

这就用到了 fmt.Errorf 函数,它先将字符串格式化,再调用 errors.New 函数来创建错误。

当我们想知道错误类型,并且打印错误的时候,直接打印 error:

fmt.Println(err)

或者:

fmt.Println(err.Error)

fmt 包会自动调用 err.Error() 函数来打印字符串。

通常,我们将 error 放到函数返回值的最后一个,没什么好说的,大家都这样做,约定俗成。

参考资料【Tony Bai】这篇文章提到,构造 error 的时候,要求传入的字符串首字母小写,结尾不带标点符号,这是因为我们经常会这样使用返回的 error:

... err := errors.New("error example")
fmt.Printf("The returned error is %s.\n", err)

error 的困局

In Go, error handling is important. The language's design and conventions encourage you to explicitly check for errors where they occur (as distinct from the convention in other languages of throwing exceptions and sometimes catching them).

在 Go 语言中,错误处理是非常重要的。它从语言层面要求我们需要明确地处理遇到的错误。而不是像其他语言,类如 Java,使用 try-catch- finally 这种“把戏”。

这就造成代码里 “error” 满天飞,显得非常冗长拖沓。

而为了代码健壮性考虑,对于函数返回的每一个错误,我们都不能忽略它。因为出错的同时,很可能会返回一个 nil 类型的对象。如果不对错误进行判断,那下一行对 nil 对象的操作百分之百会引发一个 panic

这样,Go 语言中诟病最多的就是它的错误处理方式似乎回到了上古 C 语言时代。

rr := doStuff1()
if err != nil {
    //handle error...
}

err = doStuff2()
if err != nil {
    //handle error...
}

err = doStuff3()
if err != nil {
    //handle error...
}

Go authors 之一的 Russ Cox 对于这种观点进行过驳斥:当初选择返回值这种错误处理机制而不是 try-catch,主要是考虑前者适用于大型软件,后者更适合小程序。

在参考资料【Go FAQ】里也提到,try-catch 会让代码变得非常混乱,程序员会倾向将一些常见的错误,例如,failing to open a file,也抛到异常里,这会让错误处理更加冗长繁琐且易出错。

而 Go 语言的多返回值使得返回错误异常简单。对于真正的异常,Go 提供 panic-recover 机制,也使得代码看起来非常简洁。

当然 Russ Cox 也承认 Go 的错误处理机制对于开发人员的确有一定的心智负担。

参考资料【Go 语言的错误处理机制是一个优秀的设计吗?】是知乎上的一个回答,阐述了 Go 对待错误和异常的不同处理方式,前者使用 error,后者使用 panic,这样的处理比较 Java 那种错误异常一锅端的做法更有优势。

【如何优雅的在Golang中进行错误处理】对于在业务上如何处理 error,给出了一些很好的示例。

尝试破局

这部分的内容主要来自 Dave cheney GoCon 2016 的演讲,参考资料可以直达原文。

经常听到 Go 有很多“箴言”,说得很顺口,但理解起来并不是太容易,因为它们大部分都是有故事的。例如,我们常说:

Don't communicating by sharing memory, share memory by communicating.

文中还列举了很多,都很有意思:

go proverbs

下面我们讲三条关于 error 的“箴言”。

Errors are just values

Errors are just values 的实际意思是只要实现了 Error 接口的类型都可以认为是 Error,重要的是要理解这些“箴言”背后的道理。

作者把处理 error 的方式分为三种:

  1. Sentinel errors
  2. Error Types
  3. Opaque errors

我们来挨个说。首先 Sentinel errors,Sentinel 来自计算机中常用的词汇,中文意思是“哨兵”。以前在学习快排的时候,会有一个“哨兵”,其他元素都要和“哨兵”进行比较,它划出了一条界限。

这里 Sentinel errors 实际想说的是这里有一个错误,暗示处理流程不能再进行下去了,必须要在这里停下,这也是一条界限。而这些错误,往往是提前约定好的。

例如,io 包里的 io.

首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇golang基础语法 下一篇Go Modules使用教程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目