menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right All_wiki chevron_right the-way-to-go_ZH_CN chevron_right eBook chevron_right 13.1.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    13.1.md
    5.72 KB / 2024-07-16 23:14:28
        # 13.1 错误处理
    
    Go 有一个预先定义的 error 接口类型
    
    ```go
    type error interface {
    	Error() string
    }
    ```
    
    错误值用来表示异常状态;我们可以在 [5.2 节](05.2.md)中看到它的标准用法。处理文件操作的例子可以在 [12 章](12.0.md)找到;我们将在 [15 章](15.0.md)看到网络操作的例子。`errors` 包中有一个 `errorString` 结构体实现了 `error` 接口。当程序处于错误状态时可以用 `os.Exit(1)` 来中止运行。
    
    ## 13.1.1 定义错误
    
    任何时候当你需要一个新的错误类型,都可以用 `errors` 包(必须先 `import`)的 `errors.New()` 函数接收合适的错误信息来创建,像下面这样:
    
    ```go
    err := errors.New("math - square root of negative number")
    ```
    
    在示例 13.1 中你可以看到一个简单的用例:
    
    示例 13.1 [errors.go](examples/chapter_13/errors.go):
    
    ```go
    // errors.go
    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    var errNotFound error = errors.New("Not found error")
    
    func main() {
    	fmt.Printf("error: %v", errNotFound)
    }
    // error: Not found error
    ```
    
    可以把它用于计算平方根函数的参数测试:
    
    ```go
    func Sqrt(f float64) (float64, error) {
    	if f < 0 {
    		return 0, errors.New ("math - square root of negative number")
    	}
       // implementation of Sqrt
    }
    ```
    
    你可以像下面这样调用 `Sqrt()` 函数:
    
    ```go
    if f, err := Sqrt(-1); err != nil {
    	fmt.Printf("Error: %s\n", err)
    }
    ```
    
    由于 `fmt.Printf` 会自动调用 `String()` 方法 (参见 [10.7 节](10.7.md)),所以错误信息 “`Error: math - square root of negative number`” 会打印出来。通常(错误信息)都会有像 `Error:...` 这样的前缀,所以你的错误信息不要以大写字母开头(注:英文只有句首单词首字母大写,这里应当是考虑到这一点)。
    
    在大部分情况下自定义错误结构类型很有意义的,可以包含除了(低层级的)错误信息以外的其它有用信息,例如,正在进行的操作(打开文件等),全路径或名字。看下面例子中 `os.Open()` 操作触发的 `PathError` 错误:
    
    ```go
    // PathError records an error and the operation and file path that caused it.
    type PathError struct {
    	Op string    // "open", "unlink", etc.
    	Path string  // The associated file.
    	Err error  // Returned by the system call.
    }
    
    func (e *PathError) Error() string {
    	return e.Op + " " + e.Path + ": "+ e.Err.Error()
    }
    ```
    
    如果有不同错误条件可能发生,那么对实际的错误使用类型断言或类型判断(type-switch)是很有用的,并且可以根据错误场景做一些补救和恢复操作。
    
    ```go
    //  err != nil
    if e, ok := err.(*os.PathError); ok {
    	// remedy situation
    }
    ```
    
    或:
    
    ```go
    switch err := err.(type) {
    	case ParseError:
    		PrintParseError(err)
    	case PathError:
    		PrintPathError(err)
    	...
    	default:
    		fmt.Printf("Not a special error, just %s\n", err)
    }
    ```
    
    作为第二个例子考虑用 `json` 包的情况。当 `json.Decode()` 在解析 JSON 文档发生语法错误时,指定返回一个 `SyntaxError` 类型的错误:
    
    ```go
    type SyntaxError struct {
    	msg    string // description of error
    // error occurred after reading Offset bytes, from which line and columnnr can be obtained
    	Offset int64
    }
    
    func (e *SyntaxError) Error() string { return e.msg }
    ```
    
    在调用代码中你可以像这样用类型断言测试错误是不是上面的类型:
    
    ```go
    if serr, ok := err.(*json.SyntaxError); ok {
    	line, col := findLine(f, serr.Offset)
    	return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
    }
    ```
    
    包也可以用额外的方法 (methods)定义特定的错误,比如 `net.Error`:
    
    ```go
    package net
    type Error interface {
    	Timeout() bool   // Is the error a timeout?
    	Temporary() bool // Is the error temporary?
    }
    ```
    
    在 [15.1 节](15.1.md) 我们可以看到怎么使用它。
    
    正如你所看到的一样,所有的例子都遵循同一种命名规范:错误类型以 `...Error` 结尾,错误变量以 `err...` 或 `Err...` 开头或者直接叫 `err` 或 `Err`。
    
    `syscall` 是低阶外部包,用来提供系统基本调用的原始接口。它们返回封装整数类型错误码的 `syscall.Errno`;类型 `syscall.Errno` 实现了 `Error` 接口。
    
    大部分 `syscall` 函数都返回一个结果和可能的错误,比如:
    
    ```go
    r, err := syscall.Open(name, mode, perm)
    if err != nil {
    	fmt.Println(err.Error())
    }
    ```
    
    `os` 包也提供了一套像 `os.EINAL` 这样的标准错误,它们基于 `syscall` 错误:
    
    ```go
    var (
    	EPERM		Error = Errno(syscall.EPERM)
    	ENOENT		Error = Errno(syscall.ENOENT)
    	ESRCH		Error = Errno(syscall.ESRCH)
    	EINTR		Error = Errno(syscall.EINTR)
    	EIO			Error = Errno(syscall.EIO)
    	...
    )
    ```
    
    ## 13.1.2 用 fmt 创建错误对象
    
    通常你想要返回包含错误参数的更有信息量的字符串,例如:可以用 `fmt.Errorf()` 来实现:它和 `fmt.Printf()` 完全一样,接收一个或多个格式占位符的格式化字符串和相应数量的占位变量。和打印信息不同的是它用信息生成错误对象。
    
    比如在前面的平方根例子中使用:
    
    ```go
    if f < 0 {
    	return 0, fmt.Errorf("math: square root of negative number %g", f)
    }
    ```
    
    第二个例子:从命令行读取输入时,如果加了 `--help` 或 `-h` 标志,我们可以用有用的信息产生一个错误:
    
    ```go
    if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
    	err = fmt.Errorf("usage: %s infile.txt outfile.txt", filepath.Base(os.Args[0]))
    	return
    }
    ```
    
    ## 链接
    
    - [目录](directory.md)
    - 上一节:[错误处理与测试](13.0.md)
    - 下一节:[运行时异常和 panic](13.2.md)
    
    
    links
    file_download