./read "从 Python 到 Go(五):错误处..."

从 Python 到 Go(五):错误处理:从异常到 error

从_python_到_go(五):错误处理:从异常到_err.md2025-10-15
./meta --show-details
Published
2025年10月15日
Reading
27 min
Words
26,700
Status
PUBLISHED

目录

概述

Python 和 Go 在错误处理上有着根本性的差异。Python 使用异常(Exception)机制,而 Go 使用显式的错误返回值。这种差异反映了两种不同的设计哲学。

核心差异

特性PythonGo
错误表示异常对象error 接口
错误传播自动向上冒泡显式返回和检查
处理方式try/except/finallyif err != nil
多个错误ExceptionGroup (3.11+)多返回值
错误上下文异常链 (raise from)错误包装 (fmt.Errorf %w)
严重错误无区别,都是异常panic/recover

异常 vs 错误返回值

Python 的异常机制

# Python - 异常处理
def divide(a: float, b: float) -> float:
    """除法运算,可能抛出异常"""
    if b == 0:
        raise ValueError("除数不能为零")
    return a / b

def calculate_average(numbers: list) -> float:
    """计算平均值"""
    if not numbers:
        raise ValueError("列表不能为空")

    total = sum(numbers)
    return total / len(numbers)

def process_data(data: dict):
    """处理数据,可能有多层异常"""
    try:
        x = data["x"]
        y = data["y"]

        result = divide(x, y)
        print(f"结果: {result}")

    except KeyError as e:
        print(f"缺少必要的键: {e}")
    except ValueError as e:
        print(f"值错误: {e}")
    except Exception as e:
        print(f"未知错误: {e}")
    finally:
        print("清理资源")

# 使用
process_data({"x": 10, "y": 2})   # 结果: 5.0
process_data({"x": 10, "y": 0})   # 值错误: 除数不能为零
process_data({"x": 10})           # 缺少必要的键: 'y'

# 异常链
try:
    result = divide(10, 0)
except ValueError as e:
    raise RuntimeError("计算失败") from e

Go 的错误返回值

// Go - 错误返回值
package main

import (
    "errors"
    "fmt"
)

// 返回错误作为第二个返回值
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

func calculateAverage(numbers []float64) (float64, error) {
    if len(numbers) == 0 {
        return 0, errors.New("列表不能为空")
    }

    var total float64
    for _, num := range numbers {
        total += num
    }
    return total / float64(len(numbers)), nil
}

func processData(data map[string]float64) error {
    // 检查键是否存在
    x, xOk := data["x"]
    if !xOk {
        return errors.New("缺少必要的键: x")
    }

    y, yOk := data["y"]
    if !yOk {
        return errors.New("缺少必要的键: y")
    }

    // 显式检查错误
    result, err := divide(x, y)
    if err != nil {
        return fmt.Errorf("除法运算失败: %w", err)
    }

    fmt.Printf("结果: %.1f\n", result)
    return nil
}

// 使用延迟执行清理资源
func processDataWithCleanup(data map[string]float64) error {
    fmt.Println("开始处理")
    defer fmt.Println("清理资源")  // defer 类似 finally

    return processData(data)
}

func main() {
    // 正常情况
    err := processData(map[string]float64{"x": 10, "y": 2})
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    }

    // 除零错误
    err = processData(map[string]float64{"x": 10, "y": 0})
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    }

    // 缺少键错误
    err = processData(map[string]float64{"x": 10})
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    }
}

关键差异说明

方面Python 异常Go 错误
可见性隐式传播,难以追踪显式返回,清晰可见
性能异常有性能开销错误返回性能更好
控制流改变控制流正常控制流
代码量简洁(不处理时)冗长(必须检查)
错误忽略容易忽略(不捕获)编译器提示(未使用)

错误创建与包装

Python 的错误创建

# Python - 创建和抛出异常
import traceback

# 1. 基本异常
def basic_error():
    raise Exception("基本错误消息")

# 2. 内置异常类型
def builtin_errors():
    raise ValueError("值错误")
    raise TypeError("类型错误")
    raise KeyError("键错误")
    raise FileNotFoundError("文件未找到")

# 3. 异常链(保留原始错误)
def chained_error():
    try:
        x = int("abc")
    except ValueError as e:
        raise RuntimeError("转换失败") from e

# 4. 自定义异常
class DatabaseError(Exception):
    def __init__(self, message, error_code):
        super().__init__(message)
        self.error_code = error_code

def custom_error():
    raise DatabaseError("连接失败", 1001)

# 获取完整堆栈
try:
    chained_error()
except Exception as e:
    print("错误:", str(e))
    print("堆栈:")
    traceback.print_exc()

Go 的错误创建

// Go - 创建和包装错误
package main

import (
    "errors"
    "fmt"
)

// 1. 基本错误创建
func basicError() error {
    return errors.New("基本错误消息")
}

// 2. 格式化错误
func formattedError(id int) error {
    return fmt.Errorf("用户 %d 不存在", id)
}

// 3. 错误包装(Go 1.13+)
func wrappedError() error {
    err := errors.New("原始错误")
    return fmt.Errorf("操作失败: %w", err)  // %w 包装错误
}

// 4. 哨兵错误(预定义错误)
var (
    ErrNotFound     = errors.New("未找到")
    ErrUnauthorized = errors.New("未授权")
    ErrInvalidInput = errors.New("无效输入")
)

func sentinelError() error {
    return ErrNotFound
}

// 5. 自定义错误类型
type DatabaseError struct {
    Message   string
    ErrorCode int
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("[%d] %s", e.ErrorCode, e.Message)
}

func customError() error {
    return &DatabaseError{
        Message:   "连接失败",
        ErrorCode: 1001,
    }
}

// 6. 错误解包
func unwrapError() {
    err := wrappedError()

    // 检查是否包含特定错误
    if errors.Is(err, errors.New("原始错误")) {
        fmt.Println("包含原始错误")
    }

    // 解包获取原始错误
    unwrapped := errors.Unwrap(err)
    fmt.Printf("解包后: %v\n", unwrapped)
}

func main() {
    // 基本错误
    if err := basicError(); err != nil {
        fmt.Println("基本错误:", err)
    }

    // 格式化错误
    if err := formattedError(123); err != nil {
        fmt.Println("格式化错误:", err)
    }

    // 包装错误
    if err := wrappedError(); err != nil {
        fmt.Println("包装错误:", err)
    }

    // 哨兵错误
    if err := sentinelError(); err != nil {
        if errors.Is(err, ErrNotFound) {
            fmt.Println("确认是 NotFound 错误")
        }
    }

    // 自定义错误
    if err := customError(); err != nil {
        fmt.Println("自定义错误:", err)

        // 类型断言获取详细信息
        if dbErr, ok := err.(*DatabaseError); ok {
            fmt.Printf("错误码: %d\n", dbErr.ErrorCode)
        }
    }
}

错误处理模式

模式 1:提前返回(Early Return)

# Python - 多层嵌套
def process_user(user_id: int):
    try:
        user = get_user(user_id)
        if user:
            try:
                orders = get_orders(user_id)
                if orders:
                    try:
                        total = calculate_total(orders)
                        return total
                    except Exception as e:
                        print(f"计算错误: {e}")
                else:
                    print("没有订单")
            except Exception as e:
                print(f"获取订单错误: {e}")
        else:
            print("用户不存在")
    except Exception as e:
        print(f"获取用户错误: {e}")
// Go - 提前返回,扁平化代码
func processUser(userID int) (float64, error) {
    // 提前返回错误
    user, err := getUser(userID)
    if err != nil {
        return 0, fmt.Errorf("获取用户错误: %w", err)
    }

    orders, err := getOrders(userID)
    if err != nil {
        return 0, fmt.Errorf("获取订单错误: %w", err)
    }

    if len(orders) == 0 {
        return 0, errors.New("没有订单")
    }

    total, err := calculateTotal(orders)
    if err != nil {
        return 0, fmt.Errorf("计算错误: %w", err)
    }

    return total, nil
}

模式 2:错误聚合

# Python - 收集多个错误
def validate_user(user: dict) -> list:
    errors = []

    if "name" not in user:
        errors.append("缺少 name 字段")

    if "email" not in user:
        errors.append("缺少 email 字段")
    elif "@" not in user["email"]:
        errors.append("email 格式无效")

    if "age" not in user:
        errors.append("缺少 age 字段")
    elif user["age"] < 0:
        errors.append("age 不能为负数")

    return errors

# 使用
user = {"name": "Alice", "email": "invalid", "age": -5}
errors = validate_user(user)
if errors:
    for error in errors:
        print(f"- {error}")
// Go - 多错误聚合
package main

import (
    "errors"
    "fmt"
    "strings"
)

type ValidationError struct {
    Errors []string
}

func (v *ValidationError) Error() string {
    return strings.Join(v.Errors, "; ")
}

func (v *ValidationError) Add(msg string) {
    v.Errors = append(v.Errors, msg)
}

func (v *ValidationError) HasErrors() bool {
    return len(v.Errors) > 0
}

func validateUser(user map[string]interface{}) error {
    verr := &ValidationError{}

    // 检查 name
    if _, ok := user["name"]; !ok {
        verr.Add("缺少 name 字段")
    }

    // 检查 email
    email, emailOk := user["email"]
    if !emailOk {
        verr.Add("缺少 email 字段")
    } else if emailStr, ok := email.(string); ok {
        if !strings.Contains(emailStr, "@") {
            verr.Add("email 格式无效")
        }
    }

    // 检查 age
    age, ageOk := user["age"]
    if !ageOk {
        verr.Add("缺少 age 字段")
    } else if ageInt, ok := age.(int); ok {
        if ageInt < 0 {
            verr.Add("age 不能为负数")
        }
    }

    if verr.HasErrors() {
        return verr
    }
    return nil
}

func main() {
    user := map[string]interface{}{
        "name":  "Alice",
        "email": "invalid",
        "age":   -5,
    }

    if err := validateUser(user); err != nil {
        if verr, ok := err.(*ValidationError); ok {
            fmt.Println("验证错误:")
            for _, e := range verr.Errors {
                fmt.Printf("- %s\n", e)
            }
        }
    }
}

模式 3:错误日志与监控

# Python - 错误日志
import logging
import sys

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def risky_operation():
    try:
        result = 10 / 0
    except ZeroDivisionError as e:
        logging.error(f"除零错误: {e}", exc_info=True)
        raise
    except Exception as e:
        logging.critical(f"未知错误: {e}", exc_info=True)
        raise

try:
    risky_operation()
except Exception:
    sys.exit(1)
// Go - 错误日志
package main

import (
    "fmt"
    "log"
    "os"
)

// 自定义日志记录器
type Logger struct {
    *log.Logger
}

func NewLogger() *Logger {
    return &Logger{
        Logger: log.New(os.Stdout, "", log.LstdFlags),
    }
}

func (l *Logger) Error(err error) {
    l.Printf("ERROR: %v", err)
}

func (l *Logger) Fatal(err error) {
    l.Printf("FATAL: %v", err)
    os.Exit(1)
}

func riskyOperation() error {
    // 模拟错误
    return fmt.Errorf("操作失败: %w", errors.New("除零错误"))
}

func main() {
    logger := NewLogger()

    if err := riskyOperation(); err != nil {
        // 记录错误但继续
        logger.Error(err)

        // 或者致命错误并退出
        // logger.Fatal(err)
    }
}

自定义错误

Python 的自定义异常

# Python - 自定义异常类
class AppError(Exception):
    """应用基础错误"""
    pass

class ValidationError(AppError):
    """验证错误"""
    def __init__(self, field: str, message: str):
        self.field = field
        self.message = message
        super().__init__(f"{field}: {message}")

class DatabaseError(AppError):
    """数据库错误"""
    def __init__(self, operation: str, error: Exception):
        self.operation = operation
        self.original_error = error
        super().__init__(f"数据库 {operation} 失败: {error}")

class APIError(AppError):
    """API 错误"""
    def __init__(self, status_code: int, message: str):
        self.status_code = status_code
        self.message = message
        super().__init__(f"[{status_code}] {message}")

# 使用
def validate_age(age: int):
    if age < 0:
        raise ValidationError("age", "不能为负数")
    if age > 150:
        raise ValidationError("age", "超出合理范围")

def fetch_user(user_id: int):
    raise APIError(404, "用户不存在")

# 处理特定错误
try:
    validate_age(-5)
except ValidationError as e:
    print(f"验证失败 - 字段: {e.field}, 原因: {e.message}")
except AppError as e:
    print(f"应用错误: {e}")

Go 的自定义错误

// Go - 自定义错误类型
package main

import (
    "errors"
    "fmt"
)

// ValidationError 验证错误
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

// DatabaseError 数据库错误
type DatabaseError struct {
    Operation string
    Err       error
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("数据库 %s 失败: %v", e.Operation, e.Err)
}

func (e *DatabaseError) Unwrap() error {
    return e.Err
}

// APIError API 错误
type APIError struct {
    StatusCode int
    Message    string
}

func (e *APIError) Error() string {
    return fmt.Sprintf("[%d] %s", e.StatusCode, e.Message)
}

// 使用
func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "不能为负数",
        }
    }
    if age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: "超出合理范围",
        }
    }
    return nil
}

func fetchUser(userID int) error {
    return &APIError{
        StatusCode: 404,
        Message:    "用户不存在",
    }
}

// 类型断言处理特定错误
func handleError(err error) {
    var valErr *ValidationError
    var apiErr *APIError
    var dbErr *DatabaseError

    switch {
    case errors.As(err, &valErr):
        fmt.Printf("验证失败 - 字段: %s, 原因: %s\n", valErr.Field, valErr.Message)
    case errors.As(err, &apiErr):
        fmt.Printf("API 错误 [%d]: %s\n", apiErr.StatusCode, apiErr.Message)
    case errors.As(err, &dbErr):
        fmt.Printf("数据库错误: %s\n", dbErr.Operation)
    default:
        fmt.Printf("未知错误: %v\n", err)
    }
}

func main() {
    // 测试验证错误
    if err := validateAge(-5); err != nil {
        handleError(err)
    }

    // 测试 API 错误
    if err := fetchUser(123); err != nil {
        handleError(err)
    }
}

Panic 与 Recover

Python 的异常对应

# Python - 所有错误都是异常
def critical_operation():
    # Python 中没有 panic 的概念
    raise RuntimeError("严重错误")

def safe_wrapper():
    try:
        critical_operation()
    except Exception as e:
        print(f"捕获到错误: {e}")
        # 恢复执行

safe_wrapper()
print("程序继续运行")

Go 的 Panic/Recover

// Go - Panic 和 Recover
package main

import (
    "fmt"
    "runtime/debug"
)

// Panic - 类似 Python 的 raise,但更严重
func criticalOperation() {
    panic("严重错误")  // 会终止程序(除非被 recover)
}

// Recover - 捕获 panic
func safeWrapper() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("捕获到 panic: %v\n", r)
            fmt.Printf("堆栈:\n%s\n", debug.Stack())
        }
    }()

    criticalOperation()
}

// 实际应用:保护 HTTP 处理器
func handleRequest() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("处理请求时 panic: %v\n", r)
            // 记录日志
            // 返回 500 错误
        }
    }()

    // 可能 panic 的代码
    processRequest()
}

func processRequest() {
    // 模拟 panic
    var data []int
    _ = data[10]  // index out of range
}

func main() {
    // 使用 recover 捕获 panic
    safeWrapper()
    fmt.Println("程序继续运行")

    // HTTP 处理器中使用
    handleRequest()
    fmt.Println("请求处理完成")
}

Panic vs Error 使用指南

场景使用 Error使用 Panic
预期的错误(文件不存在、网络超时)
业务逻辑错误(验证失败、权限不足)
不可恢复的错误(内存不足、数组越界)
初始化失败(配置错误、依赖缺失)可选
库代码❌ (极少使用)
应用主逻辑

实战案例

案例 1:文件操作

# Python - 文件操作错误处理
import os

def read_config(filename: str) -> dict:
    """读取配置文件"""
    try:
        with open(filename, 'r') as f:
            content = f.read()
            # 假设是 JSON
            import json
            return json.loads(content)
    except FileNotFoundError:
        raise FileNotFoundError(f"配置文件 {filename} 不存在")
    except json.JSONDecodeError as e:
        raise ValueError(f"配置文件格式错误: {e}")
    except Exception as e:
        raise RuntimeError(f"读取配置失败: {e}")

# 使用
try:
    config = read_config("config.json")
    print(config)
except FileNotFoundError as e:
    print(f"错误: {e}")
    # 使用默认配置
    config = {"default": True}
except Exception as e:
    print(f"严重错误: {e}")
    exit(1)
// Go - 文件操作错误处理
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

func readConfig(filename string) (map[string]interface{}, error) {
    // 读取文件
    data, err := os.ReadFile(filename)
    if err != nil {
        if os.IsNotExist(err) {
            return nil, fmt.Errorf("配置文件 %s 不存在: %w", filename, err)
        }
        return nil, fmt.Errorf("读取文件失败: %w", err)
    }

    // 解析 JSON
    var config map[string]interface{}
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("配置文件格式错误: %w", err)
    }

    return config, nil
}

func main() {
    config, err := readConfig("config.json")
    if err != nil {
        // 检查特定错误类型
        if os.IsNotExist(err) {
            fmt.Printf("错误: %v\n", err)
            // 使用默认配置
            config = map[string]interface{}{"default": true}
        } else {
            fmt.Printf("严重错误: %v\n", err)
            os.Exit(1)
        }
    }

    fmt.Printf("配置: %v\n", config)
}

案例 2:HTTP 客户端

# Python - HTTP 请求错误处理
import requests
from typing import Optional

class APIClient:
    def __init__(self, base_url: str):
        self.base_url = base_url

    def get_user(self, user_id: int) -> Optional[dict]:
        """获取用户信息"""
        try:
            response = requests.get(
                f"{self.base_url}/users/{user_id}",
                timeout=5
            )
            response.raise_for_status()  # 抛出 HTTP 错误
            return response.json()

        except requests.Timeout:
            raise TimeoutError("请求超时")
        except requests.HTTPError as e:
            if e.response.status_code == 404:
                return None
            raise RuntimeError(f"HTTP 错误: {e}")
        except requests.RequestException as e:
            raise RuntimeError(f"请求失败: {e}")

# 使用
client = APIClient("https://api.example.com")
try:
    user = client.get_user(123)
    if user:
        print(f"用户: {user}")
    else:
        print("用户不存在")
except TimeoutError as e:
    print(f"超时: {e}")
except RuntimeError as e:
    print(f"错误: {e}")
// Go - HTTP 请求错误处理
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

type APIClient struct {
    baseURL string
    client  *http.Client
}

func NewAPIClient(baseURL string) *APIClient {
    return &APIClient{
        baseURL: baseURL,
        client: &http.Client{
            Timeout: 5 * time.Second,
        },
    }
}

func (c *APIClient) GetUser(userID int) (map[string]interface{}, error) {
    url := fmt.Sprintf("%s/users/%d", c.baseURL, userID)

    resp, err := c.client.Get(url)
    if err != nil {
        // 检查是否是超时错误
        if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
            return nil, fmt.Errorf("请求超时: %w", err)
        }
        return nil, fmt.Errorf("请求失败: %w", err)
    }
    defer resp.Body.Close()

    // 检查状态码
    if resp.StatusCode == http.StatusNotFound {
        return nil, nil  // 用户不存在,返回 nil
    }

    if resp.StatusCode != http.StatusOK {
        body, _ := io.ReadAll(resp.Body)
        return nil, fmt.Errorf("HTTP 错误 [%d]: %s", resp.StatusCode, string(body))
    }

    // 解析响应
    var user map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, fmt.Errorf("解析响应失败: %w", err)
    }

    return user, nil
}

func main() {
    client := NewAPIClient("https://api.example.com")

    user, err := client.GetUser(123)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }

    if user == nil {
        fmt.Println("用户不存在")
    } else {
        fmt.Printf("用户: %v\n", user)
    }
}

案例 3:数据库事务

# Python - 数据库事务错误处理
import sqlite3

def transfer_money(from_user: int, to_user: int, amount: float):
    """转账操作"""
    conn = sqlite3.connect('bank.db')
    try:
        cursor = conn.cursor()

        # 开始事务
        cursor.execute("BEGIN TRANSACTION")

        # 检查余额
        cursor.execute("SELECT balance FROM accounts WHERE id = ?", (from_user,))
        balance = cursor.fetchone()[0]

        if balance < amount:
            raise ValueError("余额不足")

        # 扣款
        cursor.execute(
            "UPDATE accounts SET balance = balance - ? WHERE id = ?",
            (amount, from_user)
        )

        # 入账
        cursor.execute(
            "UPDATE accounts SET balance = balance + ? WHERE id = ?",
            (amount, to_user)
        )

        # 提交事务
        conn.commit()
        print(f"转账成功: {amount} 从 {from_user} 到 {to_user}")

    except ValueError as e:
        conn.rollback()
        print(f"业务错误: {e}")
        raise
    except sqlite3.Error as e:
        conn.rollback()
        print(f"数据库错误: {e}")
        raise
    finally:
        conn.close()

# 使用
try:
    transfer_money(1, 2, 100.0)
except Exception as e:
    print(f"转账失败: {e}")
// Go - 数据库事务错误处理
package main

import (
    "database/sql"
    "errors"
    "fmt"

    _ "github.com/mattn/go-sqlite3"
)

var ErrInsufficientBalance = errors.New("余额不足")

func transferMoney(db *sql.DB, fromUser, toUser int, amount float64) error {
    // 开始事务
    tx, err := db.Begin()
    if err != nil {
        return fmt.Errorf("开始事务失败: %w", err)
    }

    // 确保事务被回滚或提交
    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p)  // 重新抛出 panic
        }
    }()

    // 检查余额
    var balance float64
    err = tx.QueryRow("SELECT balance FROM accounts WHERE id = ?", fromUser).Scan(&balance)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("查询余额失败: %w", err)
    }

    if balance < amount {
        tx.Rollback()
        return ErrInsufficientBalance
    }

    // 扣款
    _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromUser)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("扣款失败: %w", err)
    }

    // 入账
    _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toUser)
    if err != nil {
        tx.Rollback()
        return fmt.Errorf("入账失败: %w", err)
    }

    // 提交事务
    if err := tx.Commit(); err != nil {
        return fmt.Errorf("提交事务失败: %w", err)
    }

    fmt.Printf("转账成功: %.2f 从 %d 到 %d\n", amount, fromUser, toUser)
    return nil
}

func main() {
    db, err := sql.Open("sqlite3", "bank.db")
    if err != nil {
        panic(err)
    }
    defer db.Close()

    err = transferMoney(db, 1, 2, 100.0)
    if err != nil {
        if errors.Is(err, ErrInsufficientBalance) {
            fmt.Println("业务错误:", err)
        } else {
            fmt.Println("系统错误:", err)
        }
    }
}

最佳实践

1. 总是处理错误

// ❌ 不好:忽略错误
data, _ := readFile("config.json")

// ✅ 好:处理错误
data, err := readFile("config.json")
if err != nil {
    return fmt.Errorf("读取配置失败: %w", err)
}

2. 提供上下文信息

// ❌ 不好:丢失上下文
func processUser(id int) error {
    user, err := getUser(id)
    if err != nil {
        return err  // 不知道在哪个步骤失败
    }
    // ...
}

// ✅ 好:添加上下文
func processUser(id int) error {
    user, err := getUser(id)
    if err != nil {
        return fmt.Errorf("处理用户 %d 失败: %w", id, err)
    }
    // ...
}

3. 使用哨兵错误

// ✅ 定义可导出的哨兵错误
var (
    ErrNotFound   = errors.New("not found")
    ErrDuplicate  = errors.New("duplicate entry")
    ErrForbidden  = errors.New("forbidden")
)

// 使用 errors.Is 检查
func GetUser(id int) (*User, error) {
    // ...
    return nil, ErrNotFound
}

// 调用者
user, err := GetUser(123)
if errors.Is(err, ErrNotFound) {
    // 处理未找到的情况
}

4. 错误包装和解包

// ✅ 使用 %w 包装错误
func outer() error {
    err := inner()
    if err != nil {
        return fmt.Errorf("outer failed: %w", err)
    }
    return nil
}

// 检查原始错误
err := outer()
if errors.Is(err, ErrSomeSpecific) {
    // 处理特定错误
}

// 类型断言获取错误详情
var myErr *MyCustomError
if errors.As(err, &myErr) {
    fmt.Println("错误码:", myErr.Code)
}

5. defer 清理资源

// ✅ 使用 defer 确保资源释放
func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // 无论如何都会关闭

    // 处理文件
    return nil
}

6. panic 的合理使用

// ✅ 好:仅在初始化时 panic
func init() {
    if err := loadConfig(); err != nil {
        panic(fmt.Sprintf("配置加载失败: %v", err))
    }
}

// ❌ 不好:在业务逻辑中 panic
func GetUser(id int) *User {
    user, err := db.Query(id)
    if err != nil {
        panic(err)  // 不要这样做!
    }
    return user
}

// ✅ 好:返回错误
func GetUser(id int) (*User, error) {
    user, err := db.Query(id)
    if err != nil {
        return nil, err
    }
    return user, nil
}

总结

Python 到 Go 的思维转变

Python 思维Go 思维
用异常表示所有错误用 error 表示预期错误,panic 表示严重错误
try/except 捕获异常显式检查 if err != nil
异常自动向上传播错误需要显式返回
错误处理是可选的错误必须处理(编译器检查)
finally 清理资源defer 清理资源
raise 抛出异常return 返回错误

关键要点

  1. 显式错误处理:Go 的错误是值,必须显式检查和处理
  2. 提前返回:使用提前返回模式避免深层嵌套
  3. 错误包装:使用 fmt.Errorf%w 包装错误,保留上下文
  4. 哨兵错误:使用预定义的错误变量,方便比较
  5. 自定义错误:实现 Error() 方法创建自定义错误类型
  6. panic/recover:仅在不可恢复的严重错误时使用 panic

实用建议

  • 🔧 总是检查 error,不要使用 _ 忽略
  • 🔧 使用 errors.Iserrors.As 检查错误类型
  • 🔧 为错误添加上下文信息,使用 fmt.Errorf
  • 🔧 使用 defer 确保资源清理
  • 🔧 在库代码中避免使用 panic
  • 🔧 日志记录错误但不要在库中直接 log

下一篇并发编程:从线程到 Goroutine

上一篇面向对象 vs 组合模式

系列目录从 Python 到 Go 完全指南

comments.logDiscussion Thread
./comments --show-all

讨论区

./loading comments...