Go 错误是值,需显式传递与上下文包装;跨包项目应分层处理、统一归一化错误,底层返回原始错误,上层用 errors.Wrap 或 xerrors 包装以携带调用链信息。

Go 语言本身没有异常机制,错误是值(error 接口),需显式传递和判断。在多模块(跨包)项目中,若各包自行定义错误、忽略上下文、不统一包装或丢失调用链,会导致错误排查困难、日志模糊、用户提示不友好。关键不是“捕获错误”,而是“携带上下文、分层处理、统一归一化”。
使用 errors.Wrap 或 xerrors(推荐)补充调用上下文
底层包(如 dal/)只返回原始错误(如数据库连接失败),上层包(如 service/)应包装错误,说明“在哪一步、为什么失败”。避免裸传 err。
示例:
// dal/user.go
func GetUserByID(id int) (*User, error) {
if id <= 0 {
return nil, errors.New("invalid user id")
}
// ... db query
if err != nil {
return nil, fmt.Errorf("query user from db: %w", err) // 使用 %w 包装
}
}
// service/user.go
func GetUser(ctx context.Context, id int) (*User, error) {
u, err := dal.GetUserByID(id)
if err != nil {
return nil, fmt.Errorf("failed to get user %d: %w", id, err) // 加业务语义
}
return u, nil
}
定义统一的错误类型(如 AppError),区分错误层级与用途
不建议所有错误都用 fmt.Errorf。可定义结构体错误,携带 Code(HTTP 状态码或业务码)、Message(用户可见)、Detail(调试用)、Meta(traceID、reqID 等)。
立即学习“go语言免费学习笔记(深入)”;
2010.09.03更新优化前台内核处理代码;优化后台内核、静态生成相关代码,生成速度全面提升;修改前台静态模板中所有已知错误;修正后台相关模块所有已知错误;更换后台编辑器,功能更强大;增加系统说明书。免费下载、免费使用、完全无限制。完全免费拥有:应广大用户要求,千博网络全面超值发布企业网站系统个人版程序包:内含Flash动画源码、Access数据库程序包、SQL数据库程序包。全站模块化操作,静态
示例:
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Detail string `json:"detail,omitempty"`
Meta map[string]string `json:"meta,omitempty"`
}
func (e *AppError) Error() string { return e.Message }
func (e *AppError) Unwrap() error { return nil } // 不包装其他 error,保持扁平
// 构造函数
func NewBadRequest(msg string) *AppError {
return &AppError{Code: http.StatusBadRequest, Message: msg}
}
func NewInternalErr(detail string) *AppError {
return &AppError{
Code: http.StatusInternalServerError,
Message: "服务暂时不可用",
Detail: detail,
Meta: map[string]string{"trace_id": trace.FromContext(context.TODO()).String()},
}
}
各包按职责返回对应类型的 *AppError,而非裸 error;中间层(如 handler)只负责转换为 HTTP 响应,不重新解释错误含义。
在入口层(如 HTTP handler / CLI main)做统一错误转译与日志记录
错误最终应在最外层被“终结”:记录完整堆栈(含 wrapped error)、返回用户友好的响应、必要时上报监控。不要在每个函数里 log.Printf。
- 用
errors.Is判断是否为某类预设错误(如os.IsNotExist、自定义ErrNotFound) - 用
errors.As提取具体错误类型(如提取*AppError获取 Code) - 用
fmt.Sprintf("%+v", err)打印带堆栈的完整错误(需配合xerrors或 Go 1.13+ 的%+v)
示例(HTTP handler):
func UserHandler(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.URL.Query().Get("id"))
u, err := service.GetUser(r.Context(), id)
if err != nil {
// 记录详细错误(含所有 wrap 层)
log.Error("user handler failed", "err", fmt.Sprintf("%+v", err), "req_id", middleware.ReqID(r))
var appErr *AppError
if errors.As(err, &appErr) {
render.JSON(w, appErr.Code, map[string]string{"message": appErr.Message})
} else {
render.JSON(w, http.StatusInternalServerError, map[string]string{"message": "系统错误"})
}
return
}
render.JSON(w, http.StatusOK, u)
}
避免常见陷阱
- ❌ 在 defer 中 recover 后直接返回
err—— Go 不鼓励 panic 处理业务错误 - ❌ 每层都用
log.Println(err)—— 导致重复日志、无上下文、难以关联请求 - ❌ 把
fmt.Errorf("xxx: %v", err)当成包装 —— 丢失了%w的可展开能力,errors.Is失效 - ❌ 全局变量错误(如
var ErrInvalid = errors.New("invalid"))未加包前缀,跨包易冲突









