不少团队会进行代码走查来促进代码质量,代码走查的目标可以是设计或实现的缺陷,这往往依赖于走查者的个人经验和技术功底;也有对照代码规范的专项走查,这时需要的是细致和耐心。比如团队决定使用slf4j来作为日志api,以便将系统和日志实现解耦。有时因为疏忽,开发者可能会意外地导入别的日志库,比如log4j。即使团队有良好的代码走查实践,要发现如此琐碎的问题还是挺费功夫的。计算机更适合完成这类重复的工作,我们只要找到合适的工具就行了。
比如刚才的日志依赖问题,可以交给classycle,它支持自定义类之间的依赖规则,并依此检查代码库:
list-1 检查代码库不依赖于log4j,保存在src/test/resources/classycle-dependency-definition中
show allResults
[log4j] = org.apache.log4j.*
[root] = your.project.root.*
check [root] independentOf [log4j]
如果是Maven项目,可以很方便的执行检查:
list-2 使用classycle:check执行检查
如果classycle找到了不符合规则的依赖,会输出到target/classycle目录下的报告中。
show onlyShortestPaths allResults
check [root] independentOf [log4j]
Unexpected dependencies found:
sample.bar.Bar
-> org.apache.log4j.Logger
###如果发现不良的依赖就让构建失败
静态检查工具使得团队可以把更多时间投入到推敲代码上,去发现新的问题,而不是枯燥地查找已知的缺陷。既然已经有了廉价的检查手段,本着越早发现问题越容易解决的原则,不妨把检查任务集成到项目的构建过程中。如果团队已经建立了部署流水线,可以让流水线来执行检查,如果classycle发现有违反规则的依赖会使构建失败。
list-3 将检查任务绑定到maven verify中执行
最后记得让持续集成服务器把classycle生成的报告保存下来,方便团队排问题。
如果团队有幸可以从头构建代码库,那么最好从一开始就将依赖检查加入到流水线中,并逐步按需调整。如果接手的是遗留项目,最好谨慎一些,随着重构逐步丰富检查规则。无论是哪种情况,都要根据项目的实际情况定义检查规则,如果流水线因为检查不通过而长期处于失败状态,就失去意义了。
可能你会觉得如果依赖检查不通过就终止构建太严格了,不过这招往往很有效,尤其是当项目的周期较长,并有新的成员加入时,依赖检查可以帮助团队保护架构原则。比如,一个采用port & adapter架构(onion)的系统,可以检查domain不依赖于任何适配组件。
list-4 确保domain是onion架构中的“核心”
show allResults
[root] = your.project.root.*
[port] = your.project.root.domain.*
[adapters] = [root] excluding [port]
check [port] independentOf [adapters]
另一方面,重视依赖检查也可能反过来促进制定合理的架构约束。以下两种划分package的方案,你更倾向于哪种呢?
list-5 方案一:按职责划分
repositories/
UserRepository
TransactionRepository
ShipmentRepository
serializers/
UserSerializer
TransactionSerializer
ShipmentSerializer
models/
User
Transaction
Shipment
list-6 方案二:按领域划分
user/
UserRepository
UserSerializer
User
transaction/
TransactionRepository
TransactionSerializer
Transaction
shipment/
ShipmentRepository
ShipmentSerializer
Shipment
假设user不应该依赖transaction的话,第一种方案需要将检查规则定义到类的级别,而第二种方案能以较粗的粒度(package)来管理依赖关系,这样更为经济高效。
classycle-maven-plugin还支持把依赖规则嵌入到pom.xml中,但我更推荐使用单独的规则文件。一方面,随着项目的衍化,需要检查的规则可能会增长,一大段堆规则文本会使得pom.xml变得很臃肿,另一方面,单独的规则文件方便团队对比规历史版本,查看其修订记录及原因。
对于gradle项目,我暂时还没有找到现成的插件,不过classycle本身支持ant task,所以可以用gradle ant插件来集成。
directory per domain model or directory per layer, role