演进式架构基础

共 17 题
📑 题目列表 17 题
#
★★★

1. 演进式架构的核心思想中用适应度函数守护架构约束

演进式架构(Evolutionary Architecture)的核心思想是什么?如何用适应度函数(Fitness Function)来守护架构约束,既保证架构可演进又防止其退化?

  • 演进式架构的定义(有指导的增量变更)
  • 适应度函数作为"架构的可测试性"手段
  • 在演进与约束之间保持平衡

演进式架构是一种支持有指导的(guided)、增量式架构变更的架构实践,其核心思想是"架构可以演进,但演进必须被约束和度量"。传统架构往往把约束写在文档里,一旦文档与代码脱节,约束就形同虚设。演进式架构主张把架构约束转化为可执行的、自动化的"适应度函数"(Fitness Function),即对系统某项架构属性进行客观度量的自动化测试或检查。这样架构约束就变成了 CI 流水线中的一道门禁,任何违反约束的提交都会被拦截,从而在允许架构持续演进的同时,守住不可突破的底线(如依赖方向、性能预算、技术栈约束)。其本质是把"架构治理"从人为审查变成可验证、可量化的工程纪律。

关键洞察在于"演进"与"约束"并不矛盾——约束不是阻止演进,而是确保演进有方向、可观测、可回滚。适应度函数让架构约束具备"可测试性",这正是演进式架构区别于传统"架构文档"治理的核心。它遵循"测试优先"的思想:先把约束固化成测试,再让重构驱动的演进在一个安全网内进行。

// 用 ArchUnit 把"禁止 layer 反向依赖"的架构约束固化为可执行测试
@AnalyzeClasses(packages = "com.example")
public class ArchitectureConformanceTest {
    @ArchTest
    static final ArchRule noControllerDependsOnRepository =
        layeredArchitecture()
            .consideringAllDependencies()
            .layer("Controller").definedBy("..controller..")
            .layer("Service").definedBy("..service..")
            .layer("Repository").definedBy("..repository..")
            .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
            .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");
}
#
★★★

2. 架构适应度函数(Fitness Function)的定义与分类

什么是架构适应度函数(Fitness Function)?它有哪几种分类维度?

  • 适应度函数的定义(客观、可自动化、面向架构属性)
  • 分类维度:原子/整体、触发式/持续式、静态/动态
  • 与单元测试的区别

架构适应度函数(Fitness Function)是对某项架构特性(如依赖方向、性能、安全、可修改性)进行客观度量的一种机制,通常表现为自动化测试、静态分析或监控检查。Neal Ford 在《Building Evolutionary Architectures》中给出了核心分类:原子型(Atomic)与整体型(Holistic)、触发式(Triggered)与持续式(Continuous)、静态(Static)与动态(Dynamic)。其中原子型度量单体单元(如某个模块的复杂度),整体型度量系统跨组件的全局属性(如全局依赖环检测);触发式在特定事件(如提交、PR)时运行,持续式定时/持续运行(如夜间巡检);静态检查源代码本身,动态需在运行时收集数据(如真实流量下的延迟、内存)。这些分类可以组合,例如"动态+持续+整体"的适应度函数用于监控生产环境微服务间的延迟。

分类的价值在于帮助工程团队理解"不同约束需要不同的度量方式"。例如依赖方向是静态可检查的,性能预算是动态的、需要运行时数据;单体复杂度是原子的,全局依赖环是整体的。理解分类能避免选错工具——把动态属性当成静态检查会失效,把整体属性做成原子检查会遗漏跨组件问题。

#
★★★

3. 适应度函数的分类(Neal Ford 原始分类)中原子/整体(atomic/holistic)、触发式/持续式(triggered/continuous)、静态/动态

请解释 Neal Ford 对适应度函数的原始分类:原子/整体、触发式/持续式、静态/动态,以及它们各自适用的场景?

  • 三组分类的准确含义
  • 各组合的适用场景
  • 分类如何指导工具选型

Neal Ford 把适应度函数从三个正交维度分类。第一维是粒度:原子型(Atomic)只度量单个架构单元,如单模块的内聚度、单个类的复杂度、单次请求的延迟;整体型(Holistic)度量系统各组件之间的全局关系,如全局依赖环检测、跨服务一致性、整体吞吐量。第二维是触发时机:触发式(Triggered)在特定事件(代码提交、PR 合并、发布)时执行,适合快速反馈;持续式(Continuous)按定时或持续运行,适合捕获环境漂移、运行时漂移(如生产配置变化、依赖升级后指标退化)。第三维是静态/动态:静态(Static)分析源码文本,如依赖方向、模块边界;动态(Dynamic)需在运行环境收集数据,如真实流量下的响应时间、内存占用。三者可任意组合,例如"动态+持续+整体"用于监控生产环境微服务间延迟,"静态+触发+原子"用于提交时的单类复杂度检查。

这一分类是演进式架构的"度量字典",帮助团队把抽象的架构目标映射到具体的度量机制。选择时关键看"被守护的属性有多少信息需要运行时才能获得"以及"约束会被何种变化破坏"。静态约束用静态检查即可,动态约束必须上运行时监控;容易在提交时被破坏的用触发式,环境漂移类问题用持续式。

#
★★

4. 适应度函数与单元测试/集成测试的本质区别

架构适应度函数与单元测试、集成测试的本质区别是什么?为什么不能把二者混为一谈?

  • 度量对象不同(架构属性 vs 功能行为)
  • 测试的"意图"不同(守护约束 vs 验证正确性)
  • 失败的意义不同(架构违规 vs 函数缺陷)

本质区别在于"度量对象"与"意图"的不同。单元测试和集成测试验证的是"功能是否按预期工作"——即某个方法、某个接口的行为是否正确;而适应度函数验证的是"系统的架构属性是否仍然符合约束"——即依赖方向、模块边界、性能预算、安全基线等非功能的架构特征。单元测试的失败意味着"某个功能坏了",而适应度函数的失败意味着"架构约束被突破了",后者通常不体现为功能错误,而是体现为结构退化、技术债累积、后续演进变难。此外,适应度函数往往以"整体系统"为对象(如依赖环检测、包体积),而单元测试以"单个单元"为对象;适应度函数也常依赖运行时或静态分析工具,而非单纯的测试断言。

混为一谈的代价是:团队以为"测试全绿 = 架构健康",但功能测试全绿并不能阻止依赖方向颠倒、模块边界被破坏、包体积膨胀。这正是需要独立适应度函数的原因——它守护的是"可维护性、可演进性"这类结构属性,而非可用性。二者的关系是互补:单元测试守护功能正确性,适应度函数守护架构健康。

#
★★

5. 如何把「架构意图」转化为可度量的适应度函数

如何把抽象的"架构意图"(如"我们要保持模块松耦合")转化为可度量、可自动化的适应度函数?

  • 架构意图的分解与量化
  • 从定性约束到定量指标
  • 工具与断言的落地

把架构意图转化为适应度函数,核心是"把一句定性的话翻译成一个可判定的断言"。步骤通常是:第一,明确意图背后的不可突破属性(如"松耦合"背后的真实诉求是"模块间不能有随意依赖");第二,把属性转化为可量化的指标(如"依赖方向不可反向""循环依赖数=0""依赖于具体实现的包数 < N");第三,选择工具(ArchUnit、Dependency-Cruiser、自定义静态分析)把指标固化为自动化断言;第四,把断言接入流水线作为门禁,并设定阈值与失效率。例如"模块必须只依赖相邻层"可转化为 ArchUnit 的 layeredArchitecture 规则,"禁止 GPL 依赖"可转化为许可证扫描的失败条件,"包体积不得超过预算"可转化为构建时体积检查。

关键难点在于"意图"往往是模糊的,需要架构师明确"不可量化就不可治理"。转化的成功标准是:该断言能否在违反约束时可靠地失败、在符合时稳定通过,且反馈及时。好的适应度函数是"意图的量化投影",让每个工程成员都能通过绿色/红色门禁直观感知架构约束。

// 前端模块边界约束:用 dependency-cruiser 检测"禁止 UI 层 import 数据层"
module.exports = {
  forbidden: [{
    name: 'no-ui-to-data',
    from: { path: '^src/ui' },
    to: { path: '^src/data' }
  }]
};
#
★★

6. 适应度函数的触发频率(每次提交/定时/发布前)权衡

适应度函数的触发频率(每次提交、定时巡检、发布前)应如何权衡?不同频率的优缺点是什么?

  • 触发频率的三种选择
  • 速度与覆盖的权衡
  • 不同类型约束的适配频率

触发频率的选择本质是"反馈速度"与"执行成本/覆盖范围"的权衡。每次提交(或 PR)触发:反馈最快,能在变更引入问题的第一时间拦截,适合快速、静态、原子的检查(如单类复杂度、依赖方向、lint),但成本高,不适合重量级扫描;定时巡检(如夜间持续式):适合整体型、动态、跨组件的检查(如全局依赖环、生产环境指标漂移、依赖漏洞扫描),能捕获慢速漂移但缺乏即时反馈;发布前触发:适合发布门禁类检查(如许可证、安全基线、性能验证),保证发布内容合规,但发现问题时已接近发布,修复成本高。实践上通常分层组合:快速检查在提交时跑,重量级/整体检查定时跑,发布合规检查在发布前跑。

没有一种频率是"全对"的,需要按"约束被破坏的速度"和"检查成本"来匹配。反馈越快越好,但成本必须可控;定时与发布前检查补足提交时检查无法覆盖的"环境漂移"和"整体属性"。权衡的准则是:把昂贵的检查放在低频、把廉价的检查放在高频,同时保证每种约束都至少有一个负责的触发点。

#
★★

7. 整体型适应度函数(如全局依赖环检测)相较原子型(单模块复杂度)的跨组件视角

整体型适应度函数(如全局依赖环检测)相比原子型(如单模块复杂度)的跨组件视角有什么价值与局限?

  • 整体型与原子型的视角差异
  • 全局依赖环检测的意义
  • 整体型的局限(成本、定位难)

原子型适应度函数只度量单一组件内部属性(如单模块复杂度、单类 LOC),它无法发现"组件之间的关系"问题;而整体型适应度函数从系统全局视角考察组件之间的依赖与连接,如全局依赖环检测、跨模块依赖边界、服务间调用拓扑。整体型的核心价值在于能发现"单体逐渐腐化"的端到端信号——例如一个模块复杂度很低,但多个模块互相循环依赖,导致整体不可维护,这正是原子型会漏掉的。依赖环检测能暴露"内聚被破坏、职责被共享"的坏味道,是守护模块化边界的强适应度函数。它的局限是:成本较高(需全量分析)、失败时定位到具体环需要额外分析、且可能因频繁误报而过早治理。因此整体型常作为定时/持续检查,而非每次都提交触发。

视角差异决定了工具分工:原子型管"局部整洁",整体型管"全局结构"。演进式架构推荐"整体优先"——因为结构腐化往往从组件间依赖开始,单个模块再干净也救不了整体。整体型函数适合低频、持续运行,与原子型的即时反馈互补。

#
★★

8. 持续式(temporal)适应度函数(定时巡检)弥补触发式(提交时)无法覆盖的环境漂移

为什么持续式(temporal/continuous)适应度函数能弥补触发式(提交时)无法覆盖的环境漂移?

  • 触发式与持续式的触发差异
  • 环境漂移的含义
  • 持续式在运行时监控的价值

触发式适应度函数只在代码提交、PR 合并等"代码变更"事件时运行,它只能守护"代码层面"的约束;但架构属性还会被非代码因素破坏,例如依赖版本升级、第三方服务行为变化、运行环境配置变更、灰度流量变化、真实数据增长等,这些统称为"环境漂移"。例如,微服务在压力测试下性能达标,但生产流量增长后延迟超标——这不是代码变更引起,而是在运行时逐渐漂移。持续式(temporal)适应度函数按定时或持续运行,主动采集运行时数据(延迟、错误率、内存、依赖漏洞状态),能捕捉这些随时间或环境变化退化的问题,从而弥补触发式函数"只在提交时看一次"的盲区。它是"动态+持续+整体"组合的典型应用。

核心洞察是"约束可能在不改代码的情况下被破坏"。触发式假设"变化=代码变更",但现实是环境、流量、外部依赖都在变。持续式函数把架构治理从"变更时把关"扩展到"运行中守望",让适应度函数成为持续监控而非一次性检查。两者互补而非替代。

#
★★

9. 用依赖分析守护「禁止层间反向依赖」的适应度函数

如何用依赖分析构建一个守护"禁止层间反向依赖"的适应度函数?

  • 分层架构的依赖方向约束
  • 依赖分析工具(ArchUnit 等)的用法
  • 违反时如何定位与处理

"禁止层间反向依赖"是分层架构最核心的约束,即上层可依赖下层,下层不得反向依赖上层。用依赖分析构建适应度函数时,先定义层与包的映射(如 controller、service、repository),再用 ArchUnit 的 layeredArchitecture 规则声明各层的访问权限,作为可执行的单元测试接入 CI。规则会解析字节码中所有类依赖,检测到反向依赖(如 Repository 调用了 Controller)即失败。除 ArchUnit 外,Java 可用 JDepend/SonarQube 的依赖规则,前端可用 dependency-cruiser。该函数属"静态+触发+原子"类型,适合每次提交运行,反馈迅速。它把"层间职责"从口头约定变成机器强制,防止架构在长期演进中悄悄退化。

反向依赖是分层腐化的典型前兆,一旦出现,模块边界立刻模糊,后续重构成本陡增。依赖分析函数把"规定方向"变成"可验证断言",是演进式架构中最常见、性价比最高的适应度函数之一。关键是要把层定义与包约定写清楚,否则规则会误报或漏报。

@AnalyzeClasses(packages = "com.example")
public class LayeringRuleTest {
    @ArchTest
    static final ArchRule servicesRespectLayers = layeredArchitecture()
        .layer("Controller").definedBy("..controller..")
        .layer("Service").definedBy("..service..")
        .layer("Repository").definedBy("..repository..")
        .whereLayer("Controller").mayNotBeAccessedByAnyLayer()
        .whereLayer("Repository").mayOnlyBeAccessedByLayers("Service");
}
#
★★

10. 用包体积/循环复杂度阈值守护性能预算的适应度函数

如何用包体积(bundle size)、循环复杂度(cyclomatic complexity)等阈值构建守护性能预算的适应度函数?

  • 性能预算(performance budget)概念
  • 包体积与复杂度作为可度量代理指标
  • 阈值门禁的实现

性能预算(performance budget)指对系统性能指标设定可接受的上限,如"前端首屏 bundle ≤ 200KB""关键接口 P95 ≤ 500ms"。但"真机性能"难以在 CI 中稳定测量,因此常用静态可度量的代理指标来守护:包体积(bundle size)用 size-limit、webpack-bundle-analyzer 检测,超过阈值即失败;循环复杂度用 eslint complexity 规则、lizard 等工具检测,超阈值即失败。这些函数把"性能/可维护性"的预算固化为构建/提交时的门禁,任何导致体积膨胀或复杂度超限的变更都会被拦截。它们是"静态+触发+原子"类型,适合低频昂贵检查之外的快速反馈。需注意代理指标只能反映"性能相关"而非真实性能,需与真实性能监控(持续式动态函数)互补。

关键洞察是"预算必须显式化、可度量、可门禁"。体积膨胀和复杂度超标是渐进式腐化,单次变更增量小,若不设阈值会悄悄累积。包体积/复杂度阈值让"预算"变成"红线",每次提交都能看到"我这次是否突破了预算"。它与真实性能监控形成"静态代理 + 动态实测"的双保险。

// size-limit 守护前端包体积预算
module.exports = [
  { name: 'main', path: 'dist/main.js', limit: '200 kB' },
  { name: 'vendor', path: 'dist/vendor.js', limit: '300 kB' }
];
#
★★

11. 用架构规则(ArchUnit/依赖卫士)守护模块边界

如何用架构规则工具(如 ArchUnit、依赖卫士)守护模块边界不被破坏?

  • 模块边界守护的意义
  • ArchUnit 规则类型(分层、包、类依赖)
  • 边界违例的治理

模块边界(module boundary)是包复用的根基,守护它的关键是让"边界规则"可执行。ArchUnit 提供了丰富规则:layeredArchitecture(分层)、freezingArchRule(冻结现有违规)、classes().should().onlyDependOnClassesThat()(类依赖限制)、noClasses().should().accessClassesThat()(禁止访问特定包)等。把这些规则写成 JUnit 测试即可在 CI 拦截边界违例。依赖卫士(如前端 dependency-cruiser、Java JDepend)则从依赖图层面定义"哪些包允许依赖哪些包"的规则。守卫的价值在于:边界一旦被破坏,包间耦合迅速扩散,模块化形同虚设。通过"冻结规则"(freezing)可以先接受现状违规、逐渐收敛,避免一次性大改。

模块边界是架构稳定性的基础,但也是最容易被"顺手改坏"的地方。用 ArchUnit 等工具把边界固化为测试,等于给每个模块边界装了"看门狗",任何越界依赖都会被自动发现。freezing 机制让治理可以渐进,不必推倒重来。这是"静态适应度函数"守护结构性约束的典型实践。

@ArchTest
static final ArchRule domainMustNotDependOnInfrastructure =
    noClasses().that().resideInAPackage("..domain..")
        .should().dependOnClassesThat()
        .resideInAnyPackage("..infrastructure..", "..persistence..");
#
★★

12. 用契约测试守护服务间兼容的适应度函数

如何用契约测试(Contract Testing)构建守护服务间兼容性的适应度函数?

  • 契约测试(消费者驱动契约)原理
  • 契约测试 vs 集成测试
  • 在 CI/流水线中的位置

服务间兼容是微服务架构的关键约束,契约测试(尤其消费者驱动契约 CDC,如 Pact、Spring Cloud Contract)把"服务间接口契约"固化为可验证的契约文件。消费者方生成契约(定义它期望的请求/响应),生产者方在 CI 中运行契约验证(real provider + consumer stub),验证契约是否被满足。消费者与生产者各自独立测试、互不依赖对方部署,从而避免集成测试的脆弱与昂贵。契约测试作为适应度函数,守护"接口兼容性"这一架构约束:任何破坏契约的变更都会在生产者侧失败,从而在发布前拦截兼容性破坏。它比集成测试更轻量、更聚焦于契约本身,是微服务演进的"安全带"。

契约测试的价值在于把"服务间兼容"从"运行时才能发现的集成问题"提前到"每个服务独立 CI 的静态/半静态验证"。它解决了微服务独立部署下"改了接口不知道谁受影响"的难题。契约成为服务间事实上的"接口文档+门禁",是演进式架构守护分布式边界的核心适应度函数。

#
★★

13. 用许可证合规扫描守护「禁止 GPL 依赖」的适应度函数

如何用许可证合规扫描(License Compliance Scanning)构建守护"禁止 GPL 依赖"的适应度函数?

  • 许可证合规约束的意义
  • 许可证扫描工具与策略
  • 在依赖引入时的门禁落地

开源许可证(如 GPL、LGPL、Apache-2.0、MIT)具有传染性与法律约束,盲目引入 GPL 依赖可能使整个软件被迫开源,从而破坏商业约束。构建许可证合规适应度函数时,先由法务/架构团队定义"允许/禁止"的许可证白名单与黑名单(如禁止 GPL、AGPL,允许 MIT、Apache-2.0),再引入许可证扫描工具(如 FOSSA、Black Duck、license-checker、npx license-checker)在依赖解析阶段扫描依赖树,凡命中黑名单即构建失败。该函数属于"静态+触发+原子"类型,通常在每次提交或 PR 时运行,把许可证合规从"人肉审查清单"变成 CI 门禁,防止开发者在代码中悄悄引入违规依赖。

许可证问题是"依赖越多越容易失控"的典型风险,因为传递依赖(transitive dependency)中的许可证往往被忽略。黑名单扫描的价值在于把合规从"事后追责"提前到"引入即拦截",并生成依赖清单(BOM)供追溯。它与安全基线扫描一样,本质是"依赖治理"类适应度函数,守护的是非功能、合规性约束。

#
★★

14. 用安全基线扫描守护「禁止高危 CVE」的适应度函数

如何用安全基线扫描(Security Baseline Scanning)守护"禁止高危 CVE"的适应度函数?

  • 安全基线与 CVE 的概念
  • 漏洞扫描工具(依赖漏洞、SBOM)
  • 在流水线中的门禁位置

高危 CVE(Common Vulnerabilities and Exposures)是已公开、可能有已知利用路径的安全漏洞,攻击者常通过依赖库漏洞入侵。构建安全基线扫描适应度函数时,先定义安全基线(如"禁止引入高危/严重级别 CVE 的依赖"),再引入漏洞扫描工具(如 Snyk、OWASP Dependency-Check、Trivy、GitHub Dependabot),在依赖解析或构建阶段扫描依赖树与已知漏洞库比对,命中高危/严重 CVE 即构建失败。该函数属于"静态+触发+原子"类型,配合持续式的定时扫描(捕获新披露的 CVE)形成"变更时拦截 + 运行中守望"的双保险。它把安全合规从"事后响应"变成"发布前门禁"。

安全漏洞的引入主要是"依赖升级或新增"带来的,触发式扫描能拦截新增风险;但已上线的依赖会随时间暴露新 CVE,因此必须叠加持续式定时慢扫描。安全基线扫描的本质是"依赖闭环"的治理,配合 SBOM(软件物料清单)可定位具体组件与修复版本。它与许可证扫描共享"依赖治理"的框架,守护的是安全这一非功能约束。

# OWASP Dependency-Check 在 CI 中的门禁配置(高危即失败)
allowlist:
  - CVE-2024-0001: 已验证的误报
threshold:
  critical: 0
  high: 0
  medium: 20
#

15. 适应度函数应内建在流水线的什么位置(门禁 vs 报告)

适应度函数应内建在流水线的什么位置?它在"门禁"(gate)与"报告"(report)两种角色间应如何取舍?

  • 门禁与报告两种角色的区别
  • 流水线阶段的适配
  • 权衡与渐进策略

适应度函数在流水线中有两种角色:作为门禁(gate)时,失败会阻断发布/合并,起强制约束作用;作为报告(report)时,即使失败也仅记录与展示,起观测与预警作用。它们的取舍取决于"约束的刚性"与"误报成本"。强约束、低误报、影响面大的函数(如架构依赖方向、安全高危、许可证)应作为门禁放在提交或发布前;而弱约束、高误报、探索性的函数(如架构健康分、复杂度趋势、代码整洁度)更适合作为报告,渐进引入、先观测再收紧。实践上常采用"渐进式门禁":先报告看趋势,再逐步把阈值提高为硬门禁,避免一次性强约束导致频繁误报与团队抵触。

门禁与报告的分工本质是"强约束"与"软观测"的平衡。放错位置会带来两种代价:把高误报的指标设为门禁会频繁阻塞开发;把强约束指标只做报告则形同虚设。正确做法是按"约束刚性 × 误报率"决定位置,并配合"渐进式收紧"策略,让团队先适应再强制。

#

16. 多适应度函数的加权评分与「架构健康分」

如何对多个适应度函数进行加权评分,形成"架构健康分"(Architecture Health Score)?

  • 多维度约束的聚合
  • 加权与归一化
  • 健康分的观测与决策价值

单个适应度函数只反映单一维度,而"架构健康分"把多个维度的打分聚合为一个可观测的总体指标,便于高层跟踪与趋势监控。实现时先为每个适应度函数定义"得分"(如通过=100、有警告=0~99、失败=0),再按约束的重要性分配权重(如依赖方向 30%、性能预算 20%、安全 20%、复杂度 15%、整洁度 15%),最后加权求和得到 0~100 的健康分。关键是归一化:不同函数量纲不同(布尔/阈值/百分比),需统一为同一 0~100 区间再加权。健康分适合作为"报告"角色放入仪表盘,展示随时间变化趋势,帮助识别"架构退化拐点",而非作为硬门禁,因为聚合会掩盖单一硬约束的失败。

健康分的价值在于"把复杂的多维约束压缩成一个可对话的指标",让非技术利益相关者也能理解架构趋势。但它的局限是聚合会稀释单点硬性失败——一个依赖方向违规可能被其他高分项掩盖。因此健康分应与"硬门禁"分层:健康分做趋势观测,关键硬约束仍单独设门禁。加权权重需随业务目标动态调整。

#

17. 用无用代码/死分支检测守护整洁度的适应度函数

如何用无用代码/死分支检测构建守护整洁度(cleanliness)的适应度函数?

  • 无用代码与死分支的危害
  • 检测工具与方法
  • 作为整洁度门禁的落地

无用代码(dead code)与死分支(unreachable branch)会增大维护成本、误导阅读者、掩盖真实逻辑,是整洁度恶化的常见信号。构建守护整洁度的适应度函数时,先定义"整洁度阈值"(如死代码覆盖率、未使用导出、可执行分支覆盖率),再用静态分析工具检测:Java 可用 SonarQube、PMD、jscpd,前端可用 ESLint no-unused-vars、ts-prune、Knip,语言层面可用编译器的未使用告警。检测结果作为"报告"(渐进式)或"门禁"(阈值内)接入流水线。因其误报率较高、且清理无功能风险,通常作为报告看趋势,或设定宽松阈值做软门禁,与"重构周"配合渐进清理。

无用代码是"渐进式债务"的典型:单次新增看似无害,累积后却让代码库难以理解。整洁度函数的价值在于"把单靠自觉的清理变成可度量的趋势",并帮助定位死代码集中点。由于误报与清理成本,它更适合软门禁/报告而非硬门禁,核心是防止"取舍失衡"的教条式治理。