9.9 KiB
04 · 开发规范与红线
本篇汇总所有必须遵守的工程纪律。每一条都对应过真实事故或返工,是代码审查的清单来源。改任何代码前,按相关条目自检。
1. 可编辑范围
服务端
- 唯一可编辑目录:
server/games2/<你的游戏>/。 - 禁改:
server/其余一切均为平台代码——server/class/、server/config/、server/server/、server/youle/、server/update/、server/packet.js、server/applist.js、server/minhttp.js等。
前端
- 平台代码禁改:
client/js/00_Surface/下全部文件。 - 受限接口文件(不可新增对外接口,尽量不改,确需则只在现有接口内部加逻辑):
client/js/01_SubGame/00_SubGame_Config.js、01_SubGame_modify.js、02_SubGame_Input.js。 - 新增前后端交互一律走
mod.js的mod_<游戏>.<rpc>机制,不在受限文件里新增接口。
2. 语言标准:严格 ES5
- 全部 JS 必须严格符合 ES5:用
var/function、字符串用+拼接。 - 禁止 ES6+:
let/const、箭头函数、模板字符串、解构、默认参数、展开运算符、class、for...of、Promise/async/await、对象简写等。 - 原因:线上浏览器/友乐运行时不保证 ES6+ 支持。
3. 模块加载:require 双运行时守卫
- 所有
require必须写在文件开头的if (typeof require !== 'undefined') { ... }守卫块内。 - 禁止函数体内 / 中途
require(含"延迟 require 规避循环依赖"的写法)。浏览器/友乐运行时无require,无守卫的require会抛ReferenceError: require is not defined。 - 跨模块运行时按全局名引用:Node 由守卫块
var X = require(...)得到,浏览器由mod.js加载的同名全局解析。
// ✅ 正确
if (typeof require !== 'undefined') {
var GameStateManager = require('./dataStructures/GameStateManager.js');
}
function foo() { GameStateManager.doSomething(); } // 直接用全局名
// ❌ 错误:函数体内中途 require —— 浏览器崩溃
function bar() { var GSM = require('./dataStructures/GameStateManager.js'); }
双运行时全局暴露陷阱
"构造函数 + 单例实例"式模块,module.exports 之后必须无条件把全局名暴露出去(不要放进 else 分支),否则线上浏览器拿不到该全局,报 xxx is not a function,而 Node 测试测不出来。
4. 数据权威原则
- 数据源唯一:同一业务数据只有一个权威来源;上游计算/写入/校验,下游只读取/消费,不重复推断、不重复拼装。
- 禁止猜测兜底:不用默认值掩盖缺失数据。关键权威字段缺失要显式报错或返回
null,由上层决定是否终止,禁止|| 0、|| []、|| ''、双源回退等掩盖。 - 下游不修补:下游不得为"让流程跑起来"而补写/重算上游本该提供的数据;发现下游在修补,应把责任前移到权威来源。
- 边界:只有展示层、纯 UI 兼容、非关键日志字段,才可在明确边界内用安全默认值。
审查信号:看到
|| []、|| 0、|| ''、三元默认、双源字段,先判断它是不是权威字段(影响发牌/庄家/手牌/规则/结算/重连/状态流转)。是 → 改成权威读取;只是展示/日志 → 才可保留默认。
5. 模块职责边界
- 一个职能只在一个模块实现,其他模块只调用、不重造。
- 需要某能力时,调用对应权威模块;权威能力不满足,应在权威模块内扩展,而非在调用方旁路重写。
- 禁止:在 A 模块内联 B 模块核心算法的"简化版";同一职能在两处各有一份实现并行演化。
- 后果:交叉实现产生双份并行逻辑,必随规则演化分叉、互相矛盾——这是数据权威原则在"代码职责"维度的同一条红线。
"自动操作"模块只做决策,不重造规则
服务端自动替玩家操作的模块(如 AI 托管)只负责决策(出哪张、是否碰/杠/吃/胡/过),核心算法一律复用权威实现:胡牌检测、听牌/做牌分析、合法操作枚举、牌型判定等都已在权威模块实现,决策模块只能读取/调用其结果,禁止自行重算。
- 判别:回答"能否胡/听什么/有哪些合法操作"——属核心算法,必须复用;回答"在合法选项中选哪个更好"——才属决策。
6. 房间隔离
服务端同进程并发多张牌桌(多个 o_room)。任何随对局变化的状态都必须以房间为单位隔离:
- 状态挂房间:对局数据存
o_room.o_desk.data.*,由o_room携带。禁止存在模块级单例 / 全局变量 / 静态字段。 - 不得只用
seat作 key:座位号仅 0–3,多房并发必碰撞。缓存、定时器表、决策状态、计数器等必须用房间 + seat复合维度(或每房一份实例)。 - 定时器随房生命周期:所有
setTimeout/setInterval必须能按房间定位与清理;小局结束、解散房间、开新局时,清理该房名下全部定时器与残留状态,禁止泄漏到下一局或别房。 - 覆盖范围:自动托管/决策表、算法中间态缓存(听牌/胡牌检测)、超时与回合定时器、待响应队列等,任一项跨房共享都会导致 A 房误改 B 房。
一句话:一切随对局变化的东西都属于某个房间,必须能用
o_room唯一定位、隔离与回收。
7. 服务端自动操作复用真人链路
服务端代替玩家执行的操作(AI 托管等):
- 必须复用真人手动操作的同一套数据包链路:走与真人相同的服务端处理入口与广播下发路径,产生的包结构与真人完全一致。
- 对前端透明:前端只按既有"玩家操作"逻辑解析表现,禁止为"自动操作"单开一套接收/解析/表现分支,无需区分触发源。
- 唯一区别在触发源:由"前端请求"变为"服务端决策",其后数据组织、下发协议、广播路径不变。
8. Shared 文件同步流程
前后端存在一份共享代码,必须经流程修改,禁止直接编辑前端副本:
| 角色 | 路径 |
|---|---|
| 权威源(服务端) | server/games2/<你的游戏>/shared/ |
| 前端副本(只读) | client/js/01_SubGame/codes/shared/ |
- 只改服务端
shared/下文件。 - 改完运行根目录的同步脚本(如
sync-shared.ps1)同步到前端。 - 禁止直接编辑前端
codes/shared/(同步脚本会覆盖)。
9. 硬编码常量准则
需提取为常量(满足任一):会随规则变化、跨模块复用、裸值无法自解释。典型:分数数值、状态/类型字符串标识、配置阈值、跨模块共享的 key 名。常量按语义归入 shared/constants/ 下对应文件(分数类、规则配置类、类型枚举类)。
不必提取(避免过度设计):单函数内一次性临时值(循环初值 0、空数组 [])、框架约定固定串(require 路径)、自解释布尔开关、纯展示标点文字。
10. 测试纪律
- 测试唯一目的是验证业务正确性。失败是有价值的信号,第一反应是定位根因,不是"让测试变绿"。
- 禁止任何掩盖手段:skip/条件 return、软化断言、放宽阈值、try-catch 吞异常、把硬断言改成"存在才校验"。
- 每个失败必须裁定归属(基于证据):【业务代码缺陷】还是【测试脚本缺陷】,二选一。手段:临时诊断探针、打印中间态、最小复现;拿证据再下结论,禁止凭猜测定性(诊断日志定位后清理)。
- 按归属修复:业务缺陷 → 修业务、单独提交、写明根因;脚本缺陷 → 修置场/时序,断言保持硬断言。优先级:确定性构造场景 > 有界重试采样 > 条件跳过。
- flaky 同样是缺陷:要么业务竞态、要么测试非确定性置场,须根治;验收标准是连跑 ≥5 次全绿。
测试不绑架正式代码
- 正式核心代码禁止存在专为测试服务的逻辑,更禁止为"让测试通过/兼容测试"而新增或修改正式代码。
- 方向永远是测试适配正式代码的生产契约,而非正式代码迁就测试的简化输入/不规范 stub。测试要构造符合生产契约的输入与 stub。
- 违例信号:正式代码注释出现"仅兼容测试 stub""如单元测试传 X"之类,即是违例。
11. Git 提交规范
- 及时自动提交:每完成一个可独立成立的逻辑改动(一个修复/功能/重构/一批测试)就立即提交,不堆积工作区。
- 无需逐次询问:完成阶段性改动后主动提交(
push按需)。 - 提交信息用中文,简明说明"做了什么/为什么",一次提交聚焦一件事,结尾保留
Co-Authored-By署名行。
12. 审查速查表
| 维度 | 红线 |
|---|---|
| 范围 | 只改 games2/<游戏>/ 与允许的前端范围 |
| 语言 | 纯 ES5;require 仅在顶部守卫块 |
| 成败 | 只认 data.success,推送必自带,禁 status 兜底 |
| 数据 | 权威唯一、缺失报错/返回 null、禁兜底、下游不修补 |
| 职责 | 一职能一模块,调用不重造 |
| 隔离 | 状态挂 o_desk.data.*,禁全局;房间+seat 作 key;定时器随房清理 |
| 自动操作 | 复用真人链路,对前端透明 |
| Shared | 只改服务端 shared/,跑同步脚本 |
| 测试 | 失败裁定归属、禁掩盖;正式代码不迁就测试 |
| Git | 一事一提交、中文信息、及时提交 |
至此,从框架运作(01)、子游戏接入(02)、收发协议(03)到工程红线(04),构成一套完整的服务端子游戏开发指导。回到 README 查看导航与一页纸模型。