Go 功能通用设计规范(AI 编码助手版)
版本:v1.0 | 适用对象:AI 编码助手(Claude / Copilot / Codex 等) | 适用范围:本仓库所有 Go 功能开发
阅读时机:每次接到新功能 / 修改 / 修复任务时,先通读本规范,再动手。
配套文档:开发文档.md(项目架构与协议)、docs/gomoku.proto(协议源)、docs/schema.sql(建表脚本)。
0. 如何使用本规范
本规范是 AI 写 Go 功能时的底线条款,按以下优先级裁决:
- 用户当前要求 > 本规范 > 个人编码习惯:用户明确要求了某种做法时,按用户说的做。
- 本规范 > 通用"最佳实践":不要拿网上搜来的风格覆盖本仓库约定。
- 与现有代码冲突时:以现有代码的实际约定为准(先读代码确认),并把差异写进交付说明,不要静默替换。
一句话原则:先读后写、边界先行、小步验证、不留垃圾。
1. 功能开发标准流程
AI 完成任何一个功能,必须按以下顺序推进,不得跳步:
| 步骤 | 动作 | 产出 |
|---|---|---|
| 1. 理解现状 | 读相关代码、协议、文档;找到最相似的既有实现 | 明确"改哪里、不动哪里" |
| 2. 拆解计划 | 把功能拆成可独立验证的小步骤 | 步骤清单(写进任务列表) |
| 3. 边界清单 | 按 §2 列出该功能的全部边界输入与状态 | 边界用例清单 |
| 4. 实现 | 写代码:入口校验 → 核心逻辑 → 错误处理 → 日志 | 编译通过的代码 |
| 5. 验证 | go build → go vet → go test → 冒烟(§3) | 全绿 |
| 6. 收尾 | 审查 diff、清理调试代码、同步文档/协议 | 干净的最终 diff |
铁律:
- 未读明白现有代码前,禁止开始写实现(禁止臆造 API、函数签名、字段名)。
- 每完成一步立即验证,禁止"写完一大堆再一起编译"。
- 禁止在提交里夹带与本功能无关的改动(顺手重构、改名、格式化无关文件)。
2. 边界检查规范(重点)
2.1 总则
- 每个对外入口(组件方法、HTTP handler、RPC、消息处理)第一件事必须是参数校验,非法输入直接返回错误码,不得进入核心逻辑。
- 边界值不是"不可能发生":客户端永远不可信,本仓库所有对局裁决以服务端为准(见《开发文档》§5)。
- 每个边界都必须有对应测试用例(见 §6)。
2.2 输入类边界
| 边界类型 | 必须检查的内容 | 本仓库实例 |
|---|---|---|
| 坐标/索引 | 是否在 [0, BoardSize) 内、是否整数 | 落子坐标 x,y 越界(棋盘 15×15,common.BoardSize) |
| 数值范围 | 负数、0、上限、溢出 | 计时参数、积分变化、步数 |
| 切片/数组 | nil、空、长度、索引越界、访问前是否判 len | moves 回放、匹配队列 |
| 字符串 | 空串、超长、非法 UTF-8、首尾空白 | uid、房间号、token |
| 枚举/身份 | 未知值、越界值、非法组合 | seat(0/1)、color(1=黑 2=白,落子接口不接收、由座位推导)、房间状态(RoomPlaying/RoomOver) |
| 引用 | nil 指针、nil 接口、nil map/slice | 组件内 db/rdb 为 nil 时降级返回(见 main.go openMySQL/openRedis) |
| 幂等键 | 重复请求(重复落子、重复匹配、重复结算) | 同一坐标二次落子必须拒绝 |
2.3 状态与流程边界
- 状态机:任何状态迁移必须检查当前状态合法(如房间状态为
common.RoomPlaying才允许落子;结束状态common.RoomOver才允许结算)。禁止假设调用顺序正确。 - 重复调用:功能必须可重复进入(重连、重复 push、重复通知),要么幂等,要么明确拒绝。
- 空态:空棋盘、空房间、空队列、空排行榜都要有定义好的行为(不能 panic、不能死循环)。
2.4 并发与资源边界(Go 特有)
- 共享可变状态必须加锁:本仓库对局内存态由
service.RoomManager的每房间一把sync.Mutex保护,新功能若读写共享状态,必须沿用同一把锁,禁止另起全局锁或裸读写。 - goroutine 必须可退出:
time.AfterFunc/ ticker 等必须在房间结束时 Stop,禁止泄漏计时器。 - channel 禁止对已关闭 channel 发送;关闭 channel 的责任方必须唯一。
- 禁止并发写同一个 map/slice;写
nil map会 panic。
2.5 时间与超时边界
- 所有对局计时以服务器时钟为准,禁止信任客户端上报时间。
- 时间比较用
time.Now().Before/After,不要用==或UnixNano()差值猜精度。 - 外部调用(MySQL/Redis)必须带
context超时(见main.go的context.WithTimeout写法)。 - 定时器回调里必须复查条件(房间仍为对应状态)再动作,防止"已经结束/已经销毁"后仍触发。
2.6 边界检查写法模板
入口统一校验,错误码用仓库统一错误码(internal/common/errors.go 的 Code* 常量),不要返回裸字符串。新功能请仿照本仓库真实实现 internal/app/room/room.go 的 Place(组件层校验)与 internal/service/room.go 的 Room.Place(房间层在锁内校验),风格如下:
// 组件层:入口校验,顺序为 会话 → 引用 → 前置状态
func (c *Component) Place(ctx context.Context, in *model.C2SPlacePiece) (*model.S2CPlacePiece, error) {
s := c.app.GetSessionFromCtx(ctx)
if s == nil || s.UID() == "" { // 1) 身份边界
return &model.S2CPlacePiece{Code: common.CodeNotLoggedIn, Msg: "未登录"}, nil
}
if in == nil { // 2) 引用边界
return &model.S2CPlacePiece{Code: common.CodeBadParam, Msg: "参数错误"}, nil
}
// 3) 数值边界:坐标越界在房间层一并校验(见下),组件层只做必须的浅校验
// 4) 核心逻辑(房间层在锁内完成状态/回合/坐标校验)
}
// 房间层:锁内完成全部状态与输入校验(参照 service.Room.Place)
func (r *Room) Place(uid string, x, y int32) *model.S2CPlacePiece {
r.mu.Lock()
defer r.mu.Unlock()
resp := &model.S2CPlacePiece{}
if r.State != common.RoomPlaying { // 状态边界
resp.Code, resp.Msg = common.CodeRoomState, "房间状态不允许"
return resp
}
if seat := r.seatOf(uid); seat < 0 { // 身份边界
resp.Code, resp.Msg = common.CodeRoomNotFound, "不在本房间"
return resp
} else if seat != r.TurnSeat { // 回合边界
resp.Code, resp.Msg = common.CodeNotYourTurn, "非己方回合"
return resp
}
if x < 0 || x >= common.BoardSize || // 数值边界(坐标)
y < 0 || y >= common.BoardSize ||
r.Board[x][y] != 0 { // 占位冲突
resp.Code, resp.Msg = common.CodeIllegalMove, "坐标越界或已有棋子"
return resp
}
// ... 核心逻辑;返回错误时附带上下文(见 §5)
}2.7 边界用例必须进测试
每条边界(越界 ±1、空、nil、未知枚举、重复调用、并发触发)都要在 *_test.go 里出现,否则该功能不算完成(见 §6 与 §8 DoD)。
3. 冒烟测试规范(重点)
3.1 定义
冒烟测试 = 最小闭环验证:不追求全覆盖,只验证"代码能编译、服务能启动、主链路能走通"。每个功能完成后必须跑,功能有破坏性改动时重跑。
3.2 冒烟分级
| 级别 | 内容 | 命令 / 动作 | 失败即 |
|---|---|---|---|
| L0 构建冒烟 | 编译、静态检查、单测 | go build ./...、go vet ./...、go test ./... | 立即修复,禁止继续 |
| L1 启动冒烟 | 服务能带配置启动 | go run . -c config/config.yaml,日志出现 starting gomoku server,无 panic | 修复启动问题 |
| L2 链路冒烟 | 主业务链路端到端 | 见 §3.3 清单 | 报告并修复 |
3.3 本仓库 L2 冒烟清单(对弈主链路)
启动服务后(依赖:本地 Redis;MySQL 缺失时登录会降级,冒烟不依赖它),按序验证:
- 连接:WebSocket 连上
ws://127.0.0.1:3250(端口以config/config.yaml为准)。 - 登录:
auth.login成功返回 uid/session(可用web/index.html客户端)。 - 匹配:两个账号
match.join进入同一房间,收到room.onMatchSuccess。 - 对弈:黑方
room.place落子成功 → 广播room.onPlace与room.onTurn→ 白方落子成功;非法落子(越界、重复占位、非己方回合、观战者落子)均被拒绝并返回对应错误码(CodeIllegalMove/CodeNotYourTurn等)。 - 结算:一方五连后广播
room.onGameOver(含制胜连线),记录落库(MySQL 可用时)。 - 重连(改动涉及房间状态时):对局中掉线再
room.reconnect,能恢复棋盘与计时。
3.4 冒烟脚本要求
- 冒烟步骤要能重复执行:写成脚本或固定操作序列,禁止"手点一次碰运气"。
- 冒烟输出要留证据:日志片段、测试输出,写进交付说明。
- 冒烟发现问题必须修复后重跑整条链路,禁止只修单点不回归。
3.5 什么时候必须跑
- 任何功能完成后(L0 必跑,涉及业务逻辑加跑 L2)。
- 改动协议、序列化、房间状态机、计时、重连相关代码后,至少重跑 L1 + 对应链路。
- 改动 go.mod / 依赖版本后:L0 全量 + 启动冒烟。
4. AI 常见错误与禁令(重点)
以下按类别列出 AI 编码时的高频错误,每条的禁令必须遵守。
4.1 理解与规划类
| 错误 | 原因 | 正确做法 |
|---|---|---|
| 不读代码就写,臆造 API/签名/字段名 | 凭印象编码 | 先 grep 现有调用点,确认函数真实签名 |
| 重复造轮子 | 没发现已有实现 | 本仓库已有 common.FindWinLine、房间管理器、统一错误码,优先复用 |
| 过度设计 | 引入不必要的抽象/依赖/设计模式 | 功能优先最小实现;新依赖必须说明理由并经用户确认 |
| 顺手改动无关代码 | 没控制 diff 范围 | 只改本功能相关文件,收尾审查 diff |
4.2 Go 语言陷阱类
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
忽略错误返回值(_ = err / 不判 err) | 静默失败,后续逻辑建立在错误结果上 | 所有返回 error 的调用必须处理(§5) |
| 循环变量捕获 | for i := range xs 内 go func(){...i...} 闭包捕获同一变量 | 循环体内显式 i := i,或直接传参 |
| slice 共享底层数组 | a := b[:2] 后 append 可能改写 b 的底层数据 | 需要独立数据时用 copy 或 append([]T{}, ...) |
| 并发写 map / 写 nil map | 运行期 panic(concurrent map writes) | 加锁;初始化用 make |
| 向已关闭 channel 发送 | panic | 明确关闭责任方;用 select/哨兵关闭信号 |
defer 写在循环里 | 资源句柄累积到函数结束才释放 | 循环体内用匿名函数包住 defer,或改为显式 close |
| 整数溢出 | 计时、积分、时间戳计算溢出 | 大数用 int64;溢出点加边界判断 |
| string/[]byte 互转频繁 | 每次转换都有拷贝 | 热路径避免反复转换 |
| 值拷贝大结构体 | Board [15][15]int 直接值传递拷贝 | 传指针(见 FindWinLine(b *Board, ...)) |
4.3 并发类
- 禁止在没有锁的情况下读写共享房间状态。
- 禁止"先释放锁再继续读共享数据"造成竞态;锁内完成判断与写入。
- 禁止启动无法停止的 goroutine / 定时器;房间销毁时必须清理。
- 禁止在持有锁的情况下执行阻塞 IO(DB/Redis 调用),会导致全房间卡死。
4.4 数据与存储类
- 禁止把敏感信息(密码、连接串)写进代码或提交;生产配置走环境变量/密钥管理(见《开发文档》§2 安全提醒)。
- 禁止修改表结构/协议字段而不同步
docs/schema.sql/docs/gomoku.proto。 - 禁止假设 MySQL/Redis 永远可用:本仓库约定连接失败按 nil 降级返回错误码(见
main.go),新功能沿用。 - 写 Redis/MySQL 的 key 命名、TTL 要符合仓库现有约定,禁止自造一套。
4.5 测试类
- 禁止只测 happy path:必须包含 §2.7 的边界用例。
- 禁止测试依赖真实外部服务(MySQL/Redis 不可用时测试也要能跑);外部依赖用接口/mock 隔离。
- 禁止"测试断言过弱"(如只断言不 panic):要断言返回值、错误码、状态变化。
- 禁止新增代码没有对应测试(至少覆盖核心分支与边界)。
- 禁止为凑覆盖率写无意义断言。
4.6 提交与协作类
- 禁止提交编译不过的代码。
- 禁止提交调试输出(
fmt.Println、log.Println临时打印、注释掉的代码块)。 - 禁止提交密钥、
.env、生成文件、无关产物(err.log、日志、二进制)。 - 禁止擅自升级/增删依赖(本仓库锁定 Pitaya v2.11.24 等版本,升级需用户确认)。
- 禁止在注释/文档里说谎:注释必须与代码行为一致,文档更新必须与代码同步。
5. 错误处理与日志
5.1 错误必须被处理
- 所有
error返回值必须检查:要么处理、要么带上下文向上返回。 - 禁止用
_丢弃错误后继续关键逻辑。 - 降级路径(db/rdb 为 nil)必须在入口处显式返回错误码,禁止在深层 panic。
5.2 错误包装
- 内部错误用
fmt.Errorf("...: %w", err)保留根因;对用户只暴露统一错误码。 - 错误码使用
internal/common/errors.go中已有的Code*常量(如CodeBadParam、CodeIllegalMove、CodeRoomState);新增错误码要先加定义再使用,并保持命名/编号风格一致。
5.3 日志
- 使用项目统一日志(
logrus,见main.go),禁止裸fmt.Println打日志。 - 级别使用:
Info(流程关键节点)、Warn(可降级/可恢复)、Error(功能失败)、Fatal(仅启动致命错误)。 - 日志必须带上下文(uid、roomId、请求参数摘要),便于回放问题。
5.4 panic 政策
- panic 只允许用于不可恢复的编程错误(如断言失败),禁止用 panic 做业务控制流。
- 业务异常一律返回 error。
- 不允许新增代码在正常输入下触发 panic;运行期 panic 属 bug,必须修复而不是兜底吞掉。
6. 测试规范
- 表驱动:多个用例用
t.Run(name, ...)表驱动写法(参照internal/common/board_test.go)。 - 命名:
Test<函数名>_<场景>,用例 name 用中文描述场景(仓库现有风格)。 必测清单(每项都要出现):
- 正常主路径(成功);
- 每个边界(§2 表格逐条);
- 错误路径(非法输入 → 正确错误码);
- 状态机非法迁移被拒绝;
- 重复/并发调用(幂等或被拒绝)。
- 隔离:单元测试不连 MySQL/Redis;需要时用接口注入或本地假实现。
- 回归:改动既有函数,其全部既有测试必须保持通过;有行为变更必须同步改测试并说明。
7. 代码风格与提交
7.1 强制工具
- 提交前必须:
go build ./...、go vet ./...、gofmt(代码必须 gofmt 通过)。 - 文件内 import 分组保持仓库现状(标准库 / 第三方 / 本仓库),不混排。
7.2 命名与注释
- 标识符用英文;注释可用中文(仓库现状如此),但必须准确,禁止复制粘贴错误注释。
- 魔法数必须定义成命名常量(参照
common.BoardSize、common.WinCount、common.SeatBlack等),禁止散落裸数字。 - 新文件头部注释说明职责;导出函数写清楚参数与返回值语义。
7.3 提交纪律
- 一次提交只做一件事;commit message 说明"做了什么 + 为什么"。
- 提交前自查 diff:无调试残留、无无关文件、无密钥。
- 收尾必须更新受影响的文档(
开发文档.md、docs/gomoku.proto、docs/schema.sql)。
7.4 禁止乱动清单
以下内容改动前必须经用户确认:
go.mod/go.sum(依赖变更);docs/gomoku.proto中的字段编号与类型(破坏性协议变更);docs/schema.sql已有表结构;- 现有对外方法名与路由(如
auth.login、match.join、room.place、room.reconnect、rank.list,见《开发文档》§7.3 路由表); web/lib/下官方客户端文件(《开发文档》注明"原样引入,勿改")。
8. 完成定义(DoD)检查清单
功能交付前,逐项自检,全部通过才算完成:
- [ ] 已读透相关现有代码与协议,未臆造 API;
- [ ]
go build ./...、go vet ./...通过; - [ ]
go test ./...全部通过,且新增/修改的测试覆盖 §2 全部边界用例; - [ ] 冒烟 L0 通过;涉及业务逻辑的功能 L2 链路冒烟通过(§3.3);
- [ ] 对外入口全部做了参数校验(§2),错误走统一错误码;
- [ ] 无并发竞态、无 goroutine/定时器泄漏、无阻塞锁内 IO;
- [ ] 无调试输出、无密钥、无无关改动;
- [ ] 涉及协议/表结构/文档时已同步更新(§7.3);
- [ ] 最终 diff 已整体审查,只包含本功能所需改动。
本规范为通用底线,具体功能需求优先于本规范;与仓库现有代码冲突时以现有约定为准并明示差异。