引言
错误处理是软件工程中的一个重要方面,它直接影响到程序的健壮性和用户体验。Go 语言采用了一种不同于传统异常处理的错误处理机制,鼓励开发者显式地处理错误而不是依赖于异常。
1. 错误处理原则
- 显式错误处理:
- Go 鼓励在函数中返回一个 error 类型的值,以便调用者可以检查和处理错误。
- 示例:
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
return data, nil
}
- 不要忽视错误:
- 应该始终检查并处理函数返回的错误,避免让错误无声无息地消失。
- 示例:
data, err := readFile("example.txt")
if err != nil {
log.Fatal(err)
}
- 使用 log.Fatal 和 log.Panic:
- 当遇到无法恢复的错误时,可以使用 log.Fatal 或 log.Panic 来记录错误并终止程序。
- 示例:
if err != nil {
log.Fatal(err)
}
- 避免错误的不当传播:
- 在函数中遇到错误时,应尽快处理或向上层传播,避免错误被忽略或延迟处理。
- 示例:
func processFile(filename string) error {
data, err := readFile(filename)
if err != nil {
return err
}
// 处理 data
return nil
}
- 使用 panic 和 recover:
- panic 用于标记不可恢复的错误,导致当前 goroutine 停止执行。
- recover 可以在一个 defer 函数中使用,用于捕获和处理由 panic 引发的错误。
- 示例:
func safeDivide(x, y int) int {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in safeDivide", r)
}
}()
return x / y
}
2. 错误类型和错误链
- 自定义错误类型:
- 可以定义自己的错误类型,以便更精确地描述错误的性质。
- 示例:
type FileNotFoundError struct {
Filename string
}
func (e *FileNotFoundError) Error() string {
return fmt.Sprintf("file %s not found", e.Filename)
}
- 错误链:
- 可以使用 errors.Wrap 或 errors.Cause 函数来创建错误链,这有助于追踪错误的根源。
- 示例:
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, errors.Wrap(err, "failed to open file")
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return nil, errors.Wrap(err, "failed to read file")
}
return data, nil
}
3. 实践出真知,动手写一下吧
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"github.com/pkg/errors"
)
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, errors.Wrap(err, "failed to open file")
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return nil, errors.Wrap(err, "failed to read file")
}
return data, nil
}
func parseData(data []byte) (int, error) {
strData := strings.TrimSpace(string(data))
value, err := strconv.Atoi(strData)
if err != nil {
return 0, errors.Wrap(err, "failed to parse data")
}
return value, nil
}
func main() {
filename := "example.txt"
data, err := readFile(filename)
if err != nil {
log.Fatal(err)
}
value, err := parseData(data)
if err != nil {
log.Fatal(err)
}
fmt.Println("Parsed value:", value)
}