2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

冗余代码检测与分析

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

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

关注

评论

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

计算机网络——时延、时延带宽积

StackOverflow

计算机网络 编程‘ 9月月更

程序员成长那些事儿

图灵教育

程序员 进阶 代码 计算机

「趣学前端」关于iframe跨域通信

叶一一

前端 iframe 跨域 9月月更

一起玩转!SOFA 飞船 Layotto 星球登陆计划

SOFAStack

golang 微服务 云原生 新手指南 开源软件

数学基础查可以参加Java培训

小谷哥

Linux云主机安全入侵排查步骤

京东科技开发者

Linux 安全 云主机 排查方法 云主机安全

揭秘 · 机器人酒店

澳鹏Appen

人工智能 nlp 语音识别 数据标注 语音标注

SLO新解,一种行之有效的故障处理方法

华明

监控系统 SLO 稳定性保障

启科量子出席首届量子计算产业峰

启科量子开发者官方号

【荣耀开发者服务平台—百亿曝光扶持等你来】智慧服务快应用卡片接入指南(下)

荣耀开发者服务平台

前端 手机 新手指南 安卓 honor

一文讲透B端和C端产品经理的区别

产品海豚湾

产品经理 SaaS 职业发展 B端产品 9月月更

程序员成长那些事儿

图灵社区

程序员 进阶 代码 计算机

云备份服务CBR

创意时空

量子计算与PKS信创体系首次融合,实现算力跨越

启科量子开发者官方号

启科量子离子阱全系列教学模拟机多版本满足客户需求

启科量子开发者官方号

2022年应届毕业生就业率惨淡怎么办?不要错过多金的数据科学行业

雨果

数据分析师 数据科学 数据工程师

英特尔oneAPI工具大幅提升腾讯云数据库MySQL的性能

科技之家

上(市场篇)| 量子计算与开源软件

启科量子开发者官方号

Java培训值不值得?

小谷哥

学完Java培训后有哪些就业机会呢

小谷哥

极狐GitLab CI 月来袭!2小时 get CI 流水线设计秘籍

极狐GitLab

DevOps gitlab 运维 CI/CD 持续交付

【译】像CSS一样在Flutter里应用滤镜效果

iofod jude

JAVA StreamAPI

流火

Stream API java 8 的新特性

云速ERP(WeLink认证版)上线,加速中小微企业数字化经营创新

科技云未来

Trending热榜关闭前,我把Github今年最火Java面试题汇总扒下来了

Java全栈架构师

Java 程序员 面试 程序人生 八股文

通过Jenkins构建CI/CD实现全链路灰度

阿里巴巴中间件

阿里云 云原生 jenkins 全链路灰度

人工智能、机器学习和深度学习,到底有什么区别?

Finovy Cloud

人工智能 云计算 影视渲染

关于C语言结构体(struct),你不知道的用法?(初阶篇)

Albert Edison

指针 C语言 结构体 9月月更

java培训哪里比较好?

小谷哥

下(应用篇)| 量子计算与开源软件

启科量子开发者官方号

SpringBoot源码 | prepareEnvironment方法解析

六月的雨在InfoQ

源码 springboot 源码刨析 SpringBoot实战 9月月更

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