写点什么

冗余代码检测与分析

  • 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:037255
用户头像

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

关注

评论

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

容器化 | 在 NFS 备份恢复 RadonDB MySQL 集群数据

RadonDB

MySQL 数据库 容器化 Kubernetes 集群 备份 & 恢复

荣耀互联对外开放,赋能智能硬件合作伙伴,促进全场景生态产品融合

荣耀开发者服务平台

OpenInfra Days China 2022|SelectDB与你共享 Apache Doris 在互联网广告业务中的实践

SelectDB

MySQL 数据库 数据仓库 数据湖 Doris

基于 eBPF 的 Kubernetes 可观测实践

阿里巴巴云原生

阿里云 Kubernetes 云原生 可观测

基础到高级涵盖11个技术,Alibaba最新出品711页Java面试神册真香

JAVA活菩萨

Java 程序员面试 大厂技能 秋招 大厂面经

字节二面被问到mysql事务与锁问题,我蚌埠住了

程序员小毕

Java MySQL 数据库 程序员 面试

企业即时通讯软件有哪些功能?对企业有什么帮助?

BeeWorks

运力升级助力算力流转,中国数字经济的加速时刻

脑极体

小程序+自定义插件的混合模式

Geek_99967b

小程序

JWT主动校验Token是否过期

源字节1号

软件开发 后端开发

【注册荣耀开发者】赢【荣耀70】手机

荣耀开发者服务平台

开发者 手机 活动 应用 荣耀

从云计算到函数计算

阿里巴巴云原生

阿里云 Serverless 云原生 函数计算

《机器学习的随机矩阵方法》

计算机与AI

Python 机器学习 数学

《机器学习理论到应用》电子书免费下载

计算机与AI

Python 机器学习 数据科学

移动平台助力推进智慧型科研院所信息化建设

BeeWorks

全新出品!Github总榜排行第七的SpringCloud生态全栈笔记我粉了

JAVA活菩萨

Java 程序员面试 大厂技能 秋招 大厂面经

【K8s入门必看】第三篇 —— K8s必备基础概念大梳理

Albert Edison

Docker Kubernetes 容器 云原生 8月月更

深圳大数据培训多长时间可以找工作

小谷哥

服务器

武师叔

8月月更

安装失败怎么办

和牛

测试

浅谈运用低代码技术如何实现物流企业的降本增效

王平

开发一套高容错分布式系统

JAVA活菩萨

Java 程序员 后端 java程序员 java编程

WEB前端面授培训课程

小谷哥

web前端培训课程哪个比较好?

小谷哥

如何模拟后台API调用场景,很细!

Liam

前后端分离 开发 Postman API 前后端协作

荣耀发布开发者服务平台,智慧生态合作提速

荣耀开发者服务平台

框架整合(二)- 使用Apache ShardingSphere实现数据分片

大菠萝蜜

MySQL 8月月更

【web自动化测试】Playwright快速入门,5分钟上手

和牛

自动化 测试 playwright Python. 8月月更

荣科科技:未来主要围绕在线互联化、生态化和智能大数据运营这三个方向发展

BeeWorks

倒数 3 天|RocketMQ 能力全景图即将发布,定义下一代消息队列未来方向

阿里巴巴云原生

阿里云 RocketMQ 云原生 消息队列

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