13.0-13.10

This commit is contained in:
Unknwon
2015-11-25 01:23:32 -05:00
parent b439076d21
commit 3aa7fb8bcf
14 changed files with 54 additions and 102 deletions

View File

@@ -9,7 +9,7 @@
## 翻译进度
12.12 [Go 中的密码学](eBook/12.12.md)
13.10 [性能调试:分析并优化 Go 程序](eBook/13.10.md)
## 支持本书

View File

@@ -30,4 +30,4 @@ Golang 编程245386165
|更新日期 |更新内容
|----------|------------------
|2015-11-18|12.12 Go 中的密码学
|2015-11-25|13.10 性能调试:分析并优化 Go 程序

11
TOC.md
View File

@@ -117,3 +117,14 @@
- 12.10 [XML 数据格式](12.10.md)
- 12.11 [用 Gob 传输数据](12.11.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)

View File

@@ -6,9 +6,7 @@ Go 的设计者觉得 `try/catch` 机制的使用太泛滥了,而且从底层
Go 是怎么处理普通错误的呢?通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil则没有错误发生——并且主调calling函数总是应该检查收到的错误。
```
永远不要忽略错误,否则可能会导致程序崩溃!!
```
**永远不要忽略错误,否则可能会导致程序崩溃!!**
处理错误并且在函数发生错误的地方给用户返回错误信息:照这样处理就算真的出了问题,你的程序也能继续运行并且通知给用户。`panic and recover` 是用来处理真正的异常(无法预测的错误)而不是普通的错误。
@@ -28,14 +26,12 @@ if value, err := pack1.Func1(param1); err != nil {
return // or: return err
}
// Process(value)
```
*为了更清晰的代码,应该总是使用包含错误值变量的 if 复合语句*
上例除了 `fmt.Printf` 还可以使用 log 中对应的方法(参见 13.3 节 和 15.2 节),如果程序中止也没关系的话甚至可以使用 `panic`(参见后面的章节)。
## 链接
- [目录](directory.md)

View File

@@ -3,10 +3,9 @@
Go 有一个预先定义的 error 接口类型
```go
type error interface {
type error interface {
Error() string
}
```
错误值用来表示异常状态;我们可以在 [5.2 节](05.2.md)中看到它的标准用法。处理文件操作的例子可以在 12 章找到;我们将在 15 章看到网络操作的例子。errors 包中有一个 errorString 结构体实现了 error 接口。当程序处于错误状态时可以用 `os.Exit(1)` 来中止运行。
@@ -17,7 +16,6 @@ type error interface {
```go
err := errors.New(math - square root of negative number)
```
在示例 13.1 中你可以看到一个简单的用例:
@@ -39,7 +37,6 @@ func main() {
fmt.Printf("error: %v", errNotFound)
}
// error: Not found error
```
可以把它用于计算平方根函数的参数测试:
@@ -51,7 +48,6 @@ func Sqrt(f float64) (float64, error) {
}
// implementation of Sqrt
}
```
你可以像下面这样调用 Sqrt 函数:
@@ -60,7 +56,6 @@ func Sqrt(f float64) (float64, error) {
if f, err := Sqrt(-1); err != nil {
fmt.Printf(Error: %s\n, err)
}
```
由于 `fmt.Printf` 会自动调用 `String()` 方法 (参见 [10.7 节](10.7.md)),所以错误信息 “Error: math - square root of negative number” 会打印出来。通常(错误信息)都会有像 “Error:” 这样的前缀,所以你的错误信息不要以大写字母开头。
@@ -78,17 +73,15 @@ type PathError struct {
func (e *PathError) String() string {
return e.Op + + e.Path + : + e.Err.Error()
}
```
如果有不同错误条件可能发生那么对实际的错误使用类型断言或类型判断type-switch是很有用的并且可以根据错误场景做一些补救和恢复操作。
```go
```go
// err != nil
if e, ok := err.(*os.PathError); ok {
// remedy situation
}
```
或:
@@ -99,11 +92,10 @@ switch err := err.(type) {
PrintParseError(err)
case PathError:
PrintPathError(err)
...
...
default:
fmt.Printf(Not a special error, just %s\n, err)
}
```
作为第二个例子考虑用 json 包的情况。当 json.Decode 在解析 JSON 文档发生语法错误时,指定返回一个 SyntaxError 类型的错误:
@@ -116,7 +108,6 @@ type SyntaxError struct {
}
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)
return fmt.Errorf(%s:%d:%d: %v, f.Name(), line, col, err)
}
```
包也可以用额外的方法methods定义特定的错误比如 net.Errot
@@ -137,7 +127,6 @@ type Error interface {
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
```
在 [15.1 节](15.1.md) 我们可以看到怎么使用它。
@@ -153,7 +142,6 @@ r, err := syscall.Open(name, mode, perm)
if err != 0 {
fmt.Println(err.Error())
}
```
os 包也提供了一套像 os.EINAL 这样的标准错误,它们基于 syscall 错误:
@@ -161,13 +149,12 @@ os 包也提供了一套像 os.EINAL 这样的标准错误,它们基于 syscal
```go
var (
EPERM Error = Errno(syscall.EPERM)
ENOENT Error = Errno(syscall.ENOENT)
ENOENT Error = Errno(syscall.ENOENT)
ESRCH Error = Errno(syscall.ESRCH)
EINTR Error = Errno(syscall.EINTR)
EIO Error = Errno(syscall.EIO)
...
...
)
```
## 13.1.2 用 fmt 创建错误对象
@@ -180,7 +167,6 @@ var (
if f < 0 {
return 0, fmt.Errorf(math: square root of negative number %g, f)
}
```
第二个例子:从命令行读取输入时,如果加了 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]))
return
}
```
## 链接
- [目录](directory.md)

View File

@@ -7,7 +7,6 @@
```sh
#!/bin/sh
/usr/bin/time -f %Uu %Ss %er %MkB %C$@
```
在 Unix 命令行中像这样使用 ```xtime goprogexec```,这里的 progexec 是一个 Go 可执行程序这句命令行输出类似56.63u 0.26s 56.92r 1642640kB progexec分别对应用户时间系统时间实际时间和最大内存占用。
@@ -38,7 +37,6 @@ func main() {
defer pprof.StopCPUProfile()
}
...
```
代码定义了一个名为 cpuprofile 的 flag调用 Go flag 库来解析命令行 flag如果命令行设置了 cpuprofile flag则开始 CPU 性能分析并把结果重定向到那个文件。os.Create 用拿到的名字创建了用来写入分析数据的文件)。这个分析程序最后需要在程序退出之前调用 StopCPUProfile 来刷新挂起的写操作到文件中;我们用 defer 来保证这一切会在 main 返回时触发。
@@ -54,16 +52,17 @@ gopprof 程序是 Google pprofC++ 分析器的一个轻微变种;关于此工
此工具一些有趣的命令:
1`topN`
用来展示分析结果中最开头的 N 份样本,例如:```top5```
它会展示在程序运行期间调用最频繁的 5 个函数,输出如下:
```
Total: 3099 samples
626 20.2% 20.2% 626 20.2% scanblock
309 10.0% 30.2% 2839 91.6% main.FindLoops
309 10.0% 30.2% 2839 91.6% main.FindLoops
...
```
第 5 列表示函数的调用频度。
2`web` 或 `web 函数名`
@@ -92,7 +91,6 @@ if *memprofile != “” {
f.Close()
return
}
```
用 -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
30.5 25.8% 81.6% 30.5 25.8% main.*LSG·NewLoop
...
```
从第 1 列可以看出,最上面的函数占用了最多的内存。
@@ -115,14 +112,12 @@ Total: 118.3 MB
```sh
gopprof --inuse_objects progexec progexec.mprof
```
对于 web 应用来说,有标准的 HTTP 接口可以分析数据。在 HTTP 服务中添加
```go
import _ “http/pprof”
```
会为 /debug/pprof/ 下的一些 URL 安装处理器。然后你可以用一个唯一的参数——你服务中的分析数据的 URL 来执行 gopprof 命令——它会下载并执行在线分析。
@@ -130,12 +125,10 @@ import _ “http/pprof”
```sh
gopprof http://localhost:6060/debug/pprof/profile # 30-second CPU profile
gopprof http://localhost:6060/debug/pprof/heap # heap profile
```
在 Go-blog引用 15中有一篇很好的文章用具体的例子进行了分析分析 Go 程序2011年6月
## 链接
- [目录](directory.md)

View File

@@ -14,7 +14,6 @@ func main() {
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}
```
输出如下:
@@ -33,7 +32,6 @@ runtime.goexit /go/src/pkg/runtime/proc.c:148
runtime.goexit()
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
---- Program exited with code -1073741783
```
一个检查程序是否被已知用户启动的具体例子:
@@ -46,7 +44,6 @@ func check() {
panic(Unknown user: no value for $USER)
}
}
```
可以在导入包的 init() 函数中检查这些。
@@ -57,8 +54,7 @@ func check() {
if err != nil {
panic(ERROR occurred: + err.Error())
}
```
```
<u>Go panicking</u>
@@ -68,7 +64,6 @@ if err != nil {
不能随意地用 panic 中止程序,必须尽力补救错误让程序能继续执行。
## 链接
- [目录](directory.md)

View File

@@ -12,7 +12,7 @@
func protect(g func()) {
defer func() {
log.Println(done)
// Println executes normally even if there is a panic
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Printf(run time panic: %v, err)
}
@@ -20,7 +20,6 @@ func protect(g func()) {
log.Println(start)
g() // possible runtime-error
}
```
这跟 Java 和 .NET 这样的语言中的 catch 块类似。
@@ -60,7 +59,6 @@ func main() {
test()
fmt.Printf("Test completed\r\n")
}
```
输出:
@@ -69,14 +67,12 @@ func main() {
Calling test
Panicing bad end
Test completed
```
`defer-panic-recover` 在某种意义上也是一种像 `if``for` 这样的控制流机制。
Go 标准库中许多地方都用了这个机制例如json 包中的解码和 regexp 包中的 Complie 函数。Go 库的原则是即使在包的内部使用了 panic在它的对外接口API中也必须用 recover 处理成返回显式的错误。
## 链接
- [目录](directory.md)

View File

@@ -66,7 +66,6 @@ func fields2numbers(fields []string) (numbers []int) {
}
return
}
```
示例 13.5 [panic_package.go](examples/chapter_13/panic_package.go)
@@ -99,7 +98,6 @@ func main() {
fmt.Println(nums)
}
}
```
输出:
@@ -115,10 +113,8 @@ Parsing "1st class":
pkg parse: error parsing "1st" as int
Parsing "":
pkg: no words to parse
```
## 链接
- [目录](directory.md)

View File

@@ -4,14 +4,12 @@
```go
func handler1(w http.ResponseWriter, r *http.Request) { ... }
```
假设所有的函数都有这样的签名:
```go
func f(a type1, b type2)
```
参数的数量和类型是不相关的。
@@ -20,7 +18,6 @@ func f(a type1, b type2)
```go
fType1 = func f(a type1, b type2)
```
在我们的模式中使用了两个帮助函数:
@@ -39,12 +36,11 @@ func errorHandler(fn fType1) fType1 {
defer func() {
if e, ok := recover().(error); ok {
log.Printf(run time panic: %v, err)
}
}
}()
fn(a, b)
fn(a, b)
}
}
```
当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 template 包(参见 [15.7 节](15.7.md)为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:
@@ -60,7 +56,6 @@ func f1(a type1, b type2) {
check(err2)
...
}
```
通过这种机制,所有的错误都会被 recover并且调用函数后的错误检查代码也被简化为调用 check(err) 即可。在这种模式下,不同的错误处理必须对应不同的函数类型;它们(错误处理)可能被隐藏在错误处理包内部。可选的更加通用的方式是用一个空接口类型的切片作为参数和返回值。
@@ -93,7 +88,7 @@ func f() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
@@ -108,7 +103,11 @@ func g(i int) {
fmt.Println("Printing in g", i)
g(i + 1)
}
/* Output:
```
输出:
```
Calling g.
Printing in g 0
Printing in g 1
@@ -121,15 +120,12 @@ Defer in g 1
Defer in g 0
Recovered in f 4
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返回一个整数和一个错误。请测试这个函数
## 链接
- [目录](directory.md)

View File

@@ -13,7 +13,7 @@ exec 包中也有同样功能的更简单的结构体和函数;主要是 `exec
package main
import (
"fmt"
"os/exec"
"os/exec"
"os"
)
@@ -30,13 +30,13 @@ procAttr := &os.ProcAttr{
os.Stderr,
},
}
// 1st example: list files
// 1st example: list files
pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
fmt.Printf("The process id is %v", pid)
```
输出:
@@ -53,14 +53,15 @@ The process id is &{2054 0}total 2056
```go
// 2nd example: show all processes
pid, err = os.StartProcess("/bin/ps", []string{"-e", "-opid,ppid,comm"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
fmt.Printf("The process id is %v", pid)
```
```go
// 2) exec.Run //
/***************/
@@ -70,19 +71,17 @@ fmt.Printf("The process id is %v", pid)
cmd := exec.Command("gedit") // this opens a gedit-window
err = cmd.Run()
if err != nil {
fmt.Printf("Error %v executing command!", err)
fmt.Printf("Error %v executing command!", err)
os.Exit(1)
}
fmt.Printf("The command is %v", cmd)
// 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!
```
## 链接
- [目录](directory.md)
- 上一节:[一种用闭包处理错误的模式](13.5.md)
- 下一节:[Go 中的单元测试和基准测试](13.7.md)
- 下一节:[Go 中的单元测试和基准测试](13.7.md)

View File

@@ -10,7 +10,7 @@
对一个包做(单元)测试,需要写一些可以频繁(每次更新后)执行的小块测试单元来检查代码的正确性。于是我们必须写一些 Go 源文件来测试代码。测试程序必须属于被测试的包,并且文件名满足这种形式 `*_test.go`,所以测试代码和包中的业务代码是分开的。
_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
`_test` 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
测试文件中必须导入 "testing" 包,并写一些名字以 `TestZzz` 打头的全局函数,这里的 `Zzz` 是被测试函数的字母描述,如 TestFmtInterfaceTestPayEmployees 等。
@@ -55,25 +55,22 @@ go test fmt_test.go --chatty
=== RUN fmt.TestArrayPrinter
--- PASS: fmt.TestArrayPrinter
...
```
testing 包中有一些类型和函数可以用来做简单的基准测试;测试代码中必须包含以 `BenchmarkZzz` 打头的函数并接收一个 `*testing.B` 类型的参数,比如:
```go
func BenchmarkReverse(b *testing.B) {
func BenchmarkReverse(b *testing.B) {
...
}
```
命令 ```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)
## 链接
- [目录](directory.md)
- 上一节:[启动外部命令和程序](13.6.md)
- 下一节:[测试的具体例子](13.8.md)
- 下一节:[测试的具体例子](13.8.md)

View File

@@ -16,10 +16,9 @@ import (
func main() {
for i:=0; i<=100; i++ {
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 包:
@@ -36,7 +35,6 @@ func Even(i int) bool { // Exported function
func Odd(i int) bool { // Exported function
return i%2 != 0
}
```
在 even 包的路径下,我们创建一个名为 oddeven_test.go 的测试程序:
@@ -47,7 +45,7 @@ func Odd(i int) bool { // Exported function
package even
import "testing"
func TestEven(t *testing.T) {
if !Even(10) {
t.Log(" 10 must be even!")
@@ -70,7 +68,6 @@ func TestOdd(t *testing.T) {
t.Fail()
}
}
```
由于测试需要具体的输入用例且不可能测试到所有的用例(非常像一个无穷的数),所以我们必须对要使用的测试用例思考再三。
@@ -89,14 +86,13 @@ TARG=even
GOFILES=\
even.go\
include $(GOROOT)/src/Make.pkg
```
然后执行 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 的结果。在这个简单例子中一切都正常执行。
@@ -109,7 +105,6 @@ func TestEven(t *testing.T) {
t.Fail()
}
}
```
现在会调用 t.Log 和 t.Fail得到的结果如下
@@ -118,7 +113,6 @@ func TestEven(t *testing.T) {
--- FAIL: even.TestEven (0.00 seconds)
Everything OK: 10 is even, just a test to see failed output!
FAIL
```
**练习 13.4**[string_reverse_test.go](exercises/chapter_13/string_reverse_test.go)
@@ -129,7 +123,6 @@ FAIL
实现并测试它。
## 链接
- [目录](directory.md)

View File

@@ -15,7 +15,7 @@ var tests = []struct{ // Test table
{in1, exp1},
{in2, exp2},
{in3, exp3},
...
...
}
func TestFunction(t *testing.T) {
@@ -23,10 +23,9 @@ func TestFunction(t *testing.T) {
s := FuncToBeTested(tt.in)
if s != tt.out {
t.Errorf(%d. %q => %q, wanted: %q, i, tt.in, s, tt.out)
}
}
}
}
```
如果大部分函数都可以写成这种形式,那么写一个帮助函数 verify 对实际测试会很有帮助:
@@ -35,26 +34,23 @@ func TestFunction(t *testing.T) {
func verify(t *testing.T, testnum int, testcase, input, output, expected string) {
if input != output {
t.Errorf(%d. %s with input = %s: output %s != %s, testnum, testcase, input, output, expected)
}
}
}
```
TestFunction 则变为:
```go
func TestFunction(t *testing.T) {
for i, tt := range tests {
s := FuncToBeTested(tt.in)
verify(t, i, FuncToBeTested: , tt.in, s, tt.out)
}
}
}
```
## 链接
- [目录](directory.md)
- 上一节:[测试的具体例子](13.8.md)
- 下一节:[性能调试:分析并优化 Go 程序](13.10.md)
- 下一节:[性能调试:分析并优化 Go 程序](13.10.md)