用 claude code 将业务规则转换为有限状态机代码

《用 claude code业务规则转换为有限状态机代码

去年秋天的一个深夜,我在代码审查时看到了一段让我血压飙升的业务逻辑。那是一个客户权益发放模块,涉及“待激活、已激活、使用中、已过期、已冻结、申诉中、已回收、补偿发放中、部分退款、全额退款”十个状态。负责该模块的同事用了大约 900 行的 if-else 嵌套来处理状态间的流转逻辑,其中有一段判断“冻结状态下的部分退款是否允许回滚到已激活”的代码,连续 7 层嵌套,注释写到第三层就已经和实际逻辑对不上了。

我当时做了两件事。第一,把这段代码截图存进了我私人的“代码考古学”收藏夹。第二,打开终端,把那份 27 页的业务规则 PDF 文档拖进了 Claude Code 的工作区,敲下了第一行 prompt。

这篇文章不是教程,不是工具评测,更不是“AI 编码入门指南”。它是一份完整的实战记录:我如何用 Claude Code 将一个真实的、乱得像毛线团一样的业务规则,转换成了一套可运行、可测试、可维护的状态机代码。其中包含我踩过的坑、推翻重来的决策、以及对“AI 辅助业务编码”这件事的真实判断。

读完这篇文章,你将获得的不是一个工具的用法,而是一套可以复用到你当前项目里的工作流、判断框架和风险认知

一、核心结论:先把话说清楚

在展开整个实战过程之前,我想先把经过这次深度实践后形成的几个核心判断摆出来。这些判断可能会挑战一些流行的说法,但它们都经过了这次实战的检验。

结论一:Claude Code 擅长“翻译”业务规则,但不擅长“发现”业务规则。 这意味着,你能多清晰地描述规则,它就能多准确地生成代码。你给它的如果是模糊的自然语言,它还给你的就是模糊的代码。翻译质量取决于原文质量,这个道理朴素但总被忽略。

结论二:状态机代码的生成不是一次性的对话,而是一个多轮迭代的协作过程。 我见到不少人在社交媒体上分享“一句话让 AI 生成状态机”的截图,那些例子无一例外都是“订单状态:待支付→已支付→已发货→已完成”这种教科书级别的简单流转。真实业务中的状态机,有分支回退、有条件互斥、有时间窗口限制、有并发冲突处理。这些复杂规则,需要你一步步喂给 Claude Code,然后它一步步完善代码。我的经历是:第一轮生成能覆盖约 60% 的规则,第二轮达到 85%,第三轮之后才能触达那些藏在文档角落里的边界条件。

结论三:AI 生成的代码在结构上优于多数手写代码,但业务逻辑的正确性必须由人来验证。 这不是一句正确的废话,它有实际含义:Claude Code 生成的状态机代码,在设计模式的应用、命名规范、错误处理框架方面,确实比很多赶工期的开发者写得规整。但它对业务语义的理解可能出错,比如它可能会把“用户申请退款后,若商家 48 小时未处理则自动同意”理解为一个定时器事件,而实际规则是“48 小时未处理则退款申请自动失效,用户需重新发起”。这种细微的语义差异,AI 目前还抓不住。

结论四:这个工作流的价值不仅在于“写得快”,更在于“改得动”。 状态机的一次性编写其实占整个生命周期成本的不到 30%,真正的成本在后续的规则变更和维护。用自然语言维护需求再用 AI 重新生成代码,比直接维护手写代码的认知负担低得多。这个发现出乎我的意料,我后面会详细展开。

结论五:如果你当前的业务规则没有文档化,直接用 Claude Code 重构是高风险行为。 输出质量取决于输入质量。如果你的状态流转逻辑只存在于前任开发者的脑子里、或者散落在三年的 Jira 工单里,那么你需要先把这些规则梳理成结构化的描述。否则 Claude Code 生成的状态机会变成“看起来很美的错误代码”。

核心结论讲完,接下来我会把这个实战的全过程铺开:从一个让人头疼的真实业务场景开始,到最终落地的可运行代码结束。

二、背景与场景:一个让人崩溃的“权益状态管理”模块

真实业务背景

我说的这个模块,来自一个面向企业客户的 SaaS 产品中的“客户权益管理”子系统。业务上要管理的对象是客户购买的各类权益包:可能是“100 次 API 调用额度”、可能是“30 天高级功能试用”、也可能是“3 次人工服务工单”。

这类权益天然是“有状态”的。客户购买后需要激活,使用中需要扣减次数或天数,到期要过期,有问题要冻结,退款要回收。而且这些状态之间的流转规则是过去三年里逐步叠加的,每一次叠加都伴随着一个紧急需求和一个被拉长的工单。

具体来说,这套规则包含:

  • 10 个业务状态:待激活、已激活、使用中、已过期、已冻结、申诉中、已回收、补偿发放中、部分退款待处理、全额退款已完成。
  • 23 个触发事件:包括“用户点击激活”“系统自动扣减次数”“管理员冻结”“用户提交申诉”“申诉审核通过/驳回”“财务发起退款”“退款回调通知”“补偿权益自动发放”“权益到期系统扫描”等。
  • 大量的条件分支:例如“冻结状态下的退款,如果是用户主动申请的,原路返回;如果是平台风控冻结的,进入申诉流程。”“使用中的权益如果剩余次数为0但未到期,不可退款;如果剩余次数大于0且未到期,按剩余比例退款。”
  • 时间窗口限制:过期后 7 天内用户可发起申诉,超过 7 天自动回收。
  • 并发冲突规则:用户发起退款时,若恰好系统正在扣减次数,以扣减先发生为准,退款金额需基于扣减后的剩余量计算。

这些规则,之前是怎么实现的?

一个名为 CustomerBenefitServiceImpl 的 Java 类,代码行数 2487 行。状态流转的核心逻辑分散在 19 个方法中,最深的嵌套达到 7 层。状态不是用枚举定义的,而是用字符串常量,“active”“frozen”“appealing”,这意味着你永远不知道一个方法返回的字符串可能是什么值。单元测试覆盖率约 31%,而且大部分测试是 mock 了所有外部依赖后验证某个方法“是否被调用了”,而不是验证状态流转是否正确。

在一个真实的生产事故中,一个用户在“部分退款待处理”状态下,因为支付回调延迟到达,状态被错误地更新为了“已激活”,用户凭空多出了一份权益。根因追溯的结果是:“部分退款待处理”这个状态在某个分支条件里被拼写成了“particalRefunding”(少了一个 r),而代码中另一个判断用的又是正确的拼写“partialRefunding”,字符串比较不匹配,导致分支走到了默认的 else 逻辑。

这不是一个虚构的例子。这就是我们团队面对的现状。

为什么状态机是正确答案

在开始用 Claude Code 之前,团队内部其实已经达成共识:这套逻辑必须用状态机重构。

原因很简单:

  • 状态机明确定义了“允许的状态集合”和“允许的转移集合”,不允许非法的状态跳跃。
  • 状态机的代码结构天然是声明式的,新增或修改一条规则不会牵一发而动全身。
  • 状态机的逻辑可以独立于业务服务层进行单元测试,测试用例可以直接覆盖“状态×事件”的组合。

当时阻碍我们动手的唯一原因就是,状态数量乘以事件数量再乘以条件分支,这个组合爆炸让手写状态机的工程量看起来相当恐怖。

粗略估算:10 个状态 × 23 个事件,哪怕只有三分之一的事件在每个状态下有意义,也需要处理约 80 条转移规则。每条规则还有自己的前置条件和后置动作。如果手写,一个熟练开发者需要大约 3 到 5 个完整工作日,并且出错风险极高。

正是在这个节点上,我决定尝试用 Claude Code 来完成这个转换。

三、常见误区:为什么多数“AI 生成状态机”的演示都是玩具

在真正深入这次实战之前,我需要先澄清几个在技术社区中反复出现的误区。这些误区来自我观察到的同行讨论、GitHub 上的 demo 项目和一些技术分享文章。

误区一:“把状态机代码生成等同于词法分析器的代码生成”

有些开发者一听到“用 AI 生成状态机”,脑子里浮现的是编译原理课上用 Lex/Yacc 生成词法分析器的场景。那种状态机是由正则表达式规则自动推导出的 DFA/NFA,输入是形式文法,输出是状态转移表。

但业务规则的状态机完全是另一回事。业务规则是自然语言表达的、充满二义性和隐性知识的非形式化规则。 把业务规则转成状态机代码,本质上是需求工程的工作,不是编译技术的工作。Claude Code 在这里的价值是它的自然语言理解和代码生成能力,而不是什么形式化推导能力。

想通过提供一个形式化语法文件让 AI“自动生成状态机”的,属于路径找错了。正确的路径是:用结构化的自然语言描述规则,让 AI 来做“翻译”。

误区二:“一次性把全部规则扔进去就能得到完整代码”

我试了。在第二个对话轮次中,我直接把 27 页的 PDF 全文粘贴进了 Claude Code 的对话窗口,然后写了一个 prompt:“请根据以上业务规则文档,生成完整的状态机代码。”

结果它确实生成了代码,大约 600 行,结构看起来很漂亮。但仔细审查后发现:

  • 它遗漏了“申诉中状态下,如果用户已使用过部分权益,退款金额的计算需要扣除已使用部分”,这条规则在 PDF 的第 19 页第 3 段。
  • 它把“补偿发放中”设计成了独立状态,但实际上补偿权益可能叠加在任何一个非终态状态上,不应该是独立状态,这是业务上下文隐含的信息,PDF 里并没有显式写。
  • 它对于并发冲突的处理只是加了一句注释 // TODO: handle concurrency

一次性全部扔进去的问题在于:大语言模型在处理长上下文时,对首尾部分的关注度高于中间部分,对“看起来像规则”的陈述句关注度高于那些隐藏在背景描述段落中的隐性规则。我的经验是:分步、分层、分角色地描述规则,效果远好于一次性倾倒。

误区三:“状态机只是一个技术实现选择,和业务无关”

这是在开发者群体中很常见的一种认知:“管它什么状态机不用状态机,只要代码能跑就行了。”

这次重构让我深刻意识到:状态机的结构本身就是业务规则的精确表达。 当你用状态机来建模业务时,哪些状态转移是合法的、哪些是非法的、哪些需要前置条件、哪些需要后置动作,这些结构化的决策过程,会倒逼你把业务规则真正想清楚。

事实上,在和 Claude Code 协作的过程中,有两次它生成的状态转移表帮我发现了原始 PDF 文档中自相矛盾的规则。一次是“已过期状态是否允许申诉”,文档第 6 页和第 15 页的描述互相冲突。另一次是“部分退款后剩余权益的有效期到底是怎么计算的”,文档里的公式在边界条件下会产生负数。

状态机不只是代码,它是业务规则的数学模型。 这个认知会影响你如何使用 AI 工具,它不应该只是一个代码生成器,而应该是一个帮你验证规则一致性的协作伙伴。

误区四:“AI 生成的代码不能直接用于生产”

这个说法的另一极端是“AI 生成的代码可以直接上线”。两个极端都不对。

我的实际做法是:Claude Code 生成的状态机代码,我会将其视为“高质量的初稿”。它解决了 80% 的编码工作量,但剩下的 20% 包括:

  • 验证每一条状态转移规则是否和业务文档一致
  • 补充并发控制逻辑(这个 AI 目前做不好)
  • 调整代码风格以符合团队的编码规范
  • 编写完整的单元测试覆盖所有转移路径

把这 20% 做完之后,代码是可以上生产的。事实上,这次重构后的状态机模块已经在生产环境稳定运行了四个多月,期间经历了两次业务规则的变更,变更过程比我预想的要平滑得多。

四、专业判断框架:如何正确地将业务规则描述给 Claude Code

这是我本次实践中形成的最重要的方法论红利。它不是“prompt 工程技巧”,而是一套系统性的问题分解和描述框架。我把它总结为五个步骤。

第一步:识别状态,而不是列举状态

很多人在描述状态机时,会直接从文档中把看到的所有关键词摘出来当作状态。比如文档里写着“用户激活权益后…”“权益过期前 7 天…”“管理员冻结时…”,就往状态列表里写:“待激活、已激活、使用中、即将过期、已冻结…”。

但“即将过期”真的应该是一个独立状态吗?

在我的案例中,文档里多处提到“过期前 7 天向用户发送提醒”,如果机械地把它设为一个独立状态,就需要增加“已激活→即将过期”“使用中→即将过期”等多条转移路径,还需要处理“即将过期状态下用户继续使用权益”的逻辑。但仔细分析业务后发现,“即将过期”并不是一个真正独立的状态,它只是“已激活”或“使用中”两个状态下的一个时间属性。权益的实际权利和义务在“即将过期”期间和正常状态没有任何区别。

状态的核心特征是什么?是权益在该状态下所对应的“可执行的业务操作集合”发生了变化。 如果两个状态下,用户可以执行的操作完全一样、系统处理逻辑完全一样、权利和义务完全一样,那它们就不应该是两个独立状态。

基于这个标准,我最终定义的状态是:

状态名 核心特征(可执行的业务操作)
待激活 仅允许“激活”操作
已激活 允许“开始使用”和“申请退款”
使用中 允许“继续使用”“申请退款”“被冻结”
已冻结 仅允许“申诉”“管理员解冻”“管理员强制退款”
申诉中 仅允许“管理员审核”“管理员驳回”“超时自动回收”
补偿发放中 允许“系统发放补偿”“管理员手动调整”
部分退款待处理 仅允许“退款回调成功”“退款回调失败”
全额退款已完成 无可用操作(终态)
已回收 无可用操作(终态)
已过期 允许“7天内申诉”,超7天后等同终态

这套状态定义,我在 prompt 里是这样描述的:

以下是权益模块的状态定义。请理解每个状态代表的业务含义,
以及该状态下允许发生哪些操作。

待激活(PENDING_ACTIVATION):客户已购买但尚未激活的权益。
该状态下仅允许执行“激活”操作。

激活后转入“已激活”状态。

已激活(ACTIVATED):权益已激活但尚未开始使用。
该状态下允许:开始使用(转入使用中)、申请退款(根据剩余情况转入部分退款待处理或全额退款已完成)。

... (逐个描述每个状态)

请特别注意:

一个状态之所以成为独立状态,是因为该状态下可执行的操作集合与其他状态不同。

如果一个概念只是时间属性(如“即将过期”),它不应作为独立状态,

而应作为已有状态的附加属性处理。

这么做之后,Claude Code 生成的第一版状态定义就完全正确,不需要后续调整。

第二步:把事件分类,不要混成一锅粥

在我原始的业务文档里,“触发事件”是这样散落着的:“用户点击激活按钮”“系统在每天凌晨2点扫描过期权益”“管理员在后台点击冻结”“支付中心回调通知退款结果”“用户在前端提交申诉表单”“系统自动扣减调用次数”……

如果不加分类地扔给 Claude Code,它会把所有事件平铺成一个列表,然后机械地为每个状态×每个事件生成转移。这样既冗余又容易出错。

我做了一层分类:

  • 用户主动事件:用户在前端主动触发的操作。如激活、使用权益、申请退款、提交申诉。
  • 管理员事件:管理员在后台触发的操作。如冻结权益、解冻权益、审核申诉、强制退款、手动调整补偿。
  • 系统自动事件:由定时任务或触发器自动触发的操作。如到期扫描、过期回收、超时自动驳回、补偿自动发放。
  • 外部回调事件:来自外部系统的异步通知。如退款成功回调、退款失败回调、支付争议通知。

在 prompt 里,我这样组织事件描述:

以下是可能触发状态变化的事件。我按事件来源分为了四类:
【用户主动事件】

USER_ACTIVATE:用户点击激活按钮

USER_CONSUME:用户使用权益(每次消耗一次调用次数)

USER_REFUND_REQUEST:用户提交退款申请

USER_APPEAL:用户在冻结或过期后提交申诉

【管理员事件】

ADMIN_FREEZE:管理员冻结权益(需填写冻结原因)

ADMIN_UNFREEZE:管理员解冻权益

ADMIN_APPROVE_APPEAL:管理员审核通过申诉

ADMIN_REJECT_APPEAL:管理员驳回申诉

ADMIN_FORCE_REFUND:管理员强制退款(无视当前状态)

... (省略剩余分类)

请你在生成状态转移逻辑时,针对每一类事件分别处理。

并且请标注,哪些事件在哪些状态下是“无操作(NO-OP)”的。

分类之后,Claude Code 生成的代码结构明显更清晰,它也按照事件来源分别组织了状态转移的逻辑分组,而不是把所有事件堆在一个巨大的 switch-case 里。

第三步:用“Given-When-Then”的形式描述有条件的转移

这是最关键的一步。业务规则中核心的复杂性不在“从状态 A 到状态 B”,而在于“在什么条件下从状态 A 到状态 B,在什么条件下从状态 A 到状态 C”。

直接粘贴业务文档的自然语言描述,Claude Code 会试图理解但可能理解错。我找到的最稳定的方式,是把每条有条件的转移规则改写为 Given-When-Then 三元组:

当权益处于且满足时,
若发生<某个事件>,

则权益状态转移到<目标状态>,

并执行<后置动作>。

实例如下:

Given: 权益处于"使用中"状态,且剩余调用次数 > 0,且权益未过期
When: 用户发起退款申请(USER_REFUND_REQUEST)

Then: 权益状态转移到"部分退款待处理"

后置动作:计算剩余价值 = (剩余次数/总次数) × 购买金额,

发起退款请求到支付中心,

记录退款流水。

Given: 权益处于"使用中"状态,且剩余调用次数 = 0,或权益已过期

When: 用户发起退款申请(USER_REFUND_REQUEST)

Then: 拒绝退款,返回错误提示"权益已使用完毕或已过期,无法退款"

状态不变。

我在 prompt 里把所有 80 条左右的有条件转移规则按这个格式组织了一遍。这是一件要花时间的事,大约用了我两个多小时,但它大幅降低了后续返工的成本。Claude Code 几乎零错误地理解了所有条件分支。

用 claude code 将业务规则转换为有限状态机代码

第四步:明确标注非法转移

状态机不仅是“允许发生什么”的集合,也是“不允许发生什么”的集合。很多状态机实现的 bug 不是出在正确的路径上,而是出在“本不应该发生但代码没有防御”的非法路径上。

在 prompt 中,我专门用一个章节来描述哪些转移是非法的:

以下是明确非法的状态转移。请确保生成的状态机代码在遇到这些转移时,
抛出明确的异常或返回明确的错误信息(而不是静默忽略或走到默认分支)。

从任何状态直接跳到"待激活"是非法的。待激活只能是初始状态。

终态(全额退款已完成、已回收)不允许发生任何状态变更。

"补偿发放中"状态下,不允许用户发起退款。

"已冻结"状态下,用户不能使用权益,但系统仍会扫描到期时间,

到期后应自动从已冻结转移到已回收(这是冻结状态下唯一合法的自动转移)。

申诉被驳回后,用户7天内不得再次申诉同一权益。

这个动作的价值在测试阶段就体现出来了。有一次,我们模拟了一个“管理员误操作在终态上点击了冻结按钮”的场景,状态机直接抛出了一个带有明确错误信息的异常,而不是静默地把一个终态权益改成了冻结,这就阻止了一次潜在的数据污染。

第五步:把隐性规则显式化

这是人和 AI 协作中最难、也最能体现经验差异的一步。业务文档中必然存在大量“大家都知道是这样但文档没写”的隐性规则。

举个例子:原始文档里有一句话叫“补偿权益由系统自动发放”。但这个“自动发放”到底发生在什么节点?是新权益购买后立即发放?还是旧权益过期后发放?还是客服工单解决后发放?文档没写,因为写文档的人默认“大家都知道”。

如果我不把这条规则显式化,Claude Code 就无从下手。它可能会猜测一个实现,然后我需要花时间审查并修正。所以我在 prompt 中补充了:

关于“补偿发放中”状态的补充说明:
补偿权益是指因客诉、系统故障等原因,由客服/管理员决策发放给用户的额外权益。

触发补偿的场景包括:

用户申诉审核通过后,管理员手动触发补偿发放。
系统故障导致用户权益受损,运营后台批量触发补偿发放。
补偿发放的一个重要规则是:补偿的权益与原权益是一份独立的权益实例。

用户可能同时持有多份权益,每份权益有自己独立的状态机生命周期。

因此"补偿发放中"是针对某一份权益实例的状态,不是用户的全局状态。

另外,补偿发放中状态下,该权益不可使用、不可退款。

只有补偿发放完成后(即补偿的权益本身进入待激活状态),

整个补偿流程才算结束。发放补偿的原始权益的状态不因补偿而改变。

这种显式化的工作,没有人能替代你。但它做得越好,AI 生成的代码就越不需要返工。

五、三轮迭代实录:从初稿到可上线代码

这一节直接呈现我和 Claude Code 三轮对话的核心内容、生成结果以及我的审查发现。

用 claude code 将业务规则转换为有限状态机代码

第一轮:基础骨架

输入:经过结构化整理的全部状态定义、事件分类和 Given-When-Then 规则(约 3000 字的 prompt)。

Claude Code 生成的结果

  • 一个使用 Java Enum 定义的状态类,包含 10 个状态常量。
  • 一个事件枚举,包含 23 个事件。
  • 一个 StateMachine 类,核心方法为 State transition(State currentState, Event event, Context context),约 350 行。
  • 使用状态模式(State Pattern),每个状态为一个内部类,实现了 handle(Event, Context) 方法。

我的审查发现

  1. 规则覆盖率约 62%。它主要遗漏了三类规则:时间窗口条件(如“过期后 7 天内可申诉”)、并发冲突规则(如 “退款回调和使用扣减同时发生时的处理优先级”)、以及复杂的前置条件组合(如“冻结状态下,如果冻结原因是风控且用户未申诉,则超时后自动回收”)。
  2. 状态转移表不完整。有 5 个状态×事件的组合没有正确处理。Claude Code 把它们都归为了“NO-OP”,但实际上其中 3 个应该抛出“非法操作”异常,另外 2 个是合法的但需要额外的前置条件判断。
  3. 补偿发放逻辑错误。它把补偿发放理解成了“在原权益上修改状态”,而不是“创建一个新的权益实例”。
  4. 没有处理幂等性。同一个事件连续到达时(比如退款回调重试),状态机没有判断“如果当前已经是目标状态则忽略重复事件”。

第一轮我的修正方式:我没有手动修改代码。而是在同一个对话线程中追加了以下补充描述:

第一版代码有以下需要修正的地方,请逐项处理:
时间窗口规则补充:

权益过期后,用户有7天宽限期可发起申诉。

超过7天后,状态自动变为"已回收"(终态)。

宽限期内发起申诉,状态转移到"申诉中"。

请将这个时间条件加入"已过期"状态对"USER_APPEAL"事件的处理逻辑中。

并发冲突规则:

如果权益处于"部分退款待处理"状态时,支付回调达到,

且此时用户正好在使用权益(即USER_CONSUME和REFUND_CALLBACK同时发生),

请以REFUND_CALLBACK的到达时间戳为准:

如果REFUND_CALLBACK先发生,退款成功回调应正常处理,

并且退款成功后状态进入终态,后续的USER_CONSUME应被拒绝。

如果USER_CONSUME先发生,退款金额应基于扣减后的剩余次数重新计算。

请为这个场景设计一个乐观锁机制。

补偿逻辑修正:
补偿发放不是修改原权益的状态,而是创建一份新的权益实例。

原权益的状态不变。补偿发放状态的权益实例,其生命周期独立管理。

请重新设计相关的数据结构。

... (省略其余修正描述)

第二轮:深度完善

Claude Code 基于我的补充描述,生成了第二版代码。这次的变化比较大:

  • 新增了 TransitionContext 类,承载时间戳、乐观锁版本号、事件发生时间等上下文信息。
  • 新增了 TimeWindowValidator 类,专门处理所有涉及时间窗口的条件判断。
  • 在状态转移方法中增加了乐观锁版本号校验。
  • 修正了补偿发放逻辑,新增了 BenefitInstanceBenefitInstanceId 的概念,明确每份权益实例独立管理。
  • 代码行数增加到约 580 行。

第二轮审查发现

  • 规则覆盖率提升到约 87%。
  • 仍然有 6 个遗留问题:
  1. 乐观锁的冲突重试逻辑没有实现,只抛了异常。
  2. “申诉驳回后 7 天内不可再次申诉”这个冷却期规则没有实现。
  3. 两个终态(全额退款已完成、已回收)之间没有明确的区分逻辑,文档里有一条“如果退款后又过期,以退款状态为准”的优先级规则没有被代码体现。
  4. 管理员强制退款的操作没有记录审计日志。
  5. 单元测试没有生成。
  6. 部分方法的命名不符合我们团队的规范(我们要求所有返回布尔值的方法以 is 或 has 开头,而 Claude Code 生成了 checkValidity 这种命名)。

第二轮的修正方式:继续在同一对话线程中追加 prompt,逐项描述需要修正的内容。同时,我特意加了一句:

请为你的代码生成完整的单元测试,要求覆盖:

所有合法的状态转移路径(至少一条正向测试用例)

所有你标记为“非法操作”的路径(验证是否抛出正确的异常)

所有涉及时间窗口的边界条件(窗口内、窗口外、刚好在临界点)

并发冲突的两个场景

测试框架使用 JUnit 5 + Mockito。

第三轮:锁定上线版本

第三轮 Claude Code 生成的代码达到了约 98% 的规则覆盖率。代码总行数约 720 行,另有约 600 行的单元测试代码。

剩下的最后一个遗留问题是:“管理员强制退款”这个事件,文档里写的是“无视当前状态”,但实际业务中有一个隐含限制,如果权益已经全额退款过了,就不能再强制退款。 这个规则文档里其实有写,但是在第 23 页的一个脚注里……Claude Code 第一轮和第二轮都忽略了脚注。

我在第三轮手动修正了这个逻辑,只改了 8 行代码。除此之外,全部由 Claude Code 生成。

审查完毕后的代码质量判断

  • 结构清晰:每个状态的处理逻辑封装在独立的方法中,状态转移表和业务逻辑分离。
  • 异常处理完善:所有非法转移都有明确的异常类型和错误信息。
  • 测试覆盖率高:单元测试覆盖了核心路径和边界条件。
  • 可维护性好:如果需要新增一个状态,只需要新增一个状态处理方法,并在状态转移表中注册即可。

我从第一轮 prompt 到第三轮最终代码,总耗时约 6 个半小时。作为对比,如果手写这套逻辑,我的估计是 3 到 5 个完整工作日(约 24 到 40 个工作小时)。效率提升大约 4 到 6 倍。

六、一个意外的收获:状态机代码作为“业务规则的单一真相源”

在重构完成后的第三周,发生了一件我没有预料到的事情。

产品经理找到我,说由于政策合规要求,需要调整权益退款的计算规则:“以前是按剩余次数比例退款,现在要改为:如果权益购买后 7 天内且使用次数不超过总次数的 20%,可以全额退款;否则按剩余比例。”

这个变更涉及 3 条 Given-When-Then 规则的修改。

以往遇到这种变更,我会打开 IDE,在 CustomerBenefitServiceImpl 里翻找所有涉及退款逻辑的代码段,小心翼翼地修改,然后跑一遍全量回归测试,整个过程大概需要半天。

但这次不一样。我直接打开了之前整理的那份 Given-When-Then 规则文档,修改了 3 条规则,然后把改动的内容粘贴到 Claude Code 里,告诉它:“请根据以下改动,更新状态机中相关的代码。”

Claude Code 用了不到 2 分钟,生成了约 40 行的改动,包括:

  • 修改了 2 个状态处理方法中的退款金额计算逻辑。
  • 新增了一个 RefundPolicy 工具类来封装新旧两套退款计算规则。
  • 更新了 5 个相关的单元测试。

我审查了改动后,发现一处细节需要调整(单位测试中有一个边界值我期望的断言值和它生成的不一致),手动改了 3 行代码,然后推上了预发布环境。从收到需求到代码合并,总共用了约 50 分钟。

这个体验让我意识到:当业务规则以结构化的 Given-When-Then 格式维护,并且状态机代码由 AI 负责“翻译”时,业务规则的变更成本急剧下降。

在这个模式下:

  • 业务规则文档(GWT 格式)是人类可读的单一真相源
  • 状态机代码是机器可执行的翻译产物
  • 当规则变更时,人只需要修改文档,AI 负责重新翻译。

这种工作流带来的不仅是效率提升,更是一种认知负担的转移:你不需要在脑海中维护“规则→代码”的映射关系,只需要关注规则本身是否正确。

用 claude code 将业务规则转换为有限状态机代码

七、不同场景下的行动建议与取舍

经过这次完整的实战,我总结了四种典型场景下的行动建议。你可以根据自己的实际情况对号入座。

场景一:新项目,业务规则清晰且已文档化

推荐方案:直接使用“结构化描述 + Claude Code 生成”的工作流。

步骤

  1. 用本文第四章的方法,把业务文档中的规则整理成 Given-When-Then 格式。
  2. 分 3-4 个 prompt 与 Claude Code 协作:第一轮生成基础骨架,第二轮补充边界条件,第三轮生成测试代码,第四轮微调代码风格。
  3. 人工审查每一条转移规则,确保和原始文档一致。
  4. 补齐并发控制、审计日志等非功能性需求(这些 AI 目前做得不够好)。

预估效率:相比手写,提升 3-5 倍。

场景二:新项目,业务规则存在于“前辈的脑子里”,文档不完善

推荐方案:先做规则提取工作坊,再使用 AI 辅助编码。

关键风险:如果输入的规则描述不完整或有误,AI 生成的状态机代码会系统性地出错。而且因为没有文档对照,审查时也不容易发现。

具体做法

  1. 组织一次 2-3 小时的“状态机规则梳理会”,拉上产品、业务方、资深开发,用白板把所有状态和转移关系画出来。
  2. 把白板上的内容转化为 Given-When-Then 格式,发给所有参会者确认。
  3. 确认后的规则文档再作为 Claude Code 的输入。
  4. 生成的代码需要比场景一更严格的人工审查,建议让一位不熟悉该模块的同事做交叉审查(他的视角更接近“纯看文档”)。

取舍:前期投入更多时间做规则梳理,但换来的是后续编码和维护阶段的高效率。如果跳过梳理直接让 AI 生成,几乎一定会在后期付出更大的返工代价。

场景三:存量老项目,有大量历史遗留的状态逻辑

推荐方案:增量式迁移,不要一次性全部重写。

我的建议是分四步走

  1. 先建立“隔离层”:在老代码的外围包装一层状态机接口,新的业务调用走新接口,老的调用暂时保持不变。
  2. 逐步迁移子状态集:不要把全部 10 个状态一次性迁移。可以先迁移“终态相关的逻辑”(终态相对简单),然后迁移“正常流转路径”(使用中、已激活这类高频路径),最后处理“异常路径”(冻结、申诉、补偿)。
  3. 双写对比验证:在迁移期间,同时运行老逻辑和新状态机,对比两者的输出结果是否一致。不一致的 case 记录下来分析原因,大概率发现了老代码的 bug,小概率是新状态机的规则理解有误。
  4. 逐步下线老代码:当新状态机的对比结果持续稳定(建议至少观察 2 周),再逐步切流并最终下线老代码。

取舍:这种方案的总耗时比一次性重写更长(估计多 30%-50%),但风险极低,适合核心业务链路。

场景四:团队对 AI 辅助编码的接受度不高,或合规要求不允许 AI 生成代码直接用于生产

推荐方案:把 Claude Code 作为“加速编码”的工具而非“替代编码”的工具。

调整方式

  • 不直接用 Claude Code 生成完整的状态机代码,而是让它生成代码骨架、状态转移表的框架代码、或者单元测试用例。
  • 人工完成核心的状态转移逻辑编写,然后用 Claude Code 来“补全”那些重复性的代码(如每个状态下对 NO-OP 事件的处理、日志记录、监控埋点等)。
  • 用 Claude Code 生成的代码作为“参考实现”,人工重写一遍以确保完全理解和可控。

取舍:效率提升可能只有 1.5-2 倍,但在合规和团队接受度方面没有风险。同时,团队成员在“参考 AI 生成然后人工重写”的过程中,对状态机设计本身的理解也会加深。

八、工具之外:为什么这个工作流能成立

在文章的最后,我想跳出具体的操作细节,谈一个更根本的问题。

为什么“用自然语言描述规则,让 AI 生成状态机代码”这个工作流,恰好对状态机特别适用?

我的判断是:因为状态机本身就是规则的形式化表达。 状态、事件、转移条件、后置动作,这些概念在业务域和代码域之间存在天然的、结构化的对应关系。这种对应关系不是模糊的启发式映射,而是准数学层面的同构。

对比一下其他类型的代码生成:

  • “用 AI 生成一个用户登录功能”,输入是模糊的需求,输出是多种技术上可行的实现,中间有巨大的设计空间需要人来判断。
  • “用 AI 生成一个推荐算法”,输入是业务目标,输出涉及数学建模和工程实现的多个层级,每一层都有大量决策点。

但“用 AI 生成状态机”不同。当你把规则用 Given-When-Then 描述清楚后,从规则到代码的翻译是近乎确定性的。 这不是因为 AI 特别聪明,而是因为状态机这个领域本身具有高度的结构化特性。

这个判断有一个重要推论:对于其他同样具有高度结构化特性的业务逻辑,比如校验规则引擎、费用计算规则、路由决策逻辑,“自然语言描述规则 + AI 生成代码”的工作流同样高度适用。

我最近正在尝试把这个工作流迁移到另一个领域:一个基于复杂规则的费用计算模块。初步的结果让我对这条路线的可复制性相当乐观。

用 claude code 将业务规则转换为有限状态机代码

九、风险清单:七个你必须知道的问题

我不想让这篇文章变成对 AI 工具的盲目鼓吹。以下是我在实际使用中遇到的、你必须正视的七个风险。

风险一:上下文窗口遗忘。 Claude Code 目前支持长上下文,但当你的对话超过一定长度后(我的经验大约是 4000 行代码或 3 万字以上的累积对话),它可能会忘记你在较早轮次中给出的约束。我的对策是:每三轮迭代后,把当前的规则描述和代码状态摘要重新发一次,相当于“刷新”它的上下文认知。

风险二:过度自信的代码风格。 Claude Code 生成的代码看起来总是很“自信”,结构完整、命名规范、注释详细。这种表面上的高质量可能会让你在审查时放松警惕。我的教训是:永远以“这段代码有一个我还没发现的错误”的心态来审查,而不是“这段代码看起来很对所以应该没问题”。

风险三:并发逻辑薄弱。 这是目前大语言模型的通病。它擅长生成顺序执行的业务逻辑,但对于多线程、锁机制、事务边界、一致性问题,产出的代码要么过于简单(只是在方法上加 synchronized),要么干脆留 // TODO并发控制逻辑目前还需要人工设计和实现。

风险四:对“不在文档里的规则”完全无知。 Claude Code 只能基于你给它的信息来生成代码。如果你的业务有某些“大家默认知道但没写在文档里”的规则,它不可能猜到。这些遗漏在测试阶段可能会暴露,但也可能不会,如果测试用例恰好也没有覆盖到。

风险五:生成代码和团队规范的差异。 每个团队都有自己的编码规范,命名习惯、注释风格、异常处理方式、日志框架选择等。Claude Code 生成的代码使用的是它“学到”的通用风格,不一定符合你团队的标准。你需要在 prompt 中显式描述这些规范,或者在审查阶段逐一修正。

风险六:安全敏感逻辑不应由 AI 单独生成。 如果你的状态机涉及权限校验、敏感数据访问控制、金融交易的资金操作等安全关键逻辑,我的建议是:人工编写核心安全逻辑,AI 只负责非安全相关的状态流转骨架。 这不是因为 AI 一定写错,而是因为“可能写错”的后果不可接受。

风险七:AI 生成的测试可能“测试了 AI 以为的逻辑而非实际需要的逻辑”。 Claude Code 可以生成非常完整的测试用例,但你需要警惕:它生成的测试是基于它自己对规则的理解。如果它对某条规则的理解有偏差,那么它会生成一个“验证了错误逻辑”的测试,这个测试会通过,但通过的是一条错误的规则。因此,测试用例本身也需要人工审查,不能因为“测试覆盖率很高”就放松警惕。

十、总结与下一步行动

让我把这次历时六个半小时的实战浓缩成几个可以立刻行动的要点:

关于方法:

  • 结构化描述是核心。Given-When-Then 格式是目前最稳定、最不容易产生歧义的规则描述方式。
  • 分轮迭代,不要一次求全。第一轮做骨架,第二轮补边界,第三轮磨细节。
  • 人工审查不可省略。不是审查代码风格,而是逐条验证“AI 理解的规则”和“实际业务的规则”是否一致。

关于效率:

  • 相比手写,整体效率提升 4-6 倍(以我的案例为基准)。
  • 更大的收益在后续维护阶段:规则变更时,改文档比改代码快得多。
  • 代价是前期需要花时间把业务规则整理成结构化描述。这个时间不要省。

关于风险:

  • 并发控制逻辑请人工编写。
  • 安全敏感逻辑请人工编写。
  • 测试用例本身需要审查。
  • 如果输入是垃圾,输出一定是垃圾。

你可以立刻做的事:

  1. 选一个你当前项目中状态逻辑最复杂、最让人头疼的模块。
  2. 花一个小时,用 Given-When-Then 格式写 5 条规则试试看。做完这一步你就知道自己的业务规则到底理清楚了没有。
  3. 打开 Claude Code,把这几条规则作为 prompt 输入,看看它生成的代码质量如何。
  4. 如果你发现生成的代码不能直接用,别急着否定这个工作流,回头检查你的规则描述是否足够精确。绝大多数“AI 生成的代码不对”的根因,都可以追溯到“人给出的描述不够精确”。

状态机不是银弹,Claude Code 也不是。但把两者结合起来,确实提供了一种我目前在市面上看到的最务实的“业务规则代码化”方案。它改变了我和复杂业务逻辑打交道的方式,也希望能帮到你。

常见问题解答(FAQ)

1. 如何描述业务规则才能让Claude Code生成可用的状态机代码?我试了几次都得到错误的状态转换,是不是我的自然语言描述有问题?

我在用Claude Code转换订单业务的退款规则时,第一次生成的代码里,退款成功后居然还能再次发起退款。我明明在提示里写了'退款成功后终止',为什么它没理解?是不是我描述的方式不对,到底该怎么组织业务规则才能让AI一次性生成正确的状态机?

这是个典型陷阱。我踩过三次后总结出关键:Claude Code对模糊、因果顺序颠倒的描述非常敏感。第一次我写“订单可以支付、发货、退款”,它默认所有状态双向可达。

正确做法是利用状态转移表(State Transition Table)结构来描述:先定义所有状态(枚举值),然后针对每个起始状态+事件,明确指定目标状态和前置条件。例如用一个Markdown表格,三列:当前状态、触发事件、下一状态,并强调“未列出的组合视为无效”。

这样Claude Code会自动生成switch-case合法性校验。我测试过,使用这种表格提示词的第一次正确率从40%提升到85%。另外,一定要在提示词末尾加一句“如果某个状态组合未定义,请抛出异常或返回错误”。这条边界约束能避免AI自作主张添加隐式跳转。

2. 当业务规则包含并行状态(例如同时处理物流和支付状态)时,Claude Code能正确生成吗?我怀疑一个提示词无法描述清楚。

我的系统里订单需要同时跟踪物流状态(已发货/运输中/已签收)和支付状态(待支付/已支付/退款中/已退款)。这两个状态是并行的,但又有交叉约束,比如退款只能在已支付且未签收状态下发起。我试过把所有分支写进一个提示词,结果Claude Code生成了一堆嵌套if-else,根本不像状态机。

有没有更好的分步引导策略?

并行状态机是Claude Code的薄弱环节,直接一次性描述很容易产生混乱。我的方法分三步:第一步,先让Claude Code为每个子状态机分别生成独立的枚举和状态转换核心逻辑(比如为物流状态和支付状态各生成一个独立的状态机类)。

第二步,在提示词中补充“交叉约束”规则,例如“当事件refund发生时,需同时检查paymentState==已支付且logisticsState != 已签收”,要求它生成一个顶层协调器(Coordinator)来组合两个子状态机并校验合法组合。

第三步,让Claude Code为协调器生成一个状态组合表(State Combination Matrix),列出所有有效组合以及对应的允许事件。实测中,这种分步提示比一次性描述的错误率降低60%。关键教训:Claude Code擅长拆解并行问题,但你要为它设计分割线。

3. 用Claude Code生成的状态机代码,后期修改规则时是否容易?我曾经手动维护过100+状态的状态机,改一个分支常引出十条bug。

我在实际项目接手过一个用Claude Code生成的拼车订单状态机,初期挺整齐。但后来业务新增了“司机取消”和“乘客取消”两种不同回退路径,我必须修改原有的退款状态迁移。结果发现Claude Code生成的代码里,状态转移被分散在多个if判断和工厂方法中,改一处要排查所有引用。

是不是我生成时没要求它采用某种模式?有没有办法让代码天然支持后续规则变更?

这个坑我亲身经历过。关键在于生成时必须指定实现模式:状态模式(State Pattern)而不是简单的switch-case。第一次用Claude Code时我只说“生成有限状态机”,它默认生成switch-case,虽然逻辑正确但扩展性差。

后来我强制要求“每个状态为一个独立的类,实现统一的State接口,将每个事件的转移行为封装在状态类内部”。这样修改某个状态的行为时,只需修改对应类的代码,不影响其他状态。

我用一个15个状态的请假系统做过对比测试:使用状态模式的代码,新增一种审批结果平均只需要修改2个文件(新状态类和协调器注册),而switch-case模式下需要修改4-5个分支,且容易漏改。Claude Code对模式的理解非常精准,只要你在提示词里明确指定设计模式名称并举例。

额外技巧:要求它自动生成状态转移配置表(JSON或YAML),配合代码生成器,这样修改规则时只需改配置文件,Claude Code甚至能帮忙同步更新代码。

4. 如何验证Claude Code生成的状态机逻辑与真实业务规则完全一致?人工审查太耗时,有没有自动化测试方案?

我负责的风控系统有300+条状态转换规则,交给Claude Code生成的代码用眼看不现实。上线前我手动写了20个单元测试,覆盖了主流路径,结果线上还是出了事故,有一条“充值成功后自动解除冻结”的规则,Claude Code生成的冻结状态在充值成功后没判断账户是否满足解冻条件,直接解冻了。

我该怎么高效验证AI生成的状态机没有逻辑漏洞?有没有推荐的工具或流程?

这是一步关键的防御性工作。我的做法是结合属性基测试(Property-based Testing)形式化验证

首先,让Claude Code生成状态机代码的同时,要求它输出一份形式化的状态转移矩阵(State Transition Matrix),可以是Excel表格或JSON数组。

然后,用Claude Code或自己编写一个属性测试脚本:使用工具如Hypothesis(Python)或fast-check(JS),枚举所有状态×所有事件×所有前置条件的排列组合(对于有限状态机来说组合数通常可控),断言每个组合要么是有效转移并得到预期下一状态,要么是无效组合并抛出错误。

我实际测试过一个20状态、10个事件的状态机,总共200种组合,属性测试能在5分钟内覆盖全部。其次,要求Claude Code为每个转移规则生成对应的中文注释,然后手动核对注释与业务文档的一致性,这个步骤不能省。

我经验是:AI生成的逻辑在常规路径上有95%正确率,但边界条件(如状态回退、重复事件、并发冲突)容易出错,属性测试正好专门抓这类漏洞。数据上,我之前的项目通过属性测试发现了8处Claude Code遗漏的非法转移,其中3处会导致数据不一致。

记住:绝对不要信任AI生成的状态机逻辑,但你可以信任它生成的测试框架,让AI生成测试用例,然后你只需要验证测试用例的期望值是否正确。

核心关键词

读者评论

许念

看到作者提到“Claude Code 擅长翻译规则,但不擅长发现规则”,我深有体会。上次我把企微文档里的业务描述直接喂进去,它生成的代码漏掉了两个隐藏的互斥条件,后来我才明白,不是我 prompt 写得好不好的问题,是我自己还没把规则真正梳理清楚。

孟凡

文章的“4 个核心结论”非常有价值,尤其是“维护成本”那个点。我们团队现在还在维护一个 2000 行的 if-else 状态跳转,每次改需求都心惊胆战。用自然语言维护规则再让 AI 重新生成代码的思路,确实值得试一下。

赵明轩

关于“一次性扔全部规则”的误区那一节,我必须说:我踩过完全一样的坑。我把整个页面规则文档粘贴进去,它生成了看起来很漂亮的代码,结果上线后发现“部分退款后冻结并发”的场景根本没处理。分步喂规则是王道。

何雨

作者把状态机定义为“业务规则的数学模型”这个视角太精准了。我之前一直觉得状态机只是种实现方式,读了文章发现它能倒逼我们把模糊的规则澄清,而且 AI 还能帮你发现规则文档里的矛盾,这是意外收获。

唐悦

文章里提到的并发冲突规则,用户退款时恰好扣减次数,这个场景太真实了。我们系统也遇到过类似问题。想问作者,Claude Code 生成的代码是如何处理这种并发控制的?是用乐观锁还是事务隔离?后续有没有展开讲讲?

陆景

我们也有类似的客户权益模块,但规则散落在 Jira 和前任脑中,根本没文档化。看完文章结论五,我彻底打消了直接让 AI 重构的念头。先老老实实把规则结构化梳理出来,这才是正确路径。

李卓

难得看到一篇不是“5 分钟上手”的 AI 编码文章。从真实事故出发,讲协作过程、讲风险、讲失败尝试,这种实战记录比教程有价值太多了。收藏了,准备照着工作流在自己项目上试一遍。

顾清

改得动”这个点最打动我。我们系统一半的 bug 来自修改老逻辑时没考虑全分支。如果状态机是 AI 根据自然语言规则维护的,那以后改规则就改文档,然后重新生成代码加测试,维护成本确实会降一个量级。

文章版权归“万象方舟”www.vientianeark.cn所有。发布者:程, 沐沐,转载请注明出处:https://www.vientianeark.cn/p/599312/

温馨提示:文章由AI大模型生成,如有侵权,联系 mumuerchuan@gmail.com 删除。
(0)
claude code 在数据管道 ETL 作业生成中的适用性分析
上一篇 1分钟前
claude code 处理超大代码文件时的内存与响应优化
下一篇 58秒前

相关推荐

  • 使用 claude code 编写数值计算代码时的浮点精度控制

    使用 claude code 编写数值计算代码时的浮点精度控制 上个月,我用 Claude Code 写了一个看似极其简单的脚本:把客户近三年每一笔交易的手续费累加起来,生成一个总成本报表。生成的代码干净、优雅,甚至贴心地加上了注释。我只花了 15 分钟就完成了从 prompt 到跑通的全部流程。然而,当我把结果和财务部门的 Excel 底稿比对时,误差达到了 0.47 分,不是 0.47 元,而…

    51秒前
    000
  • claude code 理解业务逻辑后生成领域驱动设计代码的尝试

    二〇二五年三月的一个深夜,我盯着屏幕上 Claude Code 生成的代码,整整沉默了二十分钟。不是因为代码太烂,恰恰相反,它生成的是一个标准的、教科书式的领域驱动设计分层结构:聚合根、值对象、领域服务、仓储接口,一应俱全。但问题是,它把合同计费规则写成了贫血模型,把一条本该在领域服务中承载的复杂业务逻辑,硬生生塞进了一个名为 ContractEntity 的 POJO 里,外加一堆 getter…

    55秒前
    000
  • claude code 处理超大代码文件时的内存与响应优化

    Claude Code 处理超大代码文件时的内存与响应优化 过去三个月,我在一个包含超过1800个文件的电商项目中重度使用Claude Code,遇到了四次OOM崩溃、无数次响应卡顿,以及两次因为上下文超限导致Claude直接拒绝继续工作。在反复测试了各种“优化秘籍”之后,我发现了一个令人沮丧的事实:大部分流传的优化建议,要么只解决了表面问题,要么带来了更严重的副作用。 比如那个广为流传的说法,“…

    58秒前
    000
  • claude code 在数据管道 ETL 作业生成中的适用性分析

    Claude Code 在数据管道 ETL 作业生成中的适用性分析 去年12月,我手头有一个紧急项目:将三张业务数据库的原始表清洗、聚合、转换后导入数据仓库,为年初的管理层经营分析会做准备。传统做法是,我带着两个数据工程师花一周时间写完所有SQL脚本、Python转换逻辑和Airflow调度配置。但当时团队里一个工程师休假,另一个被临时抽调到别的项目,只剩我一个人。 我做了个冒险的决定:把这批ET…

    1分钟前
    000
  • claude code 辅助设计 RESTful API 接口的常见错误

    去年冬天的一个周五晚上,我犯了一个让我至今记忆犹新的错误。 当时我在重构一个旧项目的支付模块,想着反正逻辑清晰,就让 Claude Code 直接生成了一组 RESTful 接口。我快速扫了一眼,路径看着合理,方法也对,状态码也正常,于是直接部署到了测试环境。结果第二天上午,前端同事在群里发了一长串报错截图,配了一句:“你这接口怎么 GET 请求也能把钱扣了?” 我脑子嗡了一下。 回头检查 Cla…

    1分钟前
    000
站长微信
站长微信
分享本页
返回顶部