为什么要约定沟通方式
你遇过格式不统一的后端项目吗?最近在重构某个 Go 后端项目时,最痛苦的问题之一就是请求与响应格式不统一。这是一个非常直白且简单的问题,但实际上会造成很大的困扰。
前端与后端之间应该存在某种契约,可能是「資数据格式文档」,例如 OpenAPI)、tRPC,或者是像 RESTful 这样的「规范」,以减少沟通上的摩擦。
如果连沟通方式都没有统一,或者使用一些奇怪且少见的自定义方案,对任何开发者或 AI 来说都会非常难以理解。举例来说,我遇过某个项目把响应状态塞进自定义的 msg 字段里,没有接触过的人就很容易写出“请求成功但实际失败”的问题。
- 需要理解自定义抽象才能修改代码
- 代码依赖于独特的上下文
- 难以维护
const { msg } = await updateUserPassword();if (msg === 'ok') {}约定俗成才是最好的
所谓「约定俗成」就是行业惯例,这种「不需要解释的默契」才是真正降低开发成本的关键,因为 AI 与开发者早已内建相关知识,处理起来最快也最稳定。
举例来说,刚刚的问题其实只要使用 HTTP 状态码就能清晰解决。下面使用 Response.ok API 来确认响应码是否处于 200~299 范围内。
const res = await updateUserPassword();if (res.ok) {}AI 时代应该怎么做?
以上其实都是烂大街的最佳实践,道理大家都懂,但如何真正落地才是最困难的问题。在 AI 辅助开发的时代,第一件事应该是把团队的共识与规范写成文档,例如 Agent Skills 标准:
---name: response-helperdescription: 提供一个标准化的 HTTP 响应范式compatibility: opencode---
## 我能做什么
我提供一个支持泛型的标准化 HTTP 响应范式。我可以帮你:
- 建立具有统一 Envelope 结构的 API 响应
- 使用泛型以保留响应数据的类型信息
- 统一处理成功与错误响应
- 在需要时包含分页 metadata
## 什么时候使用我
在以下场景使用此 skill:
- 需要在各个接口之间保持一致的响应格式
- 希望通过泛型获得类型安全的响应数据
- 实现分页响应
## 响应结构
```go
type Response[T any] struct {
Success bool `json:"success"` Msg string `json:"msg,omitempty"` Data T `json:"data,omitempty"` Error *ErrorInfo `json:"error,omitempty"` Meta *PaginationMeta `json:"meta,omitempty"`
}
type ErrorInfo struct {
Code int `json:"code"` Message string `json:"message"`
}
type PaginationMeta struct {
Page int `json:"page,omitempty"` PerPage int `json:"per_page,omitempty"` TotalCount int64 `json:"total_count,omitempty"`
}## 使用方式
### 基础成功响应
```gofunc GetUser(c *gin.Context) { var user User // ... 获取用户 response.Success(c, user)
}
// 响应示例: {"success": true, "msg": "ok", "data": {...}}### 使用泛型的成功响应
```goresponse.Success(c, MyStruct{Field: "value"})// 响应示例: {"success": true, "msg": "ok", "data": {"field": "value"}}### 带分页的成功响应
```govar users []Usermeta := &response.PaginationMeta{ Page: 1, PerPage: 10, TotalCount: 100,}// ... 获取用户列表
response.SuccessWithMeta(c, users, meta)// 响应示例: {"success": true, "msg": "ok", "data": [...], "meta": {"page": 1, "per_page": 10, "total_count": 100}}### 错误响应
```goresponse.Error(c, http.StatusBadRequest, http.StatusBadRequest, "missing id")// 响应示例: {"success": false, "error": {"code": 400, "message": "missing id"}}
response.BadRequest(c, "INVALID_PARAM", "missing id")response.NotFound(c, "user not found")response.Unauthorized(c, "token expired")response.InternalError(c, "database error")## 辅助函数
| Function | Status Code | Default Code | Description ||----------|-------------|--------------|--------------|| `Success(c, data)` | 200 | - | 带数据的成功响应 || `SuccessWithMeta(c, data, meta)` | 200 | - | 带分页信息的成功响应 || `Error(c, status, code, msg)` | custom | custom | 自定义错误响应 || `BadRequest(c, code, msg)` | 400 | code | 错误请求 || `Unauthorized(c, msg)` | 401 | UNAUTHORIZED | 未授权 || `Forbidden(c, msg)` | 403 | FORBIDDEN | 禁止访问 || `NotFound(c, msg)` | 404 | NOT_FOUND | 资源不存在 || `InternalError(c, msg)` | 500 | INTERNAL_ERROR | 内部服务器错误 |
## 优势
1. **类型安全**:泛型保留响应数据的类型,而不是使用 interface{}2. **一致性**:所有接口使用统一的响应 Envelope 格式3. **开发体验**:清晰的成功(msg: "ok")与错误结构4. **分页支持**:内建分页 metadataAI 最难负担的,其实是那些认知之外、不知道该从哪里补齐的上下文。**清晰的文档规划比任何时候都更加重要。**当拥有清晰的蓝图之后,就可以设计一个 Loop,让 AI Agent 去完成某个具有明确结果的目标。例如上面的例子:「把现有的请求全部迁移为 Skill 描述的实现方式」。
设计一个 Agent Loop 进行验收执行
这里以 Codex 内置的 Agent Loop 工具:goal 为例。
官方文档提到,一个 Goal 通常会定义六个部分:
- Outcome(完成后应该成立的状态)
- Verification surface(能够证明成功的测试、benchmark、报告或命令输出)
- Constraints(执行过程中不能退化的部分)
- Boundaries(Codex 可以使用的文件、工具与数据范围)
- Iteration(继续执行的条件)
以「统一现有 API 响应格式」这个任务为例,可以设计成如下 Prompt:
/goal 将 internal/handler/ 下所有 HTTP Handler 迁移为使用 .agents/skills/response-helper/SKILL.md 中定义的 response helper
Outcome:internal/handler/ 中的每个 handler 必须仅通过 response helper 返回响应。任何 handler 不得直接调用 c.JSON、c.String 或任何原始响应方法。
Verification surface:1. Run `grep -rn "c\.JSON\|c\.String\|c\.Data" internal/handler/` — result must be empty.2. Run `go build ./...` — must exit 0.3. Run `go test ./internal/handler/...` — must exit 0 with no failures.
Constraints:- Do not change any business logic, only the response layer.- Do not modify files outside internal/handler/ and internal/helper/.- Do not introduce new dependencies.- HTTP status codes must follow the standard defined in SKILL.md (2xx success, 4xx client error, 5xx server error). Do not preserve any custom msg or status field from the old implementation.
Boundaries:- Read .agents/skills/response-helper/SKILL.md before making any changes.- Work only on files under internal/handler/.- You may create or modify internal/helper/response.go if the helper does not yet exist.
Iteration:After each handler file is migrated, run the verification commands before moving to the next file. If any check fails, fix it before continuing. Do not batch multiple files into one turn.对应官方的六个核心要素:
- Outcome —— 使用 grep 可以进行机器验证,不是“感觉改好了”,而是只有空结果才算通过。
- Verification surface —— 三道关卡缺一不可:静态扫描(没有直接调用)、编译(没有破坏类型)、测试(没有破坏行为)。这三个都是可执行命令,exit code 就是最终答案。
- Constraints —— 明确告诉 Codex 不能做什么,防止它“顺手”修改业务逻辑,或者额外帮你引入一个新依赖。其中最重要的一条是禁止保留旧的 msg 字段,因为这正是整篇文章想解决的核心问题。
- Boundaries —— 限制工作范围,避免 Codex 在无关区域留下副作用。
- Iteration —— 要求每次只修改一个文件,改完立即验证。这样即使中途失败,损坏范围也可控,不会一次性把所有 handler 全部改坏。