Golang中错误处理
前言
在起初golang基础并未打扎实直接去学后端的gin框架,但在之前并未学习
go的错误处理,在学习gin时发现在写代码的时候几乎有将近一半的代码都在
进行错误处理日志收集。的确这样做下来在debug的时候会比较的轻松。但是我一点没有系统的看过go中的errors里的方法,只是跟着gin里的项目学
习了zap日志库与errors的一些方法配合使用。今天就看一下Go核心编程里的错误处理。
一、基本概念与使用
error类型其实是一个接口类型,也是一个 Go 语言的内建类型。在这个接口类型的声明中只包含了一个方法Error。Error方法不接受任何参数,但是会返回一个string类型的结果。它的作用是返回错误信息的字符串表示形式。我们使用error类型的方式通常是,在函数声明的结果列表的最后,声明一个该类型的结果,同时在调用这个函数之后,先判断它返回的最后一个结果值是否“不为nil”。
比如代码中所写:
package main
import (
"errors"
"fmt"
)
func echo(request string) (response string, err error) {
if request == "" {
err = errors.New("empty request")
return
}
response = fmt.Sprintf("echo: %s", request)
return
}
func main() {
for _, req := range []string{
"", "hello!"} {
fmt.Printf("request: %s\n", req)
resp, err := echo(req)
if err != nil {
fmt.Printf("error: %s\n", err)
continue
}
fmt.Printf("response: %s\n", resp)
}
}
代码中两个值得注意的地方
第一,在echo函数和main函数中,都使用到了卫述语句。简单地讲,它就是被用来检查后续操作的前置条件并进行相应处理的语句。也就是我在前言提到的在写代码时铺天盖地的语句。
第二对于echo函数来说,它进行常规操作的前提是:传入的参数值一定要符合要求。而对于调用echo函数的程序来说,进行后续操作的前提就是echo函数的执行不能出错。
二、对于具体错误的判断,Go 语言中都有哪些惯用法?
对于类型在已知范围内的一系列错误值,一般使用类型断言表达式或类型switch语句来判断;
对于已有相应变量且类型相同的一系列错误值,一般直接使用判等操作来判断;
对于没有相应变量且类型未知的一系列错误值,只能使用其错误信息的字符串表示形式来做判断。而这种是我常用的一种方法。
对于第一种情况,如果我们已知错误类型
就有以下案例:
func underlyingError(err error) error {
switch err := err.(type) {
case *os.PathError:
return err.Err
case *os.LinkError:
return err.Err
case *os.SyscallError:
return err.Err
case *exec.Error:
return err.Err
}
return err
}
函数underlyingError的作用是:
获取和返回已知的操作系统相关错误的潜在错误值。其中的类型switch语句中有若干个case子句,分别对应了上述几个错误类型。当它们被选中时,都会把函数参数err的Err字段作为结果值返回。如果它们都未被选中,那么该函数就会直接把参数值作为结果返回,即放弃获取潜在错误值。
三、怎样根据实际情况给予恰当的错误值?
构建错误值体系的基本方式有两种,即:
创建立体的错误类型体系和创建扁平的错误值列表。
对于立体的错误类型体系。由于在 Go 语言中实现接口是非侵入式的,所以我们可以做得很灵活。
例如在标准库的net代码包中,有一个名为Error的接口类型。它算是内建接口类型error的一个扩展接口,因为error是net.Error的嵌入接口
立体的错误体系可以把一个包的错误类型想象成一棵树,内建接口error就是树的根,而net.Error接口就是一个在根上延伸的第一级非叶子节点。
首先判断它是否是net.Error类型的,也就是说该值是否代表了一个网络相关的错误。如果是,那么我们还可以再进一步判断它的类型是哪一个更具体的错误类型,这样就能知道这个网络相关的错误具体是由于操作不当引起的,还是因为网络地址问题引起的,又或是由于网络协议不正确引起的。
这样就可以模拟出立体的一个体系一层一层的去判断。
用类型建立起树形结构的错误体系,用统一字段建立起可追根溯源的链式错误关联。
对于扁平的错误值列表
当我们只是想预先创建一些代表已知错误的错误值时候,扁平的
错误值列表无疑是最好的选择。
但是问题就来了
由于error是接口类型,所以通过errors.New函数生成的错误值只能被赋给变量,而不能赋给常量,又由于这些代表错误的变量需要给包外代码使用,所以其访问权限只能是公开的。
如果说恶意代码改变了这些公开变量的值,那么程序的功能就必然会受到影响。因为在这种情况下我们往往会通过判等操作来判断拿到的错误值具体是哪一个错误,如果这些公开变量的值被改变了,那么相应的判等操作的结果也会随之改变。
当然对此也有解决的方案:
先私有化此类变量,也就是说,让它们的名称首字母变成小写,然后编写公开的用于获取错误值以及用于判等错误值的函数。
比如,对于错误值os.ErrClosed,先改写它的名称,让其变成os.errClosed,然后再编写ErrClosed函数和IsErrClosed函数。