重构 — 勿以善小而不为

  重构最大的敌人不是技巧与能力,而是懒惰,或者说是态度。许多细小的重构看似无足轻重,例如方法重命名、提取方法。即使重构了,似乎对代码的结构也没有太大的影响,于是就决定淡然处之,心里想“事情还未到不可挽回的地步,实现功能要紧,至于重构,还是以后再做吧!”这样一想,于是就会滋生得过且过的想法,等到代码开始变得一团糟时,重构已经变得无比困难了。此时需要的重构技巧,将百倍难于发现坏味道之初。

  在我参加的前一个项目中,我们定义了一个处理OrderSet的Controller。刚刚开始开发时,对于OrderSet的操作并不多,主要是Search与Count操作。OrderSet分为WithDetails与WithoutDetail两种类型。为了实现的简单,我们将这两种类型的操作都放在一个Controller中。随着操作的逐渐增多,这个Controller就变得越来越庞大,逐渐变得臃肿起来。每当我需要调用或者修改Controller时,我都在想:“嗯,这个代码太糟糕了,什么时候给它重构一下。”想是这么想,却一直扮演着说话的巨人,行动的矮子。即使兴起这样的念头,又因为其他的工作将此念头浇灭。直到有一天,这个Controller的代码已经到了忍无可忍的地步,我和我的Pair终于达成一致意见,决定对此代码进行手术。我们花费了近一天的时间对Controller以及相关的Repository进行了彻底的重构。重构前后的代码天差地别,我终于可以不用像吃了苍蝇那般看着代码恶心了。这种重构后体验到的愉悦简直无与伦比,最关键是结果令人满意,重构后的代码变得更可读,更简单,也更容易增加新的功能。

  如今工作的项目,需要对遗留系统进行迁移。首要的任务是为此系统编写BDD测试,以期搭建迁移的测试保护网,并能够形成可读与可工作的测试用例文档。在开始接触这个任务时,客户方的开发人员已经基本搭建好了初步的框架。当我们在面对不良代码时,第一个浮现在脑海中的念头是“重构”,然而考虑到时间因素,随之又强迫自己灭了这个念头,因为我们需要考虑项目的进度。我们总是在这样的取舍之中艰难前进,终于,在系统需要增加一个新消息的测试时,我强烈地感受到重构的迫在眉睫。即使代码有诸多破窗,修补了一扇,总强过听之任之。经过接近一天多的重构(当然还包括run tests以及build花费的时间),结果令人满意。回顾这个过程,我发现在发现坏味道时,如果能及时地对代码进行重构,并保证重构的小步前进,并不会阻碍开发进度,相反还能够在一定程度改善代码质量,提高代码的可读性、可重用性以及可扩展性。所谓“勿以善小而不为”,千万不要因为小重构对代码质量的影响微乎其微而轻视她,或者忽略她,又或者推迟到忍无可忍再想到重构。重构并非最后的救命稻草,而是随时保持我们正确前进的一把尺子。

  说完了重构的重要性,让我再来粗略地介绍这个重构过程。

  我们的测试程序主要针对Message的发送、接收与验证。业务的处理则由部署在JBoss上的应用来处理。我们需要设定期望的Message,在发送请求到远程系统后,接收返回的消息,并验证消息以及数据库是否符合我们的期望。重构的起因在于我们需要编写新的测试覆盖之前从未测试过的消息,其类型为SO08。如果沿用之前的实现,我们就需要在测试步骤中增加MessageType的分支,根据消息类型对返回的消息进行检查。

  检查的逻辑事实上已经被抽象为MessageChecker接口,并为各种类型的消息建立了不同的MessageChecker子类。MessageCheckFactory则这些子类的工厂,负责根据类型创建对应的子类对象。这样的设计是完全合理的。然而,问题出现在MessageReceiver,它提供了接收消息的方法,通过传入的消息类型、队列名称等参数,返回消息。这个返回值被定义为MessageReader。

  MessageReader正是问题的症结。我一直强调的面向对象设计中一个重要概念就是所谓”对象的自治“,即对象的职责是自我完备的,它能够对自己拥有的数据负责,具备了“智能”处理的行为特征。MessageReader违背了这一原则,它是愚笨的对象,仿佛“坐拥宝山而不知”的笨伯,虽然拥有消息的值,却不知道该如何处理这些消息。简而言之,它提供的方法只能对XML格式的消息进行读取,却不具有真正的业务行为。于是在测试步骤中,就产生了这样的代码(省略了部分实现代码):

private void checkPropagationQueueByName(String name, Queue queue, MessageType messageType) {    MessageReader reader = messageReceiver.getMessageFor(messageType, identifier, queue);    String messageText = reader.toString();    if (messageType == SO05) {        messageCheckFactory.checkerFor(messageType, getExpectedSO05ResponseFor(name), messageText);    }    if (messageType == SO07) {        checkSO07Response(name, messageType, messageText)    }    if (messageType == SO08) {    messageCheckFactory.checkerFor(messageType, getExpectedSO08ResponseFor(name), messageText)        .checkResponse();    }} 

it知识库重构 — 勿以善小而不为,转载需保留来源!

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。