mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 05:11:49 +08:00
13.0-13.10
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
## 翻译进度
|
## 翻译进度
|
||||||
|
|
||||||
12.12 [Go 中的密码学](eBook/12.12.md)
|
13.10 [性能调试:分析并优化 Go 程序](eBook/13.10.md)
|
||||||
|
|
||||||
## 支持本书
|
## 支持本书
|
||||||
|
|
||||||
|
@@ -30,4 +30,4 @@ Golang 编程:245386165
|
|||||||
|
|
||||||
|更新日期 |更新内容
|
|更新日期 |更新内容
|
||||||
|----------|------------------
|
|----------|------------------
|
||||||
|2015-11-18|12.12 Go 中的密码学
|
|2015-11-25|13.10 性能调试:分析并优化 Go 程序
|
||||||
|
11
TOC.md
11
TOC.md
@@ -117,3 +117,14 @@
|
|||||||
- 12.10 [XML 数据格式](12.10.md)
|
- 12.10 [XML 数据格式](12.10.md)
|
||||||
- 12.11 [用 Gob 传输数据](12.11.md)
|
- 12.11 [用 Gob 传输数据](12.11.md)
|
||||||
- 12.12 [Go 中的密码学](12.12.md)
|
- 12.12 [Go 中的密码学](12.12.md)
|
||||||
|
- 第13章:[错误处理与测试](13.0.md)
|
||||||
|
- 13.1 [错误处理](13.1.md)
|
||||||
|
- 13.2 [运行时异常和 panic](13.2.md)
|
||||||
|
- 13.3 [从 panic 中恢复(Recover)](13.3.md)
|
||||||
|
- 13.4 [自定义包中的错误处理和 panicking](13.4.md)
|
||||||
|
- 13.5 [一种用闭包处理错误的模式](13.5.md)
|
||||||
|
- 13.6 [启动外部命令和程序](13.6.md)
|
||||||
|
- 13.7 [Go 中的单元测试和基准测试](13.7.md)
|
||||||
|
- 13.8 [测试的具体例子](13.8.md)
|
||||||
|
- 13.9 [用(测试数据)表驱动测试](13.9.md)
|
||||||
|
- 13.10 [性能调试:分析并优化 Go 程序](13.10.md)
|
||||||
|
@@ -6,9 +6,7 @@ Go 的设计者觉得 `try/catch` 机制的使用太泛滥了,而且从底层
|
|||||||
|
|
||||||
Go 是怎么处理普通错误的呢?通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil,则没有错误发生——并且主调(calling)函数总是应该检查收到的错误。
|
Go 是怎么处理普通错误的呢?通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil,则没有错误发生——并且主调(calling)函数总是应该检查收到的错误。
|
||||||
|
|
||||||
```
|
**永远不要忽略错误,否则可能会导致程序崩溃!!**
|
||||||
永远不要忽略错误,否则可能会导致程序崩溃!!
|
|
||||||
```
|
|
||||||
|
|
||||||
处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。`panic and recover` 是用来处理真正的异常(无法预测的错误)而不是普通的错误。
|
处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。`panic and recover` 是用来处理真正的异常(无法预测的错误)而不是普通的错误。
|
||||||
|
|
||||||
@@ -28,14 +26,12 @@ if value, err := pack1.Func1(param1); err != nil {
|
|||||||
return // or: return err
|
return // or: return err
|
||||||
}
|
}
|
||||||
// Process(value)
|
// Process(value)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
*为了更清晰的代码,应该总是使用包含错误值变量的 if 复合语句*
|
*为了更清晰的代码,应该总是使用包含错误值变量的 if 复合语句*
|
||||||
|
|
||||||
上例除了 `fmt.Printf` 还可以使用 log 中对应的方法(参见 13.3 节 和 15.2 节),如果程序中止也没关系的话甚至可以使用 `panic`(参见后面的章节)。
|
上例除了 `fmt.Printf` 还可以使用 log 中对应的方法(参见 13.3 节 和 15.2 节),如果程序中止也没关系的话甚至可以使用 `panic`(参见后面的章节)。
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -6,7 +6,6 @@ Go 有一个预先定义的 error 接口类型
|
|||||||
type error interface {
|
type error interface {
|
||||||
Error() string
|
Error() string
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
错误值用来表示异常状态;我们可以在 [5.2 节](05.2.md)中看到它的标准用法。处理文件操作的例子可以在 12 章找到;我们将在 15 章看到网络操作的例子。errors 包中有一个 errorString 结构体实现了 error 接口。当程序处于错误状态时可以用 `os.Exit(1)` 来中止运行。
|
错误值用来表示异常状态;我们可以在 [5.2 节](05.2.md)中看到它的标准用法。处理文件操作的例子可以在 12 章找到;我们将在 15 章看到网络操作的例子。errors 包中有一个 errorString 结构体实现了 error 接口。当程序处于错误状态时可以用 `os.Exit(1)` 来中止运行。
|
||||||
@@ -17,7 +16,6 @@ type error interface {
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
err := errors.New(“math - square root of negative number”)
|
err := errors.New(“math - square root of negative number”)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在示例 13.1 中你可以看到一个简单的用例:
|
在示例 13.1 中你可以看到一个简单的用例:
|
||||||
@@ -39,7 +37,6 @@ func main() {
|
|||||||
fmt.Printf("error: %v", errNotFound)
|
fmt.Printf("error: %v", errNotFound)
|
||||||
}
|
}
|
||||||
// error: Not found error
|
// error: Not found error
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
可以把它用于计算平方根函数的参数测试:
|
可以把它用于计算平方根函数的参数测试:
|
||||||
@@ -51,7 +48,6 @@ func Sqrt(f float64) (float64, error) {
|
|||||||
}
|
}
|
||||||
// implementation of Sqrt
|
// implementation of Sqrt
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
你可以像下面这样调用 Sqrt 函数:
|
你可以像下面这样调用 Sqrt 函数:
|
||||||
@@ -60,7 +56,6 @@ func Sqrt(f float64) (float64, error) {
|
|||||||
if f, err := Sqrt(-1); err != nil {
|
if f, err := Sqrt(-1); err != nil {
|
||||||
fmt.Printf(“Error: %s\n”, err)
|
fmt.Printf(“Error: %s\n”, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
由于 `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:” 这样的前缀,所以你的错误信息不要以大写字母开头。
|
||||||
@@ -78,7 +73,6 @@ type PathError struct {
|
|||||||
func (e *PathError) String() string {
|
func (e *PathError) String() string {
|
||||||
return e.Op + “ ” + e.Path + “: “+ e.Err.Error()
|
return e.Op + “ ” + e.Path + “: “+ e.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果有不同错误条件可能发生,那么对实际的错误使用类型断言或类型判断(type-switch)是很有用的,并且可以根据错误场景做一些补救和恢复操作。
|
如果有不同错误条件可能发生,那么对实际的错误使用类型断言或类型判断(type-switch)是很有用的,并且可以根据错误场景做一些补救和恢复操作。
|
||||||
@@ -88,7 +82,6 @@ func (e *PathError) String() string {
|
|||||||
if e, ok := err.(*os.PathError); ok {
|
if e, ok := err.(*os.PathError); ok {
|
||||||
// remedy situation
|
// remedy situation
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
或:
|
或:
|
||||||
@@ -103,7 +96,6 @@ switch err := err.(type) {
|
|||||||
default:
|
default:
|
||||||
fmt.Printf(“Not a special error, just %s\n”, err)
|
fmt.Printf(“Not a special error, just %s\n”, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
作为第二个例子考虑用 json 包的情况。当 json.Decode 在解析 JSON 文档发生语法错误时,指定返回一个 SyntaxError 类型的错误:
|
作为第二个例子考虑用 json 包的情况。当 json.Decode 在解析 JSON 文档发生语法错误时,指定返回一个 SyntaxError 类型的错误:
|
||||||
@@ -116,7 +108,6 @@ type SyntaxError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *SyntaxError) String() string { return e.msg }
|
func (e *SyntaxError) String() string { return e.msg }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在调用代码中你可以像这样用类型断言测试错误是不是上面的类型:
|
在调用代码中你可以像这样用类型断言测试错误是不是上面的类型:
|
||||||
@@ -126,7 +117,6 @@ if serr, ok := err.(*json.SyntaxError); ok {
|
|||||||
line, col := findLine(f, serr.Offset)
|
line, col := findLine(f, serr.Offset)
|
||||||
return fmt.Errorf(“%s:%d:%d: %v”, f.Name(), line, col, err)
|
return fmt.Errorf(“%s:%d:%d: %v”, f.Name(), line, col, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
包也可以用额外的方法(methods)定义特定的错误,比如 net.Errot:
|
包也可以用额外的方法(methods)定义特定的错误,比如 net.Errot:
|
||||||
@@ -137,7 +127,6 @@ type Error interface {
|
|||||||
Timeout() bool // Is the error a timeout?
|
Timeout() bool // Is the error a timeout?
|
||||||
Temporary() bool // Is the error temporary?
|
Temporary() bool // Is the error temporary?
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在 [15.1 节](15.1.md) 我们可以看到怎么使用它。
|
在 [15.1 节](15.1.md) 我们可以看到怎么使用它。
|
||||||
@@ -153,7 +142,6 @@ r, err := syscall.Open(name, mode, perm)
|
|||||||
if err != 0 {
|
if err != 0 {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
os 包也提供了一套像 os.EINAL 这样的标准错误,它们基于 syscall 错误:
|
os 包也提供了一套像 os.EINAL 这样的标准错误,它们基于 syscall 错误:
|
||||||
@@ -167,7 +155,6 @@ var (
|
|||||||
EIO Error = Errno(syscall.EIO)
|
EIO Error = Errno(syscall.EIO)
|
||||||
...
|
...
|
||||||
)
|
)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 13.1.2 用 fmt 创建错误对象
|
## 13.1.2 用 fmt 创建错误对象
|
||||||
@@ -180,7 +167,6 @@ var (
|
|||||||
if f < 0 {
|
if f < 0 {
|
||||||
return 0, fmt.Errorf(“math: square root of negative number %g”, f)
|
return 0, fmt.Errorf(“math: square root of negative number %g”, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
第二个例子:从命令行读取输入时,如果加了 help 标志,我们可以用有用的信息产生一个错误:
|
第二个例子:从命令行读取输入时,如果加了 help 标志,我们可以用有用的信息产生一个错误:
|
||||||
@@ -190,10 +176,8 @@ if len(os.Args) > 1 && (os.Args[1] == “-h” || os.Args[1] == “--help”) {
|
|||||||
err = fmt.Errorf(“usage: %s infile.txt outfile.txt”, filepath.Base(os.Args[0]))
|
err = fmt.Errorf(“usage: %s infile.txt outfile.txt”, filepath.Base(os.Args[0]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
```sh
|
```sh
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
/usr/bin/time -f ‘%Uu %Ss %er %MkB %C’ “$@”
|
/usr/bin/time -f ‘%Uu %Ss %er %MkB %C’ “$@”
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在 Unix 命令行中像这样使用 ```xtime goprogexec```,这里的 progexec 是一个 Go 可执行程序,这句命令行输出类似:56.63u 0.26s 56.92r 1642640kB progexec,分别对应用户时间,系统时间,实际时间和最大内存占用。
|
在 Unix 命令行中像这样使用 ```xtime goprogexec```,这里的 progexec 是一个 Go 可执行程序,这句命令行输出类似:56.63u 0.26s 56.92r 1642640kB progexec,分别对应用户时间,系统时间,实际时间和最大内存占用。
|
||||||
@@ -38,7 +37,6 @@ func main() {
|
|||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
}
|
}
|
||||||
...
|
...
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
代码定义了一个名为 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 返回时触发。
|
||||||
@@ -64,6 +62,7 @@ Total: 3099 samples
|
|||||||
309 10.0% 30.2% 2839 91.6% main.FindLoops
|
309 10.0% 30.2% 2839 91.6% main.FindLoops
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
第 5 列表示函数的调用频度。
|
第 5 列表示函数的调用频度。
|
||||||
|
|
||||||
2)`web` 或 `web 函数名`
|
2)`web` 或 `web 函数名`
|
||||||
@@ -92,7 +91,6 @@ if *memprofile != “” {
|
|||||||
f.Close()
|
f.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
用 -memprofile flag 运行这个程序:```progexec -memprofile=progexec.mprof```
|
用 -memprofile flag 运行这个程序:```progexec -memprofile=progexec.mprof```
|
||||||
@@ -106,7 +104,6 @@ Total: 118.3 MB
|
|||||||
66.1 55.8% 55.8% 103.7 87.7% main.FindLoops
|
66.1 55.8% 55.8% 103.7 87.7% main.FindLoops
|
||||||
30.5 25.8% 81.6% 30.5 25.8% main.*LSG·NewLoop
|
30.5 25.8% 81.6% 30.5 25.8% main.*LSG·NewLoop
|
||||||
...
|
...
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
从第 1 列可以看出,最上面的函数占用了最多的内存。
|
从第 1 列可以看出,最上面的函数占用了最多的内存。
|
||||||
@@ -115,14 +112,12 @@ Total: 118.3 MB
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
gopprof --inuse_objects progexec progexec.mprof
|
gopprof --inuse_objects progexec progexec.mprof
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
对于 web 应用来说,有标准的 HTTP 接口可以分析数据。在 HTTP 服务中添加
|
对于 web 应用来说,有标准的 HTTP 接口可以分析数据。在 HTTP 服务中添加
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import _ “http/pprof”
|
import _ “http/pprof”
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
会为 /debug/pprof/ 下的一些 URL 安装处理器。然后你可以用一个唯一的参数——你服务中的分析数据的 URL 来执行 gopprof 命令——它会下载并执行在线分析。
|
会为 /debug/pprof/ 下的一些 URL 安装处理器。然后你可以用一个唯一的参数——你服务中的分析数据的 URL 来执行 gopprof 命令——它会下载并执行在线分析。
|
||||||
@@ -130,12 +125,10 @@ import _ “http/pprof”
|
|||||||
```sh
|
```sh
|
||||||
gopprof http://localhost:6060/debug/pprof/profile # 30-second CPU profile
|
gopprof http://localhost:6060/debug/pprof/profile # 30-second CPU profile
|
||||||
gopprof http://localhost:6060/debug/pprof/heap # heap profile
|
gopprof http://localhost:6060/debug/pprof/heap # heap profile
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在 Go-blog(引用 15)中有一篇很好的文章用具体的例子进行了分析:分析 Go 程序(2011年6月)。
|
在 Go-blog(引用 15)中有一篇很好的文章用具体的例子进行了分析:分析 Go 程序(2011年6月)。
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -14,7 +14,6 @@ func main() {
|
|||||||
panic("A severe error occurred: stopping the program!")
|
panic("A severe error occurred: stopping the program!")
|
||||||
fmt.Println("Ending the program")
|
fmt.Println("Ending the program")
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
输出如下:
|
输出如下:
|
||||||
@@ -33,7 +32,6 @@ runtime.goexit /go/src/pkg/runtime/proc.c:148
|
|||||||
runtime.goexit()
|
runtime.goexit()
|
||||||
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
|
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
|
||||||
---- Program exited with code -1073741783
|
---- Program exited with code -1073741783
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
一个检查程序是否被已知用户启动的具体例子:
|
一个检查程序是否被已知用户启动的具体例子:
|
||||||
@@ -46,7 +44,6 @@ func check() {
|
|||||||
panic(“Unknown user: no value for $USER”)
|
panic(“Unknown user: no value for $USER”)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
可以在导入包的 init() 函数中检查这些。
|
可以在导入包的 init() 函数中检查这些。
|
||||||
@@ -57,7 +54,6 @@ func check() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(“ERROR occurred:” + err.Error())
|
panic(“ERROR occurred:” + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
<u>Go panicking</u>:
|
<u>Go panicking</u>:
|
||||||
@@ -68,7 +64,6 @@ if err != nil {
|
|||||||
|
|
||||||
不能随意地用 panic 中止程序,必须尽力补救错误让程序能继续执行。
|
不能随意地用 panic 中止程序,必须尽力补救错误让程序能继续执行。
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -20,7 +20,6 @@ func protect(g func()) {
|
|||||||
log.Println(“start”)
|
log.Println(“start”)
|
||||||
g() // possible runtime-error
|
g() // possible runtime-error
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
这跟 Java 和 .NET 这样的语言中的 catch 块类似。
|
这跟 Java 和 .NET 这样的语言中的 catch 块类似。
|
||||||
@@ -60,7 +59,6 @@ func main() {
|
|||||||
test()
|
test()
|
||||||
fmt.Printf("Test completed\r\n")
|
fmt.Printf("Test completed\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
输出:
|
输出:
|
||||||
@@ -69,14 +67,12 @@ func main() {
|
|||||||
Calling test
|
Calling test
|
||||||
Panicing bad end
|
Panicing bad end
|
||||||
Test completed
|
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 处理成返回显式的错误。
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -66,7 +66,6 @@ func fields2numbers(fields []string) (numbers []int) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
示例 13.5 [panic_package.go](examples/chapter_13/panic_package.go):
|
示例 13.5 [panic_package.go](examples/chapter_13/panic_package.go):
|
||||||
@@ -99,7 +98,6 @@ func main() {
|
|||||||
fmt.Println(nums)
|
fmt.Println(nums)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
输出:
|
输出:
|
||||||
@@ -115,10 +113,8 @@ Parsing "1st class":
|
|||||||
pkg parse: error parsing "1st" as int
|
pkg parse: error parsing "1st" as int
|
||||||
Parsing "":
|
Parsing "":
|
||||||
pkg: no words to parse
|
pkg: no words to parse
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -4,14 +4,12 @@
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
func handler1(w http.ResponseWriter, r *http.Request) { ... }
|
func handler1(w http.ResponseWriter, r *http.Request) { ... }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
假设所有的函数都有这样的签名:
|
假设所有的函数都有这样的签名:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func f(a type1, b type2)
|
func f(a type1, b type2)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
参数的数量和类型是不相关的。
|
参数的数量和类型是不相关的。
|
||||||
@@ -20,7 +18,6 @@ func f(a type1, b type2)
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
fType1 = func f(a type1, b type2)
|
fType1 = func f(a type1, b type2)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在我们的模式中使用了两个帮助函数:
|
在我们的模式中使用了两个帮助函数:
|
||||||
@@ -44,7 +41,6 @@ func errorHandler(fn fType1) fType1 {
|
|||||||
fn(a, b)
|
fn(a, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 template 包(参见 [15.7 节](15.7.md))为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:
|
当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 template 包(参见 [15.7 节](15.7.md))为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:
|
||||||
@@ -60,7 +56,6 @@ func f1(a type1, b type2) {
|
|||||||
check(err2)
|
check(err2)
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
通过这种机制,所有的错误都会被 recover,并且调用函数后的错误检查代码也被简化为调用 check(err) 即可。在这种模式下,不同的错误处理必须对应不同的函数类型;它们(错误处理)可能被隐藏在错误处理包内部。可选的更加通用的方式是用一个空接口类型的切片作为参数和返回值。
|
通过这种机制,所有的错误都会被 recover,并且调用函数后的错误检查代码也被简化为调用 check(err) 即可。在这种模式下,不同的错误处理必须对应不同的函数类型;它们(错误处理)可能被隐藏在错误处理包内部。可选的更加通用的方式是用一个空接口类型的切片作为参数和返回值。
|
||||||
@@ -108,7 +103,11 @@ func g(i int) {
|
|||||||
fmt.Println("Printing in g", i)
|
fmt.Println("Printing in g", i)
|
||||||
g(i + 1)
|
g(i + 1)
|
||||||
}
|
}
|
||||||
/* Output:
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
```
|
||||||
Calling g.
|
Calling g.
|
||||||
Printing in g 0
|
Printing in g 0
|
||||||
Printing in g 1
|
Printing in g 1
|
||||||
@@ -121,15 +120,12 @@ Defer in g 1
|
|||||||
Defer in g 0
|
Defer in g 0
|
||||||
Recovered in f 4
|
Recovered in f 4
|
||||||
Returned normally from f.
|
Returned normally from f.
|
||||||
*/
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**练习 13.3**:[panic_defer_convint.go](exercises/chapter_13/panic_defer_convint.go)
|
**练习 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,返回一个整数和一个错误。请测试这个函数!
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -53,12 +53,13 @@ The process id is &{2054 0}total 2056
|
|||||||
```go
|
```go
|
||||||
// 2nd example: show all processes
|
// 2nd example: show all processes
|
||||||
pid, err = os.StartProcess("/bin/ps", []string{"-e", "-opid,ppid,comm"}, procAttr)
|
pid, err = os.StartProcess("/bin/ps", []string{"-e", "-opid,ppid,comm"}, procAttr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error %v starting process!", err) //
|
fmt.Printf("Error %v starting process!", err) //
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Printf("The process id is %v", pid)
|
|
||||||
|
|
||||||
|
fmt.Printf("The process id is %v", pid)
|
||||||
```
|
```
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -77,10 +78,8 @@ fmt.Printf("The process id is %v", pid)
|
|||||||
// The command is &{/bin/ls [ls -l] [] <nil> <nil> <nil> 0xf840000210 <nil> true [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [] [] 0xf8400128c0}
|
// The command is &{/bin/ls [ls -l] [] <nil> <nil> <nil> 0xf840000210 <nil> true [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [0xf84000ea50 0xf84000e9f0 0xf84000e9c0] [] [] 0xf8400128c0}
|
||||||
}
|
}
|
||||||
// in Windows: uitvoering: Error fork/exec /bin/ls: The system cannot find the path specified. starting process!
|
// in Windows: uitvoering: Error fork/exec /bin/ls: The system cannot find the path specified. starting process!
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
对一个包做(单元)测试,需要写一些可以频繁(每次更新后)执行的小块测试单元来检查代码的正确性。于是我们必须写一些 Go 源文件来测试代码。测试程序必须属于被测试的包,并且文件名满足这种形式 `*_test.go`,所以测试代码和包中的业务代码是分开的。
|
对一个包做(单元)测试,需要写一些可以频繁(每次更新后)执行的小块测试单元来检查代码的正确性。于是我们必须写一些 Go 源文件来测试代码。测试程序必须属于被测试的包,并且文件名满足这种形式 `*_test.go`,所以测试代码和包中的业务代码是分开的。
|
||||||
|
|
||||||
_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
|
`_test` 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
|
||||||
|
|
||||||
测试文件中必须导入 "testing" 包,并写一些名字以 `TestZzz` 打头的全局函数,这里的 `Zzz` 是被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。
|
测试文件中必须导入 "testing" 包,并写一些名字以 `TestZzz` 打头的全局函数,这里的 `Zzz` 是被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。
|
||||||
|
|
||||||
@@ -55,7 +55,6 @@ go test fmt_test.go --chatty
|
|||||||
=== RUN fmt.TestArrayPrinter
|
=== RUN fmt.TestArrayPrinter
|
||||||
--- PASS: fmt.TestArrayPrinter
|
--- PASS: fmt.TestArrayPrinter
|
||||||
...
|
...
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
testing 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 `BenchmarkZzz` 打头的函数并接收一个 `*testing.B` 类型的参数,比如:
|
testing 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 `BenchmarkZzz` 打头的函数并接收一个 `*testing.B` 类型的参数,比如:
|
||||||
@@ -64,14 +63,12 @@ testing 包中有一些类型和函数可以用来做简单的基准测试;测
|
|||||||
func BenchmarkReverse(b *testing.B) {
|
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)
|
具体可以参见 [14.16 节](14.16.md)中用 goroutines 运行基准测试的例子以及练习 13.4:[string_reverse_test.go](exercises/chapter_13/string_reverse_test.go)
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -19,7 +19,6 @@ func main() {
|
|||||||
fmt.Printf("Is the integer %d even? %v\n", i, even.Even(i))
|
fmt.Printf("Is the integer %d even? %v\n", i, even.Even(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面使用了 even.go 中的 even 包:
|
上面使用了 even.go 中的 even 包:
|
||||||
@@ -36,7 +35,6 @@ func Even(i int) bool { // Exported function
|
|||||||
func Odd(i int) bool { // Exported function
|
func Odd(i int) bool { // Exported function
|
||||||
return i%2 != 0
|
return i%2 != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在 even 包的路径下,我们创建一个名为 oddeven_test.go 的测试程序:
|
在 even 包的路径下,我们创建一个名为 oddeven_test.go 的测试程序:
|
||||||
@@ -70,7 +68,6 @@ func TestOdd(t *testing.T) {
|
|||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
由于测试需要具体的输入用例且不可能测试到所有的用例(非常像一个无穷的数),所以我们必须对要使用的测试用例思考再三。
|
由于测试需要具体的输入用例且不可能测试到所有的用例(非常像一个无穷的数),所以我们必须对要使用的测试用例思考再三。
|
||||||
@@ -89,14 +86,13 @@ TARG=even
|
|||||||
GOFILES=\
|
GOFILES=\
|
||||||
even.go\
|
even.go\
|
||||||
include $(GOROOT)/src/Make.pkg
|
include $(GOROOT)/src/Make.pkg
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
然后执行 make(或 gomake)命令来构建归档文件 even.a
|
然后执行 make(或 gomake)命令来构建归档文件 even.a
|
||||||
|
|
||||||
测试代码不能在 GOFILES 参数中引用,因为我们不希望生成的程序中有测试代码。如果包含了测试代码,go test 会给出错误提示!go test 会生成一个单独的包含测试代码的 _test 程序。
|
测试代码不能在 GOFILES 参数中引用,因为我们不希望生成的程序中有测试代码。如果包含了测试代码,go test 会给出错误提示!go test 会生成一个单独的包含测试代码的 `_test` 程序。
|
||||||
|
|
||||||
现在我们可以用命令:```go test```(或 ```make test```)来测试 even 包。
|
现在我们可以用命令:`go test`(或 `make test`)来测试 even 包。
|
||||||
|
|
||||||
因为示例 13.5 中的测试函数不会调用 t.Log 和 t.Fail,所以会得到一个 PASS 的结果。在这个简单例子中一切都正常执行。
|
因为示例 13.5 中的测试函数不会调用 t.Log 和 t.Fail,所以会得到一个 PASS 的结果。在这个简单例子中一切都正常执行。
|
||||||
|
|
||||||
@@ -109,7 +105,6 @@ func TestEven(t *testing.T) {
|
|||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
现在会调用 t.Log 和 t.Fail,得到的结果如下:
|
现在会调用 t.Log 和 t.Fail,得到的结果如下:
|
||||||
@@ -118,7 +113,6 @@ func TestEven(t *testing.T) {
|
|||||||
--- FAIL: even.TestEven (0.00 seconds)
|
--- FAIL: even.TestEven (0.00 seconds)
|
||||||
Everything OK: 10 is even, just a test to see failed output!
|
Everything OK: 10 is even, just a test to see failed output!
|
||||||
FAIL
|
FAIL
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**练习 13.4:**[string_reverse_test.go](exercises/chapter_13/string_reverse_test.go)
|
**练习 13.4:**[string_reverse_test.go](exercises/chapter_13/string_reverse_test.go)
|
||||||
@@ -129,7 +123,6 @@ FAIL
|
|||||||
|
|
||||||
实现并测试它。
|
实现并测试它。
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
@@ -26,7 +26,6 @@ func TestFunction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果大部分函数都可以写成这种形式,那么写一个帮助函数 verify 对实际测试会很有帮助:
|
如果大部分函数都可以写成这种形式,那么写一个帮助函数 verify 对实际测试会很有帮助:
|
||||||
@@ -37,20 +36,17 @@ func verify(t *testing.T, testnum int, testcase, input, output, expected string)
|
|||||||
t.Errorf(“%d. %s with input = %s: output %s != %s”, testnum, testcase, input, output, expected)
|
t.Errorf(“%d. %s with input = %s: output %s != %s”, testnum, testcase, input, output, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
TestFunction 则变为:
|
TestFunction 则变为:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
||||||
func TestFunction(t *testing.T) {
|
func TestFunction(t *testing.T) {
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
s := FuncToBeTested(tt.in)
|
s := FuncToBeTested(tt.in)
|
||||||
verify(t, i, “FuncToBeTested: “, tt.in, s, tt.out)
|
verify(t, i, “FuncToBeTested: “, tt.in, s, tt.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
Reference in New Issue
Block a user