mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-11-13 17:36:12 +08:00
@@ -73,4 +73,4 @@ if err != nil {
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[错误处理](13.1.md)
|
||||
- 下一节:[从 panic 中恢复 (Recover)](13.3.md)
|
||||
- 下一节:[从 panic 中恢复(Recover)](13.3.md)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 13.3 从 panic 中恢复 (Recover)
|
||||
# 13.3 从 panic 中恢复(Recover)
|
||||
|
||||
正如名字一样,这个(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。
|
||||
|
||||
|
||||
@@ -26,45 +26,45 @@ import (
|
||||
|
||||
// A ParseError indicates an error in converting a word into an integer.
|
||||
type ParseError struct {
|
||||
Index int // The index into the space-separated list of words.
|
||||
Word string // The word that generated the parse error.
|
||||
Err error // The raw error that precipitated this error, if any.
|
||||
Index int // The index into the space-separated list of words.
|
||||
Word string // The word that generated the parse error.
|
||||
Err error // The raw error that precipitated this error, if any.
|
||||
}
|
||||
|
||||
// String returns a human-readable error message.
|
||||
func (e *ParseError) String() string {
|
||||
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
|
||||
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
|
||||
}
|
||||
|
||||
// Parse parses the space-separated words in in put as integers.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
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
|
||||
fields := strings.Fields(input)
|
||||
numbers = fields2numbers(fields)
|
||||
return
|
||||
}
|
||||
|
||||
func fields2numbers(fields []string) (numbers []int) {
|
||||
if len(fields) == 0 {
|
||||
panic("no words to parse")
|
||||
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})
|
||||
}
|
||||
for idx, field := range fields {
|
||||
num, err := strconv.Atoi(field)
|
||||
if err != nil {
|
||||
panic(&ParseError{idx, field, err})
|
||||
}
|
||||
numbers = append(numbers, num)
|
||||
}
|
||||
return
|
||||
numbers = append(numbers, num)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
```
|
||||
@@ -81,23 +81,23 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
var examples = []string{
|
||||
"1 2 3 4 5",
|
||||
"100 50 25 12.5 6.25",
|
||||
"2 + 2 = 4",
|
||||
"1st class",
|
||||
"",
|
||||
}
|
||||
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) // here String() method from ParseError is used
|
||||
continue
|
||||
}
|
||||
fmt.Println(nums)
|
||||
for _, ex := range examples {
|
||||
fmt.Printf("Parsing %q:\n ", ex)
|
||||
nums, err := parse.Parse(ex)
|
||||
if err != nil {
|
||||
fmt.Println(err) // here String() method from ParseError is used
|
||||
continue
|
||||
}
|
||||
fmt.Println(nums)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
@@ -122,5 +122,5 @@ Parsing "":
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[从 panic 中恢复 (Recover)](13.3.md)
|
||||
- 上一节:[从 panic 中恢复(Recover)](13.3.md)
|
||||
- 下一节:[一种用闭包处理错误的模式](13.5.md)
|
||||
|
||||
135
eBook/13.5.md
135
eBook/13.5.md
@@ -1,2 +1,137 @@
|
||||
# 13.5 一种用闭包处理错误的模式
|
||||
|
||||
每当函数返回时,我们应该检查是否有错误发生:但是这会导致重复乏味的代码。结合 defer/panic/recover 机制和闭包可以得到一个我们马上要讨论的更加优雅的模式。不过这个模式只有当所有的函数都是同一种签名时可用,这样就有相当大的限制。一个很好的使用它的例子是 web 应用,所有的处理函数都是下面这样:
|
||||
|
||||
```go
|
||||
func handler1(w http.ResponseWriter, r *http.Request) { ... }
|
||||
|
||||
```
|
||||
|
||||
假设所有的函数都有这样的签名:
|
||||
|
||||
```go
|
||||
func f(a type1, b type2)
|
||||
|
||||
```
|
||||
|
||||
参数的数量和类型是不相关的。
|
||||
|
||||
我们给这个类型一个名字:
|
||||
|
||||
```go
|
||||
fType1 = func f(a type1, b type2)
|
||||
|
||||
```
|
||||
|
||||
在我们的模式中使用了两个帮助函数:
|
||||
|
||||
1)check:这是用来检查是否有错误和 panic 发生的函数:
|
||||
|
||||
```go
|
||||
func check(err error) { if err != nil { panic(err) } }
|
||||
```
|
||||
|
||||
2)errorhandler:这是一个包装函数。接收一个 fType1 类型的函数 fn 并返回一个调用 fn 的函数。里面就包含有 defer/recover 机制,这在 13.3 节中有相应描述。
|
||||
|
||||
```go
|
||||
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 包(参见 15.7 节)为用户生成自定义的输出。check() 函数会在所有的被调函数中调用,像这样:
|
||||
|
||||
```go
|
||||
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) 即可。在这种模式下,不同的错误处理必须对应不同的函数类型;它们(错误处理)可能被隐藏在错误处理包内部。可选的更加通用的方式是用一个空接口类型的切片作为参数和返回值。
|
||||
|
||||
我们会在 15.5 节的 web 应用中使用这种模式。
|
||||
|
||||
<u>练习</u>
|
||||
|
||||
**练习 13.1**:[recover_dividebyzearo.go](exercises/chapter_13/recover_dividebyzearo.go)
|
||||
|
||||
用示例 13.3 中的编码模式通过整数除以 0 触发一个运行时 panic。
|
||||
|
||||
**练习 13.2**:[panic_defer.go](exercises/chapter_13/panic_defer.go)
|
||||
|
||||
阅读下面的完整程序。不要执行它,写出程序的输出结果。然后编译执行并验证你的预想。
|
||||
|
||||
```go
|
||||
// panic_defer.go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
f()
|
||||
fmt.Println("Returned normally from f.")
|
||||
}
|
||||
|
||||
func f() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("Recovered in f", r)
|
||||
}
|
||||
}()
|
||||
fmt.Println("Calling g.")
|
||||
g(0)
|
||||
fmt.Println("Returned normally from g.")
|
||||
}
|
||||
|
||||
func g(i int) {
|
||||
if i > 3 {
|
||||
fmt.Println("Panicking!")
|
||||
panic(fmt.Sprintf("%v", i))
|
||||
}
|
||||
defer fmt.Println("Defer in g", i)
|
||||
fmt.Println("Printing in g", i)
|
||||
g(i + 1)
|
||||
}
|
||||
/* Output:
|
||||
Calling g.
|
||||
Printing in g 0
|
||||
Printing in g 1
|
||||
Printing in g 2
|
||||
Printing in g 3
|
||||
Panicking!
|
||||
Defer in g 3
|
||||
Defer in g 2
|
||||
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 节)就 panic。然后在函数 IntFromInt64 中调用这个函数并 recover,返回一个整数和一个错误。请测试这个函数!
|
||||
|
||||
|
||||
## 链接
|
||||
|
||||
- [目录](directory.md)
|
||||
- 上一节:[自定义包中的错误处理和 panicking](13.4.md)
|
||||
- 下一节:[启动外部命令和程序](13.6.md)
|
||||
1
eBook/13.6.md
Normal file
1
eBook/13.6.md
Normal file
@@ -0,0 +1 @@
|
||||
# 13.6 启动外部命令和程序
|
||||
@@ -127,9 +127,10 @@
|
||||
- 第13章:[错误处理与测试](13.0.md)
|
||||
- 13.1 [错误处理](13.1.md)
|
||||
- 13.2 [运行时异常和 panic](13.2.md)
|
||||
- 13.3 [从 panic 中恢复 (Recover)](13.3.md)
|
||||
- 13.3 [从 panic 中恢复(Recover)](13.3.md)
|
||||
- 13.4 [自定义包中的错误处理和 panicking](13.4.md)
|
||||
- 13.5 [一种用闭包处理错误的模式](13.5.md)
|
||||
- 13.6 [启动外部命令和程序](13.6.md)
|
||||
- 第14章:goroutine 与 channel
|
||||
- 第15章:网络、模版与网页应用
|
||||
|
||||
|
||||
Reference in New Issue
Block a user