Go语言错误处理

Go语言中内置了错误接口提供简单方便的错误处理机制。请注意,异常(Exception)和错误(Error)在概念上来讲是不一样的。Go语言只有错误,没有异常,并提供了针对错误的处理机制。其他语言只有异常处理机制,没有错误处理机制。

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

异常指的是不应该出现问题的地方出现了问题,比如引用了空指针,这种情况在人们的意料之外。

错误是业务逻辑的一部分,而异常不是

如果程序遇到错误不处理,那么可能进一步的产生业务上的错误,比如给用户多扣钱了,或者进一步产生了异常;如果程序遇到异常不处理,那么结果就是进程异常退出。

Go语言中error是一个接口类型,它的源码如下:

type error interface {
	Error() string
}

在函数中,如果检测到了错误,可以使用errors.New()返回错误信息。Go语言需要你自己详细判断错误发生情况,并返回错误信息。

实例:

package main

import (
	"errors"
	"fmt"
)

func divide(x, y int) (int, error) {
	if y != 0 {
		return x / y, nil
	} else {
		return 0, errors.New("Divisor cannot be zero")
	}
}

func printRes(res int, e error)  {
	if e != nil {
		fmt.Println(e)
	} else {
		fmt.Println(res)
	}
}

func main() {
	a := 10
	b := 2
	c := 0

	res1, e1 := divide(a, b)
	printRes(res1, e1)

	res2, e2 := divide(a, c)
	printRes(res2, e2)
}

// 5
// Divisor cannot be zero 

也可以简单的使用fmt.Errorf()定义错误信息,定义好的错误信息可以直接打印。

package main

import "fmt"

func main() {
	var e error = fmt.Errorf("Error occur")
	fmt.Println(e)
}

// Error occur

这里举了个简单的例子,fmt.Errorf()f是格式化的意思,可以传入参数,然后格式化输出,例fmt.Error("%d", a)

自定义错误

自定义错误需要给出该错误的结构体定义并实现error接口。

实例如下:

package main

import "fmt"

type divideZeroError struct {
	divident int
	divisor  int
}

func (ed *divideZeroError) Error() string {
	return "divisor zero error"
}

func divide(x, y int) (int, string) {
	if y != 0 {
		return x / y, ""
	} else {
		eData := divideZeroError {x, y}
		errorMsg := eData.Error()
		return 0, errorMsg
	}
}

func main() {
	a := 10
	b := 2
	c := 0

	if res, errorMsg := divide(a, b); errorMsg == "" {
		fmt.Println(res)
	}

	if _, errorMsg := divide(a, c); errorMsg != "" {
		fmt.Println(errorMsg)
	}
}

// 5
// divisor zero error

错误捕获

注意,很多博客会称为异常捕获,这个不太严谨的,因为Go语言没有异常这个概念,因此应该叫错误捕获

类似于try-catch-finally,Go语言中错误捕获采用的是panic()-defer-recover()try-catch-finally是三个关键字,panic()-defer-recover()是两个函数加一个关键字,panic对应的中文翻译为恐慌defer对应的中文翻译为推迟recover对应的中文翻译为恢复

Go语言中可以使用panic()函数抛出一个错误,然后在defer中通过recover()函数捕获异常进程后续处理。

panic()

内置函数,用于抛出错误,定义如下:

func panic(v interface {})

defer

预留关键字,延迟指定函数的执行。通常在资源释放,链接关闭、函数结束时调用。多个defer为堆栈结构,后进先出。defer可用于异常抛出后的处理。

defer用于添加函数结束时执行的语句,注意时添加不是指定,defer是动态的。

recover()

内置函数,用于获取异常(类似java中的catch),多次调用时只有第一次能获取值,定义如下:

func recover() interface{}

实例

package main

import "fmt"

func makeError() {
	fmt.Println("start to make a error")
	panic("error occur")
	fmt.Println("end to this error")
}

func catchError1() {
	fmt.Println("start to catch error, this is the first catch function")

	err := recover()
	if err != nil {
		fmt.Println("dispose first: " + fmt.Sprintf("%s", err))
	}

	fmt.Println("end to catch error, this is the first catch function")
}

func catchError2() {
	fmt.Println("start to catch error, this is the second catch function")
	
	err := recover()
	if err != nil {
		fmt.Println("dispose second: " + fmt.Sprintf("%s", err))
	}
	
	fmt.Println("end to catch error, this is the second catch function")
}

func invokeError() {
	fmt.Println("the method may invoke error start to run")

	defer catchError1() // 声明defer才会去捕获异常
	defer catchError2()

	makeError()
	makeError()

	fmt.Println("the method may invoke error start to end")
}

func testMethod() {
	fmt.Println("test function start")
	invokeError()
	fmt.Println("test function end")
}

func main() {
	testMethod()
}

结果:

test function start
the method may invoke error start to run
start to make a error
start to catch error, this is the second catch function
dispose second: error occur
end to catch error, this is the second catch function
start to catch error, this is the first catch function
end to catch error, this is the first catch function
test function end

请注意,关键字defer后面的函数都会被推迟执行,因此先执行了第一个makeError(),然后再执行了catchError1()catchError2(),因为defer的堆栈结构,后进先出,因此先执行catchError2(),即第二个捕获异常的函数中的recover()成功捕获到了panic()抛出的异常,并正常处理。catchError2执行完毕后,catchError1()开始执行,此时没有捕获到异常,函数invokeError()执行终止,后续的语句没有被执行。

也就是说defer之后,函数将结束执行,因此有人把defer和C++的析构函数类比,称C++的析构函数析构类,Go的defer析构函数。这种说法是不准确的,析构函数的重要作用是对象生命周期结束前执行一些操作,并最终释放对象占据的存储空间,而defer只是在函数结束前执行一些操作,并不会释放空间。

错误处理优劣

知乎讨论:Go 语言的错误处理机制是一个优秀的设计吗?

这个有一些争议,设计者的初衷是try-catch-finally将异常和控制结构混在一起容易使得代码变得混乱。因为开发者很容易滥用异常,以至于小小的错误都会去抛出异常。在Go语言中,函数可以有多个返回值,因此可以返回的时候带上错误。因此Go不使用异常来代替错误,也不使用控制流程,只有遇到真的错误而不是异常的时候,Go才有去处理它。

参考文献

golang捕获异常

go中异常处理

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值