在Go中优雅地处理defer中的err

处理一个错误非常简单

1
2
3
4
5
6
7
8
package main

func main() {
    var err error
    if err != nil {
        // 处理错误
    }
}

在一个返回错误的函数中处理也很简单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main
import "os"
func doSomething() error {
    fd,err := os.Open("file.txt")
    if err != nil {
        return err
    }
    defer fd.Close()
    // 对文件读写操作
    
    return nil
}
但上面的处理方式有些问题

defer后面的文件关闭操作有可能会出错,此时调用方完全不知道发生了什么

更好的做法,参考知名项目的做法,例如 kubebuilder

返回值使用命名返回值,确保defer 中可以覆盖err

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func (s Scaffold) loadModelFromFile(path string) (f *File, err error) {
	reader, err := s.fs.Open(path)
	if err != nil {
		return nil, OpenFileError{err}
	}
	defer func() {
	  // 只有在函数主体正常,并且关闭文件错误时才会覆盖err
		if closeErr := reader.Close(); err == nil && closeErr != nil {
			err = CloseFileError{closeErr}
		}
	}()

	content, err := afero.ReadAll(reader)
	if err != nil {
		return nil, ReadFileError{err}
	}

	return &File{Path: path, Contents: string(content)}, nil
}

更加完善的错误方式,可以在defer中使用 errors.Join 来合并错误