Skip to content

📄 本页由源文件 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):验证行为而非意图——不要因为"改了应该没问题"就跳过验证,必须通过实际行为确认修复生效。

五步根因分析法

  1. 复现:先稳定复现问题,明确触发条件和预期 vs 实际行为
  2. 定位:从现象出发逐层追溯(日志 → 调用链 → 数据流 → 状态变化),找到第一个"行为偏离预期"的位置
  3. 验证根因:对疑似根因提出假设,用最小改动验证假设是否成立(如临时加 console.log / 断点)
  4. 修复:确认根因后再修复,禁止在未定位根因时尝试多种修改碰运气
  5. 闭环验证:修复后必须回到第 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)的 createSlice reducer 内部——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 已落地):

  1. 程序化优先:能进 config/gates.yaml / scripts/lints/ 的规则就不进提示词。如 R1-R5、C1-C8、name_lint、doc_platform_lint 等机械可判断规则一律下沉脚本。
  2. 单一权威源:脚本/配置是规则真相,文档只引用不重复。需要修改规则时改脚本,不改 N 处提示词。
  3. 物理事实兜底:用 .validated / .done 文件由脚本原子创建,AI 不可绕过。比"提示词约束 AI 记得跑某检查"可靠 100 倍。
  4. 元门控守护:用 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,兼得「可调整」+「不脆弱」。

bash

# 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 个核心收益

  1. API 不变:函数对外签名不变,所有上游调用零修改(dev-flow v3.1 的 25 个老测试用例零修改全过即是证据)
  2. 平滑降级:YAML 损坏不会导致系统崩溃,自动回退硬编码
  3. 可测试性:用临时 YAML + 环境变量覆盖(如 DF_GATES_YAML),可独立验证「配置驱动能力」
  4. 可监测性:用 lints 脚本检测「主路径是否仍是数据驱动」,防止有人误删 YAML 调用退化回硬编码

§20.3 配置驱动测试模式(与单元测试范式互补)

传统单元测试只验证「代码是否符合预期」,配置驱动测试额外验证「改配置 → 行为跟随变化」:

bash

# 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 实战教训)

#陷阱实例教训
1YAML 解析路径误匹配step7_variants.standard 被解析成 step_sequences.standard 的值(因 awk 只看 leaf key 名)必须用 path_match 状态数组严格按层级路径完整匹配
2行内注释剥离不彻底4.5" # 非最后批次时回到 4.5 中的 # ... 没去掉区分双引号包裹值 vs 无引号值,分别用不同剥离逻辑
3awk 状态机意外死循环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 看起来「不可能损坏」

基于「单一权威源」哲学构建 —— 文档由源文件投影生成