Hi,我是阿昌
,今天学习记录的是关于运用自动化工具诊断分析Sharing项目
的内容。
之前文章的两个遗留系统常用的分析工具:ArchUnit
和 Dependencies 依赖分析工具
。
了解了它们的基本使用方法,但是实际落地到项目中,经常会遇到一些问题,比如:
下面用 ArchUnit 和 Dependencies 对 Sharing 项目进行一次整体分析。
这个分析由五部分组成。
新的架构设计进行调整
。定义出依赖规则
。转化成 Dependencies 的 Rule 规则
,然后进行扫描分析
。转化成 ArchUnit 的用例
,进行扫描分析
。需要重构的问题清单
,作为下一阶段代码重构的输入
。首先,回顾一下 Sharing 项目目前代码的方式。
如下图所示,所有的代码是以技术维度来组织。
例如把所有页面都放在 ui 的包下
,或者把所有的模型都放在 model 下
。
这个时候就会遇到代码散落在各处
,约束规则不好写
的问题。
如果不先以未来架构设计的方式来组织代码,那么所有依赖规则的编写只能细分到类,不能按包的维度来编写。
这样首先会导致用例增多,其次规则也会比较分散,给后续维护带来不便。
所以为了更好地进行分析,首先会先按未来的架构设计组织代码。
来看看梳理的新组件划分架构。
在新的架构设计中,横向维度划分了 3 个分层,纵向维度划分了 3 个业务组件、2 个功能组件以及 3 个技术组件。
注意,需要让代码的架构以及名称与架构图的设计对应上,这样便于理解和维护。
接下来,就可以按照新的代码结构进行调整了。
这个时候要注意借助 IDE 的安全重构方法来移动代码,避免手工移动。
比如,可以通过 Move Class 的方法
(选中文件后按下 F6 快捷键或者点击 Refactor->Move Class 菜单)进行移动,这样编辑器会自动帮我们调整相关的引用,调整后 Sharing 新的目录结构是后面这样。
在实际项目落地中,这个步骤有三点注意事项。
第一点,这个步骤最好可以拉通相关的开发、架构等干系人(我们可以通过版本管理系统来查看之前这个文件主要的维护人)一起进行梳理
。因为有些文件我们能从类名判断它属于哪个组件,但是有些文件如果之前设计得不好,还需要跟相关的同学确认。
第二点,移动文件会触发 CI 判定这些文件是新增文件,进而触发增量扫描,CI 会将以前遗留的很多问题也扫描出来。但注意,这些问题不是因为移动文件产生的,而是原本就存在于代码库中。需要和相关的干系人进行澄清说明,以免移动后的代码提交无法入库
。
第三点,工程目录结构的调整会涉及到大量文件的位置变化,需要在团队中进行拉通,避免很多临时分支还在基于旧的代码目录结构进行开发,否则后期会产生大量的代码冲突,解决成本非常高
。
基于新的代码结构,就可以设计架构约束规则了。
首先,回顾一下 Sharing2.0 架构的两个重要约束原则。
基于这两个原则,结合代码结构设计出新的架构核心的 5 个约束规则,可以参考后面这张表格。
梳理出这些约束规则后,就可以将这些规则转换成工具的规则进行分析了。
首先,需要定义各个组件的 Scope,这一步相当于是定义各个组件的范围。
如下图所示,新增一个 Scope
后,可以选择在项目中对应的的目录,当然也可以直接在 Pattern 中写正则表达式
来进行定义。
定义好 Scope 之后,我们将 5 个核心约束规则转化为 Dependencies 的依赖规则。
下图中的 Deny usages of feature in library 表示 feature 范围下的代码不能被 library 范围下的代码直接依赖。
当完成依赖规则定义后,Dependencies 扫描结果会自动化将所有的异常依赖标记为红色,如下图所示。
从分析结果可以看出,目前 Sharing 项目有四个组件存在依赖问题,需要解耦
,分别为文件组件、消息组件、基座组件以及日志组件。
打开具体有异常的类,IDE 还会自动在代码加上警告的红色线。
这是 Dependencies 功能与 IDE 高度集成带来的好处,可以在日常开发中就注意到架构约束的问题,避免破坏架构规则。
接下来,ArchUnit 的分析思路也是定义包范围,并将 5 个约束转化为依赖规则。
首先,可以参考 Dependencies 的规则封装形式,将 ArchUnit 的语法也做进一步的封装,这样可以提高代码的复用性,降低后续用例的维护成本。
//某个包只能依赖另外一个包
private static ClassesShouldConjunction target_package_not_dependOn_other_package(String targetPackage, String... otherPackages) {
return noClasses().that().resideInAPackage(targetPackage)
.should().dependOnClassesThat().resideInAnyPackage(otherPackages);
}
//某个包只能依赖它自己,不能依赖其他包
private static ClassesShouldConjunction target_package_only_dependOn_itSelf(String targetPackage) {
return classes().that().resideInAPackage(targetPackage)
.should().dependOnClassesThat().resideInAPackage(targetPackage);
}
接着,定义不同的分层和组件。因为已经按未来的架构调整了工程目录,所以这一步可以很方便地定义出对应的分层和组件范围。
private static final String BASE = "com.jkb.junbin.sharing.";
private static final String FEATURE = BASE + "feature..";
private static final String FUNCTION = BASE + "function..";
private static final String LIBRARY = BASE + "library..";
private static final String FILE_BUNDLE = BASE + "feature.file..";
private static final String MESSAGE_BUNDLE = BASE + "feature.message..";
private static final String ACCOUNT_BUNDLE = BASE + "feature.account..";
最后将 5 个约束规则转化为 ArchUnit 的架构约束代码。
这个时候前面封装的约束规则方法就起作用了,每个用例只需要传递相关的组件定义就可以了。
//规则1:library包下的类只能依赖自己包下的类,不能依赖function包或者feature包下的类
@ArchTest
public static final ArchRule library_should_only_dependOn_itself =
target_package_not_dependOn_other_package(LIBRARY,FUNCTION,FEATURE);
//规则2:function包下的类不能依赖feature包下的类
@ArchTest
public static final ArchRule function_should_not_dependOn_feature =
target_package_not_dependOn_other_package(FUNCTION, FEATURE);
//规则3:account包下的类不能依赖file或者message包下的类
@ArchTest
public static final ArchRule account_bundle_should_not_dependOn_other_bundle =
target_package_not_dependOn_other_package(ACCOUNT_BUNDLE, FILE_BUNDLE, MESSAGE_BUNDLE);
//规则4:file包下的类不能依赖account或者message包下的类
@ArchTest
public static final ArchRule file_bundle_should_not_dependOn_other_feature =
target_package_not_dependOn_other_package(FILE_BUNDLE, MESSAGE_BUNDLE, ACCOUNT_BUNDLE);
//规则5:message包下的类不能依赖account或者file包下的类
@ArchTest
public static final ArchRule message_bundle_should_not_dependOn_other_bundle =
target_package_not_dependOn_other_package(MESSAGE_BUNDLE, FILE_BUNDLE, ACCOUNT_BUNDLE);
编写完成后,可以直接执行这 5 个用例。
用例执行的结果是后面这样。
从日志结果可以看出,目前 Sharing 项目有 4 个约束规则不通过,分别是文件组件存在横向依赖、消息组件存在横向依赖、功能组件存在反向依赖以及基础组件存在反向依赖。
ArchUnit 和 Dependencies 依赖分析功能对 Sharing 项目进行了一次分析。
为了更加方便地编写架构规则,需要将代码架构先按未来的架构设计进行调整。
接着只需要定义组件的范围以及约束规则就可以了。
最后结合工具辅助,梳理出 Sharing 按未来架构设计需要处理的问题清单。
开头的 3 个问题你答案:
未来的架构设计调整代码结构,方便约束规则的设计
。架构原则
以及代码结构
来进行设计
。编写规则
。