前言
如介绍函数章节里所说的,在Go语言里,没有try...catch这样的异常处理机制,不能像java那样执行抛异常操作, 但是在Go语言里可以使用defer...recover...panic的机制来实现类似try...catch的效果;今天我们就专门来探讨这个话题;
Go里为什么不设计try...catch异常机制
java里使用try/catch 机制,在函数定义声明Expception的使用层次上不够简洁,调用时对异常的处理太泛滥,并且使用内存栈空间从底层向更高的层级抛异常太耗费资源。Go设计的机制没有使用java里try...catch类似的异常机制,但是也实现了 “捕捉” 异常放入机制,但是更轻量,且只作为(处理错误的)最后的手段。
GO里预定义了一个error接口类型;错误值用来表示错误状态, 错误类型也是种数据类型,和其他数据类型一样,可以作为参数也可以作为返回值
type error interface {
Error() string
}
错误定义
在GO语言里,没有类似Java那么强的面向对象的特性, 可以通过结构体实现Error方法,来自定义一个错误对象类型;例如
type NullException struct {
}
func (ne *NullException) Error() string {
return "NullPointException"
}
func Trim(s *string) (string, error) {
if s == nil {
return "", &NullException{}
} else {
return strings.TrimSpace(*s), nil
}
}
如上述代码,就自行定义了一个异常对象类型NullException; 可以通过 err:=&NullException{}生成一个错误对象;
上面是通过type struct来自定义错误; 除了这种方式,还可以使用errors包里的内置函数errors.New来产生一个新的错误类型对象;如下面代码:
func Trim(s *string) (string, error) {
if s == nil {
return "", errors.New("NullPointException")
} else {
return strings.TrimSpace(*s), nil
}
}
这种方式相对于上面的方式来说,代码上更简单了,在很多的源代码里都是类似这样的写法; 下面来看看一个完整的实例
func TestException(t *testing.T) {
var s *string
if rtn, err := Trim(s); err == nil {
fmt.Printf("Trim(%v)=%v \n", s, rtn)
} else {
fmt.Printf("Trim(%v) throw exception: %v \n", s, err)
}
var a string
if rtn, err := Trim(&a); err == nil {
fmt.Printf("Trim(%v)=%v \n", &a, rtn)
} else {
fmt.Printf("Trim(%v) throw exception: %v \n", &a, err)
}
}
===== OUTPUT =====
=== RUN TestException
Trim(<nil>) throw exception: NullPointException
Trim(0xc000273a10)=
--- PASS: TestException (0.00s)
PASS
在上面的代码中,第一次调用Trim(s),s定义为string类型的指针,对于指针类型的变量,定义后自动初始化为nil,所以Trim(s)调用会返回NullExpcetion对象; 也就打印出Trim(<nil>) throw exception: NullPointException ;
对于第二个Trim(&a);a定义为string类型对象,string基础类型对象,定义后自动初始化为空字符串(""),所以Trim(&a)调用,传入的string指针不为空nil(而是空字符串""变量的指针),此时返回的错误对象是nil,没有错误;返回的是空字串串Trim以后的结果,还是空字符串;也就打印出Trim(0xc000273a10)=
错误的判断
有的时候在函数处理的过程中,可以会有不同类型的错误条件发生;比如对于文件处理的场景来说;有时候可能是文件路径错误,有的时候可能是文件的状态错误;当多种错误发生的时候,对错误的判断就有使用场景的价值了。使用类型断言或类型判断(type-switch)是处理这种场景非常高效的方法,并且可以 根据错误场景做一些补救和恢复操作。
来看看下面的代码
type NotFoundException struct {
}
func (ne NotFoundException) Error() string {
return "NotFoundException"
}
type NotAllowException struct {
}
func (ne NotAllowException) Error() string {
return "NotAllowException"
}
func ReadFile(path string) ([]byte, error) {
n := len(path)
if n <= 1 {
return nil, NotFoundException{}
} else if n <= 12 {
return nil, NotAllowException{}
}
return []byte(path), nil
}
func ReadOne(path string) {
if rtn, err := ReadFile(path); err == nil {
fmt.Printf("ReadFile(%v)=%v \n", path, rtn)
} else {
switch err.(type) {
case NotFoundException:
fmt.Printf("ReadFile(%v) throw NotFoundException: %v \n", path, err)
case NotAllowException:
fmt.Printf("ReadFile(%v) throw NotAllowException: %v \n", path, err)
default:
fmt.Printf("ReadFile(%v) throw UnknowException: %v \n", path, err)
}
}
}
上面的代码ReadOne函数里,就有错误判断的处理方法; 在ReadOne函数里调用ReadFile函数,在ReadFile函数里简单的实现了,如果path长度不大于1,返回错误NotFoundException;如果path长度不大于12;返回错误NotAllowException;其他的就正常返回;
ReadFile函数错误类型就可能返回多种类型;ReadOne函数就是通过type-swtich的方式,对err对象的类型进行判断; 如果是NotFoundException就执行一段逻辑;如果是NotAllowException错误类型就执行另一端逻辑;如果两种错误类型都没有匹配上就执行第三段逻辑; 错误类型NotFoundException和NotAllowException都在ReadOne函数上方进行了定义来一个现实的调用代码:
func TestException2(t *testing.T) {
var s string
s = "/"
ReadOne(s)
s = "/readme.txt"
ReadOne(s)
s = "/home/readme.txt"
ReadOne(s)
}
===== OUTPUT =====
=== RUN TestException2
ReadFile(/) throw NotFoundException: NotFoundException
ReadFile(/readme.txt) throw NotAllowException: NotAllowException
ReadFile(/home/readme.txt)=[47 104 111 109 101 47 114 101 97 100 109 101 46 116 120 116]
--- PASS: TestException2 (0.00s)
PASS
结束语
错误处理是每种编程语言里需要面对的问题,也是编程过程中必须考虑的一个问题;错误处理如果做的好的话,代码的稳定性会很好。今天的这个文章通过代码实例给大家介绍了GO语言里有关错误处理的一些学问;大家也多用代码练习一下,写成更稳定的程序。
欢迎大家继续关注 GO语言编程训练