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

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

我曾经参与一个项目,在一次结对开发某个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 条评论
登录 后参与评论

相关文章

来自专栏斑斓

【敏捷实践】推行TDD的思考

目前来看,推行TDD的障碍大约有如下几点: 开发人员的质量意识; 分析需求并进行任务分解的能力; 将测试作为开发起点的开发习惯; 开发人员的重构能力,包括如何识...

2836
来自专栏ThoughtWorks

敏捷团队需要专职QA么?|洞见

敏捷QA对职业发展的担忧 最近和组内的QA聊起以后的职业发展,发现一个有意思的事情,有说想转BA的,有说想转开发的,有说想转型作PM的,还有想以后往咨询方向发展...

3027
来自专栏大数据和云计算技术

实用调度工具Airflow

引言 前面写过一篇文章《端午搬砖:聊聊调度云服务》,主要讲云服务的。如果企业也业务上云,可以优先选用这些服务,减少工作量。 而在传统企业内部,数据集成是基础,更...

4266
来自专栏服务端技术杂谈

要成为一个 Java 架构师得学习哪些知识?

既然java架构师, 首先你要是一个高级java攻城尸, 熟练使用各种框架,并知道它们实现的原理。 jvm虚拟机原理、调优,懂得jvm能让你写出性能更好的代码;...

2656
来自专栏老安的博客

一个优雅的报警处理系统范例

1293
来自专栏Java架构

互联网一线大厂都在用的Java架构师知识体系

一、源码分析源码分析是一种临界知识,掌握了这种临界知识,能不变应万变,源码分析对于很多人来说很枯燥,生涩难懂。源码阅读,我觉得最核心有三点:技术基础+强烈的求知...

872
来自专栏Java架构师学习

Java开发必读--初识微服务一定要阅读这篇文章微服务是什么?

或许在座的高朋了解过其概念。个人认为,与其说微服务是一种技术,不如将其定义为一种架构,而架构则是“技”的实现与“术”的策略相辅相成。“术”的策略需要分析使用场景...

651
来自专栏斑斓

推行TDD的思考

我在参与的开发项目以及咨询项目中,都有实践TDD的经验。直至今日,我仍然会在某些功能开发时采用TDD的方式实现功能。虽然没有达到将TDD溶于开发血液之中形成自然...

3828
来自专栏Java架构

Java架构师如何冲击年薪40w

2184
来自专栏Albert陈凯

2018-06-21 Java技术栈知识小全--东西有点多,很有料

原文地址:https://github.com/aalansehaiyang/technology-talk

943

扫码关注云+社区