写点什么

冗余代码检测与分析

  • 2017-10-09
  • 本文字数:3456 字

    阅读完需:约 11 分钟

本文要点

  • 代码冗余的原因多种多样,从未使用的变量到未完成的变更,再到废弃的代码;
  • 冗余代码会产生一系列的影响,包括源代码臃肿、可靠性及可维护性降低。在某些情况下,死代码也会影响性能;
  • 为了检测冗余代码,作者开发了一个工具,使用 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 的初次提交时间值得注意。仅数一下作者的数量而不评估他们的贡献几乎可以肯定是没有意义的,因此我大致估计了主要的贡献者。说到这里,我推测:

  • 新开发的项目比成熟项目冗余代码多
    • 编写代码时假定将来会需要
    • 测试少,相应的,发现的 Bug 就少
  • 团队越大制造的冗余代码越多
    • 沟通成本会随团队规模的扩大而呈几何级数上升,参见《人月神话

测试结果分类

该工具测试冗余方法,和任何测试一样,分析有成功和失败之分。

真负:

  • 代码有用

假负:

  • 测试代码
    • 测试的功能只有测试使用。这可能是 TDD BDD 过程的产物,在这种情况下,它可能用来追溯不需要或者未满足的需求。
  • 死代码
    • 虽然被调用了,但代码什么都没做,这种调用可以删除。
    • 可以是重复代码
    • 注意,可以认为已废弃代码距离死代码只差一步。

真正:

  • 故意放弃的开发
    • 方法已经添加,但环境变了,这些方法不会再使用了
  • 未完成的变更
    • 代码已变更,有些方法所有引用都删除了,但后续提交时没有将其考虑在内。这种代码有时候称为 oxbow 代码。它可能是重构的意外产物。
  • 未使用的变量
    • 但愿 IDE 会生成编译器警告和 / 或报告。

假正:

  • 工具缺陷
    • 该工具没有检测使用反射代码契约的代码,它依赖构建或测试过程的错误来检测这些代码。
  • 回归
    • 在开发过程中,经过修改,一个方法不再调用标识为死亡的方法,此时,这种情况就会出现。显然,没有测试识别这种回归。
  • 缺少条件编译
    • 通常,这是指缺少#if DEBUG 保护。Roslyn 已经用于 Release 构建,这个过程会识别相应二进制文件中的代码膨胀。
  • 无意放弃的开发
    • 这是在特定的测试数据下发现的,但没有相应的测试。通常来说,我希望这是一个暂时的问题,随着代码增加会自然消失。
  • 在子系统中未使用
    • 分析低效的代码,找出使用该方法的地方。虽然反射也会导致这个问题,但这主要出现在公共方法分析时。一个常见的例子是执行测试辅助功能的时候没有相应的测试代码,不过,那会抛出一个设计问题,定位到测试辅助代码。
  • 仅限调试器使用的方法
    • 在附加了调试器时(可能是 Release 构建),有些项目会有方法打印出状态。这些方法应该以某种方式标记出来,那样就不用分析它们了。

需要注意,工具失败不一定是因为推理不足。其中有些情况是其他工具的处理范畴,如编译器警告应该标记部分问题,重复代码检测器也有用。

冗余代码的影响

假负和真正都可以视为 YAGNI 的实例,这就需要我们注意以下事项:

  • 臃肿的源代码

    • 以前,我遇到过文本编辑器无法打开大文件的情况。
    • 开发人员的认知受限于大脑中可以同时容纳的独立实体的数量,通常认为是 7 个,因此,删除方法可以提升推理能力。
    • 所有开发人员用来阅读和理解源代码的时间很可能都是浪费。
    • 现代 IDE 通常都是从源代码生成抽象语法树,因此,冗余代码降低了它们的速度。
  • 臃肿的可执行文件

    • 在手机上,这尤其是个问题,为了释放存储,升级会删除应用,最终,当功能受到过度损害,就不得不升级设备了。
  • 臃肿的运行时

    • 为黑客提供了更大的攻击面,让他们发现安全问题。
    • 部分面向嵌入式设备的应用程序会在启动时分配所有的动态内存,因此,冗余代码会减少可用堆的大小。
  • 降低性能

    • 在特定的情况下,死代码执行会浪费处理器周期。
  • 降低可靠性

    • 在特定的情况下,死代码执行可能导致崩溃。
  • 降低可维护性

    • 如果执行到冗余代码时停下了,那么行为将不可预测,如 Knight Capital 损失了 4.4 亿美元。
    • 代码覆盖率可能会因假负而升高,因假正而降低。
    • 不必要的测试会降低测试套件的执行速度。
    • 静态分析工具不得不处理更多的代码,并因此效率降低。
    • 软件的设计 / 架构看起来比实际情况复杂。考虑到冗余代码会导致各种各样的问题,最好是将其视为代码异味。

管理已有的冗余代码

处理冗余代码有四种方法:

  • 忽略

    • 虽然会增加维护成本,但至少可以通过编译。
    • 不推荐。
  • 自动从可执行文件中移除

    • 编译型语言在链接器中使用任意一种死代码移除选项移除。
    • 动态语言使用 Tree Shaking 在运行时移除。
    • 也不推荐。
  • 屏蔽

    • 注释掉或者使用预编译指令。
    • 使用 IDE 提供的代码 / 注释折叠避免看到它。
  • 删除

    • 借助源代码控制保留旧版本供参考,可以随时查看。
    • 或者你可能永远不再需要它。

如果要变更源代码,任何变更都需要由熟悉这段代码的人审核,务必保证代码真冗余。

管理新开发项目

假如目标是在开发过程中不引入新的冗余代码,需要制定什么策略?如果允许部分提交,代码库中可能就会增加暂时冗余的代码。这种情况可以通过特性分支来缓解,只有当测试已覆盖且源代码通过了静态分析才合并进主分支。特性分支的另外一个好处就是在放弃开发后可以将分支保持在未合并状态。

关于作者

Zebedee Mason 是一名有着 25 年 CAD/CAM/CAE 行业经验的数值分析员,他最近搬到了 SLAM。多年来,他一直从事与遗留代码库(其中有些在他还是个孩子的时候就已经创建)相关的工作,把许多学术代码改写成商业软件的组件,他甚至还重写了一些原始算法(商业秘密,无法确认)。这种背景使他对软件工具非常了解,他也编写了若干提高生产力的工具,即使以前的开发人员和 IT 管理人员做出了有用的选择。

查看英文原文: Detecting and Analyzing Redundant Code

2017-10-09 18:036359
用户头像

发布了 1008 篇内容, 共 393.8 次阅读, 收获喜欢 345 次。

关注

评论

发布
暂无评论
发现更多内容

怎么画ER关系图?这个在线ER图软件值得推荐!

彭宏豪95

职场 ER图 在线白板 办公软件 绘图软件

Jenkins源代码管理+接入钉钉api发送接口自动化测试报告

测试人

软件测试 jenkins

Linux 三剑客之 grep

测吧(北京)科技有限公司

测试

英伟达市值超越苹果;ChatGPT、Perplexity、Claude 同时大崩溃丨 RTE 开发者日报 Vol.220

声网

AutoMQ 生态集成 Ceph

AutoMQ

kafka java

AMD 和英伟达 COMPUTEX 2024 发布亮点

GPU算力

gpu 英伟达 芯片 AMD #人工智能

网络安全专用产品销售许可证查询的几种方式你知道吗?

行云管家

网络安全 堡垒机 销售许可证

高德面试:为什么Map不能插入null?

王磊

Java

[图数据库]Neo4j中心性算法-以红楼梦为例

alexgaoyh

neo4j 图算法 紧密中心度 红楼梦 中心度算法

「手撕Vue-CLI」拷贝模板

EquatorCoco

JavaScript vue.js 前端

SD-WAN是怎样帮助企业业务开展的?

Ogcloud

SD-WAN 企业组网 SD-WAN组网 SD-WAN服务商 SDWAN

为什么SD-WAN比MPLS更适合中小型企业

Ogcloud

SD-WAN 企业组网 SD-WAN组网 SD-WAN服务商 SDWAN

Linux 环境配置指南

测吧(北京)科技有限公司

测试

Linux 三剑客之 awk:最强大的经典数据处理工具

测吧(北京)科技有限公司

测试

【玩转IPD】未来5年,只有这种产品团队才能开启上帝视角

快乐非自愿限量之名

产品运营 IPD

百度文库与龙腾出行达成战略合作,首创「VAIP智能空间」,重构商旅人士工作流

Geek_2d6073

jenkins源代码管理+接入钉钉api发送接口自动化测试报告

测吧(北京)科技有限公司

测试

欧特克汽车创新论坛:一汽大众和东研智慧展示最新数字化方案

E科讯

这是我的新名片,望您惠存!

通明湖

云实例初始化的行业标准:Cloud-Init

AutoMQ

kafka java

网络安全等级保护测评师定义以及主要工作任务是什么?

行云管家

网络安全 等保测评师

今天的加密货币市场走势和相关信息

dappweb

Linux 三剑客之 sed:数据的流式编辑

测吧(北京)科技有限公司

测试

jenkins接入钉钉api接口自动化测试报告自动发送

霍格沃兹测试开发学社

MySQL Shell 使用指南

Simon

MySQL mysqlshell

搭建高可用k8s

不在线第一只蜗牛

Kubernetes 容器化 Linux 运维

清华博士带你掌握 Llama 大模型 40 节课助你涨薪 120%

OSCTraining

llama Llama3

企业数字化转型的主要方面有哪些?

优秀

数字化转型

「布道师系列文章」众安保险王凯解析 Kafka 网络通信

AutoMQ

kafka java

冗余代码检测与分析_.NET_Zebedee Mason_InfoQ精选文章