📄 本页由源文件
skills/dev-flow/references/core-principles.md自动投影生成(单一权威源)。请勿直接编辑本页。
核心原则(最高优先级)
§1~§12 对应「开发规范-红线」的 12 条核心原则,§13~§18 为 dev-flow 扩展原则。 本文件仅展开需要详细说明的原则,其余原则在红线/SKILL.md 精要中已有足够描述。
红线层(§1~§12)
§1. 需求理解优先于执行
收到任何需求后,禁止立即动手执行,必须先通过提问充分理解用户的真实需求:
- ✅ 默认假设自己没有完全理解需求,第一反应是"我是否真的理解了用户要什么"
- ✅ 主动提问澄清:范围和边界、期望效果、不希望改变的行为、涉及的场景/页面/端
- ✅ 达到 99% 把握再执行,不足则继续提问
- ❌ 禁止"我觉得用户大概是要这个"就开始写代码
- 💡 简单明确的需求(如"删除第 10 行的 console.log")可快速确认后执行
执行顺序:收到需求 → 分析是否完全理解 → 不确定就提问 → 确认后制定方案 → 向用户解释方案 → 执行
§2. 最小入侵
- ❌ 禁止改变原有逻辑结构(除非用户明确要求重构或存在严重 bug)
- ❌ 禁止改变函数签名或入参结构(除非修复设计缺陷且已说明影响;新参数必须追加在最后)
- ❌ 禁止随意调整原有代码位置(除非是修复 bug 或实现需求的必要步骤且已说明)
- ❌ 禁止为了"简化"或"优雅"改变原有设计(除非用户明确要求)
- ✅ 保持原有逻辑,只在必要处追加新逻辑
- ✅ 需要改变原有结构时,必须给出改动点汇总详情
§4. 根因定位与验证闭环
VBR(Verify Behavior, not intention):验证行为而非意图——不要因为"改了应该没问题"就跳过验证,必须通过实际行为确认修复生效。
五步根因分析法:
- 复现:先稳定复现问题,明确触发条件和预期 vs 实际行为
- 定位:从现象出发逐层追溯(日志 → 调用链 → 数据流 → 状态变化),找到第一个"行为偏离预期"的位置
- 验证根因:对疑似根因提出假设,用最小改动验证假设是否成立(如临时加 console.log / 断点)
- 修复:确认根因后再修复,禁止在未定位根因时尝试多种修改碰运气
- 闭环验证:修复后必须回到第 1 步的复现路径重新验证,确认问题消失且无副作用
禁止行为:
- ❌ 禁止"看起来像是这里的问题"就直接改——必须有定位依据
- ❌ 禁止一次改多处然后说"应该修好了"——每次只改一个点,验证后再改下一个
- ❌ 禁止跳过闭环验证——"改了就行"是最常见的 bug 复发原因
§7. 边界条件与防御性编码
红线精要侧重可选链 ?.,但防御性编码的覆盖面远不止于此:
必须处理的边界场景:
| 场景 | 防御方式 |
|---|---|
| 空值 / undefined / null | 可选链 ?. + 空值合并 ?? 提供默认值 |
空数组 [] | 渲染前检查 .length,避免空列表无反馈 |
空字符串 "" | 注意 "" | | fallback 会触发 fallback,用 ?? 区分 |
| 异常输入(非预期类型) | 函数入口做类型检查或使用 TypeScript 严格模式 |
| 数组越界 | 访问 arr[index] 前检查 index 范围 |
| 对象属性不存在 | 所有链式访问使用 ?.,禁止裸链式访问 |
| 除零 / NaN / Infinity | 数值计算前校验分母和输入合法性 |
| 并发 / 时序 | 见 §8 异步与竞态安全 |
可选链规则(增量优化策略):
- ✅ 本次改动行涉及的链式调用 → 必须优化为可选链
- ✅ 同函数内的其他链式调用 → 建议一并优化
- ❌ 禁止为了"统一风格"大面积改动不相关文件的链式调用(违反 §2 最小入侵)
§11. 主动思考更优方案
- ✅ 执行方案时必须主动思考是否有更好的实现,发现更优方案及时向用户建议
- ❌ 禁止只是机械执行用户指令而不独立思考
更优方案与最小入侵冲突时:必须同时提供两个完整方案——方案 A(最小入侵)和方案 B(更优方案),由用户决定。
dev-flow 扩展层(§13~§18)
§13. 不确定就先确认
- ❌ 禁止自行猜测 / 基于假设给方案 / 凭记忆回答
- ✅ 必须第一时间向用户确认,多个不确定点汇总后一次性确认
- ✅ 所有不确定点都是阻塞性的,必须确认后才能继续
用户指令有歧义时必须确认:
- "不要动"可能是"保留现状"也可能是"回退改动",必须先确认再执行
- 💡 典型案例:用户说"逐字稿的不要动"→ 应确认是"保留逐字稿已有的改动不再修改"还是"把逐字稿的改动回退"
§14. 向用户解释
必须解释改动内容:为什么这样改、改了什么、有什么影响。涉及设计权衡时,说明各方案优缺点让用户决定。
§15. 深度思考三原则
95% 把握度门槛:
- ✅ 把握不足时继续自行深入分析(读源码、加调试日志、验证假设),不把不确定性抛给用户
- ❌ 禁止在把握不足时让用户去页面反复验证
顶尖专家视角:
- 不接受表面原因,必须追到根因;不满足于"能用",必须追求"正确";不仅解决当前问题,还要预判衍生问题
对立面分析(红队思维):
- 确定方案前站在反对立场重新分析:"如果有人要否定这个方案,会从什么角度攻击?"
- 主动找出薄弱环节:假设是否成立、边界是否覆盖、是否有更简单替代方案、是否引入新问题
- 只有经过对立面分析仍站得住脚的方案才可提出
§16. 不可变性优先
默认使用不可变操作,只在有明确理由时才使用可变操作。
必须不可变的场景:
- React 状态更新:
setState必须返回新引用,禁止直接修改 state 对象 - Redux store:reducer 必须返回新对象,禁止原地修改 state
- 函数参数:禁止修改传入的对象/数组参数(纯函数原则),返回新实例
- 数组操作:使用
map/filter/[...arr]/toSorted(),禁止push/splice/sort()修改源数组 - 对象操作:使用
{ ...obj }/Object.assign({}, obj),禁止直接赋值修改属性
允许可变的例外场景:
- ✅ 函数内部的局部变量(不会泄漏到外部)
- ✅ RTK(Redux Toolkit)的
createSlicereducer 内部——Immer 会自动转为不可变操作 - ✅ 性能关键路径中经过 profiling 确认的热点(需注释说明原因)
- ✅
useRef的.current赋值(ref 本身就是可变容器)
判断标准:如果你不确定该用可变还是不可变 → 用不可变。
§17. 文件与函数大小约束
控制单个文件和函数的体量,超出阈值时必须拆分。
阈值参考:
| 维度 | 建议上限 | 硬性上限 | 超出时动作 |
|---|---|---|---|
| 单个函数/方法 | 50 行 | 80 行 | 提取子函数 |
| 单个 React 组件 | 150 行 | 250 行 | 提取子组件或自定义 Hook |
| 单个文件 | 300 行 | 500 行 | 按职责拆分为多个文件 |
| 单个函数参数 | 3 个 | 5 个 | 合并为 options 对象 |
拆分原则:
- ✅ 按「单一职责」拆分——一个函数只做一件事,一个文件只管一个模块
- ✅ 提取的子函数/子组件应该有明确的命名和独立的语义
- ❌ 禁止为了凑行数而机械拆分(如把一个连贯的逻辑拆成两半)
- ❌ 禁止因为"文件太长"就在不相关的任务中重构(违反 §2 最小入侵)——记录待优化项,后续专项处理
新增代码时的检查:每次新增代码后,检查目标函数/文件是否超过建议上限。如果超过,在当前任务中顺手拆分(如果拆分成本低);如果拆分成本高,向用户说明并记录为待优化项。
§19. 确定性用代码,模糊性用 LLM
核心理念:"能用代码兜底的,绝不让 AI 记住" — Anthropic Claude Code Hooks / 12-factor agents 共识。 dev-flow v3 重构(2026-05-14)已实战验证:红牌脚本兜底覆盖率从 28% → 100%,元门控 13/13 严格模式全绿。
判断标准:一条规则/检查/判断是否「机械可枚举」?
| 类型 | 特征 | 实现方式 | 兜底机制 |
|---|---|---|---|
| 确定性 | 关键词匹配、JSON Schema、文件存在性、行数、AST 模式、引用计数、状态转移 | bash/jq/ajv/lint 脚本 | exit code + .validated 物理文件 |
| 模糊性 | 命名是否传神、注释是否清晰、方案是否优雅、根因是否合理、需求是否理解到位 | LLM 判断 | 用户决策 + 主动确认 |
对应的 4 项实现哲学(dev-flow v3 已落地):
- 程序化优先:能进
config/gates.yaml/scripts/lints/的规则就不进提示词。如 R1-R5、C1-C8、name_lint、doc_platform_lint 等机械可判断规则一律下沉脚本。 - 单一权威源:脚本/配置是规则真相,文档只引用不重复。需要修改规则时改脚本,不改 N 处提示词。
- 物理事实兜底:用
.validated/.done文件由脚本原子创建,AI 不可绕过。比"提示词约束 AI 记得跑某检查"可靠 100 倍。 - 元门控守护:用 lints/precheck 脚本守护规则系统本身(如检查脚本可执行、配置一致性、双源真相)。
反模式(dev-flow v3 重构前的真实痛点):
- ❌ 把"step 5.5 不可跳过"写成提示词依赖 AI 记忆 → 偶尔遗漏
- ❌ 把"交互式选项数量必须一致"写在 N 个文档里 → 双源真相打架
- ❌ 把"commit 格式校验"留给 AI 肉眼检查 → 格式漂移
对 AI 行为的指令:
- ✅ 在 dev-flow / 任何 skill 中遇到一条规则时,先问自己:"这条规则能写成脚本吗?" 能就推荐用户/自己写脚本,不能再写提示词。
- ✅ 设计新 skill / 重构旧 skill 时,强制做「确定性 vs 模糊性」拆分(详见
~/.codebuddy/rules/AI行为规范.mdc的「Skill 设计哲学」章节)。 - ❌ 禁止把可程序化判断的规则塞进 SKILL.md 提示词后就当完成。
§20. 数据驱动优先于硬编码
核心理念:「能放进 YAML/JSON 的真相,绝不写在 case 语句里」——
§19的具体落地策略。 来源:dev-flow v3.1 方向 ② 实战验证(2026-05-15)。改state-machine.sh的步骤序列,原本要改 case 语句 + 改测试 + 跑回归;现在只需改gates.yaml一行。
§20.1 判断标准:何时该「数据驱动」
| 特征 | 适合数据驱动 | 仍可硬编码 |
|---|---|---|
| 数据类型 | 枚举、列表、映射、序列 | 复杂业务逻辑分支 |
| 变更频率 | 中等及以上(≥1 次/季度) | 极低或永不变 |
| 调整成本 | 改一行配置就生效 | 改了要重写函数 |
| 测试可写性 | 可临时构造配置验证行为 | 必须改源码再测 |
| 多处使用 | ≥2 处共享同一数据 | 仅 1 处使用 |
典型可数据驱动的对象:状态机步骤序列、模式 → 子类型映射、规则白名单、错误码 → 文案映射、批次循环规则、各模式的检查点列表。
典型仍应硬编码的对象:函数控制流、错误处理分支、需要类型安全的状态枚举(用 TypeScript / 强类型语言时)。
§20.2 双层结构:YAML 主路径 + 硬编码 fallback
设计要点:「数据驱动」不等于「丢掉硬编码」。最佳实践是 YAML 优先 + 硬编码 fallback,兼得「可调整」+「不脆弱」。
# dev-flow v3.1 实战范式(state-machine.sh)
get_steps_for_mode() {
local mode="$1"
local steps=""
# 主路径:从 gates.yaml 读
if [ -f "$gates_yaml" ] && type df_get_yaml_list >/dev/null 2>&1; then
steps=$(df_get_yaml_list "$gates_yaml" "state_machine.step_sequences.$mode" 2>/dev/null) || steps=""
fi
# 兜底:YAML 缺失/损坏 → 硬编码 fallback
if [ -z "$steps" ]; then
_fallback_get_steps_for_mode "$mode"
return $?
fi
echo "$steps"
}双层结构的 4 个核心收益:
- API 不变:函数对外签名不变,所有上游调用零修改(dev-flow v3.1 的 25 个老测试用例零修改全过即是证据)
- 平滑降级:YAML 损坏不会导致系统崩溃,自动回退硬编码
- 可测试性:用临时 YAML + 环境变量覆盖(如
DF_GATES_YAML),可独立验证「配置驱动能力」 - 可监测性:用 lints 脚本检测「主路径是否仍是数据驱动」,防止有人误删 YAML 调用退化回硬编码
§20.3 配置驱动测试模式(与单元测试范式互补)
传统单元测试只验证「代码是否符合预期」,配置驱动测试额外验证「改配置 → 行为跟随变化」:
# 5 类标准配置驱动测试用例
test_config_driven_custom_value() # 自定义配置生效
test_config_driven_yaml_corrupted() # 损坏配置回退 fallback
test_config_driven_yaml_missing() # 缺失配置回退 fallback
test_config_driven_extension() # 序列扩展(如加入新步骤)生效
test_config_driven_modification() # 子字段修改(如 step7 子类型)生效实施关键:被测脚本必须支持环境变量覆盖配置文件路径(如 DF_GATES_YAML),否则无法构造临时配置。
§20.4 实施陷阱(dev-flow v3.1 实战教训)
| # | 陷阱 | 实例 | 教训 |
|---|---|---|---|
| 1 | YAML 解析路径误匹配 | step7_variants.standard 被解析成 step_sequences.standard 的值(因 awk 只看 leaf key 名) | 必须用 path_match 状态数组严格按层级路径完整匹配 |
| 2 | 行内注释剥离不彻底 | 4.5" # 非最后批次时回到 4.5 中的 # ... 没去掉 | 区分双引号包裹值 vs 无引号值,分别用不同剥离逻辑 |
| 3 | awk 状态机意外死循环 | 32 元素初始化数组 + 错误的循环条件导致 30 秒卡死 | 简化解析逻辑,避免大数组初始化 |
| 4 | 测试在 set -u 下变量未定义 | [ -z "$__DF_COMMON_LOADED" ] 直接报错 | 用 ${VAR:-} 默认值兜底 |
§20.5 反模式
- ❌ 半数据驱动:只把 50% 的硬编码搬到 YAML,剩 50% 还在 case 语句里 → 双源真相不消反增
- ❌ 删掉 fallback 完全依赖 YAML:YAML 损坏 / 路径错 / 文件丢失 → 整个系统崩溃,不如保留硬编码兜底
- ❌ 只跑配置驱动测试不跑零修改回归:API 静默漂移 → 上游调用方崩溃
- ❌ 不加元门控守护:后人「优化」时把 YAML 调用注释掉退化回硬编码 → 隐患静默累积
§20.6 何时不要数据驱动
YAGNI 原则压倒 DRY——不要为了数据驱动而数据驱动。
跳过数据驱动的合理场景:
- 数据仅 1 处使用 + 永不变(如某个模块的内部常量)
- 配置文件解析成本 > 直接修改源码成本(如配置非常复杂、需要嵌套数组)
- 项目语言天然支持类型安全枚举(如 TypeScript 的字面量联合类型,硬编码反而能 catch 漏分支)
对 AI 行为的指令:
- ✅ 看到
case语句中包含「步骤列表/枚举映射/白名单」类数据时,先问:「这块数据未来会变吗?是否多处共享?」 满足任一 → 推荐数据驱动 - ✅ 数据驱动改造时,强制保留 fallback + 强制写「配置驱动测试」+ 强制给 lints 加监测项
- ❌ 禁止「数据驱动」时删掉硬编码兜底,即使 YAML 看起来「不可能损坏」