AI时代 GO通用设计规范

作者: 分类: php 时间: 2026-08-25 评论: 暂无评论

Go 功能通用设计规范(AI 编码助手版)

版本:v1.0 | 适用对象:AI 编码助手(Claude / Copilot / Codex 等) | 适用范围:本仓库所有 Go 功能开发
阅读时机:每次接到新功能 / 修改 / 修复任务时,先通读本规范,再动手。
配套文档:开发文档.md(项目架构与协议)、docs/gomoku.proto(协议源)、docs/schema.sql(建表脚本)。


0. 如何使用本规范

本规范是 AI 写 Go 功能时的底线条款,按以下优先级裁决:

  1. 用户当前要求 > 本规范 > 个人编码习惯:用户明确要求了某种做法时,按用户说的做。
  2. 本规范 > 通用"最佳实践":不要拿网上搜来的风格覆盖本仓库约定。
  3. 与现有代码冲突时:以现有代码的实际约定为准(先读代码确认),并把差异写进交付说明,不要静默替换。

一句话原则:先读后写、边界先行、小步验证、不留垃圾。


1. 功能开发标准流程

AI 完成任何一个功能,必须按以下顺序推进,不得跳步:

步骤动作产出
1. 理解现状读相关代码、协议、文档;找到最相似的既有实现明确"改哪里、不动哪里"
2. 拆解计划把功能拆成可独立验证的小步骤步骤清单(写进任务列表)
3. 边界清单按 §2 列出该功能的全部边界输入与状态边界用例清单
4. 实现写代码:入口校验 → 核心逻辑 → 错误处理 → 日志编译通过的代码
5. 验证go buildgo vetgo 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、空、长度、索引越界、访问前是否判 lenmoves 回放、匹配队列
字符串空串、超长、非法 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.gocontext.WithTimeout 写法)。
  • 定时器回调里必须复查条件(房间仍为对应状态)再动作,防止"已经结束/已经销毁"后仍触发。

2.6 边界检查写法模板

入口统一校验,错误码用仓库统一错误码(internal/common/errors.goCode* 常量),不要返回裸字符串。新功能请仿照本仓库真实实现 internal/app/room/room.goPlace(组件层校验)与 internal/service/room.goRoom.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 缺失时登录会降级,冒烟不依赖它),按序验证:

  1. 连接:WebSocket 连上 ws://127.0.0.1:3250(端口以 config/config.yaml 为准)。
  2. 登录auth.login 成功返回 uid/session(可用 web/index.html 客户端)。
  3. 匹配:两个账号 match.join 进入同一房间,收到 room.onMatchSuccess
  4. 对弈:黑方 room.place 落子成功 → 广播 room.onPlaceroom.onTurn → 白方落子成功;非法落子(越界、重复占位、非己方回合、观战者落子)均被拒绝并返回对应错误码(CodeIllegalMove/CodeNotYourTurn 等)。
  5. 结算:一方五连后广播 room.onGameOver(含制胜连线),记录落库(MySQL 可用时)。
  6. 重连(改动涉及房间状态时):对局中掉线再 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 xsgo func(){...i...} 闭包捕获同一变量循环体内显式 i := i,或直接传参
slice 共享底层数组a := b[:2] 后 append 可能改写 b 的底层数据需要独立数据时用 copyappend([]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.Printlnlog.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* 常量(如 CodeBadParamCodeIllegalMoveCodeRoomState);新增错误码要先加定义再使用,并保持命名/编号风格一致。

5.3 日志

  • 使用项目统一日志(logrus,见 main.go),禁止裸 fmt.Println 打日志。
  • 级别使用:Info(流程关键节点)、Warn(可降级/可恢复)、Error(功能失败)、Fatal(仅启动致命错误)。
  • 日志必须带上下文(uid、roomId、请求参数摘要),便于回放问题。

5.4 panic 政策

  • panic 只允许用于不可恢复的编程错误(如断言失败),禁止用 panic 做业务控制流。
  • 业务异常一律返回 error。
  • 不允许新增代码在正常输入下触发 panic;运行期 panic 属 bug,必须修复而不是兜底吞掉。

6. 测试规范

  1. 表驱动:多个用例用 t.Run(name, ...) 表驱动写法(参照 internal/common/board_test.go)。
  2. 命名Test<函数名>_<场景>,用例 name 用中文描述场景(仓库现有风格)。
  3. 必测清单(每项都要出现):

    • 正常主路径(成功);
    • 每个边界(§2 表格逐条);
    • 错误路径(非法输入 → 正确错误码);
    • 状态机非法迁移被拒绝;
    • 重复/并发调用(幂等或被拒绝)。
  4. 隔离:单元测试不连 MySQL/Redis;需要时用接口注入或本地假实现。
  5. 回归:改动既有函数,其全部既有测试必须保持通过;有行为变更必须同步改测试并说明。

7. 代码风格与提交

7.1 强制工具

  • 提交前必须:go build ./...go vet ./...gofmt(代码必须 gofmt 通过)。
  • 文件内 import 分组保持仓库现状(标准库 / 第三方 / 本仓库),不混排。

7.2 命名与注释

  • 标识符用英文;注释可用中文(仓库现状如此),但必须准确,禁止复制粘贴错误注释。
  • 魔法数必须定义成命名常量(参照 common.BoardSizecommon.WinCountcommon.SeatBlack 等),禁止散落裸数字。
  • 新文件头部注释说明职责;导出函数写清楚参数与返回值语义。

7.3 提交纪律

  • 一次提交只做一件事;commit message 说明"做了什么 + 为什么"。
  • 提交前自查 diff:无调试残留、无无关文件、无密钥。
  • 收尾必须更新受影响的文档(开发文档.mddocs/gomoku.protodocs/schema.sql)。

7.4 禁止乱动清单

以下内容改动前必须经用户确认:

  • go.mod / go.sum(依赖变更);
  • docs/gomoku.proto 中的字段编号与类型(破坏性协议变更);
  • docs/schema.sql 已有表结构;
  • 现有对外方法名与路由(如 auth.loginmatch.joinroom.placeroom.reconnectrank.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 已整体审查,只包含本功能所需改动。

本规范为通用底线,具体功能需求优先于本规范;与仓库现有代码冲突时以现有约定为准并明示差异。

标签: none

订阅本站(RSS)