错误处理与测试
Go 没有像 Java 和 .NET 那样的 try/catch
异常机制:不能执行抛异常操作。但是有一套 defer-panic-and-recover
机制
错误处理
Go 有一个预先定义的 error 接口类型
type error interface {Error() string
}
errors 包中有一个 errorString 结构体实现了 error 接口。当程序处于错误状态时可以用 os.Exit(1)
来中止运行。
定义错误
任何时候当你需要一个新的错误类型,都可以用 errors(必须先 import)包的 errors.New 函数接收合适的错误信息来创建,像下面这样:
err := errors.New(“math - square root of negative number”)
包也可以用额外的方法(methods)定义特定的错误,比如 net.Error:
package net
type Error interface {Timeout() bool // Is the error a timeout?Temporary() bool // Is the error temporary?
}
用 fmt 创建错误对象
通常你想要返回包含错误参数的更有信息量的字符串,例如:可以用 fmt.Errorf() 来实现:它和 fmt.Printf () 完全一样,接收有一个或多个格式占位符的格式化字符串和相应数量的占位变量。和打印信息不同的是它用信息生成错误对象。
比如在前面的平方根例子中使用:
if f < 0 {return 0, fmt.Errorf("math: square root of negative number %g", f)
}
运行时异常和 panic
当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时 panic,伴随着程序的崩溃抛出一个 runtime.Error 接口类型的值。这个错误值有个 RuntimeError() 方法用于区别普通错误。
panic 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 panic 函数产生一个中止程序的运行时错误。panic 接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。
package main
import "fmt"
func main() {fmt.Println("Starting the program")panic("A severe error occurred: stopping the program!")fmt.Println("Ending the program")
}
从 panic 中恢复(Recover)
正如名字一样,这个(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。
recover 只能在 defer 修饰的函数中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果。
总结:panic 会导致栈被展开直到 defer 修饰的 recover () 被调用或者程序中止
这跟 Java 和 .NET 这样的语言中的 catch 块类似。 log 包实现了简单的日志功能:默认的 log 对象向标准错误输出中写入并打印每条日志信息的日期和时间。除了 Println 和 Printf 函数,其它的致命性函数都会在写完日志信息后调用 os.Exit (1),那些退出函数也是如此。而 Panic 效果的函数会在写完日志信息后调用 panic;可以在程序必须中止或发生了临界错误时使用它们,就像当 web 服务器不能启动时那样
自定义包中的错误处理和 panicking
这是所有自定义包实现者应该遵守的最佳实践:
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic ()
2)向包的调用者返回错误值(而不是 panic)。
启动外部命令和程序
os 包有一个 StartProcess 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。
这个函数返回被启动进程的 id(pid),或者启动失败返回错误
Go 中的单元测试和基准测试
_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 gotest 会编译所有的程序:普通程序和测试程序。
测试文件中必须导入 "testing" 包,并写一些名字以 TestZzz 打头的全局函数,这里的 Zzz 是被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。
测试的编写规则:
Go 的测试必须按规则方式编写,不然 go test 将无法正确定位测试代码的位置,主要三点规则。
首先,测试代码文件的命名必须是以 _test.go 结尾,比如上节中的文件名 math_tesh.go 并非随意取的。
还有,代码中的用例函数必须满足匹配 TestXxx,比如 TestAbs。
关于 Xxx,简单解释一下,它主要传达两点含义,一是 Xxx 表示首个字符必须大写或数字,简单而言就是可确定单词分隔,二是首字母后的字符可以是任意 Go 关键词合法字符,如大小写字母、下划线、数字。
测试函数必须有这种形式的头部:
func TestAbcde(t *testing.T)
T 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。在函数的结尾把输出跟想要的结果对比,如果不等就打印一个错误。成功的测试则直接返回。
用下面这些函数来通知测试失败:
1)func (t *T) Fail()
标记测试函数为失败,然后继续执行(剩下的测试)。
2)func (t *T) FailNow()
标记测试函数为失败并中止执行;文件中别的测试也被略过,继续执行下一个文件。
3)func (t *T) Log(args ...interface{})
args 被用默认的格式格式化并打印到错误日志中。
4)func (t *T) Fatal(args ...interface{})
结合 先执行 3),然后执行 2)的效果。
package even
import "testing"
func TestEven(t *testing.T) {if !Even(10) {t.Log(" 10 must be even!")t.Fail()}if Even(7) {t.Log(" 7 is not even!")t.Fail()}
}
func TestOdd(t *testing.T) {if !Odd(11) {t.Log(" 11 must be odd!")t.Fail()}if Odd(10) {t.Log(" 10 is not odd!")t.Fail()}
}