mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-11 19:41:43 +08:00
@@ -1,24 +1,24 @@
|
||||
# 13.0 错误处理与测试
|
||||
|
||||
Go 没有像 Java 和 .NET 那样的 `try/catch` 异常机制:不能执行抛异常操作。但是有一套 `defer-panic-and-recover` 机制(参见 13.2-13.3 节)。
|
||||
Go 没有像 Java 和 .NET 那样的 `try/catch` 异常机制:不能执行抛异常操作。但是有一套 `defer-panic-and-recover` 机制(参见 [13.2](13.2.md)-[13.3](13.3.md) 节)。
|
||||
|
||||
Go 的设计者觉得 `try/catch` 机制的使用太泛滥了,而且从底层向更高的层级抛异常太耗费资源。他们给 Go 设计的机制也可以 “捕捉” 异常,但是更轻量,并且只应该作为(处理错误的)最后的手段。
|
||||
Go 的设计者觉得 `try/catch` 机制的使用太泛滥了,而且从底层向更高的层级抛异常太耗费资源。他们给 Go 设计的机制也可以“捕捉”异常,但是更轻量,并且只应该作为(处理错误的)最后的手段。
|
||||
|
||||
Go 是怎么处理普通错误的呢?通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil,则没有错误发生——并且主调(calling)函数总是应该检查收到的错误。
|
||||
Go 是怎么处理普通错误的呢?通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 `nil`,则没有错误发生——并且主调 (calling) 函数总是应该检查收到的错误。
|
||||
|
||||
**永远不要忽略错误,否则可能会导致程序崩溃!!**
|
||||
|
||||
处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。`panic and recover` 是用来处理真正的异常(无法预测的错误)而不是普通的错误。
|
||||
处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。`panic()` 和 `recover()` 是用来处理真正的异常(无法预测的错误)而不是普通的错误。
|
||||
|
||||
库函数通常必须返回某种错误提示给主调(calling)函数。
|
||||
库函数通常必须返回某种错误提示给主调函数。
|
||||
|
||||
在前面的章节中我们了解了 Go 检查和报告错误条件的惯有方式:
|
||||
|
||||
- 产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 nil 就是成功,非 nil 就是发生了错误。
|
||||
- 产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 `nil` 就是成功,非 `nil` 就是发生了错误。
|
||||
|
||||
- 为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。
|
||||
|
||||
下面这段来自 pack1 包的代码 Func1 测试了它的返回值:
|
||||
下面这段来自 `pack1` 包的代码 `Func1()` 测试了它的返回值:
|
||||
|
||||
```go
|
||||
if value, err := pack1.Func1(param1); err != nil {
|
||||
@@ -31,7 +31,7 @@ if value, err := pack1.Func1(param1); err != nil {
|
||||
|
||||
*为了更清晰的代码,应该总是使用包含错误值变量的 if 复合语句*
|
||||
|
||||
上例除了 `fmt.Printf` 还可以使用 log 中对应的方法(参见 13.3 节 和 15.2 节),如果程序中止也没关系的话甚至可以使用 `panic`(参见后面的章节)。
|
||||
上例除了 `fmt.Printf()` 还可以使用 `log` 中对应的方法(参见 [13.3](13.3.md) 节和 [15.2](15.2.md) 节),如果程序中止也没关系的话甚至可以使用 `panic()`(参见后面的章节)。
|
||||
|
||||
## 链接
|
||||
|
||||
|
@@ -8,11 +8,11 @@ type error interface {
|
||||
}
|
||||
```
|
||||
|
||||
错误值用来表示异常状态;我们可以在 [5.2 节](05.2.md) 中看到它的标准用法。处理文件操作的例子可以在 12 章找到;我们将在 15 章看到网络操作的例子。errors 包中有一个 errorString 结构体实现了 error 接口。当程序处于错误状态时可以用 `os.Exit(1)` 来中止运行。
|
||||
错误值用来表示异常状态;我们可以在 [5.2 节](05.2.md)中看到它的标准用法。处理文件操作的例子可以在 [12 章](12.0.md)找到;我们将在 [15 章](15.0.md)看到网络操作的例子。`errors` 包中有一个 `errorString` 结构体实现了 `error` 接口。当程序处于错误状态时可以用 `os.Exit(1)` 来中止运行。
|
||||
|
||||
## 13.1.1 定义错误
|
||||
|
||||
任何时候当你需要一个新的错误类型,都可以用 `errors` 包(必须先 import)的 `errors.New` 函数接收合适的错误信息来创建,像下面这样:
|
||||
任何时候当你需要一个新的错误类型,都可以用 `errors` 包(必须先 `import`)的 `errors.New()` 函数接收合适的错误信息来创建,像下面这样:
|
||||
|
||||
```go
|
||||
err := errors.New("math - square root of negative number")
|
||||
@@ -50,7 +50,7 @@ func Sqrt(f float64) (float64, error) {
|
||||
}
|
||||
```
|
||||
|
||||
你可以像下面这样调用 Sqrt 函数:
|
||||
你可以像下面这样调用 `Sqrt()` 函数:
|
||||
|
||||
```go
|
||||
if f, err := Sqrt(-1); err != nil {
|
||||
@@ -58,9 +58,9 @@ if f, err := Sqrt(-1); err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
由于 `fmt.Printf` 会自动调用 `String()` 方法 (参见 [10.7 节](10.7.md)),所以错误信息 “Error: math - square root of negative number” 会打印出来。通常(错误信息)都会有像 “Error:” 这样的前缀,所以你的错误信息不要以大写字母开头。
|
||||
由于 `fmt.Printf` 会自动调用 `String()` 方法 (参见 [10.7 节](10.7.md)),所以错误信息 “`Error: math - square root of negative number`” 会打印出来。通常(错误信息)都会有像 `Error:...` 这样的前缀,所以你的错误信息不要以大写字母开头(注:英文只有句首单词首字母大写,这里应当是考虑到这一点)。
|
||||
|
||||
在大部分情况下自定义错误结构类型很有意义的,可以包含除了(低层级的)错误信息以外的其它有用信息,例如,正在进行的操作(打开文件等),全路径或名字。看下面例子中 os.Open 操作触发的 PathError 错误:
|
||||
在大部分情况下自定义错误结构类型很有意义的,可以包含除了(低层级的)错误信息以外的其它有用信息,例如,正在进行的操作(打开文件等),全路径或名字。看下面例子中 `os.Open()` 操作触发的 `PathError` 错误:
|
||||
|
||||
```go
|
||||
// PathError records an error and the operation and file path that caused it.
|
||||
@@ -98,7 +98,7 @@ switch err := err.(type) {
|
||||
}
|
||||
```
|
||||
|
||||
作为第二个例子考虑用 json 包的情况。当 json.Decode 在解析 JSON 文档发生语法错误时,指定返回一个 SyntaxError 类型的错误:
|
||||
作为第二个例子考虑用 `json` 包的情况。当 `json.Decode()` 在解析 JSON 文档发生语法错误时,指定返回一个 `SyntaxError` 类型的错误:
|
||||
|
||||
```go
|
||||
type SyntaxError struct {
|
||||
@@ -119,7 +119,7 @@ if serr, ok := err.(*json.SyntaxError); ok {
|
||||
}
|
||||
```
|
||||
|
||||
包也可以用额外的方法(methods)定义特定的错误,比如 net.Error:
|
||||
包也可以用额外的方法 (methods)定义特定的错误,比如 `net.Error`:
|
||||
|
||||
```go
|
||||
package net
|
||||
@@ -131,11 +131,11 @@ type Error interface {
|
||||
|
||||
在 [15.1 节](15.1.md) 我们可以看到怎么使用它。
|
||||
|
||||
正如你所看到的一样,所有的例子都遵循同一种命名规范:错误类型以 “Error” 结尾,错误变量以 “err” 或 “Err” 开头。
|
||||
正如你所看到的一样,所有的例子都遵循同一种命名规范:错误类型以 `...Error` 结尾,错误变量以 `err...` 或 `Err...` 开头或者直接叫 `err` 或 `Err`。
|
||||
|
||||
syscall 是低阶外部包,用来提供系统基本调用的原始接口。它们返回封装整数类型错误码的 syscall.Errno;类型 syscall.Errno 实现了 Error 接口。
|
||||
`syscall` 是低阶外部包,用来提供系统基本调用的原始接口。它们返回封装整数类型错误码的 `syscall.Errno`;类型 `syscall.Errno` 实现了 `Error` 接口。
|
||||
|
||||
大部分 syscall 函数都返回一个结果和可能的错误,比如:
|
||||
大部分 `syscall` 函数都返回一个结果和可能的错误,比如:
|
||||
|
||||
```go
|
||||
r, err := syscall.Open(name, mode, perm)
|
||||
@@ -144,7 +144,7 @@ if err != nil {
|
||||
}
|
||||
```
|
||||
|
||||
os 包也提供了一套像 os.EINAL 这样的标准错误,它们基于 syscall 错误:
|
||||
`os` 包也提供了一套像 `os.EINAL` 这样的标准错误,它们基于 `syscall` 错误:
|
||||
|
||||
```go
|
||||
var (
|
||||
@@ -159,7 +159,7 @@ var (
|
||||
|
||||
## 13.1.2 用 fmt 创建错误对象
|
||||
|
||||
通常你想要返回包含错误参数的更有信息量的字符串,例如:可以用 `fmt.Errorf()` 来实现:它和 fmt.Printf() 完全一样,接收一个或多个格式占位符的格式化字符串和相应数量的占位变量。和打印信息不同的是它用信息生成错误对象。
|
||||
通常你想要返回包含错误参数的更有信息量的字符串,例如:可以用 `fmt.Errorf()` 来实现:它和 `fmt.Printf()` 完全一样,接收一个或多个格式占位符的格式化字符串和相应数量的占位变量。和打印信息不同的是它用信息生成错误对象。
|
||||
|
||||
比如在前面的平方根例子中使用:
|
||||
|
||||
@@ -169,7 +169,7 @@ if f < 0 {
|
||||
}
|
||||
```
|
||||
|
||||
第二个例子:从命令行读取输入时,如果加了 help 标志,我们可以用有用的信息产生一个错误:
|
||||
第二个例子:从命令行读取输入时,如果加了 `--help` 或 `-h` 标志,我们可以用有用的信息产生一个错误:
|
||||
|
||||
```go
|
||||
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
|
||||
|
@@ -13,7 +13,7 @@
|
||||
|
||||
## 13.10.2 用 go test 调试
|
||||
|
||||
如果代码使用了 Go 中 testing 包的基准测试功能,我们可以用 gotest 标准的 `-cpuprofile` 和 `-memprofile` 标志向指定文件写入 CPU 或 内存使用情况报告。
|
||||
如果代码使用了 Go 中 `testing` 包的基准测试功能,我们可以用 gotest 标准的 `-cpuprofile` 和 `-memprofile` 标志向指定文件写入 CPU 或 内存使用情况报告。
|
||||
|
||||
使用方式:```go test -x -v -cpuprofile=prof.out -file x_test.go```
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
## 13.10.3 用 pprof 调试
|
||||
|
||||
你可以在单机程序 progexec 中引入 runtime/pprof 包;这个包以 pprof 可视化工具需要的格式写入运行时报告数据。对于 CPU 性能分析来说你需要添加一些代码:
|
||||
你可以在单机程序 progexec 中引入 `runtime/pprof` 包;这个包以 pprof 可视化工具需要的格式写入运行时报告数据。对于 CPU 性能分析来说你需要添加一些代码:
|
||||
|
||||
```go
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
@@ -39,7 +39,7 @@ func main() {
|
||||
...
|
||||
```
|
||||
|
||||
代码定义了一个名为 cpuprofile 的 flag,调用 Go flag 库来解析命令行 flag,如果命令行设置了 cpuprofile flag,则开始 CPU 性能分析并把结果重定向到那个文件(os.Create 用拿到的名字创建了用来写入分析数据的文件)。这个分析程序最后需要在程序退出之前调用 StopCPUProfile 来刷新挂起的写操作到文件中;我们用 defer 来保证这一切会在 main 返回时触发。
|
||||
代码定义了一个名为 cpuprofile 的 flag,调用 Go flag 库来解析命令行 flag,如果命令行设置了 cpuprofile flag,则开始 CPU 性能分析并把结果重定向到那个文件(`os.Create` 用拿到的名字创建了用来写入分析数据的文件)。这个分析程序最后需要在程序退出之前调用 `StopCPUProfile()` 来刷新挂起的写操作到文件中;我们用 `defer` 来保证这一切会在 `main()` 返回时触发。
|
||||
|
||||
现在用这个 flag 运行程序:```progexec -cpuprofile=progexec.prof```
|
||||
|
||||
@@ -93,7 +93,7 @@ if *memprofile != "" {
|
||||
}
|
||||
```
|
||||
|
||||
用 -memprofile flag 运行这个程序:```progexec -memprofile=progexec.mprof```
|
||||
用 `-memprofile flag` 运行这个程序:```progexec -memprofile=progexec.mprof```
|
||||
|
||||
然后你可以像这样再次使用 gopprof 工具:```gopprof progexec progexec.mprof```
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发*运行时 panic*,伴随着程序的崩溃抛出一个 `runtime.Error` 接口类型的值。这个错误值有个 `RuntimeError()` 方法用于区别普通错误。
|
||||
|
||||
`panic` 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 `panic` 函数产生一个中止程序的运行时错误。`panic` 接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。在示例 13.2 [panic.go](examples/chapter_13/panic.go) 中阐明了它的工作方式:
|
||||
`panic()` 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 `panic()` 函数产生一个中止程序的运行时错误。`panic()` 接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。在示例 13.2 [panic.go](examples/chapter_13/panic.go) 中阐明了它的工作方式:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -46,9 +46,9 @@ func check() {
|
||||
}
|
||||
```
|
||||
|
||||
可以在导入包的 init() 函数中检查这些。
|
||||
可以在导入包的 `init()` 函数中检查这些。
|
||||
|
||||
当发生错误必须中止程序时,`panic` 可以用于错误处理模式:
|
||||
当发生错误必须中止程序时,`panic()` 可以用于错误处理模式:
|
||||
|
||||
```go
|
||||
if err != nil {
|
||||
@@ -58,14 +58,14 @@ if err != nil {
|
||||
|
||||
<u>Go panicking</u>:
|
||||
|
||||
在多层嵌套的函数调用中调用 panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序崩溃,并在命令行中用传给 panic 的值报告错误情况:这个终止过程就是 *panicking*。
|
||||
在多层嵌套的函数调用中调用 `panic()`,可以马上中止当前函数的执行,所有的 `defer` 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) `defer`,在栈顶处程序崩溃,并在命令行中用传给 `panic()` 的值报告错误情况:这个终止过程就是 *panicking*。
|
||||
|
||||
标准库中有许多包含 `Must` 前缀的函数,像 `regexp.MustComplie` 和 `template.Must`;当正则表达式或模板中转入的转换字符串导致错误时,这些函数会 panic。
|
||||
标准库中有许多包含 `Must` 前缀的函数,像 `regexp.MustComplie()` 和 `template.Must()`;当正则表达式或模板中转入的转换字符串导致错误时,这些函数会 `panic()`。
|
||||
|
||||
不能随意地用 panic 中止程序,必须尽力补救错误让程序能继续执行。
|
||||
不能随意地用 `panic()` 中止程序,必须尽力补救错误让程序能继续执行。
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[错误处理](13.1.md)
|
||||
- 下一节:[从 panic 中恢复(Recover)](13.3.md)
|
||||
- 下一节:[从 panic 中恢复 (recover)](13.3.md)
|
||||
|
@@ -1,12 +1,12 @@
|
||||
# 13.3 从 panic 中恢复(Recover)
|
||||
# 13.3 从 panic 中恢复 (recover)
|
||||
|
||||
正如名字一样,这个(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。
|
||||
正如名字一样,这个 (`recover()`) 内建函数被用于从 panic 或错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。
|
||||
|
||||
`recover` 只能在 defer 修饰的函数(参见 [6.4 节](06.4.md))中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 `recover` 会返回 nil,且没有其它效果。
|
||||
`recover` 只能在 `defer` 修饰的函数(参见 [6.4 节](06.4.md))中使用:用于取得 `panic()` 调用中传递过来的错误值,如果是正常执行,调用 `recover()` 会返回 `nil`,且没有其它效果。
|
||||
|
||||
<u>总结</u>:panic 会导致栈被展开直到 defer 修饰的 recover() 被调用或者程序中止。
|
||||
<u>总结</u>:`panic()` 会导致栈被展开直到 `defer` 修饰的 `recover()` 被调用或者程序中止。
|
||||
|
||||
下面例子中的 protect 函数调用函数参数 g 来保护调用者防止从 g 中抛出的运行时 panic,并展示 panic 中的信息:
|
||||
下面例子中的 `protect()` 函数调用函数参数 `g` 来保护调用者防止从 `g` 中抛出的运行时 panic,并展示 panic 中的信息:
|
||||
|
||||
```go
|
||||
func protect(g func()) {
|
||||
@@ -24,11 +24,11 @@ func protect(g func()) {
|
||||
|
||||
这跟 Java 和 .NET 这样的语言中的 catch 块类似。
|
||||
|
||||
log 包实现了简单的日志功能:默认的 log 对象向标准错误输出中写入并打印每条日志信息的日期和时间。除了 `Println` 和 `Printf` 函数,其它的致命性函数都会在写完日志信息后调用 os.Exit(1),那些退出函数也是如此。而 Panic 效果的函数会在写完日志信息后调用 panic;可以在程序必须中止或发生了临界错误时使用它们,就像当 web 服务器不能启动时那样(参见 [15.4 节](15.4.md) 中的例子)。
|
||||
`log` 包实现了简单的日志功能:默认的 log 对象向标准错误输出中写入并打印每条日志信息的日期和时间。除了 `Println` 和 `Printf` 函数,其它的致命性函数都会在写完日志信息后调用 `os.Exit(1)`,那些退出函数也是如此。而 Panic 效果的函数会在写完日志信息后调用 `panic()`;可以在程序必须中止或发生了临界错误时使用它们,就像当 web 服务器不能启动时那样(参见 [15.4 节](15.4.md) 中的例子)。
|
||||
|
||||
log 包用那些方法(methods)定义了一个 Logger 接口类型,如果你想自定义日志系统的话可以参考 [http://golang.org/pkg/log/#Logger](http://golang.org/pkg/log/#Logger) 。
|
||||
log 包用那些方法 (methods) 定义了一个 `Logger` 接口类型,如果你想自定义日志系统的话可以参考 [http://golang.org/pkg/log/#Logger](http://golang.org/pkg/log/#Logger) 。
|
||||
|
||||
这是一个展示 panic,defer 和 recover 怎么结合使用的完整例子:
|
||||
这是一个展示 `panic()`,`defer` 和 `recover()` 怎么结合使用的完整例子:
|
||||
|
||||
示例 13.3 [panic_recover.go](examples/chapter_13/panic_recover.go):
|
||||
|
||||
@@ -69,9 +69,9 @@ Panicing bad end
|
||||
Test completed
|
||||
```
|
||||
|
||||
`defer-panic-recover` 在某种意义上也是一种像 `if`,`for` 这样的控制流机制。
|
||||
`defer`-`panic()`-`recover()` 在某种意义上也是一种像 `if`,`for` 这样的控制流机制。
|
||||
|
||||
Go 标准库中许多地方都用了这个机制,例如,json 包中的解码和 regexp 包中的 Complie 函数。Go 库的原则是即使在包的内部使用了 panic,在它的对外接口(API)中也必须用 recover 处理成显式返回的错误。
|
||||
Go 标准库中许多地方都用了这个机制,例如,`json` 包中的解码和 `regexp` 包中的 `Complie()` 函数。Go 库的原则是即使在包的内部使用了 `panic()`,在它的对外接口 (API) 中也必须用 `recover()` 处理成显式返回的错误。
|
||||
|
||||
## 链接
|
||||
|
||||
|
@@ -2,15 +2,15 @@
|
||||
|
||||
这是所有自定义包实现者应该遵守的最佳实践:
|
||||
|
||||
1)*在包内部,总是应该从 panic 中 recover*:不允许显式的超出包范围的 panic()
|
||||
1)*在包内部,总是应该从 panic 中 recover*:不允许显式的超出包范围的 `panic()`
|
||||
|
||||
2)*向包的调用者返回错误值(而不是 panic)。*
|
||||
|
||||
在包内部,特别是在非导出函数中有很深层次的嵌套调用时,将 panic 转换成 error 来告诉调用方为何出错,是很实用的(且提高了代码可读性)。
|
||||
在包内部,特别是在非导出函数中有很深层次的嵌套调用时,将 panic 转换成 `error` 来告诉调用方为何出错,是很实用的(且提高了代码可读性)。
|
||||
|
||||
下面的代码则很好地阐述了这一点。我们有一个简单的 parse 包(示例 13.4)用来把输入的字符串解析为整数切片;这个包有自己特殊的 `ParseError`。
|
||||
下面的代码则很好地阐述了这一点。我们有一个简单的 `parse` 包(示例 13.4)用来把输入的字符串解析为整数切片;这个包有自己特殊的 `ParseError`。
|
||||
|
||||
当没有东西需要转换或者转换成整数失败时,这个包会 panic(在函数 fields2numbers 中)。但是可导出的 Parse 函数会从 panic 中 recover 并用所有这些信息返回一个错误给调用者。为了演示这个过程,在 [panic_recover.go](examples/chapter_13/panic_recover.go) 中 调用了 parse 包(示例 13.5);不可解析的字符串会导致错误并被打印出来。
|
||||
当没有东西需要转换或者转换成整数失败时,这个包会 `panic()`(在函数 `fields2numbers()` 中)。但是可导出的 `Parse()` 函数会从 `panic()` 中 `recover()` 并用所有这些信息返回一个错误给调用者。为了演示这个过程,在 [panic_recover.go](examples/chapter_13/panic_recover.go) 中 调用了 `parse` 包(示例 13.5);不可解析的字符串会导致错误并被打印出来。
|
||||
|
||||
示例 13.4 [parse.go](examples/chapter_13/parse/parse.go):
|
||||
|
||||
@@ -118,5 +118,5 @@ Parsing "":
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[从 panic 中恢复(Recover)](13.3.md)
|
||||
- 上一节:[从 panic 中恢复 (recover)](13.3.md)
|
||||
- 下一节:[一种用闭包处理错误的模式](13.5.md)
|
||||
|
@@ -22,13 +22,13 @@ fType1 = func f(a type1, b type2)
|
||||
|
||||
在我们的模式中使用了两个帮助函数:
|
||||
|
||||
1)check:这是用来检查是否有错误和 panic 发生的函数:
|
||||
1)`check()`:这是用来检查是否有错误和 panic 发生的函数:
|
||||
|
||||
```go
|
||||
func check(err error) { if err != nil { panic(err) } }
|
||||
```
|
||||
|
||||
2)errorhandler:这是一个包装函数。接收一个 fType1 类型的函数 fn 并返回一个调用 fn 的函数。里面就包含有 defer/recover 机制,这在 [13.3 节](13.3.md) 中有相应描述。
|
||||
2)`errorhandler()`:这是一个包装函数。接收一个 `fType1` 类型的函数 `fn` 并返回一个调用 `fn` 的函数。里面就包含有 defer/recover 机制,这在 [13.3 节](13.3.md)中有相应描述。
|
||||
|
||||
```go
|
||||
func errorHandler(fn fType1) fType1 {
|
||||
@@ -43,7 +43,7 @@ func errorHandler(fn fType1) fType1 {
|
||||
}
|
||||
```
|
||||
|
||||
当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 template 包(参见 [15.7 节](15.7.md))为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:
|
||||
当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 `template` 包(参见 [15.7 节](15.7.md))为用户生成自定义的输出。`check()` 函数会在所有的被调函数中调用,像这样:
|
||||
|
||||
```go
|
||||
func f1(a type1, b type2) {
|
||||
@@ -58,7 +58,7 @@ func f1(a type1, b type2) {
|
||||
}
|
||||
```
|
||||
|
||||
通过这种机制,所有的错误都会被 recover,并且调用函数后的错误检查代码也被简化为调用 check(err) 即可。在这种模式下,不同的错误处理必须对应不同的函数类型;它们(错误处理)可能被隐藏在错误处理包内部。可选的更加通用的方式是用一个空接口类型的切片作为参数和返回值。
|
||||
通过这种机制,所有的错误都会被 recover,并且调用函数后的错误检查代码也被简化为调用 `check(err)` 即可。在这种模式下,不同的错误处理必须对应不同的函数类型;它们(错误处理)可能被隐藏在错误处理包内部。可选的更加通用的方式是用一个空接口类型的切片作为参数和返回值。
|
||||
|
||||
我们会在 [15.5 节](15.5.md) 的 web 应用中使用这种模式。
|
||||
|
||||
@@ -122,7 +122,7 @@ Returned normally from f.
|
||||
|
||||
**练习 13.3**:[panic_defer_convint.go](exercises/chapter_13/panic_defer_convint.go)
|
||||
|
||||
写一个 ConvertInt64ToInt 函数把 int64 值转换为 int 值,如果发生错误(提示:参见 [4.5.2.1 节](04.5.md#4521-整型-int-和浮点型-float))就 panic 。然后在函数 IntFromInt64 中调用这个函数并 recover,返回一个整数和一个错误。请测试这个函数!
|
||||
写一个 `ConvertInt64ToInt()` 函数把 `int64` 值转换为 `int` 值,如果发生错误(提示:参见 [4.5.2.1 节](04.5.md#4521-整型-int-和浮点型-float))就 `panic()` 。然后在函数 `IntFromInt64` 中调用这个函数并 `recover()`,返回一个整数和一个错误。请测试这个函数!
|
||||
|
||||
## 链接
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
# 13.6 启动外部命令和程序
|
||||
|
||||
os 包有一个 `StartProcess` 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。
|
||||
`os` 包有一个 `StartProcess` 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。
|
||||
|
||||
这个函数返回被启动进程的 id(pid),或者启动失败返回错误。
|
||||
这个函数返回被启动进程的 id (pid),或者启动失败返回错误。
|
||||
|
||||
exec 包中也有同样功能的更简单的结构体和函数;主要是 `exec.Command(name string, arg ...string)` 和 `Run()`。首先需要用系统命令或可执行文件的名字创建一个 `Command` 对象,然后用这个对象作为接收者调用 `Run()`。下面的程序(因为是执行 Linux 命令,只能在 Linux 下面运行)演示了它们的使用:
|
||||
`exec` 包中也有同样功能的更简单的结构体和函数;主要是 `exec.Command(name string, arg ...string)` 和 `Run()`。首先需要用系统命令或可执行文件的名字创建一个 `Command` 对象,然后用这个对象作为接收者调用 `Run()`。下面的程序(因为是执行 Linux 命令,只能在 Linux 下面运行)演示了它们的使用:
|
||||
|
||||
示例 13.6 [exec.go](examples/chapter_13/exec.go):
|
||||
|
||||
|
@@ -12,7 +12,7 @@
|
||||
|
||||
`_test` 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
|
||||
|
||||
测试文件中必须导入 "testing" 包,并写一些名字以 `TestZzz` 打头的全局函数,这里的 `Zzz` 是被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。
|
||||
测试文件中必须导入 `"testing"` 包,并写一些名字以 `TestZzz` 打头的全局函数,这里的 `Zzz` 是被测试函数的字母描述,如 `TestFmtInterface()`,`TestPayEmployees()` 等。
|
||||
|
||||
测试函数必须有这种形式的头部:
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
func TestAbcde(t *testing.T)
|
||||
```
|
||||
|
||||
T 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。在函数的结尾把输出跟想要的结果对比,如果不等就打印一个错误,成功的测试则直接返回。
|
||||
`T` 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 `t.Log`,`t.Error`,`t.ErrorF` 等。在函数的结尾把输出跟想要的结果对比,如果不等就打印一个错误,成功的测试则直接返回。
|
||||
|
||||
用下面这些函数来通知测试失败:
|
||||
|
||||
@@ -40,11 +40,11 @@ T 是传给测试函数的结构类型,用来管理测试状态,支持格式
|
||||
|
||||
结合 先执行 3),然后执行 2)的效果。
|
||||
|
||||
运行 go test 来编译测试程序,并执行程序中所有的 TestZZZ 函数。如果所有的测试都通过会打印出 PASS。
|
||||
运行 go test 来编译测试程序,并执行程序中所有的 `TestZZZ` 函数。如果所有的测试都通过会打印出 `PASS`。
|
||||
|
||||
gotest 可以接收一个或多个函数程序作为参数,并指定一些选项。
|
||||
|
||||
结合 --chatty 或 -v 选项,每个执行的测试函数以及测试状态会被打印。
|
||||
结合 `--chatty` 或 `-v` 选项,每个执行的测试函数以及测试状态会被打印。
|
||||
|
||||
例如:
|
||||
|
||||
@@ -57,7 +57,7 @@ go test fmt_test.go --chatty
|
||||
...
|
||||
```
|
||||
|
||||
testing 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 `BenchmarkZzz` 打头的函数并接收一个 `*testing.B` 类型的参数,比如:
|
||||
`testing` 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 `BenchmarkZzz` 打头的函数并接收一个 `*testing.B` 类型的参数,比如:
|
||||
|
||||
```go
|
||||
func BenchmarkReverse(b *testing.B) {
|
||||
@@ -65,7 +65,7 @@ func BenchmarkReverse(b *testing.B) {
|
||||
}
|
||||
```
|
||||
|
||||
命令 ```go test –test.bench=.*``` 会运行所有的基准测试函数;代码中的函数会被调用 N 次(N 是非常大的数,如 N = 1000000),并展示 N 的值和函数执行的平均时间,单位为 ns(纳秒,ns/op)。如果是用 testing.Benchmark 调用这些函数,直接运行程序即可。
|
||||
命令 ```go test –test.bench=.*``` 会运行所有的基准测试函数;代码中的函数会被调用 `N` 次(`N` 是非常大的数,如 `N = 1000000`),并展示 `N` 的值和函数执行的平均时间,单位为 ns(纳秒,ns/op)。如果是用 `testing.Benchmark()` 调用这些函数,直接运行程序即可。
|
||||
|
||||
具体可以参见 [14.16 节](14.16.md) 中用 goroutines 运行基准测试的例子以及练习 13.4:[string_reverse_test.go](exercises/chapter_13/string_reverse_test.go)
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# 13.8 测试的具体例子
|
||||
|
||||
在练习 9.4 中你写了一个叫 [main_oddeven.go](exercises/chapter_9/main_oddeven.go) 的程序用来测试前 100 个整数是否是偶数。这个函数属于 even 包。
|
||||
在练习 9.4 中你写了一个叫 [main_oddeven.go](exercises/chapter_9/main_oddeven.go) 的程序用来测试前 100 个整数是否是偶数。这个函数属于 `even` 包。
|
||||
|
||||
下面是一种可能的方案:
|
||||
|
||||
@@ -21,7 +21,7 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
上面使用了 even.go 中的 even 包:
|
||||
上面使用了 even.go 中的 `even` 包:
|
||||
|
||||
示例 13.8 [even/even.go](examples/chapter_13/even/even/even.go):
|
||||
|
||||
@@ -37,7 +37,7 @@ func Odd(i int) bool { // Exported function
|
||||
}
|
||||
```
|
||||
|
||||
在 even 包的路径下,我们创建一个名为 oddeven_test.go 的测试程序:
|
||||
在 `even` 包的路径下,我们创建一个名为 oddeven_test.go 的测试程序:
|
||||
|
||||
示例 13.9 [even/oddeven_test.go](examples/chapter_13/even/even/oddeven_test.go):
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestOdd(t *testing.T) {
|
||||
- 反面的用例(错误的输入,如用负数或字母代替数字,没有输入等)
|
||||
- 边界检查用例(如果参数的取值范围是 0 到 1000,检查 0 和 1000 的情况)
|
||||
|
||||
可以直接执行 go install 安装 even 或者创建一个 以下内容的 Makefile:
|
||||
可以直接执行 go install 安装 `even` 或者创建一个 以下内容的 Makefile:
|
||||
|
||||
```bash
|
||||
include $(GOROOT)/src/Make.inc
|
||||
@@ -94,9 +94,9 @@ include $(GOROOT)/src/Make.pkg
|
||||
|
||||
现在我们可以用命令:`go test`(或 `make test`)来测试 even 包。
|
||||
|
||||
因为示例 13.5 中的测试函数不会调用 t.Log 和 t.Fail,所以会得到一个 PASS 的结果。在这个简单例子中一切都正常执行。
|
||||
因为示例 13.5 中的测试函数不会调用 t.Log 和 t.Fail,所以会得到一个 `PASS` 的结果。在这个简单例子中一切都正常执行。
|
||||
|
||||
为了看到失败时的输出,把函数 TestEven 改为:
|
||||
为了看到失败时的输出,把函数 `TestEven()` 改为:
|
||||
|
||||
```go
|
||||
func TestEven(t *testing.T) {
|
||||
@@ -119,7 +119,7 @@ FAIL
|
||||
|
||||
为练习 7.14 [string_reverse.go](exercises/chapter_7/string_reverse.go) 写一个单元测试。
|
||||
|
||||
把 string_reverse 放到自己的包 strev 中,只包含一个可导出函数 Reverse。
|
||||
把 string_reverse 放到自己的包 `strev` 中,只包含一个可导出函数 `Reverse()`。
|
||||
|
||||
实现并测试它。
|
||||
|
||||
|
@@ -28,7 +28,7 @@ func TestFunction(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
如果大部分函数都可以写成这种形式,那么写一个帮助函数 verify 对实际测试会很有帮助:
|
||||
如果大部分函数都可以写成这种形式,那么写一个帮助函数 `verify()` 对实际测试会很有帮助:
|
||||
|
||||
```go
|
||||
func verify(t *testing.T, testnum int, testcase, input, output, expected string) {
|
||||
@@ -38,7 +38,7 @@ func verify(t *testing.T, testnum int, testcase, input, output, expected string)
|
||||
}
|
||||
```
|
||||
|
||||
TestFunction 则变为:
|
||||
`TestFunction()` 则变为:
|
||||
|
||||
```go
|
||||
func TestFunction(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user