路漫漫其修远兮
吾将上下而求索

第13章总结:错误处理与测试

13.1 

Go 没有像 Java 和 .NET 那样的 try/catch 异常机制:不能执行抛异常操作。

Go 是通过在函数和方法中返回错误对象作为它们的唯一或最后一个返回值——如果返回 nil,则没有错误发生——并且主调(calling)函数总是应该检查收到的错误。
永远不要忽略错误,否则可能会导致程序崩溃!!

Go 检查和报告错误条件的惯有方式:
产生错误的函数会返回两个变量,一个值和一个错误码;如果后者是 nil 就是成功,非 nil 就是发生了错误。
为了防止发生错误时正在执行的函数(如果有必要的话甚至会是整个程序)被中止,在调用函数后必须检查错误。

Go 有一个预先定义的 error 接口类型
type error interface {
	Error() string
}

任何时候当你需要一个新的错误类型,都可以用 errors(必须先 import)包的 errors.New 函数接收合适的错误信息来创建,像下面这样:
err := errors.New(“math - square root of negative number”)

在调用代码中你可以像这样用类型断言测试错误是不是上面的类型:
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)
}


13.2
panic 可以直接从代码初始化:当错误条件(我们所测试的代码)很严苛且不可恢复,程序不能继续运行时,可以使用 panic 函数产生一个中止程序的运行时错误。
panic 接收一个做任意类型的参数,通常是字符串,在程序死亡时被打印出来。Go 运行时负责中止程序并给出调试信息。

func main() {
	fmt.Println("start the program")
	panic("something is wrong")
	fmt.Println("end the program")
}

下面是执行结果
D:\go\test2>go run test.go
start the program
panic: something is wrong

goroutine 1 [running]:
main.main()
        D:/go/test2/test.go:7 +0x8a
exit status 2

在多层嵌套的函数调用中调用 panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。
这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序崩溃,并在命令行中用传给 panic 的值报告错误情况:这个终止过程就是 panicking。

不能随意地用 panic 中止程序,必须尽力补救错误让程序能继续执行。


13.3 

正如名字一样,这个(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。

recover 只能在 defer 修饰的函数(参见 6.4 节)中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果。

总结:panic 会导致栈被展开直到 defer 修饰的 recover() 被调用或者程序中止。

下面是一个完整的示例
func badCall() {
	panic("bad end\n")
}

func test() {
	defer func() {
		if e := recover(); e != nil {
			fmt.Printf("panic %s\n", e)
		}
	}()
	badCall()
	fmt.Println("after bad call\n")
}

func main() {
	fmt.Println("call test\n")
	test()
	fmt.Println("test completed")
}

下面是执行结果
D:\go\test2>go run test.go
call test

panic bad end

test completed

程序说明:
1、最里层的函数panic,到当前语句终止,向上层函数抛异常,后面的语句不执行
2、中层函数中语句panic,到当前语句终止,后面的语句不执行,直接执行defer中的recover,里层函数里面的所有panic都可以捕获,并处理。中层函数执行完毕
3、如果中层函数将panic解决,上层函数无感知这个异常,继续后面的执行


13.4 

这是所有自定义包实现者应该遵守的最佳实践:
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic()
2)向包的调用者返回错误值(而不是 panic)。

在包内部,特别是在非导出函数中有很深层次的嵌套调用时,对主调函数来说用 panic 来表示应该被翻译成错误的错误场景是很有用的(并且提高了代码可读性)。

这在下面的代码中被很好地阐述了。我们有一个简单的 parse 包(示例 13.4)用来把输入的字符串解析为整数切片;这个包有自己特殊的 ParseError。

当没有东西需要转换或者转换成整数失败时,这个包会 panic(在函数 fields2numbers 中)。但是可导出的 Parse 函数会从 panic 中 recover 并用
所有这些信息返回一个错误给调用者。为了演示这个过程,在 panic_recover.go 中 调用了 parse 包(示例 13.4);不可解析的字符串会导致错误并被打印出来。


例程,此例程比较重要,将panic恢复,并经panic转换为错误,返回给上层函数
package parse

import (
	"fmt"
	"strings"
	"strconv"
)

type ParseError struct {
	Index int
	Word string
	Err error
}

func (e *ParseError) String() string {
	return fmt.Sprintf("pkg pars: error %q as int", e.Word)
}

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)
			}
		}
	}()

	field := strings.Fields(input)
	numbers = fields2numbers(field)
	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
}


package main

import (
	"fmt"
	"../abc"
)

func main() {
	var example = []string{
		"1 2 3 4 5",
		"1st class",
		"100 40 50 2 4",
	}
	for _, ex := range example {
		fmt.Printf("parsing %q\n", ex)
		nums,err := parse.Parse(ex)
		if err != nil {
			fmt.Println(err)
			continue
			}
		fmt.Println(nums)
	}
}


13.6 
os 包有一个 StartProcess 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,
第三个参数是含有系统环境基本信息的结构体。

这个函数返回被启动进程的 id(pid),或者启动失败返回错误。

exec 包中也有同样功能的更简单的结构体和函数;主要是 exec.Command(name string, arg ...string) 和 Run()。首先需要用系统命令或可执行文件
的名字创建一个 Command 对象,然后用这个对象作为接收者调用 Run()。

func main() {
	env := os.Environ()
	procAttr := &os.ProcAttr{
		Env: env,
		Files: []*os.File{
			os.Stdin,
			os.Stdout,
			os.Stderr,
		},
	}
	pid, err := os.StartProcess("/bin/ls",[]string{"ls", "-l"}, procAttr)
	if err != nil {
		fmt.Printf("error %v start process", err)
		os.Exit(1)
	}
	fmt.Printf("the process is %v", pid)
}

未经允许不得转载:江哥架构师笔记 » 第13章总结:错误处理与测试

分享到:更多 ()

评论 抢沙发

评论前必须登录!