06 分析工具:几百万行规模的遗留系统怎么分析?
你好,我是黄俊彬。上节课我们分析了Sharing项目旧架构的痛点,同时也对新的架构做了规划,接下来我们就开始落地改造。不过在动手之前,我们要先设想一下如何计划整个重构的工作。
首先,我们已经设计了未来的架构,那怎么把现在的代码变成未来架构的样子呢?这是个关键问题。其次,我们还得对重构的复杂度有一个整体的评估,这样才能合理安排任务,因为架构的改造没办法一蹴而就,只能分而治之。最后是怎么验收,也就是如何确保重构后的代码符合未来的架构设计?
总结一下,就是这三个方面的问题。
1.怎么评估工作量?从旧的架构重构到新的架构,我们总共需要调整哪些代码?
2.怎么制定优先级?哪些代码先重构,哪些代码后重构?
3. 怎么进行度量?如何确定阶段性成果和最终成果?
这些问题都是我们在落地改造之前要先搞清楚的,不然最终的架构重构结果,大概率很难像设计的那样落地。我们这个课程中的Sharing项目由于做了简化,代码量不多,分析起来相对容易。但在实际的项目中,我们往往面对的是几百万行规模的遗留系统,如果仅靠人工分析,效率肯定是非常低的,而且还容易出错。
所以,这里建议的做法是采用自动化工具来辅助分析遗留系统。而分析遗留系统的整体思路就是,借助自动化的工具,分析现有架构与未来架构设计的差距,梳理出需要重构的代码。然后就可以制定优先级,分阶段重构改造了。
因此这节课,我将带你一起学习两种常见的遗留系统重构分析工具,分别是ArchUnit和Android Studio的Dependencies依赖分析工具。我会给你介绍这两个工具的基本使用方法,并通过示例给你讲解如何运用它们分析遗留系统架构。
ArchUnit 架构守护
我们先来看ArchUnit。ArchUnit 是一个免费、简单且可扩展的库,它可以用任何Java单元测试框架来检查Java代码的架构,还可以检查包和类、层和片之间的依赖关系,非常适合对遗留系统做分析,还可以作为架构的守护门禁。
下面我用一个简单的例子,给你简单讲解一下ArchUnit的使用方式。首先,我们需要在配置文件中引入ArchUnit的依赖。
接着跟编写单元测试一样,在测试用例中用ArchUnit封装好的API来编写架构约束用例。
@RunWith(ArchUnitRunner.class)
@AnalyzeClasses(packages = "com.jkb.junbin.sharing")
public class MyArchitectureTest {
//消息组件不能依赖文件组件
@ArchTest
public static final ArchRule message_should_not_depends_file = noClasses().that().resideInAPackage("..message..")
.should().dependOnClassesThat().resideInAPackage("..file..");
}
编写完后,就可以像运行单元测试用例一样运行ArchUnit用例了。
如果你已经掌握了基本的测试用例编写,那么要用好ArchUnit,你只需要了解其提供的API就可以了。这里我将常用的四种ArchUnit语法规则总结到了下面的表格中,更多的使用场景,你可以参考ArchUnit在官网中总结的编写示例。
和单元测试一样,我们可以将这些架构守护用例集成到CI的质量门禁上,这样就能防止破坏架构规则的代码被合入。
我们前面介绍了如何用ArchUnit设计架构用例,那怎么将ArchUnit应用到遗留系统的架构分析中呢?
其实方法很简单,我们只需要用ArchUnit的语法,把未来的架构设计编写成架构守护规则即可。这个时候执行这些用例会有大量的错误,但如果把这些错误都解决了,那就意味着我们已经重构出符合新的约束规则的架构。
我们具体来看看怎么在Sharing项目中应用。还记得我们上节课针对Sharing新架构,总结的两个核心架构约束规则吗?一个是业务组件之间不能有直接的依赖,另一个是下层组件不能反向依赖上层的组件。
现在我们以消息组件不能直接依赖文件组件为例,设计架构守护用例,约束规则是后面这样。
@ArchTest
public static final ArchRule message_should_not_depends_file = noClasses().that().resideInAPackage("..message..")
.should().dependOnClassesThat().resideInAPackage("..file..");
然后我们执行该用例,可以发现这个用例现在是不通过的,提示的日志是后面这样。
从上图可以看到,消息组件中的MessageFragment依赖了文件组件的FileController,这个依赖不符合新的架构设计,需要进行解耦。如上图所示,错误的日志会提示具体的方法、变量以及对应的代码行,我们借此定位到具体的异常依赖代码就很方便了。
但如果每次都需要我们去分析这个日志,特别是当耦合特别复杂时,效率就会非常低。所以下面我给你介绍第二种可视化做得更好的分析工具:Android Studio的Dependencies分析功能。
Dependencies 依赖分析
Android Studio的 Dependencies依赖分析功能可以分析应用程序中组件、包和类之间的依赖关系。使用时,只需要在项目中选择:Code->Analyze Code->Dependencies,就能触发对当前工程的依赖分析。
分析完成后,在Dependency Viewer中可以查看项目的依赖情况,如下图所示,在左边的视图选择需要分析的目录后,右边的视图会自动显示其依赖的库和代码。这里我们选择消息组件主页面MessageFragment后,可以看到它依赖的类有AccountController和FileController等。
根据Sharing新架构的规则,业务组件之间不能有依赖,所以,MessageFragment对AccountController和FileController的依赖明显是异常的,需要进行解耦。
但是,如果项目中的代码规模很大,我们很难一个个进行人工排查,如何利用Dependencies依赖分析功能来自动化地进行大规模分析呢?
答案是使用Dependency Validation功能检查代码。具体来讲就是点击Edit Rule,使用正则表达式配置约束条件,从而扫描代码中不符合规则的异常依赖。就像后面图里展示的这样,我们配置消息组件不能依赖文件和账户组件。
增加完这条约束规则后,我们重新触发Dependencies功能进行分析,这个时候IDE会自动标红显示异常的依赖。每当我们在本地完成依赖解耦后,都可以频繁触发Dependencies扫描,来判断重构后的代码是否符合新的架构规则。
你可能会想,这个规则自己用着不错,怎么在团队里共享呢?很简单,在编写规则的时候勾上Share through VCS,规则文件就会自动保存在.idea目录下,然后将对应的配置文件也提交到版本管理仓库中,就可以在团队内共享同一个规则了。
另外,Dependencies依赖分析还支持将依赖关系导出为xml,你可以利用这个功能将xml导入到excel或者编写脚本中进行分析,便于定制统计筛选。
总结
好,这节课到这里就结束了。今天我给你介绍了两个常用的遗留系统依赖分析工具:ArchUnit和Dependencies依赖分析,这两个工具可以帮助我们分析当前架构与未来架构的设计差距,自动梳理出需要重构的代码。下面我给你总结一下这两个工具的优缺点。
在实际的项目中,我建议将两个工具结合使用:Dependencies依赖分析可以更多用于开发阶段,当重构完成后频繁扫描代码,验证不符合架构规则的依赖是否解耦;ArchUnit则可以作为架构守护工具在CI上进行验证,避免破坏架构规则的代码被合入版本库中。
最后,让我们再回到开头的三个问题。首先是怎么评估工作量?从旧的架构重构到新的架构,我们总共需要调整哪些代码?对此,我们可以通过ArchUnit、Dependencies依赖分析等工具将未来架构的设计转化成约束规则,对当前工程的代码进行扫描分析,这样我们就能得出重构需要处理的问题清单。
第二个问题是怎么制定优先级?哪些代码先重构,哪些后重构?其实在实际的项目中,我们需要结合业务的迭代、组件影响面以及重构的复杂度来制定优先级。我们要优先挑选出没有在开发新功能的组件;其次再优先考虑重构功能组件和技术组件,因为这些组件是支持业务的基础;最后结合分析出的异常依赖数量评估业务组件重构的复杂度。
最后一个问题是怎么进行度量?如何确定阶段性成果和最终成果?我们判断是否重构一个组件的标准是:ArchUnit的架构约束用例执行通过,或者Dependencies依赖分析的扫描没有识别出红色的警告。我们可以把这个标准作为验收一个组件是否完成解耦的标准,确保重构工作是按照新的架构设计进行的,并且落实到位,最终完成整个系统的重构。
下节课,我们将使用ArchUnit和Dependencies依赖分析,对Sharing项目进行架构分析,梳理出按未来架构设计需要处理的问题清单,作为后续架构重构落地的输入,敬请期待。
思考题
感谢你学完了今天的内容,今天的思考题是这样的:在你的项目中是如何进行架构的守护,防止代码腐化的?
欢迎你在留言区与我交流讨论,也欢迎你把它分享给你的同事或朋友,我们一起来高效、高质量交付软件!
- zenk 👍(1) 💬(1)
老师,业务组件的优先级,是不是复杂度低的优先? 我理解这样重构以后可能会降低复杂度高的业务组件
2023-02-22 - 稻草人的忧桑 👍(0) 💬(2)
dependency的依赖分析,单例是不是分析不出来
2023-03-26 - 永远年轻 👍(0) 💬(1)
答案是使用 Dependency Validation 功能检查代码。具体来讲就是点击 Edit Rule,使用正则表达式配置约束条件,从而扫描代码中不符合规则的异常依赖。就像后面图里展示的这样,我们配置消息组件不能依赖文件和账户组件。 ====== 能详细讲下上述提到「正则表达式」吗
2023-03-18 - MrsLEO 👍(0) 💬(1)
archunit支持kotlin项目么
2023-03-03 - 3.141516 👍(0) 💬(1)
老师好,ArchUnit只能找到当前module的包吗,其他module可以找到吗
2023-02-23 - peter 👍(0) 💬(1)
请教老师几个问题: Q1:“层”、“片”是什么? ArchUnit部分,提到“还可以检查包和类、层和片之间的依赖关系”,这里的“层”和“片”是什么?“层”可能是指分层,那么“片”是什么? Q2:类依赖分析API的例子中,为什么只有一个类的名字X? 类依赖,必然有两个类,应该有两个名字,但此处为什么只有一个名字X? Q3:Jekens也能用于移动端吗? 我了解一点后端,印象中Jekens是用于后端的,安卓端也可以用吗? Q4:找不到Dependency Validation. AS2021,code - Analyze code - Dependencies,没有找到配置规则的界面。文中说“点击EditRule”,但我这里没有发现。 我是win10下用AS2021,
2023-02-22 - Geek_6061ea 👍(0) 💬(0)
如果模块太多,添加了非常多的 xxx.xml 文件到 Scopes 文件夹下,这些 xml 都会展示在 Android Studio 最左边的 Project/Android 切换菜单栏里面,影响每个同学切换 Project/Android 的体验。有什么方法可以不展示在 Project/Android 切换菜单栏吗?
2023-04-20