Files
the-way-to-go_ZH_CN/eBook/05.2.md
2016-03-28 09:28:00 +08:00

165 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 5.2 测试多返回值函数的错误
Go 语言的函数经常使用两个返回值来表示执行是否成功:返回某个值以及 true 表示成功;返回零值(或 nil和 false 表示失败(第 4.4 节)。当不使用 true 或 false 的时候,也可以使用一个 error 类型的变量来代替作为第二个返回值成功执行的话error 的值为 nil否则就会包含相应的错误信息Go 语言中的错误类型为 error: `var err error`,我们将会在第 13 章进行更多地讨论)。这样一来,就很明显需要用一个 if 语句来测试执行结果;由于其符号的原因,这样的形式又称之为 comma,ok 模式pattern
在第 4.7 节的程序 `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 节)最简单的打印函数也有 2 个返回值:
```go
count, err := fmt.Println(x) // number of bytes printed, nil or 0, error
```
当打印到控制台时,可以将该函数返回的错误忽略;但当输出到文件流、网络流等具有不确定因素的输出对象时,应该始终检查是否有错误发生(另见练习 6.1b)。
## 链接
- [目录](directory.md)
- 上一节:[if-else 结构](05.1.md)
- 下一节:[switch 结构](05.3.md)