代码异味

科技工作者之家 2020-11-17

在计算机编程中,代码气味是程序源代码中可能表示更深层问题的任何特征。确定什么是代码味道和不是代码味道是主观的,并且因语言,开发人员和开发方法而异。

这个词在20世纪90年代后期由Kent Beck在WardsWiki上推广。 在“重构:改进现有代码的设计”一书中,Martin Fowler将该术语的用法增加了。它也是敏捷程序员使用的术语。

简介程序开发领域,代码中的任何可能导致深层次问题的症状都可以叫做代码异味。通常,在对代码做简短的反馈迭代时,代码异味会暴露出一些深层次的问题,这里的反馈迭代,是指以一种小范围的、可控的方式重构代码。基于这些暴露的问题,人们会进一步的检查设计和代码中是否还存在别的代码异味,然后再做进一步的重构。从负责重构的开发者的角度来看,代码异味可以启发何时重构,如何重构。因此,可以说代码异味推动着重构的进行。

该术语似乎由Kent Beck于90年代后期,在WardsWiki上首次使用。且自从在Refactoring. Improving the Design of Existing Code.被提到过,使用率就大大的提高。代码异味同时也是敏捷开发者常用的术语。

什么是,或者不是代码异味,是一个主观的判断,通常因语言、开发者、开发方法的不同而不同。对于Java开发语言,有些工具,比如Checkstyle、PMD和 FindBugs可以自动检测一些代码异味。

查看气味的一种方法是关于原则和质量:“气味是代码中的某些结构,表明违反基本设计原则并对设计质量产生负面影响”。代码味道通常不是错误;它们在技术上不正确,并且不会阻止程序运行。相反,它们表明设计中的弱点可能会减缓开发速度或增加未来的漏洞或故障风险。不良代码气味可能是导致技术债务的因素的指标。罗伯特·C·马丁(Robert C. Martin)称一系列代码闻起来是软件工艺的“价值体系”。

当代码经历一个短的反馈循环时,通常可以发现代码气味所暗示的更深层次的问题,在那里以小的,受控的步骤进行重构,并检查所得到的设计以查看是否还有任何代码味道反过来表明需要更多的重构。从负责执行重构的程序员的角度来看,代码气味是指示何时重构的启发式方法,以及使用什么特定的重构技术。因此,代码气味是重构的驱动因素。

2015年的一项研究利用自动化分析对50万个源代码提交进行了手动检查,并对9,164个确定显示“代码味道”的提交进行了手工检查,结果发现:

对于“技术债务”的后果存在经验证据,但是只存在关于这种情况发生的方式,时间或原因的轶事证据。

“普遍的观点表明,紧急维护活动和提供功能的压力,同时优先考虑产品上市时间而不是代码质量通常是造成这种气味的原因”。

有一些工具,如Checkstyle,PMD和FindBugs for Java源代码,可以自动检查某些类型的代码气味。1

常见的代码异味应用级气味:

Duplicated code:相同或非常相似的代码存在于多个位置。

复杂性:强制使用过于简单的设计模式,简单的设计就足够了。

Shotgun surgery:需要同时对多个班级进行单一更改。

类级气味:

大类:一个已经变得太大的类。

Feature envy:一个过分使用另一个类的方法的类。

Inappropriate intimacy:一个依赖于另一个类的实现细节的类。

Refused bequest:一个覆盖基类方法的类,其方式是派生类不遵守基类的约定。见Liskov替代原则。

Lazy类/ freeloader:一个做得太少的类。

过度使用文字:这些文字应编码为命名常量,以提高可读性并避免编程错误。此外,文字可以并且应该尽可能地外化到资源文件/脚本中,以便在软件旨在部署在不同区域时促进软件的本地化。

Cyclomatic复杂性:分支或循环太多;这可能表明功能需要分解为更小的功能,或者它具有简化的潜力。

向下转换:打破抽象模型的类型转换;抽象可能必须重构或消除。

孤立变量或常量类:通常具有常量集合的类,这些常量属于其他位置,其中这些常量应由其他成员类之一拥有。

数据块:当一组变量在程序的各个部分中一起传递时发生。一般来说,这表明将形式上将不同变量组合成一个对象更合适,而只是传递这个对象。

方法级气味:

参数太多:很难读取很长的参数列表,并使调用和测试函数变得复杂。它可能表明该功能的目的是错误的,并且应该重构代码,以便以更清晰的方式分配责任。

长方法:方法,功能或过程变得过大。

标识符过长:特别是使用命名约定来提供应该隐含在软件体系结构中的消歧。

标识符过短:变量名称应反映其功能,除非功能明显。

过度返回数据:返回超过每个调用者所需内容的函数或方法。

过长的代码行(或上帝行):代码行很长,使得代码难以阅读,理解,调试,重构甚至识别软件重用的可能性。例:

new XYZ(s).doSomething(buildParam1(x), buildParam2(x), buildParam3(x), a + Math.sin(x)*Math.tan(x*y + z)).doAnythingElse().build().sendRequest();本词条内容贡献者为:

王慧维 - 副研究员 - 西南大学

科技工作者之家

科技工作者之家APP是专注科技人才,知识分享与人才交流的服务平台。