Featured image of post golang学习十五:错误异常处理

golang学习十五:错误异常处理

错误

  • 在程序执行过程中出现的不正常情况称为错误
  • Go语言中使用builtin包下error接口作为错误类型,官方源码定义如下
    • 只包含了一个方法,方法返回值是string,表示错误信息
1
2
3
4
5
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}
  • Go语言中错误都作为方法/函数的返回值,因为Go语言认为使用其他语言类似try…catch这种方式会影响到程序结构
  • 在Go语言标准库的errors包中提供了error接口的实现结构体errorString,并重写了error接口的Error()方法.额外还提供了快速创建错误的函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package errors

// New returns an error that formats as the given text.
func New(text string) error {
	return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
	s string
}

func (e *errorString) Error() string {
	return e.s
}
  • 如果错误信息由很多变量(小块)组成,可以借助fmt.Errorf("verb",...)完成错误信息格式化,因为底层还是errors.New()
1
2
3
4
5
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
	return errors.New(Sprintf(format, a...))
}

自定义错误

  • 使用Go语言标准库创建错误,并返回
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func demo(i, k int) (d int, e error) {
	if k == 0 {
		e = errors.New("初始不能为0")
		d=0
		return
	}
	d = i / k
	return
}

func main() {
	result,error:=demo(6,0)
	fmt.Println(result,error)
}
  • 如果错误信息由多个内容组成,可以使用下面实现方式
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func demo(i, k int) (d int, e error) {
	if k == 0 {
		e = fmt.Errorf("%s%d和%d", "除数不能是0,两个参数分别是:", i, k)
		d = 0
		return
	}
	d = i / k
	return
}

func main() {
	result, error := demo(6, 0)
	fmt.Println(result, error)
}

Go语言中错误处理方式

  • 可以忽略错误信息,使用占位符
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
func demo(i, k int) (d int, e error) {
	if k == 0 {
		e = fmt.Errorf("%s%d和%d", "除数不能是0,两个参数分别是:", i, k)
		d = 0
		return
	}
	d = i / k
	return
}

func main() {
	result, _ := demo(6, 0)
	fmt.Println(result)
}
  • 使用if处理错误,原则上每个错误都应该解决
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func demo(i, k int) (d int, e error) {
	if k == 0 {
		e = fmt.Errorf("%s%d和%d", "除数不能是0,两个参数分别是:", i, k)
		d = 0
		return
	}
	d = i / k
	return
}

func main() {
	result, error := demo(6, 0)
	if error != nil {
		fmt.Println("发生错误", error)
		return
	}
	fmt.Println("程序执行成功,结果为:", result)
}

defer使用

  • Go语言中defer可以完成延迟功能,当前函数执行完成后执行defer功能
  • defer最常用的就是关闭连接(数据库连接,文件等)可以打开连接后代码紧跟defer进行关闭,后面在执行其他功能
    • 在很多语言中要求必须按照顺序执行,也就是必须把关闭代码写在最后,但是经常会忘记关闭导致内存溢出,而Golang中defer很好的解决了这个问题.无论defer写到哪里都是最后执行
1
2
3
4
5
6
7
8
func main() {
   fmt.Println("打开连接")
   defer func(){
      fmt.Println("关闭连接")
   }()
   fmt.Println("进行操作")
   //输出:打开连接 进行操作 关闭连接
}

多个defer

  • 多重defer采用栈结构执行,先产生后执行
  • 在很多代码结构中都可能出现产生多个对象,而程序希望这些对象倒序关闭,多个defer正好可以解决这个问题
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
   fmt.Println("打开连接A")
   defer func(){
      fmt.Println("关闭连接A")
   }()
   fmt.Println("打开连接B")
   defer func(){
      fmt.Println("关闭连接B")
   }()
   fmt.Println("进行操作")
   //输出:打开连接A 打开连接B 进行操作 关闭连接B 关闭连接A
}

defer和return结合

  • defer与return同时存在时,要把return理解成两条执行结合(不是原子指令),一个指令是给返回值赋值,另一个指令返回跳出函数

  • defer和return时整体执行顺序

    • 先给返回值赋值
    • 执行defer
    • 返回跳出函数
  • 没有定义返回值接收变量,执行defer时返回值已经赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func f() int{
	i:=0
	defer func(){
		i=i+2
	}()
	return i
}

func main() {
	fmt.Println(f())//输出:0
}
  • 声明接收返回值变量,执行defer时修改了返回值内容.
    • 由于return后面没有内容,就无法给返回值赋值,所以执行defer时返回值才有内容
1
2
3
4
5
6
7
8
9
func f() (i int){
	defer func(){
		i=i+2
	}()
	return
}
func main() {
	fmt.Println(f())//输出:2
}

panic

  • panic是builtin中函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated and the error condition is reported,
// including the value of the argument to panic. This termination sequence
// is called panicking and can be controlled by the built-in function
// recover.
func panic(v interface{})
  • panic有点类似与其他编程语言的throw,抛出异常.当执行到panic后终止剩余代码执行.并打印错误栈信息
1
2
3
4
5
func main() {
   fmt.Println("1")
   panic("panic执行了,哈哈")
   fmt.Println("2")
}
  • 执行结果
1
2
3
4
5
6
1
panic: panic执行了,哈哈

goroutine 1 [running]:
main.main()
	D:/gowork/c/main.go:7 +0x80
  • 注意panic不是立即停止程序(os.Exit(0)),defer还是执行的.
1
2
3
4
5
6
7
8
func main() {
   defer func(){
      fmt.Println("defer执行")
   }()
   fmt.Println("1")
   panic("panic执行了,哈哈")
   fmt.Println("2")
}

recover

  • recover()表示恢复程序的panic(),让程序正常运行
  • recover()是和panic(v)一样都是builtin中函数,可以接收panic的信息,恢复程序的正常运行
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() interface{}
  • recover()一般用在defer内部,如果没有panic信息返回nil,如果有panic,recover会把panic状态取消
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func main() {
	defer func() {
		if error:=recover();error!=nil{
			fmt.Println("出现了panic,使用reover获取信息:",error)
		}
	}()
	fmt.Println("11111111111")
	panic("出现panic")
	fmt.Println("22222222222")
}
  • 输出
1
2
11111111111
出现了panic,使用reover获取信息: 出现panic

函数调用过程中panic和recover()

  • recover()只能恢复当前函数级或当前函数调用函数中的panic(),恢复后调用当前级别函数结束,但是调用此函数的函数可以继续执行.
  • panic会一直向上传递,如果没有recover()则表示终止程序,但是碰见了recover(),recover()所在级别函数表示没有panic,panic就不会向上传递
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func demo1(){
	fmt.Println("demo1上半部分")
	demo2()
	fmt.Println("demo1下半部分")
}
func demo2(){
	defer func() {
		recover()//此处进行恢复
	}()
	fmt.Println("demo2上半部分")
	demo3()
	fmt.Println("demo2下半部分")
}
func demo3(){
	fmt.Println("demo3上半部分")
	panic("在demo3出现了panic")
	fmt.Println("demo3下半部分")
}
func main() {
	fmt.Println("程序开始")
	demo1()
	fmt.Println("程序结束")
}

golang学习一:从环境配置开始到HelloWorld入门 golang学习二:golang自带的工具 olang学习三:golang基础语法 golang学习四:流程控制 golang学习五:常用数学函数与数组 golang学习六:for循环 golang学习七:goto和label golang学习八:切片 golang学习九:sort包、map、双向链表、双向循环链表 golang学习十:函数 golang学习十一:包的访问权限、变量作用域、闭包 golang学习十二:值传递和引用传递 golang学习十三:结构体 golang学习十四:golang中的面向对象 golang学习十五:错误异常处理 golang学习十六:文件操作 golang学习十七:反射 golang学习十八:XML操作 golang学习十九:日志 golang学习二十:golang并发编程入门 golang学习二十一:select和GC

Licensed under CC BY-NC-SA 4.0