[翻譯]清除靜態方法三板斧之三——如何重構助手類?

高翌翔發表於2011-12-30

原文連結: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

請注意,本系列僅僅是思考的起點,思考沒有終點

相關文章