原文链接:How to Refactor the Helper Class

声明:本文与右侧相关图书《深入理解C#(第2版)》二者之间没有直接关系,之所以选中此书作为相关图书,是因为二者有个共同特点【Depth】,即对事物的深入探查(再说右边空一块儿也不美观,呵呵)。

书接上回

在前两回《清除静态方法三板斧之一——静态方法将使你大吃一惊》《清除静态方法三板斧之二——我是否应该保留助手类》中,我们先仔细分析了静态方法的利弊,然后明确了对于助手类的态度,现在就让我们一起来动手消除助手类吧!


在上一帖中,我提出了“我是否应该保留助手类”的问题。但愿我已说服你,即你不应保留助手类。

helper class

现在,我打算详细说明一些我曾用来在遗留代码中消除助手类的技巧。

首先,让我们来设置一条基本准则:我们跳入遗留代码之中并消除某些助手类不只是为了好玩。到底是为什么?

  1. 对于在助手类上所花费的时间而言,你没有得到一个好的投资回报率(ROI,return on investment )。
  2. 你的经理或主管将可能会眉头紧锁、用颇具不满的眼神来看你,因为你并没有为该产品添加任何有形的价值。
  3. 如果你搞砸了某些东西,你将让重构蒙上坏名声,而且会导致其他开发者不愿重构。你将必须穿上一件大大的红色罪衣,上面用深红色写着字母“R”。
  4. 这并不是很有趣。我是说,这不应该有趣……让我这么说吧,如果你觉得此类事情有趣的话,那么在我家里我已经有许多此类其他“有趣”的东西可以让你试一试。

因此,我们该怎么办呢?当我们修改助手类或为它添加功能时,我们将把助手类重构至真实类或现有类中。让我们开始吧……

修改一个方法

如果你必须修改一个位于助手类中的方法,首先要做的第一步是,将正如……/就像……(as it is)逻辑移到某个我们可以为其编写单元测试的具体类中。示例如下:

public static int getDependantCount(MonsterObject stuff)
{
    int dependantCount = 0;
    List<Relation> relations = stuff.getPerson().getRelations();
    foreach(Relation relation in relations)
        {
        if(RelationshipHelper.isDependant(stuff.getPerson(), relation))
        {
            dependantCount++;
        }
    }

    return dependantCount;
}

考虑一下这个例子,我们需要做的第一件事情是,弄清楚这个助手类方法属于哪个真实类。(快速旁注:注意正在讨论的这个助手类方法还用到了另一助手类。现实情况可能就是如此。)

我用来弄清楚这个问题的技巧之一是,查清该助手类方法都用到了哪些数据。在这个简单示例中,显而易见的是,即便该方法的传入参数类型为MonsterObject,而正在操作的数据是属于Person类的。通常将某个助手类方法移至的正确位置是,在那个地方你可以在该方法中最大化this运算符的调用次数。

在这个示例中,让我们把此助手类方法移至Person类。下面是处理后的样子:

private int getDependantCount()
{
    int dependantCount = 0;
    List<Relation> relations = this.getRelations();
    foreach(Relation relation in relations)
    {
        if(RelationshipHelper.isDependant(this, relation))
        {
            dependantCount++;
        }
    }

    return dependantCount;
}

请注意,我们在这里都做了什么?

  • 我们消除了一个传入参数。
  • 我们用this .替换了一些调用。
  • 我们使该方法成为非静态的、私有方法。
  • 当然,我们还把它移至所属的Person类上。

尽管我们仍有一个对最初调用过的助手类方法(RelationshipHelper.isDependant(Person p, Relation r))的引用,不过我们沿着这条路走下去稍后即可消除它。如果这个逻辑最终变得很复杂,我们可能会拥有一个接收关系列表(List)的DependantCounter类,我们的Person类的方法将实例化并调用它,从而获得该人员的(依赖)计数(dependantCount)。

我们的下一步是编写一个用于测试当前功能的单元测试,然后签入我们的代码。最后,当这么做完以后,对于我们打算对该方法进行的修改,我们可编写一个将会失败的单元测试,然后再去修改该方法。

以这种方式做事更加清晰、简单,而且我们刚刚就消除了助手类中的一个方法!

添加一个方法

为助手类是添加一个方法是非常容易的。千万不要这样做!!!而是应弄清楚此方法将会操作那些数据,并将该方法移动至包含那些数据的类中。

如果你将要添加的功能巨大,而且似乎拥有其自己的职责,那么应再接再厉,进而创建一个新类。

随着你修改代码并把助手类的一些方法放置到真实类中,或者在一些类中添加原先按照惯例会写入助手类中的新方法,你会开始察觉到,一些被移入方法的类在生长。这就行了,你正在发现你需要更多类。助手类不应是一种充斥着那些你不想放入实际所属类中的长方法的类。相反,要去做的事情恰恰是基于职责拆散助手类。

继续使用当前的例子,设想Person(人)类拥有一些与钱相关的数据。也许有个私有变量叫cashOnHand(手头现金)。随着你不断往类里增加内容,你可能最终引入与其储蓄账户、未偿还贷款等相关数据。你可能引入一些方法用于操作其储蓄账户信息、以及手头现金。这就够了,而且很高兴地发现,Person(人)类相对于此人的财务数据而言已成为一个独立的事物。到那个时候,你可能会创建一个叫Financials(财务状况)的类,并且Person类将拥有一个对于该类的引用。

重构助手类就是弄清楚各种事物所属的位置。

重构助手类就像收拾你厨房中的杂物抽屉一样。你必须认真检查每一样物品,并找到其该在的地方。如果没有这样一个地方,那么你可能必须造一个出来。

如果某个方法操作一些数据,那么该方法应该尽可能靠近那些数据。不要一下子解决那种巨大的助手类,而是随着你更改或添加功能时,一点一点地蚕食助手类。

写在系列之后——本人增补

实话实说,本系列是俺凭空捏造出来的,最初的阅读顺序是完全相反的,即三二一的倒序,读完才发现三篇博文相互呼应,形成了一个小系列,并命名为“清除静态方法三板斧”。而“三板斧”源自隋唐演义中“混世魔王”程咬金梦中习得三板斧绝技的故事(俺没看过书,只听过广播里的评书,嘿嘿),用以比喻解决问题的方法虽不多,但却非常管用。

“三板斧”对付二三流敌手很管用,但遇到一流高手时只能风紧扯呼(逃跑)了。当然,我们的“三板斧”系列也是如此,它只能用于处理一部分职责划分不明确的助手类或静态方法。以下是俺想到的若干文中尚未解答的问题:

  • 静态类(static class)的优缺点是什么?何时禁用?何时适用?还有哪些替代方案?
  • 静态方法(static method)的优缺点是什么?何时禁用?何时适用?还有哪些替代方案?
  • 静态类与静态方法的关系是怎样的?

欢迎大家积极分享自己的问题或观点。俺也会继续思考以上问题,并整理出来与大家分享 :D

请注意,本系列仅仅是思考的起点,思考没有终点