Go函数--error接口

1 Go语言错误处理思想

Go语言的错误处理思想和设计原理包含以下特征:

  • 一个可能发生错误的函数,需要返回值中返回一个错误接口(error)。如果函数调用是成功的,错误接口返回 nil,否则返回错误。
  • 在函数调用后需要检查错误,如果发生错误,进行必要的错误处理。

<提示> Go语言没有类似Java、.Net中的异常处理机制,虽然可以使用defer、panic、recover模拟,但是官方并不主张这样做。Go语言的设计者认为其他语言的异常机制已被过度使用,上层逻辑需要为函数发生的异常付出太多的资源。同时,如果函数使用者觉得错误处理很麻烦而忽略错误,那么程序将在不可预知的时刻崩溃。

Go语言希望开发者将错误处理视为正常开发必须实现的环节,正确地处理每一个可能发生错误的函数。同时,Go语言使用返回值返回错误的机制,也能大幅降低编译器、运行时处理错误的复杂度,让开发者真正地掌握错误的处理。

2 error接口的定义格式

// src/builtin/builtin.go
type error interface {
    Error() string
}

所有符合 Error() string 格式的方法,都能实现错误接口。Error()方法返回错误的具体描述信息,使用者可以通过这字符串知道发生了什么错误。

Go语言标准库提供了两个函数返回实现了 error 接口的具体类型实例,一般的错误可以使用这两个函数进行封装。遇到复杂的错误,用户可以自定义错误类型,只要实现 error 接口的 Error()方法即可。

2.1 errors包 -- errors.New() 方法

// src/errors/errors.go
// 创建错误对象
func New(text string) error {
    return &errorString{text}
}

// 声明错误字符串结构体
type errorString struct {
    s string
}

// 实现error接口的Error()方法,返回错误描述
func (e *errorString) Error() string {
    return e.s
}

示例1:除数为0的错误。

import (
    "errors"    //需要导入errors包
    "fmt"
)

//定义一个除数为0的错误对象
var errDivisionByZero = errors.New("division by zero")

func div(dividend int, divisor int) (int, error){
    //判断除数为0的情况并返回
    if divisor == 0 {
        return 0, errDivisionByZero
    }
    //正常计算,返回空错误
    return dividend / divisor, nil
}

func main(){
    fmt.Println(div(24, 8)) //3 <nil>
    fmt.Println(div(8, 0))  //0 division by zero
}

《代码说明》当div()函数返回除数为0的错误对象时,它会自动调用该结构体类型已经实现的Error()方法,从而输出错误字符串描述信息。

2.2 fmt包 -- fmt.Errorf() 方法

// src/fmt/errors.go
func Errorf(format string, a ...interface{}) error

说明》该fmt.Errorf()函数返回一个格式化内容的错误对象。

示例2:修改上面的代码,改用fmt.Errorf()函数。

import (
    "fmt"
)

func div(dividend int, divisor int) (int, error){
    //判断除数为0的情况并返回
    if divisor == 0 {
        errDivisionByZero := fmt.Errorf("division by zero")
        return 0, errDivisionByZero
    }
    //正常计算,返回空错误
    return dividend / divisor, nil
}

func main(){
    fmt.Println(div(24, 8)) //3 <nil>
    fmt.Println(div(8, 0))  //0 division by zero
}

2.3 自定义错误类型

使用 errors.New() 定义的错误字符串的错误类型是无法提供丰富的错误信息的。如果需要携带多种错误信息返回,就需要借助自定义错误结构体类型并实现error接口来实现。

示例3:实现一个解析错误(ParseError)结构体类型,这个错误结构体包含两个内容:文件名和行号。解析错误结构体需要实现error接口的Error()方法,返回错误描述时,就需要将文件名和行号返回。

//声明一个自定义错误类型结构体
type ParseError struct{
    Filename string
    Line     int
}

//实现error接口的Error()方法,返回错误描述
func (e *ParseError) Error() string {
    return fmt.Sprintf("%s:%d", e.Filename, e.Line)
}

//创建解析错误函数,返回的是结构体对象指针
func newParseError(filename string, line int) error{
    return &ParseError{filename, line}
}

func main(){
    var e error
    //创建一个错误实例,包含文件名和行号
    e = newParseError("demo.go", 10)
    //通过error接口查看错误信息
    fmt.Println(e.Error())
    
    //根据错误接口的具体类型,获取详细的错误信息
    switch detail := e.(type){
        case *ParseError:
            fmt.Printf("Filename: %s, Line: %d\n", detail.Filename, detail.Line)
        default:
            fmt.Println("other error")
    }
}

运行结果:

demo.go:10
Filename: demo.go, Line: 10

<提示> 自定义错误结构体类型都要实现error 接口的Error()方法,这样,所有的错误都可以获得字符串的描述。如果想进一步知道错误的详细信息,可以通过类型断言,将错误对象转换为具体的错误类型进行错误详细信息的获取。

3 错误处理的最佳实践

1、在多个返回值的函数中,error 通常作为函数最后一个返回值。

2、如果一个函数返回 error类型变量,则先用if 语句处理 error != nil 的异常情况,正常逻辑放到if 语句块的后面,保持代码平坦。

3、defer 语句应该放到 err 判断的后面,不然有可能产生 panic。示例代码如下:

// 正确写法
f, err := os.Open("defer.txt")
if err != nil {
    return nil, errors.New("Open file failed")
}
defer f.Close()

// 错误写法
f, err := os.Open("defer.txt")

defer f.Close()

if err != nil {
    return nil, errors.New("Open file failed")
}

// 如果f为空,就不能调用f.Close()函数了,会直接导致程序panic。


4、在错误逐级向上层传递的过程中,错误信息应该不断地丰富和完善,而不是简单地抛出下层调用的错误。这在错误日志分析时非常有用和友好。

参考

《Go语言从入门到进阶实战(视频教学版)》

《Go语言核心编程》

《Go语言学习笔记》

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值