go语言错误处理(一)https://developer.aliyun.com/article/1391521
log 包实现了简单的日志功能:默认的 log 对象向标准错误输出中写入并打印每条日志信息的日期和时间。除了 Println 和 Printf 函数,其它的致命性函数都会在写完日志信息后调用 os.Exit(1),那些退出函数也是如此。而 Panic 效果的函数会在写完日志信息后调用 panic;可以在程序必须中止或发生了临界错误时使用它们.下面展示 panic,defer 和 recover 怎么结合使用的完整例子:
// panic_recover.go package main import ( "fmt" ) func badCall() { panic("bad end") } func test() { defer func() { if e := recover(); e != nil { fmt.Printf("Panicing %s\r\n", e) } }() badCall() fmt.Printf("After bad call\r\n") // <-- wordt niet bereikt } func main() { fmt.Printf("Calling test\r\n") test() fmt.Printf("Test completed\r\n") }
输出:
Calling test
Panicing bad end
Test completed
defer-panic-recover 在某种意义上也是一种像 if , for 这样的控制流机制。
自定义包中的错误处理和 panicking
这是所有自定义包实现者应该遵守的最佳实践:
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic()。
2)向包的调用者返回错误值(而不是 panic)。
在包内部,特别是在非导出函数中有很深层次的嵌套调用时,对主调函数来说用 panic 来表示应该被翻译成错误的错误场景是很有用的。
// parse.go package parse import ( "fmt" "strings" "strconv" ) // ParseError 表示将单词转换为整数时出错。 type ParseError struct { Index int // 以空格分隔的单词列表的索引。 Word string // 生成分析错误的单词。 Err error // 引发此错误的原始错误(如果有)。 } // func (e *ParseError) String() string { return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word) } // Parse 将 put 中以空格分隔的单词解析为整数。 func Parse(input string) (numbers []int, err error) { defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("pkg: %v", r) } } }() fields := strings.Fields(input) numbers = fields2numbers(fields) return } func fields2numbers(fields []string) (numbers []int) { if len(fields) == 0 { panic("no words to parse") } for idx, field := range fields { num, err := strconv.Atoi(field) if err != nil { panic(&ParseError{idx, field, err}) } numbers = append(numbers, num) } return } // panic_package.go package main import ( "fmt" "./parse/parse" ) func main() { var examples = []string{ "1 2 3 4 5", "100 50 25 12.5 6.25", "2 + 2 = 4", "1st class", "", } for _, ex := range examples { fmt.Printf("Parsing %q:\n ", ex) nums, err := parse.Parse(ex) if err != nil { fmt.Println(err) continue } fmt.Println(nums) } }
输出:
Parsing “1 2 3 4 5”:
[1 2 3 4 5]
Parsing “100 50 25 12.5 6.25”:
pkg parse: error parsing “12.5” as int
Parsing “2 + 2 = 4”:
pkg parse: error parsing “+” as int
Parsing “1st class”:
pkg parse: error parsing “1st” as int
Parsing “”:
pkg: no words to parse
一种用闭包处理错误的模式
每当函数返回时,我们应该检查是否有错误发生:但是这会导致重复乏味的代码。结合
defer/panic/recover 机制和闭包可以得到一个我们马上要讨论的更加优雅的模式。不过这个模式只有当所有的函数都是同一种签名时可用,这样就有相当大的限制。一个很好的使用它的例子是 web 应用,所有的处理函数都是下面这样:
func handler1(w http.ResponseWriter, r *http.Request) { ... }
假设所有的函数都有这样的签名:
func f(a type1, b type2)
参数的数量和类型是不相关的。
我们给这个类型一个名字:
fType1 = func f(a type1, b type2)
在我们的模式中使用了两个帮助函数:
1)check:这是用来检查是否有错误和 panic 发生的函数:func check(err error) { if err != nil { panic(err) } }
2)errorhandler:这是一个包装函数。接收一个 fType1 类型的函数 fn 并返回一个调用 fn 的函数。里面就包含有 defer/recover 机制。
func errorHandler(fn fType1) fType1 { return func(a type1, b type2) { defer func() { if e, ok := recover().(error); ok { log.Printf(“run time panic: %v”, err) } }() fn(a, b) } }
当错误发生时会 recover 并打印在日志中;除了简单的打印,应用也可以用 template 包为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:
func f1(a type1, b type2) { ... f, _, err := // call function/method check(err) t, err := // call function/method check(err) _, err2 := // call function/method check(err2) ... }
通过这种机制,所有的错误都会被 recover,并且调用函数后的错误检查代码也被简化为调用 check(err)即可。在这种模式下,不同的错误处理必须对应不同的函数类型;