钱行慕

导航

【译】重构:这个类太庞大了【上】

来自于一个真实(有缺陷)的代码环境的重构示例。

在这篇文章中我将浏览一些列来自于真实代码环境的重构示例。我并不打算演示一下完美的情形,但它确实代表了一些事实。

问题大纲

       故事开始于一件无聊的家务事。我之前写了某种个人账户软件 - Reconciliate。它在命令行中运行并执行如下动作:

  • 加载一些我个人的以逗号分隔数据:
    • 我的最近的银行以及信用卡事务的记录
    • 我计划中的月度以及年度事务(基于一个中央电子表格的数据)。
    • 任何未经协调的先前记录的数据。
  • 加载第三方以逗号隔开的数据(这些数据来自于银行以及信用卡公司)。
  • 协调第三方数据以及我的个人数据。
  • 将所有的东西都写回中央电子表格。

       我需要修正一些bug并添加一些新的特性。但我通常的工作流程是把我的最小的孩子送上床睡觉之后,晚上很晚的时候才会到达笔记本跟前,此时在我睡眠之前只有很少的时间了,而且通常距离我上一次看这个代码已经过去好几个礼拜了。在这种情形下我们很容易像这样来考虑事情(这是很可怕的):“好吧我知道这个代码很混乱,但是我现在没有时间来考虑并立即修复它。”

      很明显这并不理想。

      特别是其中有一个类 - ReconciliationIntro - 每次当我看到它的时候都会头疼不已。它是臃肿的,卷曲的,并且不可能“fit in my head”。这也产生了一个令人讨厌的反馈循环:因为这个代码是如此的难以证明,重构会花费比我所拥有的更多的时间和精力,所以我将会忍受它更久的时间。即使其意味着我不能对其做任何更改,因为它将花费我太多的时间来理解代码的当前状态以及决定更改将在代码的什么地方进行。

     举个例子,我想要添加处理另一张信用卡的能力。在许多的地方我都使用了polymorphism 和 the strategy pattern 来保持封装整齐的各个信用卡的唯一的行为。但是 ReconciliationIntro 类是一个地方,在那里由于缺乏对代码的设计以及清晰的上下文分离,如果我添加另一张信用卡,我会使得一个已经臃肿的类变得更糟糕。它对于四种数据类型包含了四个重复的代码路径(Bank In, Bank Out, Credit Card 1 and Credit Card 2),并且如果我现在添加了Credit 3,我最终会遵循同样的反模式。

      我的总体目标是通过策略模式,用更通用的代码来替换这些代码。但仍旧有四个问题站在我的面前:

  • 这一个巨大的类(ReconciliationIntro)负有太多的责任。
  • 有许多私有的内嵌的代码,其很难进行单元测试,因为它们没有公共的接口。
  • 在类ReconciliationIntro 的内部,有一个做了大量工作的大方法。
  • 在类ReconciliationIntro 的内部有几个方法,它们使用了相同的重复模式,但具有不同的细节。

     我计划以上述列出的顺序来修复所有的这些问题。而这个预重构将会使得我更容易封装各个信用卡/账户的行为,正如 Kent Beck说的那样,“让改变变得容易,然后做容易的改变”。

        本文讨论的是上面列表中的第一个问题:这个类太大。

为什么重构

       我不能轻易的讲明白ReconciliationIntro 类,因为它具有太多的责任了。它最初被设计为软件的入口大厅,它所做的所有的事情就是显示一些消息然后初始化一些做主要工作的类。但是一段时间之后,大量其他代码被插入进来,我想要使得添加另一个信用卡变得很容易,所以我将以将这个巨大的类拆分成更新的类来开始我的计划。

       其好处会有如下几条:

       通过将一组方法移动到相互独立的类中,我将创建清晰的上下文并会将紧耦合降到最低,这就意味着:

  • 当我想做一些更改或者修复一些问题的时候,我会知道要从哪儿下手。
  • 当我调整一些代码的时候,我仅仅需要处理一些小的独立的部分,其与我的头脑相适应。我将不必理解整个系统。
  • 任何对状态的操作都只会发生在本地很小的上下文之中 - 所以我不必担心系统的一个区域会在另一个区域中改变状态。

我是如何做的?

      To be continued...

posted on 2020-04-15 13:01  钱行慕  阅读(241)  评论(0编辑  收藏  举报