為什麼要約定溝通方式
你有遇過格式不一的後端專案嗎?近期在翻新某個 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) { // body}AI 時代怎麼做?
以上是爛大街的最佳實踐介紹,道理所有人都懂,但怎麼有效的落地才是最難的問題。在 AI 輔助開發的現代第一件事應該是把團隊的共識與規格寫成文件如 Agent Skills 標準:
---name: response-helperdescription: Provide a standardized HTTP response patterncompatibility: opencode---
## What I do
I provide a standardized HTTP response pattern with generic type support. I help you:
- Build consistent API responses with a standard envelope structure- Use generics to preserve type information in response data- Handle success and error responses uniformly- Include pagination metadata when needed
## When to use me
Use this skill when:- Need consistent response format across endpoints- Want type-safe response data with generics- Implementing pagination responses
## Response Structure
```gotype 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"`}## Usage
### Basic Success Response
```gofunc GetUser(c *gin.Context) { var user User // ... fetch user
response.Success(c, user)}// Response: {"success": true, "msg": "ok", "data": {...}}### Success with Type (Generic)
```goresponse.Success(c, MyStruct{Field: "value"})// Response: {"success": true, "msg": "ok", "data": {"field": "value"}}### Success with Pagination
```govar users []Usermeta := &response.PaginationMeta{ Page: 1, PerPage: 10, TotalCount: 100,}// ... fetch users
response.SuccessWithMeta(c, users, meta)// Response: {"success": true, "msg": "ok", "data": [...], "meta": {"page": 1, "per_page": 10, "total_count": 100}}### Error Response
```goresponse.Error(c, http.StatusBadRequest, http.StatusBadRequest, "missing id")// Response: {"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")## Helper Functions
| Function | Status Code | Default Code | Description ||----------|-------------|--------------|--------------|| `Success(c, data)` | 200 | - | Success with data || `SuccessWithMeta(c, data, meta)` | 200 | - | Success with pagination || `Error(c, status, code, msg)` | custom | custom | Custom error || `BadRequest(c, code, msg)` | 400 | code | Bad request error || `Unauthorized(c, msg)` | 401 | UNAUTHORIZED | Unauthorized || `Forbidden(c, msg)` | 403 | FORBIDDEN | Forbidden || `NotFound(c, msg)` | 404 | NOT_FOUND | Not found || `InternalError(c, msg)` | 500 | INTERNAL_ERROR | Internal server error |
## Benefits
1. **Type Safety**: Generics preserve the type of response data instead of using `interface{}`2. **Consistency**: Single response envelope format across all endpoints3. **Developer Experience**: Clear success (`msg: "ok"`) and error structure4. **Pagination**: Built-in meta for paginated responsesAI 最沒辦法負擔的就是認知外不知道從哪裡補齊的上下文,清晰的文件規劃遠比任何時候都還重要,當有了清晰的藍圖後,就可以規劃一個 Loop 來讓 AI Agent 來達成某個有明確成果的目的,例如上面的例子:「把現有的請求都換成 Skill 描述的方式製作」。
設計一個 Agent Loop 驗收執行
拿 Codex 內建的讓 Agent 跑 Loop 的工具:goal 作為範例。官方文件提到 Goal 通常定義六個東西:
- Outcome(完成時應該為真的狀態)
- Verification surface(能證明成功的測試、benchmark、報告或指令輸出)
- Constraints(工作過程中不能退步的東西)
- Boundaries(Codex 可以使用的檔案、工具、資料範圍)
- Iteration(繼續工作的條件)
以「統一現有 API 回應格式」這個任務為例,可以設計成這樣的 prompt:
/goal Migrate all HTTP handlers in internal/handler/ to use the response helper defined in .agents/skills/response-helper/SKILL.md
Outcome:Every handler in internal/handler/ must return responses exclusively through the response helper. No handler may call c.JSON, c.String, or any raw response method directly.
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。