Appearance
自定义错误
1. 概述
在 Go 语言中,错误是一个实现了 error 接口的类型。虽然标准库提供了基本的错误创建方法,但在实际开发中,我们经常需要创建带有额外信息的自定义错误,以便更好地描述错误的性质和上下文。
本章节将详细介绍如何创建和使用自定义错误,包括自定义错误的实现方式、最佳实践以及在实际项目中的应用场景。
2. 基本概念
2.1 语法
创建自定义错误的基本语法:
go
// 定义自定义错误类型
type MyError struct {
// 自定义字段
Message string
Code int
Cause error
}
// 实现 error 接口
func (e *MyError) Error() string {
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}
// 可选:实现 Unwrap 方法,用于错误链
func (e *MyError) Unwrap() error {
return e.Cause
}
// 创建自定义错误的函数
func NewMyError(code int, message string, cause error) *MyError {
return &MyError{Code: code, Message: message, Cause: cause}
}2.2 语义
- 自定义错误类型:通过实现
error接口创建的错误类型 - 错误字段:自定义错误中包含的额外信息,如错误码、错误消息、原始错误等
- 错误链:通过实现
Unwrap()方法,将自定义错误与原始错误链接起来
2.3 规范
- 自定义错误类型应该提供清晰、准确的错误信息
- 自定义错误类型应该包含足够的上下文信息
- 包装其他错误的自定义错误应该实现
Unwrap()方法 - 错误类型的命名应该清晰反映其用途
3. 原理深度解析
3.1 自定义错误的实现原理
自定义错误的核心是实现 error 接口,该接口定义如下:
go
type error interface {
Error() string
}任何实现了 Error() string 方法的类型都可以作为错误使用。为了支持错误链,自定义错误还可以实现 Unwrap() error 方法,该方法返回被包装的原始错误。
3.2 错误链的实现
错误链是通过 Unwrap() 方法实现的,当使用 errors.Is() 或 errors.As() 函数时,这些函数会遍历错误链,找到原始错误或特定类型的错误。
go
// 错误链的遍历逻辑
type causer interface {
Cause() error
}
type unwrapper interface {
Unwrap() error
}
func is(err, target error) bool {
if err == target {
return true
}
// 尝试通过 Unwrap() 方法获取原始错误
if u, ok := err.(unwrapper); ok {
if is(u.Unwrap(), target) {
return true
}
}
// 尝试通过 Cause() 方法获取原始错误(兼容第三方库)
if c, ok := err.(causer); ok {
if is(c.Cause(), target) {
return true
}
}
return false
}4. 常见错误与踩坑点
4.1 忘记实现 Error() 方法
错误表现:自定义类型无法作为错误使用
产生原因:没有实现 error 接口的 Error() string 方法
解决方案:确保自定义错误类型实现了 Error() string 方法
4.2 没有实现 Unwrap() 方法
错误表现:包装错误后,无法通过 errors.Is() 和 errors.As() 函数正确处理错误链
产生原因:自定义错误类型包装了其他错误,但没有实现 Unwrap() 方法
解决方案:为包装了其他错误的自定义错误类型实现 Unwrap() 方法
4.3 错误信息不够详细
错误表现:错误信息过于简单,无法提供足够的上下文信息
产生原因:自定义错误类型没有包含足够的上下文信息
解决方案:在自定义错误类型中添加必要的字段,提供详细的错误信息
4.4 错误类型判断错误
错误表现:使用 == 直接比较自定义错误,导致判断失败
产生原因:不同的错误实例即使字段值相同,也不是同一个对象
解决方案:使用 errors.As() 函数进行错误类型判断
5. 常见应用场景
5.1 带错误码的错误
场景描述:需要在错误中包含错误码,以便客户端根据错误码采取不同的处理策略
使用方法:定义包含错误码字段的自定义错误类型
示例代码:
go
type AppError struct {
Code int
Message string
}
func (e *AppError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func NewAppError(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}
// 使用示例
func divide(a, b int) (int, error) {
if b == 0 {
return 0, NewAppError(400, "division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
if appErr, ok := err.(*AppError); ok {
fmt.Printf("Error code: %d\n", appErr.Code)
}
return
}
fmt.Printf("Result: %d\n", result)
}5.2 带上下文信息的错误
场景描述:需要在错误中包含更多的上下文信息,如请求 ID、用户 ID 等
使用方法:定义包含上下文信息字段的自定义错误类型
示例代码:
go
type ContextError struct {
Message string
RequestID string
UserID string
Cause error
}
func (e *ContextError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s (request ID: %s, user ID: %s): %v", e.Message, e.RequestID, e.UserID, e.Cause)
}
return fmt.Sprintf("%s (request ID: %s, user ID: %s)", e.Message, e.RequestID, e.UserID)
}
func (e *ContextError) Unwrap() error {
return e.Cause
}
func NewContextError(message, requestID, userID string, cause error) *ContextError {
return &ContextError{Message: message, RequestID: requestID, UserID: userID, Cause: cause}
}
// 使用示例
func processRequest(req Request) error {
// 验证请求
if err := validateRequest(req); err != nil {
return NewContextError("invalid request", req.ID, req.UserID, err)
}
// 处理请求
if err := saveRequest(req); err != nil {
return NewContextError("failed to save request", req.ID, req.UserID, err)
}
return nil
}5.3 业务逻辑错误
场景描述:需要定义特定业务逻辑的错误类型,如验证错误、权限错误等
使用方法:为不同类型的业务错误定义不同的自定义错误类型
示例代码:
go
// 验证错误
type ValidationError struct {
Field string
Value interface{}
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on field %s with value %v: %s", e.Field, e.Value, e.Message)
}
func NewValidationError(field string, value interface{}, message string) *ValidationError {
return &ValidationError{Field: field, Value: value, Message: message}
}
// 权限错误
type PermissionError struct {
UserID string
Resource string
Action string
}
func (e *PermissionError) Error() string {
return fmt.Sprintf("user %s does not have permission to %s %s", e.UserID, e.Action, e.Resource)
}
func NewPermissionError(userID, resource, action string) *PermissionError {
return &PermissionError{UserID: userID, Resource: resource, Action: action}
}
// 使用示例
func validateUser(user User) error {
if user.Name == "" {
return NewValidationError("name", user.Name, "name is required")
}
if user.Age < 18 {
return NewValidationError("age", user.Age, "age must be at least 18")
}
return nil
}
func checkPermission(userID, resource, action string) error {
// 检查权限
if !hasPermission(userID, resource, action) {
return NewPermissionError(userID, resource, action)
}
return nil
}5.4 带堆栈信息的错误
场景描述:需要在错误中包含堆栈信息,以便更好地定位错误发生的位置
使用方法:定义包含堆栈信息字段的自定义错误类型
示例代码:
go
import (
"bytes"
"debug"
"fmt"
)
type StackError struct {
Message string
StackTrace string
Cause error
}
func (e *StackError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s\nStack trace:\n%s\nCause: %v", e.Message, e.StackTrace, e.Cause)
}
return fmt.Sprintf("%s\nStack trace:\n%s", e.Message, e.StackTrace)
}
func (e *StackError) Unwrap() error {
return e.Cause
}
func captureStackTrace() string {
var buf bytes.Buffer
stackTrace := debug.Stack()
buf.Write(stackTrace)
return buf.String()
}
func NewStackError(message string, cause error) *StackError {
return &StackError{
Message: message,
StackTrace: captureStackTrace(),
Cause: cause,
}
}
// 使用示例
func processFile(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return NewStackError("failed to read file", err)
}
// 处理数据
return nil
}5.5 可恢复的错误
场景描述:需要标识某些错误是可恢复的,可以通过重试等方式解决
使用方法:定义包含可恢复标识的自定义错误类型
示例代码:
go
type RecoverableError struct {
Message string
Cause error
Retryable bool
}
func (e *RecoverableError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Cause)
}
return e.Message
}
func (e *RecoverableError) Unwrap() error {
return e.Cause
}
func NewRecoverableError(message string, cause error, retryable bool) *RecoverableError {
return &RecoverableError{Message: message, Cause: cause, Retryable: retryable}
}
// 使用示例
func fetchData(url string) error {
resp, err := http.Get(url)
if err != nil {
return NewRecoverableError("failed to fetch data", err, true)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
// 处理响应
return nil
}
// 尝试执行操作,处理可恢复的错误
func TryOperation(attempts int, f func() error) error {
for i := 0; i < attempts; i++ {
err := f()
if err == nil {
return nil
}
var recoverableErr *RecoverableError
if errors.As(err, &recoverableErr) && recoverableErr.Retryable {
log.Printf("Attempt %d failed, retrying: %v\n", i+1, err)
time.Sleep(time.Second * time.Duration(i+1))
continue
}
return err
}
return fmt.Errorf("failed after %d attempts", attempts)
}6. 企业级进阶应用场景
6.1 错误分类系统
场景描述:在企业级应用中,需要对错误进行分类,以便采取不同的处理策略
使用方法:创建带有错误类型的自定义错误,并实现错误分类系统
示例代码:
go
// 错误类型定义
const (
ErrorTypeValidation = iota
ErrorTypeDatabase
ErrorTypeNetwork
ErrorTypeInternal
)
// 自定义错误类型
type AppError struct {
Type int
Code int
Message string
Cause error
}
func (e *AppError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s (code: %d): %v", e.Message, e.Code, e.Cause)
}
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}
func (e *AppError) Unwrap() error {
return e.Cause
}
// 创建不同类型的错误
func NewValidationError(code int, message string, cause error) *AppError {
return &AppError{Type: ErrorTypeValidation, Code: code, Message: message, Cause: cause}
}
func NewDatabaseError(code int, message string, cause error) *AppError {
return &AppError{Type: ErrorTypeDatabase, Code: code, Message: message, Cause: cause}
}
func NewNetworkError(code int, message string, cause error) *AppError {
return &AppError{Type: ErrorTypeNetwork, Code: code, Message: message, Cause: cause}
}
func NewInternalError(code int, message string, cause error) *AppError {
return &AppError{Type: ErrorTypeInternal, Code: code, Message: message, Cause: cause}
}
// 错误处理策略
var errorHandlers = map[int]func(*AppError) error{
ErrorTypeValidation: handleValidationError,
ErrorTypeDatabase: handleDatabaseError,
ErrorTypeNetwork: handleNetworkError,
ErrorTypeInternal: handleInternalError,
}
func handleValidationError(err *AppError) error {
// 处理验证错误
log.Printf("Validation error: %v\n", err)
return nil
}
func handleDatabaseError(err *AppError) error {
// 处理数据库错误
log.Printf("Database error: %v\n", err)
// 可以尝试重试
return retryDatabaseOperation(err)
}
func handleNetworkError(err *AppError) error {
// 处理网络错误
log.Printf("Network error: %v\n", err)
// 可以尝试重试
return retryNetworkOperation(err)
}
func handleInternalError(err *AppError) error {
// 处理内部错误
log.Printf("Internal error: %v\n", err)
// 发送报警
sendAlert(err)
return err
}
// 统一错误处理函数
func HandleError(err error) error {
var appErr *AppError
if errors.As(err, &appErr) {
if handler, ok := errorHandlers[appErr.Type]; ok {
return handler(appErr)
}
}
// 处理未知错误
log.Printf("Unknown error: %v\n", err)
return err
}6.2 错误监控系统
场景描述:在企业级应用中,需要监控错误的发生情况,统计错误类型和频率
使用方法:创建带有监控信息的自定义错误,并实现错误监控系统
示例代码:
go
// 错误监控信息
type MonitoredError struct {
err error
timestamp time.Time
service string
environment string
stackTrace string
}
func (e *MonitoredError) Error() string {
return e.err.Error()
}
func (e *MonitoredError) Unwrap() error {
return e.err
}
func MonitorError(err error, service, environment string) error {
if err == nil {
return nil
}
// 捕获堆栈信息
stackTrace := captureStackTrace()
// 创建监控错误
monitoredErr := &MonitoredError{
err: err,
timestamp: time.Now(),
service: service,
environment: environment,
stackTrace: stackTrace,
}
// 记录错误信息
log.Printf("Error occurred in %s (%s): %v\nStack trace: %s\n", service, environment, err, stackTrace)
// 发送到监控系统
sendToMonitoringSystem(monitoredErr)
return monitoredErr
}
// 使用示例
func processRequest(req Request) error {
err := validateRequest(req)
if err != nil {
return MonitorError(fmt.Errorf("invalid request: %w", err), "api-service", "production")
}
err = saveRequest(req)
if err != nil {
return MonitorError(fmt.Errorf("failed to save request: %w", err), "api-service", "production")
}
return nil
}6.3 错误国际化
场景描述:在企业级应用中,需要支持多语言错误信息
使用方法:创建带有国际化支持的自定义错误类型
示例代码:
go
// 国际化错误
type I18nError struct {
Key string
Params map[string]interface{}
Locale string
Cause error
}
func (e *I18nError) Error() string {
message := translate(e.Key, e.Params, e.Locale)
if e.Cause != nil {
return fmt.Sprintf("%s: %v", message, e.Cause)
}
return message
}
func (e *I18nError) Unwrap() error {
return e.Cause
}
func NewI18nError(key string, params map[string]interface{}, locale string, cause error) *I18nError {
return &I18nError{Key: key, Params: params, Locale: locale, Cause: cause}
}
// 翻译函数(示例实现)
func translate(key string, params map[string]interface{}, locale string) string {
// 从翻译文件中获取翻译
// 这里只是示例,实际实现可能会从数据库或配置文件中获取
translations := map[string]map[string]string{
"en": {
"error.division_by_zero": "Division by zero",
"error.invalid_input": "Invalid input: {{field}}",
},
"zh": {
"error.division_by_zero": "除零错误",
"error.invalid_input": "无效输入:{{field}}",
},
}
if langTranslations, ok := translations[locale]; ok {
if translation, ok := langTranslations[key]; ok {
// 替换参数
result := translation
for k, v := range params {
placeholder := fmt.Sprintf("{{%s}}", k)
result = strings.ReplaceAll(result, placeholder, fmt.Sprintf("%v", v))
}
return result
}
}
// 如果没有找到翻译,返回键
return key
}
// 使用示例
func divide(a, b int, locale string) (int, error) {
if b == 0 {
return 0, NewI18nError("error.division_by_zero", nil, locale, nil)
}
return a / b, nil
}
func validateUser(user User, locale string) error {
if user.Name == "" {
params := map[string]interface{}{"field": "name"}
return NewI18nError("error.invalid_input", params, locale, nil)
}
return nil
}7. 行业最佳实践
7.1 为自定义错误实现 Unwrap 方法
实践内容:为包装了其他错误的自定义错误类型实现 Unwrap() 方法
推荐理由:这样可以确保 errors.Is() 和 errors.As() 函数能够正确处理错误链
7.2 错误信息应该包含上下文
实践内容:在自定义错误中包含足够的上下文信息
推荐理由:上下文信息有助于快速定位问题,减少调试时间
7.3 为不同类型的错误定义不同的错误类型
实践内容:为不同类型的错误(如验证错误、权限错误等)定义不同的自定义错误类型
推荐理由:这样可以根据错误类型采取不同的处理策略,提高错误处理的灵活性
7.4 统一错误创建方式
实践内容:创建统一的错误创建函数,确保错误格式一致
推荐理由:统一的错误格式有助于错误处理的一致性,便于错误监控和统计
7.5 错误类型的命名应该清晰
实践内容:为自定义错误类型选择清晰、描述性的名称
推荐理由:清晰的命名有助于理解错误的性质和用途
8. 常见问题答疑(FAQ)
8.1 问:如何判断一个错误是否是自定义错误类型?
回答:使用 errors.As() 函数进行错误类型判断
示例代码:
go
var appErr *AppError
if errors.As(err, &appErr) {
// 处理 AppError 类型的错误
fmt.Printf("Error code: %d\n", appErr.Code)
}8.2 问:如何在自定义错误中包含堆栈信息?
回答:使用 debug 包捕获堆栈信息,并将其包含在自定义错误中
示例代码:
go
func captureStackTrace() string {
var buf bytes.Buffer
stackTrace := debug.Stack()
buf.Write(stackTrace)
return buf.String()
}
type StackError struct {
Message string
StackTrace string
Cause error
}
func (e *StackError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s\nStack trace:\n%s\nCause: %v", e.Message, e.StackTrace, e.Cause)
}
return fmt.Sprintf("%s\nStack trace:\n%s", e.Message, e.StackTrace)
}
func (e *StackError) Unwrap() error {
return e.Cause
}
func NewStackError(message string, cause error) *StackError {
return &StackError{
Message: message,
StackTrace: captureStackTrace(),
Cause: cause,
}
}8.3 问:如何创建带有错误码的自定义错误?
回答:定义包含错误码字段的自定义错误类型
示例代码:
go
type AppError struct {
Code int
Message string
}
func (e *AppError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func NewAppError(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}8.4 问:如何处理多层嵌套的自定义错误?
回答:实现 Unwrap() 方法,使用 errors.Is() 和 errors.As() 函数遍历错误链
示例代码:
go
type AppError struct {
Code int
Message string
Cause error
}
func (e *AppError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s (code: %d): %v", e.Message, e.Code, e.Cause)
}
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}
func (e *AppError) Unwrap() error {
return e.Cause
}
// 使用示例
func level1() error {
if err := level2(); err != nil {
return &AppError{Code: 500, Message: "level1 error", Cause: err}
}
return nil
}
func level2() error {
if err := level3(); err != nil {
return &AppError{Code: 500, Message: "level2 error", Cause: err}
}
return nil
}
func level3() error {
return os.ErrNotExist
}
func main() {
err := level1()
if err != nil {
fmt.Printf("Error: %v\n", err)
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Original error is os.ErrNotExist")
}
}
}8.5 问:自定义错误会影响性能吗?
回答:自定义错误的开销很小,主要是创建一个新的结构体实例。在大多数情况下,这种开销可以忽略不计
示例代码:
go
// 自定义错误的性能测试
func benchmarkCustomError(b *testing.B) {
for i := 0; i < b.N; i++ {
err := NewAppError(400, "test error")
_ = err
}
}8.6 问:如何在 HTTP 响应中返回自定义错误?
回答:将自定义错误转换为适当的 HTTP 响应
示例代码:
go
func handleRequest(w http.ResponseWriter, r *http.Request) {
// 处理请求
err := processRequest(r)
if err != nil {
var appErr *AppError
if errors.As(err, &appErr) {
// 根据错误码设置 HTTP 状态码
statusCode := http.StatusInternalServerError
switch appErr.Code {
case 400:
statusCode = http.StatusBadRequest
case 401:
statusCode = http.StatusUnauthorized
case 403:
statusCode = http.StatusForbidden
case 404:
statusCode = http.StatusNotFound
}
// 返回 JSON 响应
response := map[string]interface{}{
"error": map[string]interface{}{
"code": appErr.Code,
"message": appErr.Message,
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(response)
return
}
// 处理其他错误
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
// 返回成功响应
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
}9. 实战练习
9.1 基础练习
练习内容:实现一个带有错误码和错误信息的自定义错误类型
解题思路:
- 定义一个结构体,包含错误码和错误信息字段
- 实现
Error() string方法 - 创建函数返回自定义错误
- 测试错误处理
常见误区:忘记实现 Error() string 方法,或者错误信息不清晰
分步提示:
- 定义结构体
AppError,包含Code和Message字段 - 实现
Error() string方法,返回格式化的错误信息 - 创建函数
NewAppError,用于创建新的AppError - 编写测试代码,验证错误处理
参考代码:
go
package main
import (
"fmt"
)
type AppError struct {
Code int
Message string
}
func (e *AppError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func NewAppError(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, NewAppError(400, "division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Printf("Error: %v\n", err)
if appErr, ok := err.(*AppError); ok {
fmt.Printf("Error code: %d\n", appErr.Code)
}
return
}
fmt.Printf("Result: %d\n", result)
}9.2 进阶练习
练习内容:实现一个带有上下文信息和错误链的自定义错误类型
解题思路:
- 定义一个结构体,包含错误信息、上下文信息和原始错误字段
- 实现
Error() string方法,返回格式化的错误信息 - 实现
Unwrap() error方法,返回原始错误 - 创建函数返回自定义错误
- 测试错误链的处理
常见误区:没有实现 Unwrap() error 方法,导致错误链断裂
分步提示:
- 定义结构体
ContextError,包含Message、Context和Cause字段 - 实现
Error() string方法,返回格式化的错误信息 - 实现
Unwrap() error方法,返回原始错误 - 创建函数
NewContextError,用于创建新的ContextError - 编写测试代码,验证错误链的处理
参考代码:
go
package main
import (
"errors"
"fmt"
"os"
)
type ContextError struct {
Message string
Context map[string]interface{}
Cause error
}
func (e *ContextError) Error() string {
contextStr := ""
if len(e.Context) > 0 {
contextStr = " (context: "
for k, v := range e.Context {
contextStr += fmt.Sprintf("%s=%v, ", k, v)
}
contextStr = contextStr[:len(contextStr)-2] + ")"
}
if e.Cause != nil {
return fmt.Sprintf("%s%s: %v", e.Message, contextStr, e.Cause)
}
return fmt.Sprintf("%s%s", e.Message, contextStr)
}
func (e *ContextError) Unwrap() error {
return e.Cause
}
func NewContextError(message string, context map[string]interface{}, cause error) *ContextError {
if context == nil {
context = make(map[string]interface{})
}
return &ContextError{Message: message, Context: context, Cause: cause}
}
func level1() error {
context := map[string]interface{}{"level": 1, "operation": "level1"}
if err := level2(); err != nil {
return NewContextError("level1 error", context, err)
}
return nil
}
func level2() error {
context := map[string]interface{}{"level": 2, "operation": "level2"}
if err := level3(); err != nil {
return NewContextError("level2 error", context, err)
}
return nil
}
func level3() error {
return os.ErrNotExist
}
func main() {
err := level1()
if err != nil {
fmt.Printf("Error: %v\n", err)
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Original error is os.ErrNotExist")
}
}
}9.3 挑战练习
练习内容:实现一个错误处理中间件,处理自定义错误并返回适当的 HTTP 响应
解题思路:
- 定义一个 HTTP 中间件函数,接受下一个处理函数
- 在中间件中执行下一个处理函数,捕获错误
- 根据错误类型返回适当的 HTTP 响应
- 测试中间件功能
常见误区:没有正确处理不同类型的错误,或者没有返回适当的 HTTP 响应
分步提示:
- 定义中间件函数
ErrorHandlerMiddleware,签名为func(http.Handler) http.Handler - 实现中间件逻辑,包括错误捕获和处理
- 创建一个
responseRecorder来记录响应状态码 - 根据错误类型返回适当的 HTTP 响应
- 编写测试代码,验证中间件功能
参考代码:
go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httptest"
)
type AppError struct {
Code int
Message string
}
func (e *AppError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func NewAppError(code int, message string) *AppError {
return &AppError{Code: code, Message: message}
}
// 错误处理中间件
func ErrorHandlerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 记录 panic
panicErr := NewAppError(http.StatusInternalServerError, "Internal Server Error")
log.Printf("Panic recovered: %v\n", err)
sendErrorResponse(w, panicErr)
}
}()
// 执行下一个处理函数
next.ServeHTTP(w, r)
})
}
// 发送错误响应
func sendErrorResponse(w http.ResponseWriter, err error) {
var appErr *AppError
if ae, ok := err.(*AppError); ok {
appErr = ae
} else {
appErr = NewAppError(http.StatusInternalServerError, "Internal Server Error")
}
// 设置 HTTP 状态码
statusCode := http.StatusInternalServerError
switch appErr.Code {
case 400:
statusCode = http.StatusBadRequest
case 401:
statusCode = http.StatusUnauthorized
case 403:
statusCode = http.StatusForbidden
case 404:
statusCode = http.StatusNotFound
}
// 返回 JSON 响应
response := map[string]interface{}{
"error": map[string]interface{}{
"code": appErr.Code,
"message": appErr.Message,
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(response)
}
// 测试处理函数
func testHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/error" {
err := NewAppError(400, "Bad Request")
sendErrorResponse(w, err)
return
}
fmt.Fprintln(w, "Hello, World!")
}
func main() {
// 创建测试服务器
handler := ErrorHandlerMiddleware(http.HandlerFunc(testHandler))
// 测试正常请求
req, _ := http.NewRequest("GET", "/", nil)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
fmt.Printf("Normal request: %d %s\n", rr.Code, rr.Body.String())
// 测试错误请求
req, _ = http.NewRequest("GET", "/error", nil)
rr = httptest.NewRecorder()
handler.ServeHTTP(rr, req)
fmt.Printf("Error request: %d %s\n", rr.Code, rr.Body.String())
// 启动服务器
http.ListenAndServe(":8080", handler)
}10. 知识点总结
10.1 核心要点
- 自定义错误是通过实现
error接口创建的 - 自定义错误可以包含额外的字段,如错误码、上下文信息等
- 包装其他错误的自定义错误应该实现
Unwrap()方法,以便支持错误链 - 使用
errors.As()函数可以判断错误是否是特定类型的自定义错误 - 自定义错误可以根据业务需求进行扩展,如添加堆栈信息、国际化支持等
10.2 易错点回顾
- 忘记实现
Error() string方法,导致自定义类型无法作为错误使用 - 没有实现
Unwrap()方法,导致错误链断裂 - 错误信息不够详细,无法提供足够的上下文信息
- 使用
==直接比较自定义错误,导致判断失败 - 没有为不同类型的错误定义不同的错误类型,导致错误处理不够灵活
11. 拓展参考资料
11.1 官方文档链接
11.2 进阶学习路径建议
- 学习
context包,了解如何在错误处理中使用上下文 - 学习
errgroup包,掌握并发错误处理 - 学习第三方错误处理库,如
pkg/errors - 学习如何在大型项目中设计错误处理策略
12. 代码规范
12.1 自定义错误代码风格
- 自定义错误类型的命名应该清晰、描述性
- 错误信息应该清晰、具体,包含足够的上下文
- 包装其他错误的自定义错误应该实现
Unwrap()方法 - 为自定义错误提供创建函数,确保错误格式一致
- 错误码应该有明确的含义和范围
12.2 示例代码
go
// 定义错误变量
var (
ErrNotFound = errors.New("resource not found")
ErrInvalidInput = errors.New("invalid input")
ErrInternalError = errors.New("internal error")
)
// 自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}
// 创建自定义错误的函数
func NewValidationError(field, message string) *ValidationError {
return &ValidationError{Field: field, Message: message}
}
// 带错误链的自定义错误
type AppError struct {
Code int
Message string
Cause error
}
func (e *AppError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s (code: %d): %v", e.Message, e.Code, e.Cause)
}
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}
func (e *AppError) Unwrap() error {
return e.Cause
}
func NewAppError(code int, message string, cause error) *AppError {
return &AppError{Code: code, Message: message, Cause: cause}
}
// 使用示例
func validateUser(user User) error {
if user.Name == "" {
return NewValidationError("name", "name is required")
}
return nil
}
func getUser(id int) (User, error) {
user, err := db.GetUser(id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return User{}, NewAppError(404, "user not found", err)
}
return User{}, NewAppError(500, "failed to get user", err)
}
return user, nil
}本章节介绍了 Go 语言中自定义错误的实现和使用方法。通过学习这些内容,开发者可以创建更加灵活、功能丰富的错误类型,提高代码的可读性和可维护性。
