4.6. 处理运行时错误

Go语言的错误处理私信及设计包含一些特征:

  • 一个可能造成错误的函数,需要返回值中返回一个错误接口(error).如果调用是成功的。错误接口将返回nil,否则返回错误。

  • 在函数调用后需要检查错误,如果发生错误,进行必要的错误处理。

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

4.6.1. 1.net包中的例子

net.Dial()是Go语言系统包net即中的一个函数,一般用于创建Socket连接。

net.Dial()拥有两个返回值,即Conn和error,这个函数是阻塞的,因此在Socket操作后,会返回Conn连接对象和error;如果发生错误,error会告知错误的类型,Conn会返回为空。

根据Go语言的错误处理机制,Conn是其重要的返回值,因此为这个函数增加了一个错误返回, 类似为error,参见如下代码:

func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}

在io包中的Writer接口也拥有错误返回,代码如下:

type Writer interface {
    Write(p []byte) (n int, err error)
}

io包中还有一个Closer接口,只有一个错误返回,代码如下:

type Closer interface {
    Close() error
}

4.6.2. 2.错误接口的定义格式

error是Go系统声明的接口类型,代码如下:

type error interface {
    Error() string
}

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

4.6.3. 3.自定义一个错误

返回错误前,需要定义会生成哪些可能的错误。在Go语言中,使用errors包进行错误的定义,格式如下:

var err = errors.New("this is an error")

错误字符串由于相对固定,一般在包作用域声明,应尽量减少在使用时直接使用errors.New返回。

3.1 errors包

Go语言的errors中对New的定义非常简单,代码如下:

// 创建错误对象
// 将errorString结构体实例化,并赋值错误描述的成员。
func New(text string) error {
    return &errorString{text}
}



// 错误字符串
// 声明errorString结构体,拥有一个成员,描述错误内容
type errorString struct {
    s string
}

// 返回发生任何错误
// 实现error接口的Error()方法,该方法返回成员中的错误描述
func (e *errorString) Error() string {
    return e.s
}

3.2 在代码中使用错误定义

package main

import (
    "errors"
    "fmt"
)

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

// 除法函数,定义传入2个数都为int,返回一个int或error
func div(dividend, divisor int) (int, error) {
    // 判断除数为0的情况并返回
    if divisor == 0 {
        return 0, errDivisionByZero
    }
    // 返回计算后的值,error为nill
    return dividend / divisor, nil
}

func main() {
    fmt.Println(div(1, 0))  //0 division by zero
}

3.3 示例:在解析中使用自定义错误

使用errors.New定义的错误字符串的错误类型是无法提供丰富的错误信息的。那么需要携带错误信息返回, 就需要借助自定义结构体实现错误接口。

package main

import "fmt"

// 声明一个解析错误的结构体,解析错误包含2个成员,filename和line
type ParseError struct {
    Filename string // 文件名
    Line     int    // 行号
}

// 实现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("main.go", 1)
    // 通过error接口插件错误描述
    fmt.Println(e.Error())      //main.go 1

    // 根据错误接口的具体类型,获取详细的错误信息
    switch detail := e.(type) {
    case *ParseError: // 这是一个解析错误
        fmt.Printf("Filename: %s Line: %d\n", detail.Filename, detail.Line) //Filename: main.go Line: 1
    default:
        fmt.Println("other error")

    }

}