SuperAPI AI 工具集

arch-fitness-functions

编程与开发

查看项目

更新于 10-16 16:26

技能介绍

概述

该技能提供一套把架构意图编码为自动化检查的做法,即“适应度函数(fitness function)”。一句话概括:把架构意图编码为在每次变更时运行的自动化检查。如果架构规定“领域层不依赖 Spring”,那么当它真的依赖时,适应度函数会让构建失败。

用途与适用场景

当某项架构决策很重要、并且需要长期保持时使用,例如依赖方向、层边界、无循环依赖、性能预算。当团队规模足够大,非正式的“我们约定不做 X”无法在所有 PR 中维持时使用。在迁移项目(arch-strangler-fig)中,可用它强制新代码保持在新结构中而非旧结构。任何原本依赖代码评审来发现架构漂移的架构决策,都适合使用。

不适用的情况

小型项目、单人开发、没有扩展问题。架构属性本身是主观的、无法自动化的(品味、美学、边缘情况下的命名风格)。过度管制——每条架构规则都有成本,只应强制那些你愿意为之阻断 PR 的规则。

核心内容

适应度函数是一种断言某个架构属性的测试。与单元测试类似,它们:在每次变更时于 CI 中自动运行;一旦违反就让构建失败;以可执行形式记录架构意图。

适应度函数的分类(出自 Ford、Parsons、Kua 的《Building Evolutionary Architectures》):原子与整体——原子检查单一属性(例如该模块无循环依赖),整体组合多个属性(例如负载下的性能加正确性)。触发式与持续式——触发式在每次构建时运行(最有用),持续式在生产环境运行(延迟预算、错误率)。静态与动态——静态分析代码(依赖方向),动态运行系统(响应时间检查)。

常见静态适应度函数(最容易起步):依赖方向,例如“模块 X 不得依赖模块 Y”,可通过构建图或专用库执行(JVM 用 ArchUnit,TypeScript 用 ts-arch,JS 用 dependency-cruiser);无循环依赖,检测因错误放置的 import 而引入的意外循环;命名约定,例如“controller/ 中的每个类必须以 Controller 结尾”,多属表面性但易于强制;公共 API 稳定性,例如“该接口上不得新增无对应测试的公共方法”,需自定义检查;层边界违规,例如“domain/ 中的代码不得从 infrastructure/ 导入”,这是经典六边形检查。

生产架构的动态适应度函数:p99 延迟低于阈值;内存使用有界;特定业务不变量成立(无负余额、无重复订单)。

ArchUnit 示例(JVM,最成熟的生态):

@ArchTest val domainHasNoSpringDependencies = noClasses().that().resideInAPackage("..domain..") .should().dependOnClassesThat().resideInAPackage("org.springframework..")

决策启发式

从一两个强制架构最重要属性(通常是依赖方向)的适应度函数开始,随着漂移出现再增加。让适应度函数失败真正阻断构建——一条被忽视一年的警告不算是适应度函数。像对待测试一样对待适应度函数:每条都有清晰的失败信息,说明规则存在的原因而不仅是检查什么。不要为你不会阻断 PR 的规则编写适应度函数。对于迁移项目,适应度函数可编码“新代码放在路径 X,而非遗留路径 Y”,对保持迁移的诚实性非常有价值。

反模式

无人负责的适应度函数:一旦失败就被人禁用,从此被永久绕过。过度管制:用数百个适应度函数强制每一种偏好,CI 变慢,开发者学会忽略失败。把风格当架构:“所有方法名必须为 camelCase”是 linter 规则,不是适应度函数,不要稀释这个术语的含义。只有文档没有自动化:Wiki 页面描述架构但没有自动检查,新加入者几周内就会违反。一次性检查:架构审计只做一次,结果归档,之后漂移立刻累积。

相关技能

arch-layered——层边界是适应度函数最典型的用例。arch-hexagonal——依赖方向朝向领域是六边形架构最典型的适应度函数。arch-adr——适应度函数编码“做什么”,ADR 解释“为什么”,二者应配合使用。

参考资料

Neal Ford、Rebecca Parsons、Patrick Kua,《Building Evolutionary Architectures》(O'Reilly,第 2 版)——基础参考。ArchUnit 文档(JVM)。Richards & Ford,《Fundamentals of Software Architecture》治理/适应度章节。