Go 语言采用了一种与众不同的错误处理方式。与其他语言使用 try-catch 异常机制不同,Go 使用返回值来表示错误,这使得错误处理成为代码逻辑的一部分。
// Java 风格的异常处理
try {
file = openFile("test.txt");
content = readFile(file);
process(content);
} catch (FileNotFoundException e) {
// 处理文件不存在
} catch (IOException e) {
// 处理 IO 错误
}
// Go 风格的错误处理
file, err := os.Open("test.txt")
if err != nil {
// 处理错误
return err
}
content, err := io.ReadAll(file)
if err != nil {
return err
}
process(content)
Go 的方式虽然看起来更冗长,但它让错误处理变得非常清晰和可控。
Go 中的错误是通过 error 接口表示的:
type error interface {
Error() string
}
任何实现了 Error() string 方法的类型都可以作为错误使用。
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("结果:", result)
result, err = divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("结果:", result)
}
在 Go 中,惯例是:如果错误为 nil,表示操作成功;如果错误不为 nil,表示操作失败。
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("nonexistent.txt")
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer file.Close()
fmt.Println("文件打开成功")
}
errors.New 是最简单的创建错误的方式:
package main
import (
"errors"
"fmt"
)
func checkAge(age int) error {
if age < 0 {
return errors.New("年龄不能为负数")
}
if age > 150 {
return errors.New("年龄不合理")
}
return nil
}
func main() {
if err := checkAge(-5); err != nil {
fmt.Println(err)
}
if err := checkAge(200); err != nil {
fmt.Println(err)
}
}
fmt.Errorf 可以创建格式化的错误信息:
package main
import (
"fmt"
)
func checkName(name string) error {
if len(name) == 0 {
return fmt.Errorf("名字不能为空")
}
if len(name) > 50 {
return fmt.Errorf("名字长度 %d 超过限制 50", len(name))
}
return nil
}
func main() {
if err := checkName(""); err != nil {
fmt.Println(err)
}
if err := checkName("这是一个非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常非常长的名字"); err != nil {
fmt.Println(err)
}
}
你可以创建自己的错误类型,实现 error 接口:
package main
import "fmt"
// 自定义错误类型
type ValidationError struct {
Field string
Message string
}
// 实现 error 接口
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证错误 [%s]: %s", e.Field, e.Message)
}
func validateUser(name string, age int) error {
if len(name) == 0 {
return &ValidationError{
Field: "name",
Message: "名字不能为空",
}
}
if age < 0 || age > 150 {
return &ValidationError{
Field: "age",
Message: "年龄必须在 0-150 之间",
}
}
return nil
}
func main() {
if err := validateUser("", 25); err != nil {
fmt.Println(err)
}
if err := validateUser("Alice", -5); err != nil {
fmt.Println(err)
}
}
package main
import "fmt"
type DatabaseError struct {
Operation string
Table string
Err error
}
func (e *DatabaseError) Error() string {
return fmt.Sprintf("数据库错误: 操作=%s, 表=%s, 原因=%v", e.Operation, e.Table, e.Err)
}
func (e *DatabaseError) Unwrap() error {
return e.Err
}
func queryUser(id int) error {
return &DatabaseError{
Operation: "SELECT",
Table: "users",
Err: fmt.Errorf("连接超时"),
}
}
func main() {
if err := queryUser(1); err != nil {
fmt.Println(err)
}
}
使用 == 比较错误:
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("记录不存在")
func findUser(id int) error {
if id <= 0 {
return ErrNotFound
}
return nil
}
func main() {
if err := findUser(0); err != nil {
if err == ErrNotFound {
fmt.Println("用户不存在,请检查 ID")
} else {
fmt.Println("其他错误:", err)
}
}
}
Go 1.13 引入了 errors.Is,用于错误链比较:
package main
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("记录不存在")
ErrTimeout = errors.New("操作超时")
)
func queryDatabase() error {
return fmt.Errorf("查询失败: %w", ErrTimeout)
}
func main() {
err := queryDatabase()
if errors.Is(err, ErrTimeout) {
fmt.Println("是超时错误")
}
if errors.Is(err, ErrNotFound) {
fmt.Println("是未找到错误")
}
}
errors.As 用于从错误链中提取特定类型的错误:
package main
import (
"errors"
"fmt"
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证错误 [%s]: %s", e.Field, e.Message)
}
func validateInput(input string) error {
if len(input) == 0 {
return &ValidationError{
Field: "input",
Message: "输入不能为空",
}
}
return nil
}
func main() {
err := validateInput("")
if err != nil {
var validationErr *ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("字段 %s 验证失败: %s\n", validationErr.Field, validationErr.Message)
} else {
fmt.Println("其他错误:", err)
}
}
}
也可以使用类型断言检查错误类型:
package main
import (
"fmt"
"net"
)
func main() {
_, err := net.LookupHost("nonexistent.invalid")
if err != nil {
// 使用类型断言
if dnsErr, ok := err.(*net.DNSError); ok {
fmt.Printf("DNS 错误: %s\n", dnsErr.Name)
if dnsErr.IsTimeout {
fmt.Println("是超时错误")
}
if dnsErr.IsNotFound {
fmt.Println("主机不存在")
}
} else {
fmt.Println("其他错误:", err)
}
}
}
Go 1.13 引入了错误包装机制:
package main
import (
"errors"
"fmt"
)
func readFile(filename string) error {
return errors.New("文件不存在")
}
func processFile(filename string) error {
err := readFile(filename)
if err != nil {
return fmt.Errorf("处理文件 %s 失败: %w", filename, err)
}
return nil
}
func main() {
err := processFile("test.txt")
if err != nil {
fmt.Println("错误:", err)
// 解包获取原始错误
unwrapped := errors.Unwrap(err)
if unwrapped != nil {
fmt.Println("原始错误:", unwrapped)
}
}
}
错误可以多层包装,形成错误链:
package main
import (
"errors"
"fmt"
)
func level3() error {
return errors.New("底层错误")
}
func level2() error {
err := level3()
if err != nil {
return fmt.Errorf("level2: %w", err)
}
return nil
}
func level1() error {
err := level2()
if err != nil {
return fmt.Errorf("level1: %w", err)
}
return nil
}
func main() {
err := level1()
if err != nil {
fmt.Println("完整错误:", err)
// 逐层解包
for err != nil {
fmt.Printf(" - %v\n", err)
err = errors.Unwrap(err)
}
}
}
package main
import (
"errors"
"fmt"
)
var ErrPermission = errors.New("权限不足")
func checkPermission() error {
return ErrPermission
}
func operation() error {
err := checkPermission()
if err != nil {
return fmt.Errorf("操作失败: %w", err)
}
return nil
}
func main() {
err := operation()
if errors.Is(err, ErrPermission) {
fmt.Println("检测到权限错误")
}
}
panic 用于不可恢复的错误,会导致程序立即停止:
package main
import "fmt"
func main() {
fmt.Println("程序开始")
panic("发生严重错误!")
fmt.Println("这行不会执行")
}
panic 应该只在真正不可恢复的情况下使用:
package main
import "fmt"
func mustCompile(pattern string) {
if pattern == "" {
panic("正则表达式不能为空")
}
fmt.Println("编译成功:", pattern)
}
func main() {
mustCompile("") // 会 panic
}
recover 用于捕获 panic,只能在 defer 中使用:
package main
import "fmt"
func mayPanic() {
panic("出问题了!")
}
func safeCall() {
defer func() {
if r := recover(); r != nil {
fmt.Println("捕获到 panic:", r)
}
}()
mayPanic()
fmt.Println("这行不会执行")
}
func main() {
safeCall()
fmt.Println("程序继续运行")
}
package main
import (
"fmt"
"http"
)
func middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("捕获 panic: %v\n", err)
http.Error(w, "内部服务器错误", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
panic("出错了!")
})
fmt.Println("服务器启动在 :8080")
http.ListenAndServe(":8080", nil)
}
// 不好的做法
func process(data string) error {
var err error
if len(data) > 0 {
if data[0] == 'A' {
// 处理逻辑
} else {
err = errors.New("无效数据")
}
} else {
err = errors.New("空数据")
}
return err
}
// 好的做法
func process(data string) error {
if len(data) == 0 {
return errors.New("空数据")
}
if data[0] != 'A' {
return errors.New("无效数据")
}
// 处理逻辑
return nil
}
// 不好的做法
return errors.New("错误")
// 好的做法
return fmt.Errorf("用户 %d 不存在", userID)
// 不好的做法
file, _ := os.Open("config.txt")
// 好的做法
file, err := os.Open("config.txt")
if err != nil {
return fmt.Errorf("打开配置文件失败: %w", err)
}
package main
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("未找到")
ErrUnauthorized = errors.New("未授权")
ErrBadRequest = errors.New("请求错误")
)
func getResource(id int) error {
if id <= 0 {
return ErrNotFound
}
return nil
}
func main() {
err := getResource(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("资源不存在")
}
}
// 不好的做法:重复记录错误
func process() error {
err := doSomething()
if err != nil {
log.Println("错误:", err) // 第一次处理
return err
}
return nil
}
func main() {
if err := process(); err != nil {
log.Println("错误:", err) // 第二次处理
}
}
// 好的做法:只在一处处理
func process() error {
err := doSomething()
if err != nil {
return fmt.Errorf("处理失败: %w", err)
}
return nil
}
func main() {
if err := process(); err != nil {
log.Println("错误:", err) // 只在这里处理
}
}
本章介绍了 Go 语言错误处理的基础知识:
errors.New 和 fmt.ErrorfError() 方法创建自己的错误类型==、errors.Is、errors.As%w 包装错误,形成错误链Go 的错误处理哲学是:错误是代码逻辑的一部分,应该显式处理。这种方式虽然看起来冗长,但让代码更加清晰和可维护。在下一章中,我们将学习更多关于自定义错误和错误包装的高级用法。