从_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 使用显式的错误返回值。这种差异反映了两种不同的设计哲学。
核心差异
特性 | Python | Go |
---|---|---|
错误表示 | 异常对象 | error 接口 |
错误传播 | 自动向上冒泡 | 显式返回和检查 |
处理方式 | try/except/finally | if 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 返回错误 |
关键要点
- 显式错误处理:Go 的错误是值,必须显式检查和处理
- 提前返回:使用提前返回模式避免深层嵌套
- 错误包装:使用
fmt.Errorf
和%w
包装错误,保留上下文 - 哨兵错误:使用预定义的错误变量,方便比较
- 自定义错误:实现 Error() 方法创建自定义错误类型
- panic/recover:仅在不可恢复的严重错误时使用 panic
实用建议
- 🔧 总是检查 error,不要使用
_
忽略 - 🔧 使用
errors.Is
和errors.As
检查错误类型 - 🔧 为错误添加上下文信息,使用
fmt.Errorf
- 🔧 使用 defer 确保资源清理
- 🔧 在库代码中避免使用 panic
- 🔧 日志记录错误但不要在库中直接 log
上一篇:面向对象 vs 组合模式
系列目录:从 Python 到 Go 完全指南