必须手动控制 WriteHeader 和 Content-Type,w.WriteHeader() 需在任何写入前且仅调用一次;应实现 HTTPError 接口封装错误,避免裸 int 或字符串匹配;前端须同时校验 status 与 data.code。

writeError 必须手动控制 WriteHeader 和 Content-Type
硬写 json.NewEncoder(w).Encode() 容易漏状态码、错 Content-Type、重复写错误分支。HTTP 层不认 JSON body,只看状态码和 header;前端靠 response.status 做网络层判断,靠 data.code 做业务层判断,两者缺一不可。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
-
w.WriteHeader()必须在任何w.Write()或json.Encoder.Encode()之前调用,且只能调一次;否则 Go 会输出superfluous response.WriteHeader警告,实际生效的是第一次写入的状态码 - 别用
http.Error()——它只发纯文本,Content-Type是text/plain,前端response.json()会直接报错 - 封装统一的
writeError()函数,强制所有错误路径走它,避免各 handler 自行拼接
让错误自己带状态码:实现 HTTPError 接口
每次 if err != nil { writeError(); return } 太啰嗦,状态码散落在各处,改起来容易漏。Go 的哲学是“用类型表达意图”,不是靠字符串或数字匹配。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 定义接口:
type HTTPError interface { error; StatusCode() int; ErrorCode() string; ErrorMessage() string } - 所有业务错误(如
UserNotFoundError、InvalidPhoneError)都实现该接口,字段语义清晰可导出 - handler 中统一做类型断言:
if he, ok := err.(HTTPError); ok { writeError(w, he.StatusCode(), ...) } - 标准库错误(如
json.UnmarshalTypeError)也要包一层再抛,否则全落到500降级处理
别用 int 错误码,也别用字符串匹配
if err == 404 编译不通过;err.Error() == "USER_NOT_FOUND" 脆弱又难维护——只要日志格式微调、加个标点,逻辑就失效。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 拒绝裸
int错误码:它丢失语义、引发隐式转换、无法支持errors.Is/errors.As - 用结构体实现
error接口,比如type ValidationError struct { Field string; Msg string },并实现Unwrap()和Error() - 包装错误必须用
%w:return fmt.Errorf("validate user: %w", err),否则errors.Is(err, ErrInvalidField)会失败 - 对
os.IsNotExist()这类系统错误保留原方式,但不要混进业务错误判断链
前端必须双校验:status + data.code
后端返回了结构化错误,但前端如果只看 response.status,会把 400 和 500 都当网络失败处理;如果只信 data.code,又可能忽略 HTTP 层异常(比如网关超时返回 504 却没 JSON body)。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 前端请求后先检查
response.status是否在200–299范围;不在则视为传输层异常,不尝试.json() - status 合法后再解析 body,检查
data.code字段是否为预期值(如"USER_NOT_FOUND"),再决定提示文案或跳转逻辑 - 服务端
details字段必须过滤敏感信息(如 DB 错误详情、堆栈),生产环境禁止透出
w.WriteHeader() 的调用时机和 %w 包装方式——前者导致状态码失效,后者让错误类型判断完全失效。


















