188足球比分直播重构:这个类是太大

从真实(有缺陷的)代码基础重188足球比分直播构的一个例子。

在本文中,我通过从一个真正的代码库一组重构的行走。188足球比分直播这不是为了证明完美,但它确实代表了现实。

2020年4月14日



这是一个关于重构的故事。188足球比分直播这是在TDD第三项红 - 绿 - 重整循环[1]这是我们一直在做的事,对吧?除非我们没有。

我已经从重构忽视折磨,所以我一直把它放回线不羁的代码库。188足球比分直播在本文中,我将采取类太大,并使其更小。

问题概述

故事开始于一个无聊的苦差事国内。我已经写了一些个人财务软件 -Reconciliate。它在命令行上工作,并执行以下操作:

  1. 负载我自己逗号分隔的数据:
    • 我最近的银行和信用卡交易记录。
    • 我预测的月度和年度交易(基于中央电子表格中的数据)。
    • 任何未经协调的先前记录的数据。
  2. 加载以逗号分隔的第三方数据(来自银行和信用卡公司)。
  3. 和解对不住自己的数据的第三方数据。
  4. 一切都写入到中央电子表格。

我有一些bug需要修复,还有一些特性需要添加。但我通常的工作流程是在晚上把我最小的孩子放到床上之后,在很短的时间内就可以拿到笔记本电脑我的就寝时间,通常是在我最后一次看到代码的几周后。在这种情况下,很容易想到这样的事情:“我知道这段代码一团糟,但我现在没有时间去考虑和修复它…”

显然,这是不理想的。

还有一个特别的类 - 的ReconciliationIntro类 - 这是让我头疼每当我看着它。这是臃肿,令人费解,也不可能适合我的头脑[2]。这已经创造了一个讨厌的反馈回路:“因为这个代码是这么难的原因有关,重构将需要更多的时间和精力,比我有,所以我忍了它甚至更长的时间 - 我即使这意味着即使不188足球比分直播做小的变化了,因为它需要这么长时间,我听不懂代码的当前状态,并决定在哪里的变化应该去“。

例如,我想添加处理另一张信用卡的能力。我在很多地方都用过多态性战略模式让每个信用卡整齐封装的独特行为。但ReconciliationIntro类是一个地方,因为代码的设计不好和缺乏清晰bet188足球背景分离如果我添加其他信用卡我会使得原本臃肿的类更糟。它包含每个四种类型的数据(在银行,银行出,信用卡1和信用卡2)四个重复的代码路径,如果我添加信用卡3,现在我将最终遵循同样的反模式。

我的总体目标是将这段代码有更多的通用代码,通过策略模式[3]。但也有四个问题站在我的方式:

  1. 这个大班级(ReconciliationIntro),承担了太多的责任。
  2. 有很多的私人嵌套的代码,这是很难进行单元测试,因为它没有公共接口。
  3. ReconciliationIntro,有一个大的方法在做太多。
  4. 有几种方法ReconciliationIntro使用相同的重复模式,但不同的细节。

我计划按上面列出的顺序解决所有这些问题。这个预备重构188足球比分直播将使我能够轻松地概括每个信用卡/帐户的行为。作为肯特Beck说“进行更改容易,然后进行简单的变化。”

这篇文章是关于解决上述列表中的第一个问题:这个班太大了

为什么重构?

我很难解释ReconciliationIntro类,因为它有太多的责任。它最初被设计为入口大厅的软件,并且它bet188足球所作的只是显示一些消息,然后实例化办的主要工作类。但随着时间的推移很多其他的代码已经悄悄进来,我想更轻松地添加其他信用卡,所以我要通过启动打破这个大班级分成小班

带来的好处将是几个:

  • 通过移动的方式组分为单独的班,我会明确的创建上下文尽量减少紧耦合。这意味着:
    • 我就知道去哪里,当我想更改或修复问题。
    • 当我修改代码时,我只需要处理我能想到的小的、独立的部分[2]- 我不会要了解整个系统。
    • 任何对状态的操纵只会在一个小的上下文中局部发生——因此我不必担心系统的一个区域会改变另一个区域的状态。

我该怎么做?

在我看来,重构(以及编写新代码)时最重要的原则是分步进行。我有几个共同的目标:188足球比分直播

  • 在每一步,我都希望编译代码并运行测试。
  • 如果更改导致任何测试失败,我希望能够立即修复它们。通过进行小的更改和小的提交,我可以看到哪些更改导致测试失败,我只需要倒带/检查少量代码就可以找到问题。
  • 我可以在我的头脑中跟踪我的位置。进行相对简单的重构非常容易,但却发现它的影响超出了您的最初意图。此时,如果您没有保持代码编译和测试在每一步通过的习惯,您可能会发现自己陷入难以退出的困境:代码没有编译,测试甚至没有运行,更不用说通过了。

对于这项工作,我需要被重构的代码被测试覆盖。这已经完成之前,我开始重构,但值得一提的在未来重构我的目标是一个将是使连码的可测试性188足球比分直播。

188足球比分直播重构,由Martin Fowle188bet足球充值r建议进一步阅读,并列出了一些基本的重构原则。188足球比分直播他还强调了小步前进的价值,以及在每次小的提交之后构建代码/运行测试。

除了这些基本原则,我的目标是遵循逻辑系列,其概述如下步骤。

一。重新排列方法

我将首先使用地区重新排列ReconciliationIntro分门别类(提交f2d9932)[4]:

图1:方法重新排列为区域后的ReconciliationIntro

现在我已经将我的方法分组为一个合理的集合上下文,我想拉这些分成不同的类别。但是,从哪里开始?该文件加载代码包含最复制,并造成最痛。最后,我想使这个代码更通用的,但首先我要拉出来,所以我可以看到它,不用分心。我将抽取新FileLoader类。其他小组也将成为独立的类,但他们会简单得多,所以我将重点本文的大部分内容上的文件加载代码。

2.分析文件加载方法之间的关系

到目前为止,我所做的只是在同一个类中移动一些代码。在提交之前,我重新构建了代码并运行了所有的测试,但是,除非我很笨,否则我希望我之前的提交是微不足道的。我需要考虑一下我下一步要做什么。

我用我的地区之一,以确定方法拔到一个新的FileLoader类,但我怎么能肯定这是否行得通呢?是否有任何隐藏的依赖?我会抽出我想移动的方法之间的关系,发现这一点。被移动的那些标记为蓝色。

图3:文件加载方法,以移动(缩写)

我可以直接看到它不是一个严格的自包含上下文:一些蓝色的方法正在调用回我想保留在单独类中的方法(以黑色和绿色显示)。我有几种方法可以处理,我在下面讨论。但现在我可以制定行动计划了。

行动计划

到时候我做,原来的大ReconciliationIntro类将被细分为削减的版本加上如下图所示五个新类。请注意,没有区域和类之间的一个一对一的映射,因为以后当我到达上第7步,我将最终提炼我的第一个分组到一些细粒度的边界。

图4:行动计划

我的总体目标是把这个大班分成小班。在上面的步骤1和2中,我使用地区识别代码的不同区域,然后分析某些方法之间的关系。这给了我足够的信息,让我开始思考我如何能以微小的步骤来完成这项工作。

将得到的计划总结如下,然后进一步详细描述下来。我达到了这个计划,想着我怎么能继续使用的步骤,分别为尽可能小。通常我在做什么是达设定的东西,这样的下一个变化将是小而简单越好。我在做小的变化,以促进更小的变化 - 再一次以下Kent Beck的格言“交易的变化,然后进行简单的变化。”

根据您的情况,您可能不会遵循相同的计划,但如果您不确定如何进行,这是一个不错的模板。你的首要任务是为安全的增量变化做好准备:

  1. 重新排列方法

    集团在全班分成明智集团的所有方法,如描述在上面

  2. 分析文件加载方法之间的关系

    识别文件加载代码是如何连接到代码在休息ReconciliationIntro类,如描述在上面

  3. 修改在留守的方法

    有这将留在父类,但目前由那些移动调用的方法。一些修改将被要求使这项工作。

  4. 创建一个覆盖测试新FileLoader

    新的FileLoader类需要通过试验来覆盖。移动来进行代码测试已经存在,但他们会被转移到一个新的FileLoaderTests类。

  5. 创建新的FileLoader类并移动两个方法

    遵循小步走的原则,在移动其余方法之前,我只移动两个方法(一个公共方法和一个由其调用的私有方法)。

  6. 移动等方法来新FileLoader

    这是它得到令人振奋的。所有这些文件加载​​代码,我很感兴趣,将最终得到一个新的家!

  7. 提取更多的新类

    处理完文件加载代码后,我将为其他代码区域创建更多的新类。

新的FileLoader类将负责加载以逗号分隔的各种数据源,并将它们合并,以便进行调整。这个文件加载代码是目前最痛苦的地方,所以这是我将要详细描述的部分。重构之前可以看到原始代码188足球比分直播在这里,但如果你按照这个链接,你只会知道它太大了,不适合你的脑袋!精简的重构后版本是在这里

所以,现在我可以继续与计划:

3.修改那些留守的方法

我已经确定了两种方法 -Set_pathRecursively_ask_for_budgeting_months- 它通过文件加载方法被称为:

图5:方法用文件的加载方法称为(缩写)

只要这些方法没有与文件加载类紧密耦合,我也可以这样做

  • 让他们公开,所以我可以从新叫回他们FileLoader类。
  • 将调用从文件加载代码移到其他本机ReconciliationIntro方法代替。

我将使用第一种方法Recursively_ask_for_budgeting_months第二个为Set_path,这将导致我出现以下情况:

图6:文件加载移动后的方法(缩写)

注意,这些方法标记为FileLoader图中的方法仍然会在ReconciliationIntro类在这个步骤结束,但这会启用我将它们移动到FileLoader类,这将发生在第5步第6步下面。

Recursively_ask_for_budgeting_months最终会成为另一个类中的公共方法,但现在我只想确保我能从其他地方调用它。事实证明它已经公开了,这样做是为了进行测试。这本身就是一种代码味道——这是一个信号,表明它最好作为单独类的公共接口的一部分。

从文件加载代码调用另一种方法是Set_path。这改变的内部路径变量的值,所以我会选择选项2:我会分别调用它,并且经由一个参数所得到的数据传递到文件装载方法。

注意,这些可能无法正常继续担任不同的提交(我可以用微提交,然后把它们挤进了较大的提交),但我是小提交完整,使步骤清晰。我编译和运行在每一步的测试:

  1. 修改调用方法(Create_pending_csvs),因此它需要一个参数,但最初给它一个默认值(提交acc3519)[4]因此,该代码仍然编译:

    private void Create_pending_csvs(){//Some code}
    私人无效Create_pending_csvs(串路径=“”){//一些代码}
  2. 呼叫Set_path分别致电前Create_pending_csvs。取得结果_path成员变量(请参见侧边栏),并将其传递给Create_pending_csvs(提交c5ebc2f)[4]:

    情况下为 “1”:{Create_pending_csvs();}打破;
    情况下为 “1”:{Set_path();Create_pending_csvs(_path);}打破;
  3. 删除调用Set_path从内部Create_pending_csvs,并使用传入值代替成员变量(提交6df8f97)[4]:

    private void Create_pending_csv(string path=){尝试{Set_path();var pending_csv file_creator=new PendingCsvFileCreator(_path);
    私人无效Create_pending_csvs(字符串路径= “”){{尝试Set_path();VAR pending_csv_file_creator =新PendingCsvFileCreator(路径);
  4. 最后,从Create_pending_csvs参数——从而强制所有客户端向(提交2 be56ea)[4]。请注意,在这个顺序做的事情,我把代码在任何时候都编译:

    私人无效Create_pending_csvs(字符串路径= “”){//一些代码}
    私人无效Create_pending_csvs(字符串路径= “”){//一些代码}

4.创建覆盖测试新FileLoader

方法将我的第一选择将是Bank_and_Bank_out_umerge_bespoke_data_with_pending_文件。我想的第一件事要做的就是复制任何涉及测试到一个新的测试类的有关将要创建的FileLoader上课。此方法已经有一个测试-M_MergeBespokeDataWithPendingFile_ WillAddMostRecentCredCardDirectDebits-它的工作是确保这个方法将新的直接借记数据正确地与一个“挂起”文件合并(这个文件被构建为包含所有新的交易数据)。

请注意,我只移动测试中,不写新的。人们可以如此习惯于在TDD[1]在编写代码之前编写测试的概念,它们假设您需要编写新的测试每当你在代码工作。这往往不是重构时的情况。188足球比分直播理想的情况是我的功能将已经被测试覆盖,并重构时,我不会改变的功能。188足球比分直播因此,而不是写新的测试中,我使用的是现有的验证仍然会作为原定的功能。

这是检查本次测试的好时机:这是一段时间,因为我写的,所以我应该能够迅速发现是否有意义。我想我的测试是明确和易于阅读 - 他们应该为我的系统文件的行为采取行动。的第一件事我注意到的是,它包含一个断言方法 -Assert_direct_debit_details_are_correct- 他的名字是不够的。什么是“正确”的定义是什么?我改写了测试,使其更容易阅读,其中涉及了很多的变化。在保持这种利益可消化读我不会进入所做的更改,但你可以查看它们提交f090f26提交6a6cece[4]

现在我已经重构了测试,我将把它复制到一个新的测试类中,以及一些相关的私有助手方法。请注意,尽管此测试和其他测试注定要在新的FileLoader一流的,他们仍然会作用于老ReconciliationIntro类,直到我敢肯定,我的新的测试类有它需要的一切。还要注意的是,直到一切都被安全地移动,我的测试代码被复制。

我首先在与原始测试类相同的文件中创建新的测试类(提交491c795)[4],所以很容易看到我复制。然后,我可以得到Resharper[5]和Visual Studio的一切进入我的一个新文件(提交c9317c0)[4]

图7:将BBO测试

5个。新建FileLoader类并移动两个方法

现在,我的测试类是建立和运行,我可以创建新的FileLoader类。

链上最下端的方法,也就是最低的叶子我的方法树-是Bank_and_bank_out__ Add_most_recent_credit_card_direct_debits. 这是一个私有方法,没有独立的测试(它是通过公共调用方法测试的),所以我将移动它和它的调用方(Bank_and_bank_out__ Merge_bespoke_data_with_pending_file)。这两个方法将被移动到我的新类中。

同样,我会在婴儿学步移动,以免我的测试去红色,并保持我的代码建筑在任何时候。每个下面的步骤后,我确保代码生成和测试都通过。

  1. 这就是我开始的情况。一个FileLoaderTests类已被创建,但它正在测试的代码,仍然住在ReconciliationIntro类:

    图8:新FileLoader类部分1(缩写)

  2. 我开始通过创建一个新FileLoader类。我的一个文件装载方法(Bank_and_bank_out__ Add_most_recent_credit_card_direct_debits)是私有的,而且也将是私人在这个过程结束。这将只通过这就要遵循它到新类中的方法被调用。这不是单独测试所覆盖,这样我就可以将其复制到新的类,并准备好,当它的调用移动等(见提交0341476)[4]。但我要让它暂时市民:

    图9:新的FileLoader类第2部分(缩写)

  3. 现在我可以创建我的new的一个实例FileLoader类的ReconciliationIntro类并调用而不是旧的私有方法的新的公共方法。我还可以删除旧的私有方法(见提交bde2ae2)[4]:

    图10:新的FileLoader类第3部分(缩写)

  4. 复制原调用到新类(见提交f0a5a59)[4]。请注意,在这一点上是重复的:

    图11:新的FileLoader类第4部分(缩写)

  5. 更改我从一个测试对象ReconciliationIntro实例是一个新的FileLoader实例。在原主叫方的新副本点测试。需要注意的是,因为它调用私有方法也被复制,我的测试将通过(见提交3 d573e3)[4]:

    图12:新FileLoader类部分5(缩写)

  6. 现在我可以从直接调用原调用ReconciliationIntro类(请参见提交77年c0b14)[4]:

    图13:新FileLoader类部分6(缩写)

  7. 再次使原本-私有方法私有,并删除原调用。我将删除旧的测试类也一样,所有的测试现在已经在复制FileLoaderTests. (见提交27 f1a59)[4]:

    图14:新FileLoader类部分7(缩写)

6.将其它方法给新FileLoader

现在我可以移动所有其他方法。我将一个接一个地处理它们,从最简单的开始,注意依赖关系(方法之间和任何成员数据)。我需要考虑以下几点:

  • 难道我要重命名的任何方法?
  • 我想和新的对象来代替任何的参数列表?
  • 是否有任何多余的参数?
  • 是否有任何内部嵌套被调用者改变状态的?什么样的影响,这都?

我可以内嵌移动之前的低了下去链中的,但如果我这样做我会打破其称他们为公共方法的测试。所以不是我移动它们自己。我这样做的顺序解释如下,作用于一个在时间和那些在链的末端开始 - 即在下面的树的叶子最外层:

图15:FileLoader方法树(缩写)

对于每一个,我用下面的办法:

  • 在目的地类来创建方法的副本,保持原始数据的位置。使新的方法公开。
  • 调用新方法而不是原来的方法。
  • 将任何覆盖测试复制到新的测试类中,并确保它们测试新代码。
  • 删除旧方法和旧测试。

我已经感动Bank_and_bank_out__ Merge_bespoke_data_with_pending_fileBank_and_bank_out__ Add_most_recent_credit_card_direct_debits(提交0341476提交27 f1a59),所以现在我重复同样的动作为每个其他方法(提交7 cd53f6提交7 ab95f2)[4]。这次我没有执行每一小步,但我仍然会执行相同的步骤。在每一步之后,我确保代码构建和测试通过(除了我故意让测试失败的时候)。

我先前重构了一些测试,我可以对遵循相同模式的测试重复这些更改。对于某些方法来说,移动非常简单,因为它们没有测试覆盖率。这就是我进行重构的部分原因,使代码更容易测试。

7.提取更多的新类

什么时候?开始时添加区域,我发现了一些获取预算个月创建自己清晰的功能上下文,所以我提取这些方法进行到一个新的BudgetingMonthService类。这是非常快速和简单,因为这些方法只能有一个公共入口点(见提交6103f0b)[4]

ReconciliationIntro仍然太大,但是所有的方法都互相调用,现在我花了更多的时间来重构代码,我不相信剩下的两个区域使用说明及输入调试电子表格操作是分割剩余的代码的最佳途径。为了帮助自己想想,我用一个电子表格来快速说明调用层级。

图16:调用层级

这使我看到有代码的三个自包含的领域,而不是两个:用户说明,收集文件/路径信息调试模式切换码

删除原始区域(提交4c57927),并与四个新地区取代他们(提交3446a54)[4]。我重新排列方法,以适应新的地区,而这些现在将转化为三个新的类别:通讯,PathSetter调试模式切换器(FileLoaderBudgetingMonthService此图中未显示,因为他们已经被提取):

图17:识别最终的ReconciliationIntro类

我使用与for相同的原则逐步安全地创建这些新类BudgetingMonthService,消除新的地区,一旦他们达到了目的(见提交7f464a4提交7e118c1)。

值得注意的是,当从更大的类中提取新类时,我希望它们是独立的。出于这个原因,我故意避免了有时会出现的反模式,即提取的类作为依赖项自动注入到原始类的构造函数中。

PathSetter类真可谓是不平凡的 - 见提交2921220提交398539a[4]。这是由于路径设置码当前略微曲折性,这是我注意到在步骤3的端部。通过提取该代码放到一个单独的类,并给它自己的明确上下文,我已经使这个代码好一点 - 但它仍然需要一些关注。

最后,我ReconciliationIntro类是更干净更简单,和原来的41点的方法已被减少到三个:开始,ReconciliateDo_matching:

图18:最终ReconciliationIntro类

回顾

我的班级过大。我制定了不良的编码习惯,我想停下来添加任何新功能之前,把事情做好。

要解决我的过大的班,我采取了以下措施:

在小步移动和我编译和运行的每一步测试。现在我有一个更小的类,它从其他几个也-小班电话功能,并且他们每个人更容易适应在我的头上。

接下来是什么?

注意,在重构的中间这篇文章的目的,所以,如果你想看到移动到下一个步骤之前代码的国家有关,您需要查看提交6103f0b。还需要注意的是我完成的最第7步在这篇文章中犯下(解释在这里)。

提交6103f0b,我已经拉了文件加载代码伸到FileLoader并对主要挑战有了更清晰的认识。以下四种方法(原位可见)在这里)显然是有问题的:

  • Load_bank_and_bank_in
  • Load_bank_and_bank_out
  • Load_cred_card1_and_cred_card1_in_out
  • Load_cred_card2_and_cred_card2_in_out

它们具有以下问题:

  • 有很多重复的 - 一目了然,他们看起来完全相同。
  • 他们是太长了。
  • 他们在内部创建对象,并将它们传递到这是不可测试的密切相互依赖的方式彼此。

这些都是我想解决的问题,但在我进行任何重构之前,我真的需要围绕这些方法进行一些测试。那是我下一步要做的。我的愿望是写一篇后续文章,188足球比分直播但我学到了很多,而不是对这类事情做出承诺,所以现在对读者来说,这是一个练习。


致谢

非常感谢以下人员,他们非常友好地阅读了本文的初稿,并提供了宝贵的反馈和建议:宝拉保罗,188bet足球充值,Priti Biyani,里卡尔多·Novaglia,丹Terhorst-North,凯夫林·海尼,史蒂夫·弗里曼,斯基特,萨尔科德宝,乔·雷,克里斯·谢泼德,卢克·莫顿,斯科特·吉米尼亚尼,理查德·福斯特,马科斯贝泽拉,萨姆·卡林顿

脚注

1:TDD代表测试驱动开发(Test Driven Development),这是一种确保所有代码单元都经过测试,并且测试描述了系统的行为的技术。这是通过编写测试来完成的之前编写代码以使这些测试通过。关于它还有很多可以说的,但这不是本文的重点。你可以读到更多在这里

2:“符合我的想法”是丹·特霍斯特-诺斯的模式之一吗软件,更快的书(正在进行的工作还)。他谈到,因为能理解的代码块任何的重要性,“在你的头上安装它。”该名字的由来詹姆斯·刘易斯和丹形容在这次谈话中但丹告诉我,这个概念可能起源与阿利斯泰尔·琼斯

3:188足球比分直播对战略模式进行重构:重构的主要目的是启用188足球比分直播战略模式。的“重构到模式”的企业有一个整体188足球比分直播约书亚·克里耶夫斯基的专著- 是值得一读,如果你想知道更多。事实上,正如马丁·福188bet足球充值勒说,“很多人都表示,他们找到一个重构的方法是学习模式的一种更好的方式,因为你指逐步看到的188足球比分直播问题和解决方案的相互作用。”

4:提交链接:不要觉得有义务遵循提交链接!更多的是,在如何阅读这篇文章侧边栏

5:Resharper是一个常用的Visual Studio扩展,用于代码编辑等工作。然而,它不是免费的,并且越来越被本机Visual Studio工具所取代。

重大修改

2020年4月14日:首次出版