1. Go语言错误处理哲学
Go语言采用了显式的错误处理机制,与其他语言的异常处理不同。Go认为错误是程序正常流程的一部分,应该被显式地检查和处理,而不是通过异常机制来处理。这种设计理念让错误处理更加透明和可控。
1.1 Go错误处理的特点
Go的错误处理哲学:
- 错误是值,不是异常
- 显式检查比回避更好
- 错误应该被处理,而不是被忽略
- 失败的请求是正常的,成功的请求才是例外
与其他语言的对比:
go
// Java风格的异常处理
try {
result = riskyOperation();
} catch (Exception e) {
// 处理异常
}
// Go风格的错误处理
result, err := riskyOperation();
if err != nil {
// 处理错误
return err
}
1.2 为什么Go选择显式错误处理
优点:
- 清晰可见:错误处理逻辑明确,不会隐藏在深处
- 强制处理:编译器会提醒你处理返回的错误
- 性能更好:避免了异常处理的性能开销
- 更可控:程序员完全控制错误处理流程
缺点:
- 代码冗长:需要大量的if err != nil检查
- 容易忽略:新手可能习惯性忽略错误检查
2. error接口详解
2.1 error接口定义
go
// error接口定义在builtin包中
type error interface {
Error() string
}
2.2 errors包的基本使用
go
package main
import (
"errors"
"fmt"
)
func main() {
// 创建简单错误
err1 := errors.New("这是一个简单的错误")
fmt.Printf("错误1:%v\n", err1)
fmt.Printf("错误1类型:%T\n", err1)
// 使用fmt.Errorf创建格式化错误
name := "张三"
age := -5
err2 := fmt.Errorf("用户 %s 的年龄 %d 无效", name, age)
fmt.Printf("错误2:%v\n", err2)
// 错误的字符串表示
fmt.Printf("错误1的字符串:%s\n", err1.Error())
fmt.Printf("错误2的字符串:%s\n", err2.Error())
// nil错误表示没有错误
var noError error
fmt.Printf("nil错误:%v,是否为nil:%t\n", noError, noError == nil)
}
运行结果:
bash
错误1:这是一个简单的错误
错误1类型:*errors.errorString
错误2:用户 张三 的年龄 -5 无效
错误1的字符串:这是一个简单的错误
错误2的字符串:用户 张三 的年龄 -5 无效
nil错误:<nil>,是否为nil:true
2.3 自定义错误类型
go
package main
import (
"fmt"
)
// 自定义错误类型1:简单结构体
type ValidationError struct {
Field string
Value interface{}
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("验证错误 - 字段:%s,值:%v,原因:%s",
e.Field, e.Value, e.Message)
}
// 自定义错误类型2:带错误码
type APIError struct {
Code int
Message string
Details string
}
func (e APIError) Error() string {
return fmt.Sprintf("API错误[%d]: %s (详情: %s)", e.Code, e.Message, e.Details)
}
// 自定义错误类型3:包装其他错误
type WrappedError struct {
Operation string
Err error
}
func (e WrappedError) Error() string {
return fmt.Sprintf("操作 %s 失败: %v", e.Operation, e.Err)
}
func (e WrappedError) Unwrap() error {
return e.Err
}
// 使用自定义错误的函数
func validateUserAge(age int) error {
if age < 0 {
return ValidationError{
Field: "age",
Value: age,
Message: "年龄不能为负数",
}
}
if age > 150 {
return ValidationError{
Field: "age",
Value: age,
Message: "年龄不能超过150岁",
}
}
return nil
}
func callAPI(endpoint string) error {
if endpoint == "/admin" {
return APIError{
Code: 403,
Message: "权限不足",
Details: "需要管理员权限才能访问",
}
}
if endpoint == "/timeout" {
return APIError{
Code: 504,
Message: "请求超时",
Details: "服务器响应超时",
}
}
return nil
}
func processData(data string) error {
if data == "" {
return WrappedError{
Operation: "数据处理",
Err: errors.New("输入数据为空"),
}
}
return nil
}
func main() {
fmt.Println("=== 自定义错误类型演示 ===")
// 测试验证错误
ages := []int{-5, 25, 200}
for _, age := range ages {
fmt.Printf("验证年龄 %d: ", age)
if err := validateUserAge(age); err != nil {
fmt.Printf("❌ %v\n", err)
} else {
fmt.Printf("✅ 验证通过\n")
}
}
// 测试API错误
endpoints := []string{"/user", "/admin", "/timeout"}
fmt.Println("\n=== API调用测试 ===")
for _, endpoint := range endpoints {
fmt.Printf("调用 %s: ", endpoint)
if err := callAPI(endpoint); err != nil {
fmt.Printf("❌ %v\n", err)
} else {
fmt.Printf("✅ 调用成功\n")
}
}
// 测试包装错误
fmt.Println("\n=== 包装错误测试 ===")
testData := []string{"", "有效数据"}
for _, data := range testData {
fmt.Printf("处理数据 '%s': ", data)
if err := processData(data); err != nil {
fmt.Printf("❌ %v\n", err)
// 解包错误
if wrapped, ok := err.(WrappedError); ok {
fmt.Printf(" 原始错误: %v\n", wrapped.Err)
}
} else {
fmt.Printf("✅ 处理成功\n")
}
}
}
运行结果:
bash
=== 自定义错误类型演示 ===
验证年龄 -5: ❌ 验证错误 - 字段:age,值:-5,原因:年龄不能为负数
验证年龄 25: ✅ 验证通过
验证年龄 200: ❌ 验证错误 - 字段:age,值:200,原因:年龄不能超过150岁
=== API调用测试 ===
调用 /user: ✅ 调用成功
调用 /admin: ❌ API错误[403]: 权限不足 (详情: 需要管理员权限才能访问)
调用 /timeout: ❌ API错误[504]: 请求超时 (详情: 服务器响应超时)
=== 包装错误测试 ===
处理数据 '': ❌ 操作 数据处理 失败: 输入数据为空
原始错误: 输入数据为空
处理数据 '有效数据': ✅ 处理成功
3. 错误检查和处理模式
3.1 基本错误处理模式
go
package main
import (
"fmt"
"os"
)
// 模式1:立即处理错误
func readFileBasic(filename string) error {
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("打开文件失败 %s: %w", filename, err)
}
defer file.Close()
// 文件操作...
fmt.Printf("成功打开文件:%s\n", filename)
return nil
}
// 模式2:错误累积处理
func processMultipleFiles(filenames []string) error {
var errors []error
for _, filename := range filenames {
if err := readFileBasic(filename); err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
return fmt.Errorf("处理文件时发生 %d 个错误: %v", len(errors), errors)
}
return nil
}
// 模式3:错误重试
func retryOperation(operation func() error, maxRetries int) error {
var lastErr error
for i := 0; i < maxRetries; i++ {
err := operation()
if err == nil {
return nil // 成功
}
lastErr = err
fmt.Printf("第 %d 次尝试失败:%v\n", i+1, err)
}
return fmt.Errorf("重试 %d 次后仍然失败,最后错误:%w", maxRetries, lastErr)
}
// 模式4:错误忽略(谨慎使用)
func ignoreErrors() {
// 只在确实可以忽略错误的情况下使用
dir, _ := os.Getwd() // 忽略错误
fmt.Printf("当前目录:%s\n", dir)
}
func main() {
fmt.Println("=== 基本错误处理模式 ===")
// 测试基本模式
fmt.Println("1. 基本错误处理:")
if err := readFileBasic("不存在的文件.txt"); err != nil {
fmt.Printf("❌ %v\n", err)
}
// 测试累积模式
fmt.Println("\n2. 错误累积处理:")
files := []string{"file1.txt", "file2.txt", "file3.txt"}
if err := processMultipleFiles(files); err != nil {
fmt.Printf("❌ %v\n", err)
}
// 测试重试模式
fmt.Println("\n3. 错误重试:")
failingOp := func() error {
return fmt.Errorf("模拟操作失败")
}
if err := retryOperation(failingOp, 3); err != nil {
fmt.Printf("❌ %v\n", err)
}
// 测试忽略错误
fmt.Println("\n4. 错误忽略:")
ignoreErrors()
}
运行结果:
bash
=== 基本错误处理模式 ===
1. 基本错误处理:
❌ 打开文件失败 不存在的文件.txt: open 不存在的文件.txt: The system cannot find the file specified.
2. 错误累积处理:
❌ 处理文件时发生 3 个错误: [打开文件失败 file1.txt: open file1.txt: The system cannot find the file specified. 打开文件失败 file2.txt: open file2.txt: The system cannot find the file specified. 打开文件失败 file3.txt: open file3.txt: The system cannot find the file specified.]
3. 错误重试:
第 1 次尝试失败:模拟操作失败
第 2 次尝试失败:模拟操作失败
第 3 次尝试失败:模拟操作失败
❌ 重试 3 次后仍然失败,最后错误:模拟操作失败
4. 错误忽略:
当前目录:F:\blog\zg-blog
3.2 错误类型断言处理
go
package main
import (
"fmt"
"os"
"strconv"
)
type ValidationError struct {
Field string
Message string
}
func (e ValidationError) Error() string {
return fmt.Sprintf("验证错误 - %s: %s", e.Field, e.Message)
}
type NetworkError struct {
Code int
Message string
}
func (e NetworkError) Error() string {
return fmt.Sprintf("网络错误[%d]: %s", e.Code, e.Message)
}
func parseAndValidate(input string) error {
// 尝试转换为整数
num, err := strconv.Atoi(input)
if err != nil {
return ValidationError{
Field: "input",
Message: "输入必须是有效的整数",
}
}
// 验证数值范围
if num < 0 {
return ValidationError{
Field: "number",
Message: "数字不能为负数",
}
}
// 模拟网络操作
if num == 999 {
return NetworkError{
Code: 503,
Message: "服务暂时不可用",
}
}
return nil
}
func handleSpecificErrors() {
inputs := []string{"abc", "-5", "999", "42"}
for _, input := range inputs {
fmt.Printf("处理输入 '%s': ", input)
err := parseAndValidate(input)
if err != nil {
// 方法1:类型断言
if validationErr, ok := err.(ValidationError); ok {
fmt.Printf("❌ 验证错误 - 字段:%s,消息:%s\n",
validationErr.Field, validationErr.Message)
continue
}
// 方法2:类型选择
switch specificErr := err.(type) {
case NetworkError:
fmt.Printf("❌ 网络错误 - 代码:%d,消息:%s\n",
specificErr.Code, specificErr.Message)
case ValidationError:
fmt.Printf("❌ 验证错误 - 字段:%s,消息:%s\n",
specificErr.Field, specificErr.Message)
default:
fmt.Printf("❌ 未知错误:%v\n", err)
}
} else {
fmt.Printf("✅ 处理成功\n")
}
}
}
func main() {
fmt.Println("=== 错误类型断言处理 ===")
handleSpecificErrors()
// 检查文件是否存在
fmt.Println("\n=== 文件错误处理 ===")
filenames := []string{"existing.txt", "missing.txt"}
for _, filename := range filenames {
fmt.Printf("检查文件 '%s': ", filename)
_, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
fmt.Printf("❌ 文件不存在\n")
} else if os.IsPermission(err) {
fmt.Printf("❌ 权限不足\n")
} else {
fmt.Printf("❌ 其他错误:%v\n", err)
}
} else {
fmt.Printf("✅ 文件存在\n")
}
}
}
运行结果:
bash
=== 错误类型断言处理 ===
处理输入 'abc': ❌ 验证错误 - 字段:input,消息:输入必须是有效的整数
处理输入 '-5': ❌ 验证错误 - 字段:number,消息:数字不能为负数
处理输入 '999': ❌ 网络错误 - 代码:503,消息:服务暂时不可用
处理输入 '42': ✅ 处理成功
=== 文件错误处理 ===
检查文件 'existing.txt': ✅ 文件存在
检查文件 'missing.txt': ❌ 文件不存在
4. 错误包装和上下文
4.1 errors包的新功能(Go 1.13+)
go
package main
import (
"errors"
"fmt"
"io"
"os"
)
// 模拟数据库操作
func databaseQuery(query string) error {
if query == "invalid" {
return errors.New("数据库查询语法错误")
}
if query == "timeout" {
return errors.New("数据库连接超时")
}
return nil
}
// 业务层函数
func getUserData(userID string) error {
// 验证用户ID
if userID == "" {
return fmt.Errorf("用户ID不能为空")
}
// 查询数据库
if err := databaseQuery("SELECT * FROM users WHERE id = " + userID); err != nil {
return fmt.Errorf("获取用户数据失败: %w", err) // 包装错误
}
return nil
}
// 服务层函数
func handleUserRequest(userID string) error {
if err := getUserData(userID); err != nil {
return fmt.Errorf("处理用户请求失败: %w", err) // 再次包装
}
return nil
}
func demonstrateErrorWrapping() {
userIDs := []string{"", "invalid", "timeout", "123"}
for _, userID := range userIDs {
fmt.Printf("处理用户请求 UserID='%s': ", userID)
err := handleUserRequest(userID)
if err != nil {
fmt.Printf("❌ %v\n", err)
// 检查特定错误
if errors.Is(err, os.ErrNotExist) {
fmt.Printf(" 原因:文件不存在\n")
}
// 查找特定类型的错误
var pathError *os.PathError
if errors.As(err, &pathError) {
fmt.Printf(" 路径错误:操作=%s,路径=%s\n",
pathError.Op, pathError.Path)
}
// 打印错误链
fmt.Printf(" 错误链:")
for err != nil {
fmt.Printf("%v", err)
if err = errors.Unwrap(err); err != nil {
fmt.Printf(" -> ")
}
}
fmt.Println()
} else {
fmt.Printf("✅ 请求处理成功\n")
}
fmt.Println()
}
}
// 自定义可包装错误
type CustomError struct {
Code int
Message string
Err error // 嵌入底层错误
}
func (e CustomError) Error() string {
if e.Err != nil {
return fmt.Sprintf("自定义错误[%d]: %s (原因: %v)", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("自定义错误[%d]: %s", e.Code, e.Message)
}
func (e CustomError) Unwrap() error {
return e.Err
}
func main() {
fmt.Println("=== 错误包装和上下文 ===")
demonstrateErrorWrapping()
// 自定义错误包装示例
fmt.Println("=== 自定义错误包装 ===")
baseErr := errors.New("底层错误")
wrappedErr := CustomError{
Code: 1001,
Message: "业务逻辑错误",
Err: baseErr,
}
fmt.Printf("包装错误:%v\n", wrappedErr)
fmt.Printf("底层错误:%v\n", errors.Unwrap(wrappedErr))
fmt.Printf("是否有底层错误:%t\n", errors.Is(wrappedErr, baseErr))
}
运行结果:
bash
=== 错误包装和上下文 ===
处理用户请求 UserID='': ❌ 处理用户请求失败: 获取用户数据失败: 用户ID不能为空
错误链:处理用户请求失败: 获取用户数据失败: 用户ID不能为空
处理用户请求 UserID='invalid': ❌ 处理用户请求失败: 获取用户数据失败: 数据库查询语法错误
错误链:处理用户请求失败: 获取用户数据失败: 数据库查询语法错误
处理用户请求 UserID='timeout': ❌ 处理用户请求失败: 获取用户数据失败: 数据库连接超时
错误链:处理用户请求失败: 获取用户数据失败: 数据库连接超时
处理用户请求 UserID='123': ✅ 请求处理成功
=== 自定义错误包装 ===
包装错误:自定义错误[1001]: 业务逻辑错误 (原因: 底层错误)
底层错误:底层错误
是否有底层错误:true
4.2 多错误处理
go
package main
import (
"errors"
"fmt"
"strings"
)
// 错误集合类型
type ErrorCollection []error
func (ec ErrorCollection) Error() string {
if len(ec) == 0 {
return ""
}
var errorMsgs []string
for _, err := range ec {
errorMsgs = append(errorMsgs, err.Error())
}
return fmt.Sprintf("发生 %d 个错误: %s",
len(ec), strings.Join(errorMsgs, "; "))
}
// 并行任务处理
func processTasks(taskNames []string) error {
var errorsList ErrorCollection
// 模拟并行处理多个任务
for _, taskName := range taskNames {
if err := processSingleTask(taskName); err != nil {
errorsList = append(errorsList,
fmt.Errorf("任务 '%s' 失败: %w", taskName, err))
}
}
if len(errorsList) > 0 {
return errorsList
}
return nil
}
func processSingleTask(taskName string) error {
switch taskName {
case "task1":
return errors.New("网络连接失败")
case "task2":
return errors.New("文件不存在")
case "task3":
return nil // 成功
case "task4":
return errors.New("权限不足")
default:
return errors.New("未知任务")
}
}
// 错误分组处理
func handleGroupedErrors() {
taskGroups := [][]string{
{"task1", "task2", "task3"},
{"task3", "task4"},
{"task1", "task4", "task5"},
}
for i, tasks := range taskGroups {
fmt.Printf("处理任务组 %d %v:\n", i+1, tasks)
err := processTasks(tasks)
if err != nil {
if errorCollection, ok := err.(ErrorCollection); ok {
fmt.Printf(" ❌ 发生 %d 个错误:\n", len(errorCollection))
for j, singleErr := range errorCollection {
fmt.Printf(" %d. %v\n", j+1, singleErr)
}
} else {
fmt.Printf(" ❌ 单个错误: %v\n", err)
}
} else {
fmt.Printf(" ✅ 所有任务成功完成\n")
}
fmt.Println()
}
}
func main() {
fmt.Println("=== 多错误处理 ===")
handleGroupedErrors()
// 错误合并示例
fmt.Println("=== 错误合并 ===")
err1 := errors.New("第一个错误")
err2 := errors.New("第二个错误")
err3 := errors.New("第三个错误")
combined := ErrorCollection{err1, err2, err3}
fmt.Printf("合并错误:%v\n", combined)
// 空错误集合
empty := ErrorCollection{}
fmt.Printf("空错误集合:%v,是否为nil:%t\n", empty, empty == nil)
}
运行结果:
bash
=== 多错误处理 ===
处理任务组 1 [task1 task2 task3]:
❌ 发生 2 个错误:
1. 任务 'task1' 失败: 网络连接失败
2. 任务 'task2' 失败: 文件不存在
处理任务组 2 [task3 task4]:
❌ 发生 1 个错误:
1. 任务 'task4' 失败: 权限不足
处理任务组 3 [task1 task4 task5]:
❌ 发生 3 个错误:
1. 任务 'task1' 失败: 网络连接失败
2. 任务 'task4' 失败: 权限不足
3. 任务 'task5' 失败: 未知任务
=== 错误合并 ===
合并错误:发生 3 个错误: 第一个错误; 第二个错误; 第三个错误
空错误集合:,是否为nil:true
5. 实际应用中的错误处理
5.1 Web服务错误处理
go
package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
)
// API错误响应结构
type APIErrorResponse struct {
Error bool `json:"error"`
Code int `json:"code"`
Message string `json:"message"`
Time string `json:"time"`
}
// 自定义HTTP错误类型
type HTTPError struct {
Code int
Message string
Err error
}
func (e HTTPError) Error() string {
if e.Err != nil {
return fmt.Sprintf("HTTP %d: %s (原因: %v)", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("HTTP %d: %s", e.Code, e.Message)
}
// 错误处理中间件
func errorHandler(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("恐慌恢复:%v\n", err)
sendErrorResponse(w, http.StatusInternalServerError, "内部服务器错误")
}
}()
next(w, r)
}
}
// 发送错误响应
func sendErrorResponse(w http.ResponseWriter, code int, message string) {
response := APIErrorResponse{
Error: true,
Code: code,
Message: message,
Time: time.Now().Format(time.RFC3339),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
if err := json.NewEncoder(w).Encode(response); err != nil {
fmt.Printf("JSON编码错误:%v\n", err)
}
}
// 模拟用户服务
type UserService struct{}
func (s *UserService) GetUser(id string) (map[string]interface{}, error) {
if id == "" {
return nil, HTTPError{
Code: http.StatusBadRequest,
Message: "用户ID不能为空",
}
}
if id == "admin" {
return nil, HTTPError{
Code: http.StatusForbidden,
Message: "禁止访问管理员账户",
}
}
if id == "missing" {
return nil, HTTPError{
Code: http.StatusNotFound,
Message: "用户不存在",
}
}
// 成功情况
return map[string]interface{}{
"id": id,
"name": fmt.Sprintf("用户%s", id),
"age": 25,
}, nil
}
// HTTP处理器
func userHandler(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("id")
userService := &UserService{}
user, err := userService.GetUser(userID)
if err != nil {
if httpErr, ok := err.(HTTPError); ok {
sendErrorResponse(w, httpErr.Code, httpErr.Message)
} else {
sendErrorResponse(w, http.StatusInternalServerError, "内部错误")
}
return
}
// 成功响应
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(user)
}
func demonstrateWebErrors() {
// 模拟HTTP请求处理
testCases := []string{"", "admin", "missing", "normal"}
fmt.Println("=== Web服务错误处理演示 ===")
for _, userID := range testCases {
fmt.Printf("请求用户ID '%s': ", userID)
// 模拟请求
userService := &UserService{}
user, err := userService.GetUser(userID)
if err != nil {
if httpErr, ok := err.(HTTPError); ok {
fmt.Printf("❌ HTTP错误 %d: %s\n", httpErr.Code, httpErr.Message)
} else {
fmt.Printf("❌ 未知错误: %v\n", err)
}
} else {
fmt.Printf("✅ 成功获取用户: %+v\n", user)
}
}
}
func main() {
demonstrateWebErrors()
// 启动HTTP服务器示例(注释掉避免实际启动)
/*
http.HandleFunc("/user", errorHandler(userHandler))
fmt.Println("服务器启动在 :8080")
http.ListenAndServe(":8080", nil)
*/
// 错误日志记录示例
fmt.Println("\n=== 错误日志记录 ===")
logError := func(operation string, err error) {
fmt.Printf("[%s] ERROR: %v\n",
time.Now().Format("2006-01-02 15:04:05"), err)
}
// 模拟各种错误场景
errorsToLog := []error{
errors.New("数据库连接失败"),
fmt.Errorf("文件读取错误: %w", errors.New("权限不足")),
HTTPError{Code: 500, Message: "内部服务器错误"},
}
for _, err := range errorsToLog {
logError("数据处理", err)
}
}
运行结果:
bash
=== Web服务错误处理演示 ===
请求用户ID '': ❌ HTTP错误 400: 用户ID不能为空
请求用户ID 'admin': ❌ HTTP错误 403: 禁止访问管理员账户
请求用户ID 'missing': ❌ HTTP错误 404: 用户不存在
请求用户ID 'normal': ✅ 成功获取用户: map[age:25 id:normal name:用户normal]
=== 错误日志记录 ===
[2024-01-15 15:30:45] ERROR: 数据库连接失败
[2024-01-15 15:30:45] ERROR: 文件读取错误: 权限不足
[2024-01-15 15:30:45] ERROR: HTTP 500: 内部服务器错误
5.2 数据库操作错误处理
go
package main
import (
"database/sql"
"errors"
"fmt"
)
// 模拟数据库驱动
type MockDB struct {
tables map[string][]map[string]interface{}
}
func NewMockDB() *MockDB {
return &MockDB{
tables: make(map[string][]map[string]interface{}),
}
}
func (db *MockDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
if query == "invalid sql" {
return nil, errors.New("SQL语法错误")
}
if query == "timeout" {
return nil, errors.New("查询超时")
}
// 模拟成功查询
return &sql.Rows{}, nil
}
func (db *MockDB) Exec(query string, args ...interface{}) (sql.Result, error) {
if query == "duplicate key" {
return nil, errors.New("违反唯一约束")
}
if query == "foreign key violation" {
return nil, errors.New("外键约束违规")
}
return &mockResult{rowsAffected: 1}, nil
}
type mockResult struct {
rowsAffected int64
}
func (r *mockResult) LastInsertId() (int64, error) {
return 1, nil
}
func (r *mockResult) RowsAffected() (int64, error) {
return r.rowsAffected, nil
}
// 数据访问层
type UserRepository struct {
db *MockDB
}
func NewUserRepository(db *MockDB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) CreateUser(user map[string]interface{}) error {
// 验证用户数据
if user["name"] == nil || user["name"] == "" {
return fmt.Errorf("用户名不能为空")
}
if user["email"] == nil || user["email"] == "" {
return fmt.Errorf("邮箱不能为空")
}
// 执行数据库插入
query := "INSERT INTO users (name, email) VALUES (?, ?)"
_, err := r.db.Exec(query, user["name"], user["email"])
if err != nil {
// 根据不同错误类型返回不同的业务错误
if err.Error() == "违反唯一约束" {
return fmt.Errorf("邮箱地址已存在:%w", err)
}
if err.Error() == "外键约束违规" {
return fmt.Errorf("关联数据不存在:%w", err)
}
return fmt.Errorf("数据库操作失败:%w", err)
}
return nil
}
func (r *UserRepository) FindUserByEmail(email string) (map[string]interface{}, error) {
if email == "" {
return nil, fmt.Errorf("邮箱地址不能为空")
}
query := "SELECT * FROM users WHERE email = ?"
rows, err := r.db.Query(query, email)
if err != nil {
return nil, fmt.Errorf("查询用户失败:%w", err)
}
defer rows.Close()
// 模拟查询结果
if email == "notfound@example.com" {
return nil, fmt.Errorf("用户不存在")
}
return map[string]interface{}{
"id": 1,
"name": "张三",
"email": email,
}, nil
}
func demonstrateDatabaseErrors() {
db := NewMockDB()
repo := NewUserRepository(db)
fmt.Println("=== 数据库错误处理演示 ===")
// 测试创建用户的各种错误情况
testUsers := []map[string]interface{}{
{"name": "", "email": "test@example.com"}, // 名字为空
{"name": "张三", "email": ""}, // 邮箱为空
{"name": "李四", "email": "duplicate@example.com"}, // 邮箱重复
{"name": "王五", "email": "valid@example.com"}, // 正常情况
}
for i, user := range testUsers {
fmt.Printf("测试%d - 创建用户 %+v: ", i+1, user)
err := repo.CreateUser(user)
if err != nil {
fmt.Printf("❌ %v\n", err)
} else {
fmt.Printf("✅ 创建成功\n")
}
}
// 测试查询用户
fmt.Println("\n=== 查询用户测试 ===")
testEmails := []string{
"", // 空邮箱
"notfound@example.com", // 用户不存在
"valid@example.com", // 正常查询
}
for _, email := range testEmails {
fmt.Printf("查询邮箱 '%s': ", email)
user, err := repo.FindUserByEmail(email)
if err != nil {
fmt.Printf("❌ %v\n", err)
} else {
fmt.Printf("✅ 找到用户: %+v\n", user)
}
}
}
func main() {
demonstrateDatabaseErrors()
// 事务错误处理示例
fmt.Println("\n=== 事务错误处理 ===")
simulateTransaction := func() error {
// 开始事务
fmt.Println("开始事务...")
// 第一步:创建用户
if err := errors.New("创建用户失败"); err != nil {
fmt.Printf("❌ 事务步骤1失败:%v\n", err)
return fmt.Errorf("事务回滚:创建用户失败 - %w", err)
}
// 第二步:创建用户资料
if err := errors.New("创建资料失败"); err != nil {
fmt.Printf("❌ 事务步骤2失败:%v\n", err)
return fmt.Errorf("事务回滚:创建资料失败 - %w", err)
}
// 第三步:发送欢迎邮件
if err := errors.New("发送邮件失败"); err != nil {
fmt.Printf("⚠️ 非关键步骤失败:%v\n", err)
// 非关键步骤失败,可以选择继续
}
fmt.Println("✅ 事务提交成功")
return nil
}
if err := simulateTransaction(); err != nil {
fmt.Printf("事务最终失败:%v\n", err)
}
}
运行结果:
bash
=== 数据库错误处理演示 ===
测试1 - 创建用户 map[email:test@example.com name:]: ❌ 用户名不能为空
测试2 - 创建用户 map[email: name:张三]: ❌ 邮箱不能为空
测试3 - 创建用户 map[email:duplicate@example.com name:李四]: ❌ 邮箱地址已存在:违反唯一约束
测试4 - 创建用户 map[email:valid@example.com name:王五]: ✅ 创建成功
=== 查询用户测试 ===
查询邮箱 '': ❌ 邮箱地址不能为空
查询邮箱 'notfound@example.com': ❌ 用户不存在
查询邮箱 'valid@example.com': ✅ 找到用户: map[email:valid@example.com id:1 name:张三]
=== 事务错误处理 ===
开始事务...
❌ 事务步骤1失败:创建用户失败
事务最终失败:事务回滚:创建用户失败 - 创建用户失败
6. 错误处理最佳实践
6.1 错误处理原则
go
package main
import (
"errors"
"fmt"
"log"
)
// 好的错误处理实践
// 1. 及时处理错误,不要延迟
func goodErrorHandling1() error {
data, err := readConfig()
if err != nil {
return fmt.Errorf("读取配置失败: %w", err) // 立即处理
}
if err := validateConfig(data); err != nil {
return fmt.Errorf("配置验证失败: %w", err) // 立即处理
}
return nil
}
// 2. 提供有意义的错误信息
func goodErrorHandling2(filename string) error {
file, err := openFile(filename)
if err != nil {
// 好的错误信息包含上下文
return fmt.Errorf("无法打开配置文件 '%s': %w", filename, err)
}
defer file.Close()
return nil
}
// 3. 区分不同类型的错误
func goodErrorHandling3(userID string) error {
user, err := findUser(userID)
if err != nil {
// 根据错误类型采取不同行动
switch {
case errors.Is(err, ErrUserNotFound):
return fmt.Errorf("用户不存在,请先注册")
case errors.Is(err, ErrDatabaseDown):
return fmt.Errorf("系统维护中,请稍后再试")
default:
return fmt.Errorf("获取用户信息失败: %w", err)
}
}
if !user.IsActive {
return fmt.Errorf("用户账号已被禁用")
}
return nil
}
// 4. 记录错误但不暴露内部细节
func goodErrorHandling4() {
if err := sensitiveOperation(); err != nil {
// 记录详细错误到日志
log.Printf("敏感操作失败(内部错误):%+v", err)
// 返回通用错误给用户
fmt.Println("操作失败,请联系管理员")
}
}
// 模拟函数
func readConfig() (interface{}, error) {
return nil, errors.New("文件不存在")
}
func validateConfig(data interface{}) error {
return errors.New("配置格式错误")
}
func openFile(filename string) (interface{}, error) {
return nil, errors.New("权限被拒绝")
}
var (
ErrUserNotFound = errors.New("用户不存在")
ErrDatabaseDown = errors.New("数据库宕机")
)
func findUser(userID string) (interface{}, error) {
return nil, ErrUserNotFound
}
type User struct {
IsActive bool
}
func sensitiveOperation() error {
return errors.New("内部数据库凭证泄露")
}
// 不好的错误处理实践
// 1. 忽略错误
func badErrorHandling1() {
data, _ := readConfig() // 错误被忽略
processConfig(data) // 可能使用nil数据
}
// 2. 错误信息不明确
func badErrorHandling2(filename string) error {
_, err := openFile(filename)
if err != nil {
return err // 错误信息太简单
}
return nil
}
// 3. 过度包装错误
func badErrorHandling3() error {
err := lowLevelError()
if err != nil {
return fmt.Errorf("高层错误: %w",
fmt.Errorf("中层错误: %w",
fmt.Errorf("底层错误: %w", err))) // 过度包装
}
return nil
}
func lowLevelError() error {
return errors.New("底层错误")
}
func processConfig(data interface{}) {
// 处理配置
}
func main() {
fmt.Println("=== 错误处理最佳实践 ===")
fmt.Println("好的实践示例:")
if err := goodErrorHandling1(); err != nil {
fmt.Printf("❌ %v\n", err)
}
if err := goodErrorHandling2("config.json"); err != nil {
fmt.Printf("❌ %v\n", err)
}
if err := goodErrorHandling3("user123"); err != nil {
fmt.Printf("❌ %v\n", err)
}
goodErrorHandling4()
fmt.Println("\n避免的坏实践:")
fmt.Println("1. 忽略错误可能导致程序崩溃")
fmt.Println("2. 不明确的错误信息难以调试")
fmt.Println("3. 过度包装使错误链过于复杂")
// 演示错误日志的最佳实践
fmt.Println("\n=== 错误日志最佳实践 ===")
logError := func(context string, err error) {
// 开发环境:详细日志
log.Printf("[DEBUG] %s 失败: %+v", context, err)
// 生产环境:简化日志
log.Printf("[ERROR] %s 失败,请查看详细日志", context)
}
// 模拟错误记录
sampleErr := fmt.Errorf("数据库连接失败: %w",
errors.New("网络超时"))
logError("用户登录", sampleErr)
}
运行结果:
bash
=== 错误处理最佳实践 ===
好的实践示例:
❌ 读取配置失败: 文件不存在
❌ 无法打开配置文件 'config.json': 权限被拒绝
❌ 用户不存在,请先注册
操作失败,请联系管理员
避免的坏实践:
1. 忽略错误可能导致程序崩溃
2. 不明确的错误信息难以调试
3. 过度包装使错误链过于复杂
=== 错误日志最佳实践 ===
2024/01/15 15:30:45 [DEBUG] 用户登录 失败: 数据库连接失败: 网络超时
2024/01/15 15:30:45 [ERROR] 用户登录 失败,请查看详细日志
7. 总结
Go语言的错误处理机制体现了其"简单、明确、实用"的设计哲学:
核心要点:
- 错误是值,应该被显式检查和处理
- 使用error接口统一错误表示
- 通过errors包提供现代错误处理功能
- 合理使用错误包装和上下文信息
最佳实践:
- 及时处理:发现错误立即处理,不要延迟
- 提供上下文:错误信息应该包含足够的上下文
- 区分错误类型:根据错误类型采取不同处理策略
- 记录详细日志:内部记录详细错误,对外返回适当信息
- 避免错误忽略:除非确实可以忽略,否则都应该处理
现代发展:
良好的错误处理不仅能提高程序的健壮性,还能大大改善用户体验和系统的可维护性。在实际开发中,应该根据具体场景选择合适的错误处理策略。