配置、错误、日志与测试结构:横切能力怎样成为工程默认项
业务代码没变,为什么换环境后配置、错误和日志会一起失真? 如果答案只落在“大家遵守约定”,边界就会随人员、交付压力和代码规模一起失效。横切能力若靠开发者记忆接入,就会在每个模块产生不同语义。配置解析、错误分类、日志上下文和测试夹具必须由统一边界提供,同时允许业务模块声明自己的类型化需求。
结构质量首先是一张可计算的图
代码结构可以表示为有向图:类、包或模块是节点,import、字段类型、方法签名、继承、注解、调用和反射配置形成边。目录树只显示节点被放在哪里,依赖图才显示谁真正知道谁。所谓“内层”“外层”也不是视觉位置,而是边被允许指向的方向。
配置源按明确优先级合并为不可变类型对象,启动时完成未知键、范围和组合校验;错误由边界翻译为稳定身份;trace/correlation 通过同步与异步上下文传播;测试按单元、模块、契约和集成划分真实依赖。
从一次变更追踪耦合怎样扩散
变化传播可以用 blast radius 近似:直接依赖者、传递依赖者、需要重新编译的模块、需要联合发布的制品、需要重建的测试夹具。指标不追求一个“优秀常数”,而是比较重构前后以及同类模块的趋势。高扇入的 public API 要稳定,高扇出的应用编排要薄,领域对象不应对框架产生高出度。
建立 application bootstrap 负责配置与依赖装配,错误目录负责稳定类型和协议映射,logging policy 规定字段与脱敏,test kit 只提供构造器和外部假对象。模块可以扩展值,但不能重新定义优先级、错误身份或敏感日志规则。
静默忽略拼错配置会带默认值启动;全局 catch 打印堆栈再抛出会重复日志;ThreadLocal 上下文跨线程丢失;共享可变 Fixture 让测试顺序相关;为测试加入生产分支会污染设计。
这类失败常被误诊为“开发不规范”,实际根因通常是规则不可执行、职责没有 owner 或历史债务没有迁移路径。正确修复不是再写一页规范,而是把允许边、禁止边、转换点、事务 owner 和例外期限放进代码与流水线。
模块边界要同时控制可见性和依赖方向
Java 的 public 只说明类型在其可见范围内可访问;命名模块还通过 requires 控制可读模块,通过 exports 控制编译/运行访问,通过 opens 控制反射访问。classpath 项目也可以用多模块构建、internal 包和架构测试建立近似边界。关键是公开 API 数量要小于内部实现,而不是把所有类型都 public 后靠命名劝阻。
模型转换是边界的收费站
Service、Repository 与 Mapper 按决策所有权分工
配置、错误、日志和测试必须有统一默认语义
配置需要固定源优先级、类型绑定、未知键策略、范围/组合校验和启动失败规则。字符串散落在业务代码会让同一配置被不同模块以不同默认值解释。密钥不进入普通配置回显,动态配置还要有版本、原子快照、回调隔离和失败保留旧值策略。
错误在其 owner 边界翻译。底层 SQLException 不穿透到 API,领域拒绝也不伪装成基础设施异常。稳定 error type/code 用于机器判断,message 面向人且可本地化,日志只在真正处理或跨进程边界记录一次。traceId、operationId、tenant 与业务身份通过显式上下文传播并脱敏。
测试结构与生产结构对应:领域单元测试验证不变量,应用测试用 fake port 验证编排,adapter 集成测试验证真实协议,契约测试验证边界兼容,端到端只覆盖关键链。共享 Fixture 应不可变且最小;随机数据要保存 seed;测试不能通过生产代码的 testOnly 分支获得特权。
静态检查要按可见事实选择工具
文本/格式工具擅长确定风格,AST 工具看到语法结构,编译器插件看到类型与数据流,字节码分析看到编译后调用,架构测试看到包/类依赖,运行测试看到动态行为。工具能力不同,不应要求单文件检查器证明跨模块环,也不应靠集成测试检查 import 方向。
高置信安全/正确性问题可立即阻断;大量历史风格问题适合 baseline 后增量阻断;实验规则先度量误报。规则失败消息应包含 ruleId、违规边、为何危险、允许替代和例外入口,否则开发者只会添加 suppress。
可运行模型一:让结构退化变成数字
javac --release 17 -Xlint:all -Werror ConfigPrecedenceDemo.java
java ConfigPrecedenceDemo预期输出:
sources=[default, file, env, cli] resolvedPort=9090 winner=cli unknownKeys=1 startupRejected=true模型把“感觉不整洁”转换为依赖违规、字段泄漏、事务 owner、未知配置、质量预算或例外状态。反向实验要修改一个输入使门禁从通过变失败,并解释失败为何对应真实风险。只输出一个综合分数会掩盖根因,计数必须能回到具体边、字段、用例或规则。
可运行模型二:证明失败不会被平均值隐藏
javac --release 17 -Xlint:all -Werror ErrorContextDemo.java
java ErrorContextDemo预期输出:
failures=6 classified=6 correlationPresent=5 sensitiveLogged=0 missingContext=1 observable=false第二个模型专门保留少数失败:一个循环、两处 SQL 泄漏、一个缺失上下文、一项未锁定依赖或一个无 owner 例外。质量门禁不能因为总体通过率很高就放过系统性缺口;某些不变量是 all-or-nothing。
依赖治理要看解析结果,不只看声明
发布保存 declared graph、resolved graph、lock/BOM、仓库来源、artifact checksum、许可和漏洞报告。动态版本与 changing module 会让相同源码在不同时间解析不同结果;锁定能固定结果,但锁文件也必须在受控升级中重新解析、测试和审查差异。
漏洞优先级结合可达性、运行环境、数据暴露和修复可用性;unused 依赖仍扩大供应链与镜像;重复库可能引起类冲突。升级不是“版本越新越好”,而是小批次更新、兼容测试、行为对比和可回滚制品。
技术债必须有预算、趋势和终止条件
预算至少区分高风险正确性债、结构债、依赖债、测试债和可读性债。严重度乘数量的简单总分会让大量格式问题稀释一个安全缺陷。门禁采用分层阈值:高风险零容忍,中风险不得新增,低风险看趋势和变更触达范围。
偿债应与业务变化绑定:当某模块被修改时顺手收紧其 baseline;重大重构用依赖图和测试证明 blast radius 下降。不要把“重写”当唯一偿债方式,strangler、端口抽取、模型隔离和增加 characterization test 往往更可控。
模板、评审与例外形成闭环
模板提供最小可运行骨架:模块边界、依赖约束、类型化配置、错误入口、日志字段、测试层和构建门禁。模板不预装所有组件,不复制业务层空壳,也不生成无法删除的抽象。生成后就是普通代码,持续接受同一门禁。
评审根据风险分级。低风险由自动检查和一名 owner 验证;涉及数据迁移、公开契约、并发、一致性、权限或不可逆变更时,必须给出反向实验、指标、灰度和回滚。评审者验证证据能否支撑不变量,而不是重新编写提交者方案。
例外不是口头批准或永久 suppress。它具有规则、范围、owner、原因、风险、补偿控制、到期和删除条件;到期后流水线自动失败或要求重新评估。全局例外风险最大,优先缩小到文件、模块、依赖坐标或具体违规指纹。
安全与可观测不能被“代码质量”稀释
结构边界也是安全边界:只有 adapter 可访问外部网络,只有授权组件可读密钥,只有审计模块写审计事件,domain 不直接记录敏感对象。架构规则可阻止危险包依赖,配置规则可阻止默认口令,日志规则可阻止 token/PII 字段进入输出。
质量信号本身要可观测:规则通过率、baseline 总量与新增量、循环数、模块 API 面积、依赖更新年龄、未知配置、错误上下文缺失、flaky test 和例外到期。趋势按模块和 owner 展示,不用一个全项目“质量分”驱动表面优化。
门禁失败保存可行动证据而非完整源码或秘密。依赖报告与 SBOM 需要访问控制;日志样本脱敏;评审记录避免复制客户数据。质量平台拥有读取代码的高权限,其插件、规则包和生成模板也必须锁版本、审计来源并最小授权。
架构决策记录应该留下什么
记录模块节点、允许边、公开 API、模型 owner、转换损失、事务 owner、查询边界、配置优先级、错误目录、日志字段、测试层、规则能力、baseline、解析依赖图、例外与回滚。每条约束都链接到自动检查或反向实验。
横切能力若靠开发者记忆接入,就会在每个模块产生不同语义。配置解析、错误分类、日志上下文和测试夹具必须由统一边界提供,同时允许业务模块声明自己的类型化需求。 对这一主题,最终必须守住:应用只在类型化配置有效时启动,任一失败都保留稳定身份与脱敏关联上下文。如果结构只能从架构图看出、无法从构建产物和测试证明,它还不是工程事实。
应用服务、领域、Repository、Mapper 和 Query Service 的所有权唯一。配置未知键与组合错误阻止启动,错误身份和日志脱敏稳定。测试层对应真实边界,不共享可变 Fixture 或生产特权分支。
静态规则匹配其可观察事实,历史 baseline 不吸收新增违规。declared/resolved/locked 依赖图可复现,升级和漏洞有验证证据。技术债与例外有 owner、补偿、期限和可执行关闭条件。
高风险变更没有反向实验、观测和回滚证据就不能发布。两个 Java 17 模型严格编译运行,输出与正文一致。
实现依据
以下官方资料用于核对语言可见性、架构检查和依赖解析能力;具体工具只实现部分规则,不替代工程边界本身:
