工程模板、评审与例外治理:规范怎样变成可执行门禁
新项目需要选择 JDK、组织构建、配置日志并建立测试入口。已有项目则不断接收功能、依赖升级和结构调整。模板可以减少重复的初始化工作,评审帮助判断每次修改,构建规则持续检查能够自动判断的部分。
三者的维护周期不同:模板影响新建时刻,评审围绕具体变更,自动规则随每次构建运行。旧项目不会因为模板更新就自动获得新能力,需要单独安排迁移。
团队共同约定由哪些部分组成
模板提供可以启动的最小工程
模板适合固定构建入口、工具版本、目录组织和少量有用示例。生成后至少应能编译、运行测试,并知道怎样添加业务代码。只生成一批空目录,仍把依赖选择和运行方法留给使用者,节省的工作很有限。
共同部分可以分层维护:
| 载体 | 适合内容 | 更新方式 |
|---|---|---|
| 项目模板 | 初始化目录、示例测试、说明 | 新项目生成,旧项目按差异迁移 |
| Maven parent / BOM | 插件、依赖版本及共同配置 | 项目显式升级引用版本 |
| 运行库或 Starter | 可复用实现与受控默认配置 | 升级依赖并验证自动装配变化 |
| 构建工作流 | 执行命令、制品与报告步骤 | 版本化引用或受控复制更新 |
| 简短规范 | 设计理由、例外条件和人工判断 | 随实际问题修订并通知使用者 |
Maven 的继承、聚合和依赖管理用途不同。parent 更新也会改变插件执行或依赖解析,需要在代表性项目上构建验证;不能将共享 parent 当成不需要测试的文本配置。Maven 多模块与继承
自动检查处理明确判断
编译错误、固定依赖规则、明确的禁用 API 和必需测试,可以交给工具。代码评审不必反复指出这些已经能机械识别的问题。
是否应拆分模块、接口是否符合业务、失败后用户看到什么,仍需要结合变更目的判断。自动检查可以提供信息,却不能仅凭类数量、覆盖率或复杂度分数批准整个设计。
规则也应有使用成本。一次很小的文案修改无需启动全部数据库集群;数据库事务修改则需要相关适配器测试。根据影响范围安排快速反馈和完整构建,但保留防止漏判的最终组合检查。
例外表达暂缓与补救安排
旧代码可能暂时不能遵守一条新规则,或某个框架接入确实需要特殊访问。记录例外时,需要说明具体规则、类型或模块、原因、维护人、影响、替代措施及到期条件。
批准例外不改变事实:依赖仍然存在,风险仍然由相应团队承担。记录应足够精确,使新的违规不会自动落进旧例外,也让已修复项及时退出名单。
代码评审怎样围绕一次具体修改
作者先交代意图和可观察结果
一个容易评审的改动应有单一主要目的,例如“取消订单时同时释放预留记录”。描述说明旧行为、新行为、数据影响及已执行测试,涉及部署顺序或兼容窗口时一并交代。
格式调整、文件移动和业务改变尽量分开,让评审者能看到真正需要判断的内容。很大的功能可以拆成保持构建通过的小步提交,例如先增加兼容字段,再迁移调用方,最后移除旧路径。
评审者从整体设计和行为开始,再检查具体代码、测试、命名与说明。反馈区分必须修复的正确性问题、风险讨论和可选风格建议,避免将个人偏好写成没有理由的阻塞条件。Google 代码评审指南
按变化选择问题
| 修改内容 | 值得重点追问 |
|---|---|
| 新增写接口 | 谁可以写,重复请求怎样识别,部分失败留下什么 |
| 数据库结构 | 旧应用还能否读取,迁移是否锁表,回退路径是什么 |
| 并发或异步 | 谁拥有状态,取消是否生效,线程上下文和资源是否释放 |
| 外部接口 | 超时后结果如何确认,重试是否产生重复副作用 |
| 缓存或查询优化 | 返回是否过期,排序是否稳定,实际计划与规模如何 |
| 公共库升级 | 哪些使用方受影响,协议与行为是否兼容 |
这些问题按变更选取,不要求每个提交都粘贴整份清单。没有涉及并发的常量重命名,无需制造线程风险说明;增加重试循环时,则不能只贴一份格式检查通过报告。
测试报告需要能回到代码
“测试通过”应能对应命令、测试对象和关键断言。事务修改需要看到失败后数据库的状态,权限修改需要看到拒绝分支,生成 SDK 修改需要编译旧调用方。日志截图和执行次数可以辅助定位,不能替代行为检查。
评审意见解决后,重读最终差异。修改期间新增的文件、删除的断言或改变的配置,可能不在最初讨论范围内。最终提交应只包含当前变更,避免顺带加入个人配置、运行数据库和临时输出。
生成工程并验证精确例外
从模板建立真正可编译的项目
下载 构建规则实验,在 Linux amd64 解压进入 quality-build-rules。需要 Bash、Docker Engine;普通用户拥有 Docker 权限和当前目录写权限。工具镜像为 Maven 3.9.12 与 Java 17,使用宿主 UID/GID,源码目标为 Java 17。
bash new-project.sh sample-service
bash run-docker.sh -f generated/sample-service/pom.xml clean verify
test -f generated/sample-service/target/surefire-reports/example.generated.QuantityTest.txt || exit 1脚本只在当前实验目录的 generated 下创建指定项目。名称限制为小写字母开头、后续小写字母数字或短横线,长度最多 40;拒绝将 generated 作为符号链接,也不覆盖已有目标。
实际生成内容为:
generated/sample-service/
├── pom.xml artifactId=sample-service
├── src/main/java/.../Quantity.java
└── src/test/java/.../QuantityTest.java预期 QuantityTest 为 1 项通过:正数量 2 被保留,0 被拒绝并返回 POSITIVE_QUANTITY_REQUIRED。可以在 target/classes 查看编译后的 Quantity.class,在 surefire-reports 查看测试结果。
该模板是普通 Java 工程,没有 HTTP 服务、认证或生产部署配置。引入这些能力后,应追加相应启动、配置和访问测试,而不能沿用一个值对象测试宣称服务已经可上线。
防止重复生成覆盖已有修改
再次使用相同名称应拒绝:
if bash new-project.sh sample-service > existing.log 2>&1; then
echo '重复生成不应覆盖现有目录' >&2
exit 1
fi
grep -q DESTINATION_EXISTS existing.log || exit 1
bash run-docker.sh -f generated/sample-service/pom.xml clean verify修正方式是选择新名称,或者在确认内容后手工处理旧工程,不由模板脚本递归删除。源 template 目录中不应包含 target、缓存或实际密钥;发布模板时检查最终生成目录里的源码和构建结果。
Java 25 对照使用 BUILD_IMAGE=maven:3.9.12-eclipse-temurin-25 加到 run-docker 命令前,编译目标不变。
从真实 ArchUnit 结果识别历史违规
另下载 工程质量实验,解压进入 quality-engineering。执行:
bash run-docker.sh -Dtest=ExceptionScopeTest -Dsurefire.failIfNoSpecifiedTests=false
test -f verification/target/surefire-reports/example.quality.verification.ExceptionScopeTest.txt || exit 1预期 ExceptionScopeTest 为 2 项通过。它实际导入编译后的 BadDomain 和 NewBadDomain,用 ArchUnit 1.5.0 检查领域包对适配器的依赖,从 EvaluationResult 取得违规明细。ArchUnit User Guide
暂缓记录只匹配一项具体签名:
rule: domain-no-adapter
Field <example.quality.fixture.domain.BadDomain.leakedRepository>
has type <example.quality.adapter.JdbcOrders>
owner: orders-maintainers
reason: 将旧存储字段替换为应用端口
expiresAt: 测试时钟之后七天规则名和完整成员签名共同限定范围。实现只移除诊断末尾的源码行位置,保留来源类、字段及目标类型;增加一个新字段或新来源类,不会自动匹配原记录。固定 Clock 用于稳定测试到期判断,真实审批系统应使用受控时间及持久记录。
新违规、过期和已修复分别处理
| 实际情况 | 实验结果 |
|---|---|
| 只有指定 BadDomain,审批仍有效 | 允许当前这项违规 |
| 增加 NewBadDomain,沿用旧审批 | UNAPPROVED,包含新类型 |
| 时钟到达审批期限 | EXPIRED_APPROVAL |
| 违规已经消失但审批仍存在 | STALE_OR_DUPLICATE_APPROVAL |
| 违规与审批都已移除 | 通过 |
普通测试将这些拒绝作为预期结果。要观察新增违规直接中断构建,可以运行:
if bash run-docker.sh -Dtest=ExceptionScopeTest \
-Dsurefire.failIfNoSpecifiedTests=false -Dquality.exception.negative=true \
> exception-negative.log 2>&1; then
echo '新违规不应被旧审批放行' >&2
exit 1
fi
grep -q UNAPPROVED exception-negative.log || exit 1
grep -q NewBadDomain exception-negative.log || exit 1
bash run-docker.sh -Dtest=ExceptionScopeTest -Dsurefire.failIfNoSpecifiedTests=false恢复时去掉负例开关,两项测试重新通过。夹具只在测试源码中,生产模块仍执行正常依赖规则。测试使用已给定的审批记录,检查它与当前违规集合的匹配;审批人的判断和记录持久化由团队工作流负责。
规范更新与例外到期怎样持续处理
模板也需要发布和迁移
模板升级时记录改变了什么、适用哪些项目,以及使用方需要执行哪些步骤。选取最小工程和一个有代表性的已有工程,分别验证新建与升级路径。只测试空项目,容易漏掉已经添加自定义配置的使用方。
生成项目可以保存模板版本,帮助后来定位来源。安全或兼容修复发布后,应通过依赖升级、迁移脚本或明确变更通知推动旧项目采用,不能等它们重新生成。
共享 Starter 的默认行为更需谨慎。自动创建 Bean、注册 Filter 或改变序列化配置,会影响使用方现有行为;提供明确的启用条件、覆盖方式和升级说明,并在真实应用上下文中验证。
到期触发重新判断,不自动扩大授权
例外接近到期时,维护者先检查原问题是否仍存在,补救措施是否有效,以及能否完成修复。确需延期,应重新说明原因和影响,不无限顺延同一个日期。
例外已消失时删除对应记录,让下一次新增同类问题重新被发现。修改规则或升级工具导致诊断格式变化时,单独审查转换后的匹配集合,不能为了让构建变绿而重新生成全量允许列表。
哪些例外可以阻止合并、哪些只需要通知,属于团队工作流选择。对高风险权限或数据完整性问题,应有明确负责人作出决定;机械的到期脚本无法替代这种判断。
观察规范是否真正帮助开发
可以记录新项目首次成功耗时、重复出现的配置错误、评审等待时间,以及规则产生的真实问题和误报。用这些信息调整模板和流程,不把评审意见数量或规则数量作为工作质量目标。
规范让常见工作更容易正确完成,也应让例外容易解释。一个需求如果总是需要绕过三层模板包装,可能是模板承担了不适合通用化的业务逻辑,需要简化。
| 现象 | 需要核对 | 改进方向 |
|---|---|---|
| 模板生成成功却无法构建 | 实际生成文件、插件、仓库与 JDK | 将生成产物编译测试纳入模板维护 |
| 旧项目长期没有新默认项 | 模板版本与迁移方式 | 提供升级路径并逐项验证 |
| 例外越来越宽 | 匹配范围、延期理由、维护人 | 收缩到具体问题,修复后移除 |
| 评审只讨论格式 | 自动格式化、检查反馈和变更说明 | 自动处理机械项,讨论行为和风险 |
| 构建通过仍发生事故 | 测试对象是否代表真实运行 | 加入相关失败对照,修正遗漏假设 |
模板生成目录、构建产物和负例日志都保留在实验解压目录,没有修改其他项目。清理前确认生成工程是否已有自己的修改;需要保留的代码应先进入正常版本管理。
权威资料与规范地址
工程模板与自动规则
- Maven 多模块与继承:https://maven.apache.org/guides/mini/guide-multiple-modules.html
- ArchUnit:https://www.archunit.org/userguide/html/000_Index.html
代码评审方法
- Google 代码评审指南:https://google.github.io/eng-practices/review/reviewer/
