sunwenfei
  • 关于我
  • Golang
    • Goglang基础教程【译】
      • 介绍
        • 安装
        • Hello World
      • 变量、基本类型以及常量
        • 变量
        • 基本类型
        • 常量
      • 函数和包
        • 函数
        • 包
      • 条件、循环流程控制语句
        • if else条件语句
        • switch语句
        • 循环语句
      • 数组、切片、变参函数
        • 数组(Array)
        • 切片(Slice)
        • 变参函数
      • 其他数据类型
        • 映射(Map)
        • 字符串
      • 指针、结构体和方法
        • 指针
        • 结构体
        • 方法
      • 面向对象编程
        • 结构体 vs 类
        • 组合 vs 继承
        • 接口
        • 多态
      • 并发
        • 并发介绍
        • 协程(goroutine)
        • 管道(channel)
        • 带缓存的管道(buffered channel)
        • 协程池
        • 管道选择器(select)
        • 互斥锁(Mutex)
      • Defer
      • 一等公民函数
      • 反射
      • 错误
        • 错误处理
        • 自定义错误类型
        • panic和recover
      • 文件读写
        • 读文件
        • 写文件
    • Golang面向对象编程
    • Golang函数式编程
    • Golang并发编程
    • Golang web服务编程
    • Golang数据结构与算法
  • Shell编程
    • Find命令
  • JavaScript
    • browser
    • Node.JS
    • Deno
  • TypeScript
  • HTTP
    • 【译】通过信鸽理解HTTPS交互原理
  • React
    • React16
      • Hooks
        • 使用React Hooks拉取数据
  • 移动端开发
    • 原生
    • Flutter
    • ReactNative
    • 小程序
  • 前端测试
Powered by GitBook
On this page

Was this helpful?

  1. Golang
  2. Goglang基础教程【译】
  3. 错误

错误处理

Golang中的错误类型

程序执行的过程中会发生的异常情况称为错误。比如打开一个文件系统不存在的文件就会发生错误。

Golang中有个专门用来表示错误的接口类型:error。

跟int、float64等其他数据类型类似,error类型的值也可以拿来赋值给变量,传参给函数,或者作为函数返回值。

举例

我们举个例子,尝试打开一个系统不存在的文件。

package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}

这里我们尝试打开路径为/test.txt的文件。os.Open函数的签名如下:

func Open(name string) (file *File, err error))

如果文件打开成功,则Open函数返回值file为该文件的指针,同时返回值err为nil。如果读取文件发生异常,比如文件不存在,则返回值err不为nil。

在Golang中,如果一个函数/方法会返回错误,一般都是作为最后一个返回值,这里的Open函数返回的错误就是位于第二个位置,也就是最后一个位置。

通常情况下我们都是将函数返回的error字段跟nil比较。如果是nil,则表示无错误,否则表示有错误发生。

前面的程序我们就是将error跟nil比较,来判断是否正常打开文件。该程序执行结果为:

open /test.txt: No such file or directory

很明显,该错误信息表示文件/test.txt不存在(我的机器确实不存在该文件,你的机器应该也不会凑巧存在该文件吧)。

error接口类型

Golang中error是一个接口类型,定义如下:

type error interface {
    Error() string
}

该接口仅包含一个方法Error() string,该方法用来表示错误信息。任何实现了该接口的类型都是错误类型。当我们通过fmt.Println打印一个错误类型的变量时,其实就是调用其Error() string方法获取错误信息并打印。

获取更多错误信息

Golang中的error是一个接口类型,现在我们看下如果从error变量中提取更多错误相关的信息。

前面的例子中我们打印出了错误的描述信息,但是如果我们想获取发生错误的文件路径怎么实现呢?

这肯定是能够实现的,比如我们打印出的错误信息里面就有这个信息,可以解析出来。但是直接从错误文本信息中解析文件路径非常不明智,我们应该寻找更加正规靠谱的方法。

1.断言error具体类型并通过其属性获取信息

type PathError struct {  
    Op   string
    Path string
    Err  error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

从Error()函数的实现我们就能理解前面的输出了:

open /test.txt: No such file or directory

结构体类型PathError的属性Path就是用来表示文件路径的,因此我们可以读取该字段来获取文件路径。

package main

import (  
    "fmt"
    "os"
)

func main() {  
    f, err := os.Open("/test.txt")
    if err, ok := err.(*os.PathError); ok {
        fmt.Println("File at path", err.Path, "failed to open")
        return
    }
    fmt.Println(f.Name(), "opened successfully")
}

这里我们通过类型断言来获取了error接口内部的具体值。接着我们通过err.Path来获取文件路径。该程序输出为:

File at path /test.txt failed to open

2.断言error具体类型并通过其方法获取信息

type DNSError struct {  
    ...
}

func (e *DNSError) Error() string {  
    ...
}
func (e *DNSError) Timeout() bool {  
    ... 
}
func (e *DNSError) Temporary() bool {  
    ... 
}

该结构体类型有两个方法Timeout() bool和Temporary() bool用来表示具体错误原因是超时还是暂时解析失败。下面我们实现一个例子,来断言*DNSError类型,如果断言成功则调用其方法来判断错误原因。

package main

import (
    "fmt"
    "net"
)

func main() {
    addr, err := net.LookupHost("golangbot123.com")
    if err, ok := err.(*net.DNSError); ok {
        if err.Timeout() {
            fmt.Println("operation timed out")
        } else if err.Temporary() {
            fmt.Println("temporary error")
        } else {
            fmt.Println("generic error: ", err)
        }
        return
    }
    fmt.Println(addr)
}

这里我们尝试获取域名golangbot123.com的ip地址,由于该域名是不存在的,因此会报域名解析错误,即net.DNSError类型的错误。我们断言该类型错误,然后调用该类型错误上的方法来判断具体错误原因。该程序运行结果如下:

generic error:  lookup golangbot123.com: no such host

3.直接判断相等

第三种获取更多错误相关信息的方法就是直接将错误字段跟某个error类型的已有变量做比较。我们还是通过例子来了解下。

var ErrBadPattern = errors.New("syntax error in pattern")

这里errors.New()用来创建一个error类型的变量。由于这里定义的ErrBadPattern是大写开头的,因此属于导出型变量,我们能够直接在包外访问到该变量。下面我们通过例子来判断下错误是否是ErrBadPattern。

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    files, error := filepath.Glob("[")
    if error != nil && error == filepath.ErrBadPattern {
        fmt.Println(error)
        return
    }
    fmt.Println("matched files", files)
}

这里我们传给Glob的参数是[,格式是错误的(具体为什么是错误的可以去看Glob相关文档)。然后我们直接将返回的错误字段跟变量filepath.ErrBadPattern去比较是否相等,如果相等则证明确实是该错误。该程序执行结果如下:

syntax error in pattern

Golang中基本都是基于这3种方式来提供更多错误相关的细节信息。我们下一节也会基于这几种方式来自定义错误类型。

不要忽略错误

注意,永远不要忽略错误字段。比如下面的程序就忽略了filepath.Glob()返回的错误字段。

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    files, _ := filepath.Glob("[")
    fmt.Println("matched files", files)
}

由于入参[是格式错误的,因此会返回错误字段,但是我们通过_将该错误忽略了。该程序输出为matched files [],表面上看起来是未匹配到任何字段,其实是发生了错误。因此我们在Golang编程中永远不要随意的忽略一个错误。

Previous错误Next自定义错误类型

Last updated 5 years ago

Was this helpful?

我们看下函数的官方文档会发现,该函数返回的错误字段的类型为*PathError。PathError是一个结构体类型,该类型实现了Error() string方法,实现了error接口,因此属于error类型。

这几行源码可以参考。

当我们断言出error内部的具体类型后,不仅可以通过其属性获取更多有用信息,还可以调用其方法来获取信息。我们通过例子来理解下。在Golang标准库中,结构体类型的定义如下:

filepath包中有个函数用来返回跟某个正则匹配的文件目录。如果正则写的格式有问题会返回一个错误变量ErrBadPattern。ErrBadPattern在filepath包中定义为:

Open
https://golang.org/src/os/error.go?s=653:716#L11
DNSError
Glob