一次 CR 引发的思考:我的 rules.ts 构想,究竟属于哪种开发哲学?

在一次普通的 Code Review 里,我提出我从一篇讲解 Spec-Driven Development 的文章中受到启发,想把组件里复杂的业务逻辑抽离成一个 rules.ts 文件作为核心维护管理的源头文件。

没想到这个想法引来了团队里两位同学的不同回应------后端同学说"这更像是 Business-Driven Development",另一位前端同学说"其实这是面向 AI 编程的重构"。

我当时有点懵:我只是想让代码好维护一点,怎么突然冒出来三个不同的概念?它们说的是同一件事吗?还是各说各的?

这篇文章是我事后整理的一些思考,不是标准答案,更多是一次概念厘清的过程。


起因:一段让人"胃疼"的组件逻辑

场景大概是这样的:一个表单组件,里面有十几个字段,每个字段的显示、禁用、必填状态都依赖彼此的值,还受到用户角色、当前流程状态、后端返回的配置项等多个因素影响。

随着需求迭代,这些判断逻辑开始散落在 JSX 的各个 disabledhiddenrequired 属性里,或者藏在 useEffect 的某个角落。改一个需求要跳好几个地方,测试也很难覆盖。

我的想法是:把这些判断统一收进一个文件。

javascript 复制代码
// env: React + TypeScript
// scene: extract form business rules into a single file

// OrderForm.rules.ts

export type OrderState = {
  items: CartItem[];
  address: Address | null;
  userRole: 'guest' | 'member' | 'vip';
  flowStatus: 'draft' | 'pending' | 'submitted';
  isPending: boolean;
};

// Can the order be submitted?
export const canSubmitOrder = (state: OrderState): boolean =>
  state.items.length > 0 &&
  state.address !== null &&
  !state.isPending &&
  state.flowStatus === 'draft';

// Why can't it be submitted? (for tooltip / disabled hint)
export const getSubmitDisabledReason = (state: OrderState): string | null => {
  if (state.items.length === 0) return 'Cart is empty';
  if (!state.address) return 'Please fill in the delivery address';
  if (state.flowStatus !== 'draft') return 'Order has been submitted';
  return null;
};

// Should the VIP discount field be shown?
export const shouldShowVipDiscount = (state: OrderState): boolean =>
  state.userRole === 'vip' && state.items.length > 0;

这些函数有几个共同点:纯函数、无副作用、不依赖任何 UI 层的东西。它们可以被独立测试,也可以被 AI copilot 单独修改,不会误伤组件渲染逻辑。

然后 CR 的时候,争论来了。


三个概念,说的是同一件事吗?

Spec-Driven Development:先定规范,再写实现

后端同学最初提的是 Spec-Driven Development(规范驱动开发) ,但随即他自己也觉得这个词不太准。

SDD 的核心是:有一份"单一真相来源"(Single Source of Truth),开发围绕它展开。最典型的例子是 API 开发里先写 OpenAPI YAML,再生成 server stub 和 client SDK;或者先定 TypeScript 类型,再写实现。

我的 rules.ts 某种程度上也在做这件事------先定义"业务规则是什么",再让组件去引用它。这个文件就是那份 source of truth。

但 SDD 更多强调的是流程,而不是文件结构本身。它关心的是"你是不是先写了规范才动手写实现",而不是"你把规范放在哪里"。

Business-Driven Development:让代码说人话

后端同学后来改口说"其实更像是 BDD",这里他说的不是测试领域里的 Behavior-Driven Development(那个 BDD 特指用 Gherkin 语法写测试用例的实践),而是一种更广义的业务驱动 思想:代码要贴近业务语言,要让不写代码的人也能读懂意图

这背后其实是 DDD(领域驱动设计)里"通用语言(Ubiquitous Language)"的落地------领域专家、产品、开发用同一套词汇描述同一件事。

放在我的场景里,这层意思是:rules.ts 里的函数名要用业务词汇。

javascript 复制代码
// Business-oriented naming (recommended)
export const canSubmitOrder = ...
export const shouldShowVipDiscount = ...
export const isAddressRequired = ...

// Technical-oriented naming (harder to maintain)
export const checkFlag = ...
export const validateConditionA = ...
export const controlVisibility = ...

前者用业务动词命名,看名字就能理解意图;后者命名模糊,要深入读代码才能知道它在判断什么业务规则。

这个维度说的是语义,和放不放在一个文件里其实是两回事,但两者结合起来会更有价值。

AI-First Refactoring:为工具协作重新组织代码

另一位前端同学说的"面向 AI 编程的重构",是最近才开始被广泛讨论的工程实践,目前还没有一个统一的正式名字。

它的核心假设是:AI copilot 在处理小的、单一职责的、自描述的文件时效果最好 。一个 500 行的组件文件里混杂着 UI 结构、样式逻辑、副作用和业务判断,AI 改起来容易"误伤";而一个 60 行的 rules.ts,里面只有纯函数和类型定义,AI 一眼就能理解意图,改起来精准且可预测。

从这个角度看,rules.ts 是在为 AI 协作创造一个"安全操作区":

vbnet 复制代码
OrderForm/
  index.tsx           ← UI structure, calls rules, doesn't contain logic
  OrderForm.rules.ts  ← pure business rules, safe area for AI to operate
  OrderForm.test.ts   ← only tests rules, no need to mount the component
  OrderForm.types.ts  ← shared type definitions

这个维度说的是工具链适配,和前两个是完全不同的维度。


那我的构想到底叫什么?

把三位同学的视角放在一起,我意识到他们说的其实是同一件事的三个面:

维度 概念 对 rules.ts 的意义
流程 Spec-Driven rules.ts 是规范,先写它再写组件
语义 Business-Driven rules.ts 里要用业务词汇命名
工具 AI-First 小文件单职责,让 AI 每次只改一处

但如果要给这个模式找一个最贴切的名字,我查阅资料后觉得它最接近的是业务规则外置(Business Rules Externalization) ,这是企业架构领域的成熟实践;在面向对象设计里,有时也叫 Policy Object 模式

思路很简单:把"判断逻辑"从"执行逻辑"里分离出来,单独成文件,单独测试,单独演化。

这种模式以前在前端的存在感不强,因为 Redux、MobX 这类状态管理库的 action/reducer 结构在一定程度上替代了它。但在 AI copilot 普及之后,它的价值被重新放大了------不只是为了人类维护,也是为了让 AI 能精准地修改业务规则,而不是在一个巨型组件文件里大海捞针。


落地时的几个小取舍

这个模式并不是银弹,在考虑引入的时候有几个地方值得权衡,我目前的理解是这样的(不一定对):

适合放进 rules.ts 的:

  • 返回 boolean 的状态判断(canXxxshouldXxxisXxx
  • 返回提示文案的逻辑(getXxxMessagegetXxxReason
  • 基于当前状态的派生值计算(getXxxConfig

不太适合放进去的:

  • 需要调用 API 的异步逻辑(那更适合放在 service 或 hook 里)
  • 直接操作 DOM 或依赖 React context 的逻辑(破坏了纯函数的特性)
  • 过于简单的单行判断(items.length > 0,直接内联更清晰)

还有一个细节:当 rules.ts 里的函数数量增多,可以考虑按业务子域继续拆分,比如 OrderForm.submit.rules.tsOrderForm.display.rules.ts,而不是一个文件越堆越大。


延伸想了一些问题

在整理这些思路的过程中,我产生了几个新的疑问,暂时还没有答案:

  1. 规则文件里的测试应该怎么组织? 纯函数很好测,但当 OrderState 的字段越来越多,构造 mock 数据会变得繁琐,有没有更好的测试策略?
  2. 如果规则本身来自后端配置(比如 feature flag 或动态表单配置),这个模式还成立吗? 这时候"规则"本身是运行时数据,而不是编译时代码,边界就模糊了。
  3. 这和 Zod 之类的 schema 验证库是什么关系? 两者都在做"约束表达",但侧重点不同------Zod 偏数据合法性校验,rules.ts 偏业务状态判断。能不能配合使用?
  4. AI 工具真的会更倾向于修改小文件吗? 这个假设我还没有系统性地验证过,只是直觉上觉得合理。

小结

回头看这次 CR 的对话,三位同学说的都有道理,只是各自在不同的维度切入。Spec-Driven、Business-Driven、AI-First,这三个标签并不是互斥的选择,它们描述的是同一个设计决策在不同语境下的意义。

把业务规则从组件里抽出来,这件事本身并不新鲜;但在 AI 工具成为日常开发协作者的今天,这种结构的价值被重新放大了。它既是给人类读者的清晰表达,也是给 AI 工具的精准操作界面。

这让我开始思考:我们在做代码组织决策的时候,"对 AI 是否友好",会不会慢慢变成和"可读性"、"可测试性"同等重要的考量维度?


参考资料

相关推荐
DO_Community2 小时前
如何使用DigitalOcean Gradient 平台上的无服务器推理
人工智能·aigc·ai编程·ai推理
AI2中文网2 小时前
AppInventor2 AI助手:美化界面 还是非常有用的!!
ai·ai编程·ai助手·appinventor·agentic·appinventor2·美化界面
可视之道2 小时前
前端大屏适配方案:rem、vw/vh、scale 到底选哪个?
前端
小码哥_常2 小时前
别在Android ViewModel里处理异常啦,真的很坑!
前端
05大叔2 小时前
RAG开发
java·服务器·前端
console.log('npc')2 小时前
在 React 中,useRef、ref 属性以及 forwardRef 是处理“引用”(访问 DOM 节点或组件实例)的核心概念
前端·react.js·前端框架
小小小小宇2 小时前
语法全景对照
前端
weixin_704266052 小时前
Spring Boot 入门了解
前端·firefox
冲浪中台2 小时前
如何实现低代码源码级交付和私有化部署
前端·低代码·私有化部署·源代码管理