敏捷实践 | 代码是如何腐烂的

代码是如何腐烂的?这是一个很大的命题,因为这种腐化的代码样本可能会体现不同的特征。若要彻底总结,可能会又是一本《重构》。我自然没有这个能力和知识。好在有一个简便的说法,即可以诉诸于“破窗理论”的威力。无论多少坏味道识别,重构手法运用,提高代码质量的最佳实践,以及运用诸多甄别代码质量体征的工具,都仅仅限于“术”的运用而已。若未能在开发人员内心树立整洁代码的习惯,时时刻刻对各种代码臭味保持敏感,且具有一颗期待卓越代码之心,那么,随着项目的演进,时间的推移,代码最终还是会慢慢腐烂。

我曾经参与一个项目,在一次结对开发某个User Story时,从诸多测试代码(包括集成测试与验收测试)中,依然观察到了一些接近腐烂的代码坏味。这些代码虽然不是产品代码,但同样是我们交付工件的一部分。最关键之处在于:它让我察觉到一种危险的趋势,若不能及时扭转,可能会让代码陷入腐烂的泥沼。若能及时解决这些糟糕代码,其实仅仅需要一些简单的重构手法,付出几个小时时间即可。

首先是针对集成测试的数据准备。

我们要编写的集成测试针对Spring Batch Job,这些Job需要访问数据库,以验证Job的执行是否符合期望。我们发现在之前已有与Spring Batch Job相关的集成测试存在,并提供了访问数据库,以及启动、访问和停止Ftp服务器的功能。其中,与数据准备有关的功能放到单独定义的Fixture类中。这些Fixture是为特定目的编写的数据准备;可是,随着越来越多的Batch Job出现,有诸多集成测试都需要准备数据,慢慢产生了测试数据的重叠,逐步浮现出违背DRY原则的征兆了。

对于多数程序员而言,并非不重视重用,但多数却不愿意为了重用付出一些代价。例如针对一些具备差异性的功能,一些程序员更愿意使用Copy And Paste,然后再针对自己的需求对实现进行修改或调整。观察目前的一些集成测试,正是这样一些陋习导致的。

在这些集成测试中,使用了继承的方式来重用数据准备的功能。如下图所示:

在CustomerIntegratedDataFixture中,提供了相关方法实现了对Customer数据的创建。由于需要提供访问FtpServer的功能,因此又定义了CustomerIntegratedDataAndFtpPrepareFixture类,使其继承CustomerIntegratedDataFixture。它定义了startFtpServer()和stopFtpServer()方法,并在JUnit中,运用了@BeforeClass与@AfterClass标记,使其避免为每个测试启动和停止专有的FtpServer。

现在,我们编写的集成测试同样需要与Customer有关的数据,但并不需要Ftp功能。换言之,我们希望重用CustomerIntegratedDataFixture。目前似乎并没有问题。例如,我们可以让新增的测试直接继承CustomerIntegratedDataFixture。然而,就在同样的集成测试模块中,我们还发现了其他集成测试同样编写了自己的数据准备类。这些数据准备与Spring Batch Job无关,却同样提供了准备Customer数据的功能。存在的差异是它除了提供Customer数据外,还提供了依赖Customer的Consent数据。

我们没有着急去重用CustomerIntegratedDataFixture,因为我们察觉到代码会随着这种继承体系的延伸,会变得越来越难以重用。如上图的继承体系,使得数据准备与Spring Batch Job紧耦合了,同时又在CustomerIntegratedDataAndFtpPrepareFixture子类中引入了与Ftp有关的耦合,明显违背了单一职责原则。我们需要单独剥离出数据准备的类,它即可以作为超类被集成测试类继承,也可以通过组合的方式被继承了JobLauncherTestUtils的测试子类所调用。这符合Bridge模式的设计原则。因此,我们运用了“Replace Inheritance with Delegation”手法,对其进行了简单重构:

之后,我们对Customer和Consent对应的数据准备类进行了相应的重构与修改,使得这些数据的准备更为内聚,并去除一些不必要的重复,使之更容易被重用。

在这个例子中,代码的坏味道并不隐蔽,运用的重构手法也并不复杂,我相信大多数开发人员都具备这样的技能。然而,我们总是因为种种原因,对这种还不太严重的“破窗”风景视而不见。殊不知当我们开始对这种不够整洁的代码采取纵容态度时,就可能会是代码腐烂之始。一旦真正腐烂,就将积重难返,到了那时,我们就可能真正无能为力了。

你是否遭遇过这样的情形?面对一个承担了无数职责似乎无所不能的上帝类,它被无数多的Client调用,且又没有足够覆盖率的测试,你是否会产生心有余而力不足的感慨。这时的你,是否像一位奋战沙场,出生入死却无力挽回败局的将军,面对那汹涌而来占据压倒性优势的敌军,唯有对天长叹:“某有心杀贼,却无力回天啊!”

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2014-09-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏SDNLAB

SDN实战团分享(六):OpenDayLight实战入门

今天讲的是SDN的实战,但说到实战,可能还是牵强了点,SDN技术代表了某种意义上的未来,所以现在来说,并不是大规模应用的阶段。所以,我们可以去设想SDN会在时候...

2547
来自专栏腾讯Bugly的专栏

【MIG专项测试组】如何量化Android应用的“卡”?---流畅度原理&定义篇

腾讯Bugly特邀鹅厂MIG专项测试组,陆续为大家分享移动应用质量的有效评估方法。 MIG专项测试组 ? 致力于为腾讯移动互联网事业群(MIG)提供专项评测及...

3855
来自专栏IMWeb前端团队

AS3解析FLV格式和视频相关问题总结

为什么要解析FLV格式? 在直播项目里面遇到需要统计flash视频帧间隔时长,首帧,GOP,等关键数据的时候,不可避免的需要对flv文件进行解析。 名词定义 首...

1966
来自专栏即时通讯技术

约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源

关于微信内部正在使用的网络层封装库Mars开源的消息,1个多月前就已满天飞(参见《微信Mars:微信内部正在使用的网络层封装库,即将开源》),不过微信团队没有失...

521
来自专栏钱曙光的专栏

一周极客热文:2014年最值得学习的编程语言

经过数据分析和研究Jobs Tractor的45000个开发人员招聘职位数据,我们得到了上图的结果: 自上一年,主要的变化如下: PHP和Java换了位置,但...

1725
来自专栏微信公众号:Java团长

成为优秀Java程序员的10大技巧

Java程序员有许多应遵循的守则或最佳实践方式。本文概述了每个开发者最应该遵循的10条守则或戒律,如果不遵循它们,将会导致灾难性后果。

491
来自专栏微服务生态

由参加领域驱动大会与自己所想的

2017首届领域驱动技术大会一直是我非常期望的,要非常感谢右军赠送的门票能够让我领略大会风采。

792
来自专栏张善友的专栏

依赖注入是否值得?

在博客的世界里进行了一场关于使用依赖注入(DI)之优点和缺点的有趣讨论。论题是:依赖注入是否真的值得? 讨论始自Jacob Proffitt,他撰文解释他的观...

1699
来自专栏web前端教室

前端开发与数学

image.png 前端开发一般只是操作一些DOM,请求一些JSON,重绘一些DOM,处理一些缓存,触发一些事件,有什么难的?值那么多钱?二十K,三十三K?前端...

2026
来自专栏哲学驱动设计

UML 图使用心得

在软件开发中,从需求工程到代码工程,都离不开 UML 图的绘制。今天简要总结一下我以往使用 UML 图的一些体会。 很多图,都是由原始需求到代码的一种转换,只是...

1726

扫描关注云+社区