Files
youle_framework/server/docs/development-guide/04-开发规范与红线.md
T

169 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` 加载的同名全局解析。
```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/` |
1. **只改服务端** `shared/` 下文件。
2. 改完运行根目录的同步脚本(如 `sync-shared.ps1`)同步到前端。
3. **禁止直接编辑前端 `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](./README.md) 查看导航与一页纸模型。
</content>