@lsmn
2017-10-07T10:22:17.000000Z
字数 3807
阅读 5732
C#
C++
Roslyn
MSBuild
软件开发项目的规模越来越大,很容易加入冗余的代码层。本文作者分析了GitHub上的几个大型开源项目,统计了每个项目的冗余代码量。他在文中分享了自己的发现,并提出了若干改善代码管理的建议。
本文要点
- 代码冗余的原因多种多样,从未使用的变量到未完成的变更,再到废弃的代码;
- 冗余代码会产生一系列的影响,包括源代码臃肿、可靠性及可维护性降低。在某些情况下,死代码也会影响性能;
- 为了检测冗余代码,作者开发了一个工具,使用Roslyn创建C#源码抽象语法树。作者使用包括Roslyn和MSBuild在内的多个GitHub项目对这个工具进行了训练;
- 检测到冗余代码可以手动删除或添加注释,也可以使用一种自动化工具。使用特性分支有助于防止冗余代码检入主代码分支。
导言
前段时间,我开发了一款工具,分析源代码中的依赖关系。它使用Roslyn创建C#源代码抽象语法树,使用libclang创建C++源代码抽象语法树。为了验证它是否可以取得预期效果,我接下来实现了识别未使用方法的功能。结果显示,C#代码解析比C++代码解析准确得多,因此,我选择把重点放在C#分析器的进一步开发和其他人开发的更复杂的C#代码上。
起初,该工具会标记出冗余方法所在的行,在弄清楚问题范围之后,我实现了自动删除那些行的选项。一个典型的分析过程会多次执行这个工具,尽可能地修剪源代码树。接下来是多个变更还原循环,以便可以成功地构建并通过测试。失败的原因是工具行为异常或者已知的局限性,例如,反射或代码契约。
我选择了多个自己用过而又想回馈的C#项目,用它们的GitHub库训练了这个工具。最后,我向社区提交了pull request,请求他们讨论我在自己的分支里做的变更。由于这个工具很苛刻,而我又是第一次在网上与人交流,不懂技巧,所以希望我没有冒犯太多的人。在向社区做贡献及参与后续讨论的过程中,我对问题的理解更深入了,本文旨在将我的所得回馈给更广泛的社区。
分析过的GitHub项目
由JB Evain编写的Mono.Cecil可以将.NET代码反编译成C#。根据建议,只有36行代码需要删除,经过审核,JB选择单独添加部分变更,而不是合并分支。
Automatic Graph Layout是微软官方的一个项目,由Lev Nachmanson、Sergey Pupyrev、Tim Dwyer、Ted Hart和Roman Prutkin开发,用于绘制图和有向图,Visual Studio也用它显示各种交互图。Pull request要求删除4674行代码,其中有一些和SilverLight有关(已于2015年宣布停用)。不经过修改或讨论,分支就被合并了进去。
Roslyn是一个现代化的C#编译器,由.NET基金会的一个团队负责维护。在这个例子下,Pull request要求删除18364行代码,这引发了有益的讨论,并产生了下面讨论的大多数分类。显然,这个分支太大了,无法合并,取而代之,多个单独的议题被提了出来。
MSBuild是微软官方的一个项目,Visual Studio的用户应该比较熟悉。根据分析,我提交了删除3722行代码的pull request,遗憾的是,其团队当时没有余力审核我提出的变更建议。
最后分析的是.NET Core基础库里的System.XML程序集。这些库由.NET基金会负责维护,为了删除死代码,其团队发布了一条问题追踪信息。该问题的解决方法是逐个修剪程序集(通常被称为死代码消除),然后比较未修剪程序集和已修剪程序集之间的差异,从而确定哪些编译代码被删除了。通过这些差异可以知道哪些源代码被删除了,这项工作通常是由志愿者社区承担。
鉴于只用该工具抽样分析了五个项目,得出任何结论都是不明智的,尤其是在缺少可靠的统计数据的情况下:
Mono.Cecil | MSAGL | Roslyn | MSBuild | CoreFX (System.XML) | |
---|---|---|---|---|---|
已删除 | 36 | 4674 | 18364 | 3722 | 427 |
初次提交时间 | 2010/04/12 | 2015/02/22 | 2014/03/18 | 2015/03/12 | 2014/11/08 |
作者(主要) | 39 (1) | 25 (4) | 285 (31) | 90 (6) | 526 (29) |
鉴于其悠久的历史,MSBuild的初次提交时间值得注意。仅数一下作者的数量而不评估他们的贡献几乎可以肯定是没有意义的,因此我大致估计了主要的贡献者。说到这里,我推测:
测试结果分类
该工具测试冗余方法,和任何测试一样,分析有成功和失败之分。
真负:
假负:
真正:
假正:
需要注意,工具失败不一定是因为推理不足。其中有些情况是其他工具的处理范畴,如编译器警告应该标记部分问题,重复代码检测器也有用。
冗余代码的影响
假负和真正都可以视为YAGNI的实例,这就需要我们注意以下事项:
臃肿的可执行文件
臃肿的运行时
降低性能
降低可靠性
降低可维护性
管理已有的冗余代码
处理冗余代码有四种方法:
忽略
自动从可执行文件中移除
屏蔽
删除
如果要变更源代码,任何变更都需要由熟悉这段代码的人审核,务必保证代码真冗余。
管理新开发项目
假如目标是在开发过程中不引入新的冗余代码,需要制定什么策略?如果允许部分提交,代码库中可能就会增加暂时冗余的代码。这种情况可以通过特性分支来缓解,只有当测试已覆盖且源代码通过了静态分析才合并进主分支。特性分支的另外一个好处就是在放弃开发后可以将分支保持在未合并状态。
关于作者
Zebedee Mason是一名有着25年CAD/CAM/CAE行业经验的数值分析员,他最近搬到了SLAM。多年来,他一直从事与遗留代码库(其中有些在他还是个孩子的时候就已经创建)相关的工作,把许多学术代码改写成商业软件的组件,他甚至还重写了一些原始算法(商业秘密,无法确认)。这种背景使他对软件工具非常了解,他也编写了若干提高生产力的工具,即使以前的开发人员和IT管理人员做出了有用的选择。