Fitness Functions(架构适应度函数)是自动化架构测试,像守门员一样阻止违规代码进入主分支,用ArchUnit等工具强制执行分层依赖和命名规范,让架构腐烂成为历史。
代码架构的自动守门员:用Fitness Functions(架构适应度函数)让你的架构不再烂成意大利面
开场:架构腐烂的恐怖故事
你花了三个月设计了一套超酷的代码架构,开会时所有人都点头说好,PPT做得跟艺术品似的,文档写得比小说还详细。六个月后,你打开代码库一看,差点当场去世。有人直接把数据库操作塞进了前端展示层,你的美丽架构正在慢慢变成一盘意大利面。
这就是传说中的架构腐烂,每个程序员都经历过这种噩梦。你以为靠代码审查和大家的自觉性就能守住阵地?太天真了。人性经不起考验,懒才是第一生产力。解决方案很简单:别指望人类了,让机器来当这个坏人。
什么是Fitness Functions:给架构请个自动裁判
这个词来自Neal Ford、Rebecca Parsons和Patrick Kua写的《Building Evolutionary Architectures》。
这本书讲的是进化式架构(evolutionary architectures),教你搭一套系统,让架构师和开发者有信心对系统最重要的部分大刀阔斧地改。书里介绍了一系列实践,帮你打造持续演进的架构,不用水晶球也能 clean evolution(干净地进化)。
进化式架构支持在多个维度上进行有指导的、增量式的变更。
这本书分三部分,对应定义里的三个关键词。
1、增量式变更
增量式变更靠的是持续交付(continuous delivery)那一套:部署流水线、成熟的 DevOps、良好的测试文化,以及其他当下最潮的敏捷工程最佳实践。再加上细粒度、模块化的架构,增量式变更让开发者可以在架构层面做小改动,而不会把不相关的部分搞崩。增量式变更是进化式架构能够进化的 machinery(引擎)。
2、多维度
架构师通常只盯着一个狭窄的维度:技术架构,比如框架、依赖、集成架构之类的。但真实的软件项目有一大堆 orthogonal concerns(正交关注点):数据架构、安全、可扩展性、可测试性,等等等等。现代架构要支持进化,就必须持续地照顾到所有这些重要维度。
搭进化式架构时,架构师得考虑每个受影响的维度。比如,技术架构搭得再灵活,数据 schema 硬得像石头也没用;或者进化的时候把安全性搞砸了,那也是白搭。
3、适应度函数(Fitness Functions)
进化式架构允许系统的不同部分按最合理的方式进化来解决问题。但我们不想让系统进化到伤害某个架构维度的地步。比如,为了性能搞缓存,结果不小心把安全性搞砸了,这种事怎么防?
架构适应度函数为某些架构特性提供客观的完整性评估。
适应度函数用各种各样的机制来实现:测试、指标、监控、日志等等,来保护一个或多个架构维度。我们定义了多种适应度函数类别:触发式 vs 持续式、原子式 vs 整体式,还有其他各种类型。
当我们识别出架构中受变更影响的维度时,就定义适应度函数来防止不期望的破坏。
进化式架构提供了一种全新的视角来看待架构演进,把可进化性(evolvability)提升为软件项目的一级公民(first-class "-ility")。
它提供了一个逻辑框架,用于识别和保护架构中需要进化的部分:识别重要维度,定义适应度函数确保合规,使用增量式变更工程实践(如部署流水线)来自动验证适应度。
进化式架构的概念还帮助自动化了以前被忽视的方面("非功能性需求"),通过提供一个框架来识别重要维度及其关键特性,并通过适应度函数持续验证这些属性的真实性。这让架构师能够搭建支持持续变更的系统,并有信心重要的质量不会退化。
架构维度和适应度函数的识别既发生在项目启动时,也作为持续的关注点,打造持续架构。
适应度函数Fitness Functions 是“进化式架构”中的核心度量标准,就像生物学中的“适者生存”法则一样,它用可执行、可量化的标准,来自动评估一个软件系统架构的适应度、合规性和演进方向是否符合预期。
想象你要训练一个AI机器人保持直立行走(这是你的架构目标):
- Fitness Function(适应度函数) 就是:“测量身体与垂直线的夹角,角度必须小于10度”。
- 机器人每走一步(每次代码提交、每次构建),系统就会自动测量这个角度。
- 如果角度大于10度(违反规则),系统就会自动报警、甚至阻止这次行动,直到调整到符合标准为止。
适应度函数Fitness Function说白了就是一套自动测试,专门检查你的代码结构是不是按设计图纸来的。
单元测试验证代码行为对不对,适应度函数Fitness Functions验证代码结构是不是遵守了架构规则。
核心思路就是把治理从靠人眼检查变成靠规则自动执行。与其祈祷有人在代码审查时发现问题,不如写个自动化测试,每次有人提交PR就自动检查,违规直接报错。这叫治理左移,问题在开发阶段就被揪出来,而不是等到审计时或者系统崩了才发现。
三大支柱:适应度函数Fitness Functions的核心理念
适应度函数Fitness Functions建立在三个原则之上。
第一,规则治理胜过人工检查。把规则写成代码,别指望人类去记去执行。
第二,让团队自己发现问题胜过独立审计。团队违规时立即收到反馈,而不是几周后从外部审查报告里才知道。
第三,持续治理胜过专门审计阶段。每次提交都检查,不是每季度每年检查一次,是每一次提交都检查。
代码架构测试:ArchUnit和ArchUnitTS实战
最常见的适应度函数Fitness Functions用法就是强制执行代码架构规则。
Java用ArchUnit,TypeScript用ArchUnitTS。
ArchUnit是Java架构测试的首选库,可以检查包依赖、类关系、命名规范等等。比如你可以写个测试确保服务层不会直接访问控制器层,或者检查包之间没有循环依赖,或者确保只有服务层能访问仓库层。
@Test |
TypeScript的ArchUnitTS也是同样道理,比如检查展示层不依赖数据库层,检查没有循环依赖,确保领域层不依赖特定框架。
it('presentation layer should not depend on database layer', async () => { |
你能测试什么:从分层到命名规范
常见的架构规则包括分层依赖(展示层不能直接访问数据库,服务层不能调用控制器,领域逻辑不能依赖框架)、循环依赖检查(这是架构癌症,让重构几乎不可能)、命名规范(服务类必须以Service结尾,控制器必须以Controller结尾,让代码库可预测可搜索)、包边界(模块化单体或微服务中,模块不能互相侵入内部,必须通过公共API交互)、指标限制(文件不能超过N行,类依赖不能超过M个,方法圈复杂度不能超过X)、框架隔离(领域逻辑不能导入Express、NestJS、Spring等框架,保持核心业务逻辑的可移植性)。
关键是把这些放进CI流水线,每次PR都运行,失败就阻止合并,架构违规永远进不了主分支。
超越代码:数据产品治理
适应度函数Fitness Functions不限于代码架构,任何有规则想自动执行的地方都能用。比如数据网格治理,自主团队拥有各自的数据产品,但要让这些产品在整个组织里可互操作有用,就得符合某些标准。怎么在不制造中央数据管理员瓶颈的情况下强制执行?用适应度函数Fitness Functions。
大多数组织有数据目录(Collibra、DataHub等)存储数据产品元数据,你可以对这些元数据运行断言:可发现性(搜索数据产品名称能否在前几个结果里找到)、自描述性(是否有有意义的描述,字段是否有文档)、可信度(是否定义了SLO并且正在达成)、安全性(访问是否被正确限制)、互操作性(是否遵循标准格式,是否有业务键)、可寻址性(是否通过唯一URI可访问)。
def test_existence_of_service_level_objectives(): |
这些断言可以在目录工具里运行,也可以通过API拉取元数据在外部运行,结果发布到仪表盘,团队看到自己数据产品的通过情况,没人想当那个红叉最多的团队,社会压力加自动化胜过人工检查。
LLM大模型登场:处理模糊规则的神器
2026年最酷的是有些适应度标准很难用确定性规则表达。
怎么自动检查数据产品是否代表了领域内的连贯信息概念?怎么检查代码注释是否真正解释了为什么而不是做了什么?怎么检查API端点是否遵循RESTful约定?这些都需要人工判断。但LLM在这些评估上出奇地好用。
你可以用函数调用(结构化输出)从LLM获得一致可解析的结果,比如评估数据产品内聚性,或者评估代码注释质量。
def evaluate_data_product_cohesion(metadata: dict) -> dict: |
LLM能识别出像Product_Id这样只是代表产品ID的数据产品不够内聚,需要和其他数据连接才有用;
def evaluate_comment_quality(code_snippet: str, comment: str) -> dict: |
你可以用这个功能来揪出那些混日子的注释,比如那种 // 把i加1 的废话注释,这种注释看了等于没看,纯粹是凑行数骗工资。同时也能发现真正有价值的注释,比如 // 我们重试3次是因为支付服务商在高负载下会抽风 这种,一看就知道为啥这么写,出了问题也能快速定位,这才是注释该有的样子。
LLM的权衡:不是银弹
基于大模型的适应度函数有局限性:非确定性(相同输入可能给出略微不同结果,即使temperature设为0也可能有差异)、成本(API调用会累积,可能不想每次提交都运行,可以每晚或每周运行)、可解释性(在结构化输出里要求理由有帮助,但不如确定性规则透明)。但对于以前太模糊无法自动化的标准,LLM打开了新可能性,现在可以自动化以前需要人工审查的治理检查。
实战模式:仪表盘、渐进严格、豁免机制
仪表盘模式:把适应度函数结果发布到可见的仪表盘,按团队或领域分组,显示红绿状态并能下钻到具体失败项。这创造健康竞争,团队不想当那个适应度函数失败最多的,也帮助数据产品或服务消费者做出明智决策,你自然会优先选择通过所有检查的服务。
渐进严格模式:第一天别搞100条规则,从一条重要的开始,让整个代码库通过,然后再加一条,再加一条。如果一下子给团队一堆失败的适应度函数,他们会全部无视;如果逐步引入并保持套件全绿,团队会认真对待。豁免模式:有时你需要打破规则,适应度函数不应该没有逃生通道就阻止你。可以标记遗留代码计划在Q2重构,追踪豁免并定期审查,但允许豁免,否则团队会完全绕过你的适应度函数。
为什么这很重要:持续治理的威力
持续治理:不再是每季度的架构审查,而是每次提交都获得反馈。客观测量:不再有主观争论,测试通过或失败。活的文档:你的适应度函数就是用代码写的架构规则,不可能与现实脱节,因为每次构建都强制执行。更快 onboarding:新团队成员通过违规并看到清晰错误信息来学习规则。重构信心:你知道破坏架构规则会被立即捕获。可扩展治理:在去中心化架构(微服务、数据网格)中,不可能有中央团队审查所有东西,适应度函数让你在不制造瓶颈的情况下强制执行标准。
开始行动:从小做起
选一条经常被违反的规则,写个测试,加进CI,逐步扩展。Java从ArchUnit开始,TypeScript从ArchUnitTS开始,数据产品从针对目录API的简单断言开始。别试图第一天就编码每个架构决策,随着发现什么重要而逐步构建F适应度函数套件。目标不是完美,而是让违规可见且自动,这样你的架构才有机会在现实中存活下来。