# 5.2 测试多返回值函数的错误

Go 语言的函数经常使用两个返回值来表示执行是否成功：返回某个值以及 `true` 表示成功；返回零值（或 `nil`）和 `false` 表示失败（[第 4.4 节](04.4.md)）。当不使用 `true` 或 `false` 的时候，也可以使用一个 `error` 类型的变量来代替作为第二个返回值：成功执行的话，`error` 的值为 `nil`，否则就会包含相应的错误信息（Go 语言中的错误类型为 `error`: `var err error`，我们将会在[第 13 章](13.0.md) 进行更多地讨论）。这样一来，就很明显需要用一个 `if` 语句来测试执行结果；由于其符号的原因，这样的形式又称之为“逗号 ok 模式”(comma, ok pattern)。

在[第 4.7 节](04.7.md) 的程序 [string_conversion.go](examples/chapter_4/string_conversion.go) 中，函数 `strconv.Atoi()` 的作用是将一个字符串转换为一个整数。之前我们忽略了相关的错误检查：

```go
anInt, _ = strconv.Atoi(origStr)
```

如果 `origStr` 不能被转换为整数，`anInt` 的值会变成 `0` 而 `_` 无视了错误，程序会继续运行。

这样做是非常不好的：程序应该在最接近的位置检查所有相关的错误，至少需要暗示用户有错误发生并对函数进行返回，甚至中断程序。

我们在第二个版本中对代码进行了改进：


示例 1：

示例 5.3 [string_conversion2.go](examples/chapter_5/string_conversion2.go)

```go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var orig string = "ABC"
	// var an int
	var newS string
	// var err error

	fmt.Printf("The size of ints is: %d\n", strconv.IntSize)	  
	// anInt, err = strconv.Atoi(origStr)
	an, err := strconv.Atoi(orig)
	if err != nil {
		fmt.Printf("orig %s is not an integer - exiting with error\n", orig)
		return
	} 
	fmt.Printf("The integer is %d\n", an)
	an = an + 5
	newS = strconv.Itoa(an)
	fmt.Printf("The new string is: %s\n", newS)
}
```

这是测试 `err` 变量是否包含一个真正的错误（`if err != nil`）的习惯用法。如果确实存在错误，则会打印相应的错误信息然后通过 `return` 提前结束函数的执行。我们还可以使用携带返回值的 `return` 形式，例如 `return err`。这样一来，函数的调用者就可以检查函数执行过程中是否存在错误了。

**习惯用法**

```go
value, err := pack1.Function1(param1)
if err != nil {
	fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
	return err
}
// 未发生错误，继续执行：
```

由于本例的函数调用者属于 `main` 函数，所以程序会直接停止运行。

如果我们想要在错误发生的同时终止程序的运行，我们可以使用 `os` 包的 `Exit` 函数：

**习惯用法**

```go
if err != nil {
	fmt.Printf("Program stopping with error %v", err)
	os.Exit(1)
}
```

（此处的退出代码 `1` 可以使用外部脚本获取到）

有时候，你会发现这种习惯用法被连续重复地使用在某段代码中。

当没有错误发生时，代码继续运行就是唯一要做的事情，所以 `if` 语句块后面不需要使用 `else` 分支。

示例 2：我们尝试通过 `os.Open` 方法打开一个名为 `name` 的只读文件：

```go
f, err := os.Open(name)
if err != nil {
	return err
}
doSomething(f) // 当没有错误发生时，文件对象被传入到某个函数中
doSomething
```

**练习 5.1** 尝试改写 [string_conversion2.go](examples/chapter_5/string_conversion2.go) 中的代码，要求使用 `:=` 方法来对 `err` 进行赋值，哪些地方可以被修改？

示例 3：可以将错误的获取放置在 `if` 语句的初始化部分：

**习惯用法**

```go
if err := file.Chmod(0664); err != nil {
	fmt.Println(err)
	return err
}
```

示例 4：或者将 ok-pattern 的获取放置在 `if` 语句的初始化部分，然后进行判断：

**习惯用法**

```go
if value, ok := readData(); ok {
…
}
```

**注意事项**

如果您像下面一样，没有为多返回值的函数准备足够的变量来存放结果：
```go
func mySqrt(f float64) (v float64, ok bool) {
	if f < 0 { return } // error case
	return math.Sqrt(f),true
}

func main() {
	t := mySqrt(25.0)
	fmt.Println(t)
}
```

您会得到一个编译错误：`multiple-value mySqrt() in single-value context`。

正确的做法是：

```go
t, ok := mySqrt(25.0)
if ok { fmt.Println(t) }
```

**注意事项 2**

当您将字符串转换为整数时，且确定转换一定能够成功时，可以将 `Atoi()` 函数进行一层忽略错误的封装：

```go
func atoi (s string) (n int) {
	n, _ = strconv.Atoi(s)
	return
}
```

实际上，`fmt` 包（[第 4.4.3 节](04.4.md)）最简单的打印函数也有 2 个返回值：

```go
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
```

当打印到控制台时，可以将该函数返回的错误忽略；但当输出到文件流、网络流等具有不确定因素的输出对象时，应该始终检查是否有错误发生（另见[练习 6.1b](06.1.md)）。

## 链接

- [目录](directory.md)
- 上一节：[if-else 结构](05.1.md)
- 下一节：[switch 结构](05.3.md)
