[Golang]-4 错误处理、Panic、Defer


Go 语言使用一个独立的·明确的返回值来传递错误信息的。这与使用异常的 Java 和 Ruby 以及在 C 语言中经常见到的超重的单返回值/错误值相比,

Go 语言的处理方式能清楚的知道哪个函数返回了错误,并能像调用那些没有出错的函数一样调用。

错误和异常

  • 错误:指的是可能出现问题的地方出现了问题;
    比如打开一个文件时失败,这种情况在人们的意料之中 ;

  • 异常:指的是不应该出现问题的地方出现了问题;
    比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是 。

Golang中引入error接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含error。
error处理过程类似于C语言中的错误码,可逐层返回,直到被处理。

案例

package main

import (
	"errors"
	"fmt"
)

// 按照惯例,错误通常是最后一个返回值并且是 error 类型,一个内建的接口。
func f1(arg int) (int, error) {
	if arg == 42 {
		// errors.New 构造一个使用给定的错误信息的基本error 值。
		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 中是一个普遍的用法。
	// range 提供每项的索引和值。我们不需要索引,所以们使用空值定义符_来忽略它。有时候是需要这个索引的。
	for _, i := range []int{7, 42} {
		if r, e := f1(i); e != nil {
			fmt.Println("f1 failed:", e)
		} else {
			fmt.Println("f1 worked:", r)
		}
	}
	for _, i := range []int{7, 42} {
		if r, e := f2(i); e != nil {
			fmt.Println("f2 failed:", e)
		} else {
			fmt.Println("f2 worked:", r)
		}
	}

	// 你如果想在程序中使用一个自定义错误类型中的数据,你需要通过类型断言来得到这个错误类型的实例。
	_, e := f2(42)
	if ae, ok := e.(*argError); ok {
		fmt.Println(ae.arg)
		fmt.Println(ae.prob)
	}
}

输出结果:

f1 worked: 10
f1 failed: can't work with 42
f2 worked: 10
f2 failed: 42 - can't work with it
42
can't work with it

Golang中引入两个内置函数panic (['pænɪk]恐惧)和recover ([.ri'kʌvər]恢复)来触发和终止异常处理流程,同时引入关键字defer([dɪ'fɜr])来延迟执行defer后面的函数。

Panic

panic 意味着有些出乎意料的错误发生。通常我们用它来表示程序正常运行中不应该出现的,或者我们没有处理好的错误。

import "os"

func main() {

	// 我们将使用 panic 来检查预期外的错误。这个是为 panic 准备的例子。
	panic("a problem")

	// panic 的一个基本用法就是在一个函数返回了错误值但是我们并不知道(或者不想)处理时终止运行。
	// 这里是一个在创建一个新文件时返回异常错误时的panic 用法。
	_, err := os.Create("/tmp/file")
	if err != nil {
		panic(err)
	}
}
输出:
panic: a problem

goroutine 1 [running]:
main.main()
	/.../panic.go:10 +0x45

Defer

Defer 被用来确保一个函数调用在程序执行结束前执行。同样用来执行一些清理工作。 defer 用在像其他语言中的 ensure 和 finally用到的地方。

假设我们想要创建一个文件,向它进行写操作,然后在结束时关闭它。这里展示了如何通过 defer 来做到这一切。

import (
	"fmt"
	"os"
)

func main() {
	// 在 closeFile 后得到一个文件对象,我们使用 defer通过 closeFile 来关闭这个文件。
	// 这会在封闭函数(main)结束时执行,就是 writeFile 结束后。
	f := createFile("defer.txt")
	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()
}
输出:
creating
writing
closing

明显,这个文件在写入后是已关闭的。

使用 defer+recover 来处理错误

import (
	"fmt"
)

func main() {
	// 使用defer + recover来捕获和处理异常
	defer func() {
		// recover 是内置函数,可以捕获异常
		err := recover()
		if err != nil {
			fmt.Println("err=", err)
		}
	}()
	num1 := 10
	num2 := 0
	res := num1 / num2
	fmt.Println("res=", res)
}
输出:
err= runtime error: integer divide by zero

参考文章:

posted @ 2020-12-15 23:44  哆啦梦乐园  阅读(162)  评论(0编辑  收藏  举报