在Golang开发公共API时,错误返回的设计是接口易用性的核心影响因素之一。不合理的错误返回会让调用方难以判断错误类型、定位问题根源,也会增加后续接口迭代的维护成本。本文将从多个维度给出Golang公共API返回错误的规范建议,帮助开发者设计出更友好的公共接口。

优先使用error接口作为错误返回载体
Golang内置的error接口是处理错误的标准方式,公共API应优先将错误作为函数的最后一个返回值,遵循值, error的返回顺序,不要自定义其他错误返回格式。调用方可以通过标准的方式判断错误是否存在,不需要额外学习接口的错误处理逻辑。
错误返回的基本规范示例如下:
package mathutil
import "errors"
// Add 两个整数相加的公共API
// 当输入参数出现溢出时返回错误
func Add(a, b int) (int, error) {
// 模拟溢出判断
if a > 0 && b > 0 && a > (1<<31-1)-b {
return 0, errors.New("int add overflow")
}
return a + b, nil
}
避免使用基础类型错误,自定义错误类型传递更多信息
如果公共API需要让调用方区分不同的错误类型,或者需要传递错误码、上下文信息等额外内容,应该自定义实现error接口的结构体,而不是仅返回字符串类型的错误。自定义错误类型可以通过类型断言让调用方精准判断错误类别,也方便后续扩展错误信息字段。
自定义错误类型的实现示例:
package apierror
// APIError 公共API自定义错误类型
type APIError struct {
Code int // 错误码
Message string // 错误描述
Cause error // 原始错误
}
// Error 实现error接口
func (e *APIError) Error() string {
if e.Cause != nil {
return e.Message + ": " + e.Cause.Error()
}
return e.Message
}
// 预定义常见错误实例
var (
ErrInvalidParam = &APIError{Code: 1001, Message: "invalid parameter"}
ErrNotFound = &APIError{Code: 1002, Message: "resource not found"}
)
错误信息要简洁明确,包含必要的上下文
公共API返回的错误信息需要同时满足两个要求:一是简洁易懂,调用方不需要额外查文档就能知道错误的大致原因;二是包含必要的上下文,比如哪个参数不合法、哪个资源不存在,方便调用方快速定位问题。不要返回空的错误信息,也不要返回过于模糊的描述比如error occurred。
错误信息编写的正确和错误示例对比:
| 场景 | 错误示例 | 正确示例 |
|---|---|---|
| 参数校验失败 | errors.New("param error") | errors.New("param age must be greater than 0, got -5") |
| 资源不存在 | errors.New("not found") | errors.New("user with id 123 not found") |
错误包装使用fmt.Errorf时保留原始错误链
当公共API内部调用其他函数返回错误时,如果需要添加上下文信息,应该使用fmt.Errorf结合%w动词包装错误,而不是用%v丢弃原始错误。保留错误链后,调用方可以通过errors.Is和errors.As方法判断原始错误类型,方便做针对性的错误处理。
错误包装的示例:
package userapi
import (
"database/sql"
"fmt"
"github.com/yourname/apierror"
)
// GetUserByID 根据ID获取用户的公共API
func GetUserByID(id int) (*User, error) {
user, err := queryUserFromDB(id)
if err != nil {
// 使用%w包装原始错误,保留错误链
if err == sql.ErrNoRows {
return nil, fmt.Errorf("get user failed: %w", apierror.ErrNotFound)
}
return nil, fmt.Errorf("get user failed, id: %d: %w", id, err)
}
return user, nil
}
不要忽略错误直接返回nil
公共API内部如果调用了返回错误的函数,一定要处理错误或者将错误向上传递,不要忽略错误直接返回nil。忽略错误会导致调用方无法感知内部的问题,出现不符合预期的行为,增加问题排查的难度。如果确实不需要处理错误,也应该在代码中添加注释说明忽略的原因。
错误处理的错误和正确示例:
// 错误示例:忽略错误直接返回
func SaveUser(user *User) error {
_, err := db.Insert(user)
// 忽略了插入的错误,直接返回nil
return nil
}
// 正确示例:传递错误
func SaveUser(user *User) error {
_, err := db.Insert(user)
if err != nil {
return fmt.Errorf("save user failed: %w", err)
}
return nil
}
预定义公共错误实例,避免重复的error创建
对于公共API中频繁出现的错误,比如参数不合法、资源不存在、权限不足等,应该预定义对应的错误实例,而不是每次返回错误时都新建errors.New。预定义错误实例可以让调用方通过errors.Is直接判断错误类型,也减少了重复代码的编写。
预定义错误的使用示例:
package userapi
import "github.com/yourname/apierror"
// 预定义当前包的公共错误
var (
ErrInvalidUserID = apierror.NewAPIError(2001, "invalid user id")
ErrUserAlreadyExist = apierror.NewAPIError(2002, "user already exist")
)
func CreateUser(id int, name string) error {
if id <= 0 {
return ErrInvalidUserID
}
// 其他逻辑
return nil
}
不要在错误中暴露敏感信息
公共API返回的错误信息中不要包含敏感内容,比如数据库连接字符串、内部服务地址、用户密码、密钥等信息。这些信息如果被外部获取,会带来安全风险。错误信息应该只包含调用方需要知道的公开内容,敏感的内部细节应该记录到内部日志中,而不是返回给调用方。
注意:如果公共API是对内使用的,可以根据内部规范调整错误信息的详细程度,但如果是对外开放的公共API,必须严格避免敏感信息泄露。
保持错误返回的一致性
同一个公共API包中的所有函数,应该遵循统一的错误返回规则,包括错误类型、错误码格式、错误信息的编写风格等。不要出现部分函数返回自定义错误类型,部分函数返回字符串错误的情况,也不要部分错误带错误码,部分错误不带错误码。一致的错误返回规则可以降低调用方的学习成本,也方便调用方统一做错误处理。