The Go Blog

Error handling and Go

bantana
1 September 2014

错误和异常

大多数语言对错误和异常是没怎么区分的,e.g. 大多数java程序把错误和异常混为一谈,你很难从代码上一眼看出来是错误还是异常; 对大型工程来说,程序的健壮性优先级要高,否则就像C++,Java那样,后期的维护成本逐步升高; 对一些很精密的程序,根据异常的不同,来源的不同,程序会做出不同的反应,这很常见,这种情况下try+catch方式就显得冗长;

go程序的错误处理机制先得从函数多返回值说起

多返回值的出现促进了"comma-ok"的模式。有可能失败的函数可以返回第二个布尔结果来表示成功。作为替代,也可以返回一个错误对象,因此像下面这样的代码也就不见怪了:

if result, ok := moreMagic(); ok {
/* Do something with result */
}

在Go语言中,规定的方式是,函数返回错误信息

func Open(name string) (file *File, err error)

然后使用如下方式调用:

file, err := os.Open("file.go") // For read access.
if err != nil {
    log.Fatal(err)
}

如果一个文件并不存在,os.Open函数会返回一个错误信息。 如果你向你一个中断了的网络连接里写数据,net.Conn里的Write方法会返回一个错误。这种状况在这种程序中是可以预料到的。这种操作就是容易失败,你知道程序会如何运行,因为API的设计者通过内置了一种错误情况的结果而让这一切显得很清楚。

panic() & recover()

RUSS COX说法: 从另一方面讲,有些操作基本上不会出错,所处的环境根本不可能给你提示错误信息,不可能控制错误。这才是让人痛苦的地方。典型的例子;一个程序执行x[j],j值超出数组边界,这才痛苦。像这样预料之外的麻烦在程序中是一个严重的bug,一般会弄死程序的运行。不幸的是,由于这种情况的存在,我们很难写出健壮的,具有自我防御的服务器——例如,可以应付偶然出现的有bug的HTTP请求处理器时,不影响其他服务的启动和运行。为解决这个问题,我们引入了恢复机制,它能让一个go例程从错误中恢复,服务余下设定的调用。然而,代价是,至少会丢失一个调用。这是特意而为之的。引用邮件中的原话:“这种设计不同于常见的异常控制结构,这是一个认真思考后的决定。我们不希望像java语言里那样把错误和异常混为一谈。”

Russ Cox针对“为什么数组越界造成的麻烦会比错误的网址或断掉的网络引出的问题要大?”这个问题给出了自己的答案:

我们没有一种内联并行的方法来报告在执行x[j]期间产生的错误,但我们有内联并行的方法报告由错误网址或网络问题造成的错误。

使用Go语言中的错误返回模式的规则很简单:如果你的函数在某种情况下很容易出错,那它就应该返回错误。当我调用其它的程序库时,如果它是这样写的,那我不必担心那些错误的产生,除非有真正异常的状况,我根本没有想到需要处理它们。

Russ Cox指出Go语言是为大型软件设计的:

我们都喜欢程序简洁清晰,但对于一个由很多程序员一起开发的大型软件,维护成本的增加很难让程序简洁。异常捕捉模式的错误处理方式的一个很有吸引力的特点是,它非常适合小程序。但对于大型程序库,如果对于一些普通操作,你都需要考虑每行代码是否会抛出异常、是否有必要捕捉处理,这对于开发效率和程序员的时间来说都是非常严重的拖累。我自己做开发大型Python软件时感受到了这个问题。Go语言的返回错误方式,不可否认,对于调用者不是很方便,但这样做会让程序中可能会出错的地方显的很明显。对于小程序来说,你可能只想打印出错误,退出程序。对于一些很精密的程序,根据异常的不同,来源的不同,程序会做出不同的反应,这很常见,这种情况中,try + catch的方式相对于错误返回模式显得冗长。当然,Python里的一个10行的代码放到Go语言里很可能会更冗长。毕竟,Go语言主要不是针对10行规模的程序的。

参考这个blog (需要翻墙)

也有本地访问方式:

godoc -http=":8080"

然后访问下面地址:

Errors are values

error的用法

error 是个接口类型,

Related articles