gopl-zh.github.com/ch5/ch5-04.md
2016-02-15 11:06:34 +08:00

3.9 KiB
Raw Blame History

5.4. 错误

在Go中有一部分函数总是能成功的运行。比如strings.Contains和strconv.FormatBool函数对各种可能的输入都做了良好的处理使得运行时几乎不会失败除非遇到灾难性的、不可预料的情况比如运行时的内存溢出。导致这种错误的原因很复杂难以处理从错误中恢复的可能性也很低。

还有一部分函数只要输入的参数满足一定条件也能保证运行成功。比如time.Date函数该函数将年月日等参数构造成time.Time对象除非最后一个参数时区是nil。这种情况下会引发panic异常。panic是来自被调函数的信号表示发生了某个已知的bug。一个良好的程序永远不应该发生panic异常。

对于大部分函数而言永远无法确保能否成功运行。这是因为错误的原因超出了程序员的控制。举个例子任何进行I/O操作的函数都会面临出现错误的可能只有没有经验的程序员才会相信读写操作不会失败即时是简单的读写。因此当本该可信的操作出乎意料的失败后我们必须弄清楚导致失败的原因。

在Go的错误处理中错误是软件包API和应用程序用户界面的一个重要组成部分程序运行失败仅被认为是几个预期的结果之一。

对于那些将运行失败看作是预期结果的函数它们会返回一个额外的返回值通常是最后一个来传递错误信息。如果导致失败的原因只有一个额外的返回值可以是一个布尔值通常被命名为ok。比如cache.Lookup失败的唯一原因是key不存在那么代码可以按照下面的方式组织

value, ok := cache.Lookup(key)
if !ok {
	// ...cache[key] does not exist…
}

通常导致失败的原因不止一种尤其是对I/O操作而言用户需要了解更多的错误信息。因此额外的返回值不再是简单的布尔类型而是error类型。

内置的error是接口类型。我们将在第七章了解接口类型的含义以及它对错误处理的影响。现在我们只需要明白error类型可能是nil或者non-nil。nil意味着函数运行成功non-nil表示失败。对于non-nil的error类型,我们可以通过调用error的Error函数或者输出函数获得字符串类型的错误信息。

fmt.Println(err)
fmt.Printf("%v", err)

通常当函数返回non-nil的error时其他的返回值是未定义的(undefined),这些未定义的返回值应该被忽略。然而有少部分函数在发生错误时仍然会返回一些有用的返回值。比如当读取文件发生错误时Read函数会返回可以读取的字节数以及错误信息。对于这种情况正确的处理方式应该是先处理这些不完整的数据再处理错误。因此对函数的返回值要有清晰的说明以便于其他人使用。

在Go中函数运行失败时会返回错误信息这些错误信息被认为是一种预期的值而非异常exception这使得Go有别于那些将函数运行失败看作是异常的语言。虽然Go有各种异常机制但这些机制仅被使用在处理那些未被预料到的错误即bug而不是那些在健壮程序中应该被避免的程序错误。对于Go的异常机制我们将在5.9介绍。

Go这样设计的原因是由于对于某个应该在控制流程中处理的错误而言将这个错误以异常的形式抛出会混乱对错误的描述这通常会导致一些糟糕的后果。当某个程序错误被当作异常处理后这个错误会将堆栈根据信息返回给终端用户这些信息复杂且无用无法帮助定位错误。

正因此Go使用控制流机制如if和return处理异常这使得编码人员能更多的关注错误处理。

{% include "./ch5-04-1.md" %}

{% include "./ch5-04-2.md" %}