错误处理
一、错误
在Go中有一部分函数总是能成功的运行。比如strings.Contains和strconv.FormatBool函数;对于大部分函数而言,永远无法确保能否成功运行。
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
这里有一个错误处理的例子:
package main
import "errors"
import "fmt"
// 按照惯例,错误通常是最后一个返回值并且是 error 类型,一个内建的接口。
func f1(arg int) (int, error) {
// errors.New 构造一个使用给定的错误信息的基本error 值。
if arg == 42 {
return -1, errors.New("can't work with 42")
}
// 返回错误值为 nil 代表没有错误。
return arg + 3, nil
}
// 通过实现 Error 方法来自定义 error 类型是可以的。
// 这里使用自定义错误类型来表示上面的参数错误。
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
// 在这个例子中,我们使用 &argError 语法来建立一个新的结构体,并提供了 arg 和 prob 这个两个字段的值。
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
// 下面的两个循环测试了各个返回错误的函数。
// 注意在 if行内的错误检查代码,在 Go 中是一个普遍的用法。
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 失败:", e)
} else {
fmt.Println("f1 工作:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 失败:", e)
} else {
fmt.Println("f2 工作:", r)
}
}
// 你如果想在程序中使用一个自定义错误类型中的数据,你需要通过类型断言来得到这个错误类型的实例。
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}
二、Deferred函数
defer语句经常被用于处理成对的操作,如打开、关闭、连接、断开连接、加锁、释放锁。通过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。
示例:
package main
import "fmt"
import "os"
func main() {
// 假设我们想要创建一个文件,向它进行写操作,然后在结束时关闭它。
// 这里展示了如何通过 defer 来做到这一切。
f := createFile("D:/defer.txt") // f := createFile("/tmp/defer.txt")
// 在 closeFile 后得到一个文件对象,我们使用 defer通过 closeFile 来关闭这个文件。这会在封闭函数(main)结束时执行,就是 writeFile 结束后。
defer closeFile(f)
writeFile(f)
}
func createFile(p string) *os.File {
fmt.Println("creating")
f, err := os.Create(p)
if err != nil {
panic(err)
}
return f
}
func writeFile(f *os.File) {
fmt.Println("writing")
fmt.Fprintln(f, "data")
}
func closeFile(f *os.File) {
fmt.Println("closing")
f.Close()
}
三、异常
Go的类型系统会在编译时捕获很多错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引起painc异常。
示例如下:
package main
import (
"fmt"
"os"
)
func main() {
// 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。
panic("一个异常")
// panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或者不想)处理时终止运行。
// 这里是一个在创建一个新文件时返回异常错误时的panic 用法。
fmt.Println("继续")
_, err := os.Create("/tmp/file")
if err != nil {
panic(err)
}
// 运行程序将会引起 panic,输出一个错误消息和 Go 运行时栈信息,并且返回一个非零的状态码。
}
四、捕获异常
通常来说,不应该对panic异常做任何处理,但有时,也许我们可以从异常中恢复,至少我们可以在程序崩溃前,做一些操作。举个例子,当web服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭;如果不做任何处理,会使得客户端一直处于等待状态。如果web服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。
如果在deferred函数中调用了内置函数recover,并且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。导致panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recover,recover会返回nil。
示例:
package main
import "fmt"
func main() {
// 这里我们对异常进行了捕获
defer func() {
if p := recover(); p != nil {
err := fmt.Errorf("internal error: %v", p)
if err != nil {
fmt.Println(err)
}
}
}()
// 我们将在这个网站中使用 panic 来检查预期外的错误。这个是唯一一个为 panic 准备的例子。
panic("一个异常")
}