Refactoring of Gilded Rose

前言

本文簡單介紹一個(gè)重構(gòu)到策略模式的小例子。
GitHub地址:https://github.com/janipeng/GildedRose.git

里面有三個(gè)branch,分別是我做了三次重構(gòu)的過程,有一點(diǎn)點(diǎn)差別。重構(gòu)過程中的視頻如下。錄制的是forVideo這個(gè)branch。視頻地址如下:
http://v.youku.com/v_show/id_XMzQ2MTI0ODczNg==.html?spm=a2h3j.8428770.3416059.1

因?yàn)橛信笥颜f視頻中沒有講解過程,所有我又重新錄制了一個(gè)視頻。這個(gè)視頻是錄制的forVideoTwo 這個(gè)branch的,有一些簡單的講解和IntelliJ IDEA快捷鍵的說明,IDE快捷鍵的使用可以幫助我們提升寫代碼的體驗(yàn)和效率,也是我這里想要表達(dá)的,完成這個(gè)重構(gòu)練習(xí)的過程中,完全可以不需要使用鼠標(biāo),所有的操作都可以用鍵盤快捷鍵來操作。視頻地址如下:
http://v.youku.com/v_show/id_XMzQ2NDE4MDMxNg==.html?spm=a2h3j.8428770.3416059.1

需要重構(gòu)的代碼

package com.jani;

import java.util.ArrayList;
import java.util.List;

public class GildedRose {

    static List<Item> items = null;

    /**
     * @param args
     */
    public static void main(String[] args) {
        
        System.out.println("OMGHAI!");
        
        items = new ArrayList<Item>();
        items.add(new Item("+5 Dexterity Vest", 10, 20));
        items.add(new Item("Aged Brie", 2, 0));
        items.add(new Item("Elixir of the Mongoose", 5, 7));
        items.add(new Item("Sulfuras, Hand of Ragnaros", 0, 80));
        items.add(new Item("Backstage passes to a TAFKAL80ETC concert", 15, 20));
        items.add(new Item("Conjured Mana Cake", 3, 6));

        updateQuality();
}


    
    public static void updateQuality()
    {
        for (int i = 0; i < items.size(); i++)
        {
            if ((!"Aged Brie".equals(items.get(i).getName())) && !"Backstage passes to a TAFKAL80ETC concert".equals(items.get(i).getName())) 
            {
                if (items.get(i).getQuality() > 0)
                {
                    if (!"Sulfuras, Hand of Ragnaros".equals(items.get(i).getName()))
                    {
                        items.get(i).setQuality(items.get(i).getQuality() - 1);
                    }
                }
            }
            else
            {
                if (items.get(i).getQuality() < 50)
                {
                    items.get(i).setQuality(items.get(i).getQuality() + 1);

                    if ("Backstage passes to a TAFKAL80ETC concert".equals(items.get(i).getName()))
                    {
                        if (items.get(i).getSellIn() < 11)
                        {
                            if (items.get(i).getQuality() < 50)
                            {
                                items.get(i).setQuality(items.get(i).getQuality() + 1);
                            }
                        }

                        if (items.get(i).getSellIn() < 6)
                        {
                            if (items.get(i).getQuality() < 50)
                            {
                                items.get(i).setQuality(items.get(i).getQuality() + 1);
                            }
                        }
                    }
                }
            }

            if (!"Sulfuras, Hand of Ragnaros".equals(items.get(i).getName()))
            {
                items.get(i).setSellIn(items.get(i).getSellIn() - 1);
            }

            if (items.get(i).getSellIn() < 0)
            {
                if (!"Aged Brie".equals(items.get(i).getName()))
                {
                    if (!"Backstage passes to a TAFKAL80ETC concert".equals(items.get(i).getName()))
                    {
                        if (items.get(i).getQuality() > 0)
                        {
                            if (!"Sulfuras, Hand of Ragnaros".equals(items.get(i).getName()))
                            {
                                items.get(i).setQuality(items.get(i).getQuality() - 1);
                            }
                        }
                    }
                    else
                    {
                        items.get(i).setQuality(items.get(i).getQuality() - items.get(i).getQuality());
                    }
                }
                else
                {
                    if (items.get(i).getQuality() < 50)
                    {
                        items.get(i).setQuality(items.get(i).getQuality() + 1);
                    }
                }
            }
        }
    }

}

這段代碼很明顯的壞味道就是if else 太多,而且很多嵌套。代碼的可讀性和可維護(hù)性非常差。

重構(gòu)

有必要再啰嗦一下重構(gòu)的定義:

名詞定義:對軟件內(nèi)部結(jié)構(gòu)的一種調(diào)整,目的是在不改變軟件可觀察行為的前提下,提高其可靠性,降低其修改成本。

動(dòng)詞定義:使用一系列軟件重構(gòu)手法,在不改變軟件可觀察行為的前提下,調(diào)整其結(jié)構(gòu)。

重構(gòu)的幾個(gè)簡單規(guī)則:

去除代碼壞味道(Remove Code Smell)
代碼總是工作(Always Work)
可以隨時(shí)停止(Can Stop at Anytime)
可以隨時(shí)開始(Can Continue at Anytime)
可以隨時(shí)恢復(fù)(Can Revert at Anytime)

重構(gòu)的一個(gè)有趣的口訣:

舊的不變(Keep Old)
新的創(chuàng)建(Create New)
一步切換(Switch to New)
舊的再見(Bye to Old)

在這個(gè)重構(gòu)練習(xí)中,我會盡量遵守上面的規(guī)則。并且做到“小步提交,頻繁測試”。

重構(gòu)有一個(gè)非常重要的前提: 需要有足夠的自動(dòng)化測試。
在這次重構(gòu)練習(xí)之前,我先給需要重構(gòu)的代碼加上了單元測試。加單元測試的過程不在錄制視頻中。單元測試的代碼可以去GitHub上面看。

重構(gòu)過程分析

GildedRose 中的 updateQuality 方法,if else 條件分支和嵌套很多。代碼的可讀性非常差,想要通過看代碼理解代碼邏輯會非常困難。簡單分析一下,可以發(fā)現(xiàn),updateQuality 方法是更新item的quality和sellIn的,并且不同的item會有不同的更新規(guī)則。不同的item有不同的策略進(jìn)行更新,比較容易可以想到用策略模式。

根據(jù)上面提到的重構(gòu)的口訣,我們先不去修改舊的代碼,而是添加一個(gè)新的function。

public static void updateQuality2() {
        ItemStrategy itemStrategy;
        for (Item item : items) {
            switch (item.getName()) {
                case "Aged Brie":
                    itemStrategy = new AgedBrie();
                    break;
                case "Backstage passes to a TAFKAL80ETC concert":
                    itemStrategy = new BackstagePasses();
                    break;
                case "Sulfuras, Hand of Ragnaros":
                    itemStrategy = new Sulfuras();
                    break;
                default:
                    itemStrategy = new NormalItem();
            }
            itemStrategy.update(item);
        }
}

在這個(gè)function里面先創(chuàng)建一個(gè)ItemStrategy的接口類。里面有一個(gè)update的function。然后根據(jù)item的name去創(chuàng)建不同的實(shí)現(xiàn)類。
接著,把updateQuality里面的代碼拷貝到每一個(gè)具體的實(shí)現(xiàn)類里面的update方法。然后把updateQuality2替換updateQuality。跑一下所有的單元測試。

接著,看每一個(gè)ItemStrategy的具體實(shí)現(xiàn)類。如下面的NormalItem。把所有跟NormalItem沒關(guān)系的代碼都刪掉。就變成下面的樣子:

public class NormalItem implements ItemStrategy {
    @Override
    public void update(Item item) {
        if (item.getQuality() > 0) {
            item.setQuality(item.getQuality() - 1);
        }

        item.setSellIn(item.getSellIn() - 1);

        if (item.getSellIn() < 0) {
            if (item.getQuality() > 0) {
                item.setQuality(item.getQuality() - 1);
            }
        }
    }
}

當(dāng)然,這個(gè)里面的代碼還是可以繼續(xù)在進(jìn)行一些重構(gòu)的,但不是這次重構(gòu)的重點(diǎn)。

更多更詳細(xì)的內(nèi)容可以去GitHub上面看,這里就不過多闡述了。

小結(jié)

本文介紹的一個(gè)比較簡單的重構(gòu)練習(xí)題,但是如果沒有想到用這樣的方式去重構(gòu)的話,其實(shí)也并不是很容易。在實(shí)際工作的生產(chǎn)代碼里面,假如有根據(jù)不同的類型有不同的處理邏輯,可以考慮用這個(gè)的方式去做重構(gòu)。
同時(shí),在反復(fù)多次完成這個(gè)重構(gòu)練習(xí)的過程中,對IntelliJ IDEA的使用會有很好的練習(xí)。
如果想成為畫家,可以先從練習(xí)畫雞蛋開始。就像這樣。

最后,這份代碼的需求是一個(gè)Coding Dojo的題目。大家有興趣可以看下圖中的需求去用TDD的形式做一個(gè)練習(xí)。后期,我也會嘗試寫一些Coding Dojo的文章分享出來。希望可以自己練習(xí)和給大家一定的幫助。

Gilded Rose.JPG
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容