“硬核”程式碼重構

樑小生0101發表於2019-02-22

在學習程式設計的路上,相信大家這幾個詞一定不少聽,什麼 面相物件、封裝繼承多型、內功心法21種設計模式 等等 。但是卻很少用到,或者說用到的都是被動使用。大牛們在寫程式碼前早就構思好了,介面,基類等等。自己寫程式碼的時候,很少有把物件導向想的面很全,很容易在遇上不夠優秀的程式碼,這時候就需要重構了。

但是我們卻很少去重構,可能原因有很多,比如很重要的一點:不想改出Bug;不想增加工作量(我是要5點半下班的男人,女朋友還在等我做飯);時間很緊,先實現功能先;程式碼是82年的不敢動!!!

其實重構可以寫出更健壯的程式碼、減少後面的工作量、讓開發者更好閱讀。看了很多重構的文章,發現很多是一些基本的,命名規範或者拆函式什麼的。這篇文章寫下我重構的一些思路和重構之前程式碼對比。好了,廢話不多說,上專案。威武、威武、威武........

簡單介紹一下專案,專案就是一個客戶端小工具用來稽核編寫遞交的說明是否規範。這裡說下,物件導向是一種思想,和語言無關。只要是面嚮物件語言,無論你是C#、Java、TypeScript、Python,都是可以用這種思想重構的。

tools.png

上圖就是小工具了一個部分截圖,有若干個欄,每個欄都是填寫一些對應的修改內容,在稽核校驗時,會檢查寫的內容是否符合標準。

前輩已經完成一些欄的校驗,我的任務是完成剩下欄:寫正規表示式,然後在不標準的時候提示就好了,是不是覺得根本沒有必要重構,依葫蘆畫瓢就好了在我拿到程式碼的時候,是這樣的程式碼:

// 下面變數是和介面繫結的變數,RaisePropertyChanged作用在變數改變的時候通知前端重新渲染
// 不熟悉C#程式碼的,只用知道這些變數就是和前臺繫結的就是了

    private string _editDescription;
    /// <summary>
    /// 修改說明內容
    /// </summary>
    public string EditDescription
    {
        get { return _editDescription; }
        set
        {
            _editDescription = value;
            RaisePropertyChanged(() => EditDescription);
        }
    }

    private string _editDescriptionRules;
    /// <summary>
    /// 修改說明校驗規則
    /// </summary>
    public string EditDescriptionRules
    {
        get { return _editDescriptionRules; }
        set
        {
            _editDescriptionRules = value;
            RaisePropertyChanged(() => EditDescriptionRules);
        }
    }

    private bool _editDescriptionHasValidationError;
    /// <summary>
    /// 修改說明校驗標誌
    /// </summary>
    public bool EditDescriptionHasValidationError
    {
        get { return _editDescriptionHasValidationError; }
        set
        {               
            _editDescriptionHasValidationError = value;
            RaisePropertyChanged(() => EditDescriptionHasValidationError);
        }
    }

    private string _integratedNote;
    /// <summary>
    /// 整合注意內容
    /// </summary>
    public string IntegratedNote
    {
        get { return _integratedNote; }
        set
        {
            _integratedNote = value;
            RaisePropertyChanged(() => IntegratedNote);
        }
    }

    private string _integratedNoteRules;
    /// <summary>
    /// 整合注意規則
    /// </summary>
    public string IntegratedNoteRules
    {
        get { return _integratedNoteRules; }
        set
        {
            _integratedNoteRules = value;
            RaisePropertyChanged(() => IntegratedNoteRules);
        }
    }

    private bool _integratedNoteHasValidationError;
    /// <summary>
    /// 整合注意校驗標誌
    /// </summary>
    public bool IntegratedNoteHasValidationError
    {
        get { return _integratedNoteHasValidationError; }
        set
        {
            _integratedNoteHasValidationError = value;
            RaisePropertyChanged(() => IntegratedNoteHasValidationError);
        }
    }
     
// 這裡隨便舉了兩欄的變數,後面還有若干欄。
複製程式碼

依葫蘆畫瓢以後呢,我發現原來是這樣的。每一欄用了單獨三個變數直接去繫結:編寫的內容、是否標準的標誌、不標準提示語。我是一個懶人啊,在我畫了兩個瓢以後,就很煩(一直在那複製變數,在那改來改去),這些個變數都是差不多一個意思。為啥讓我重複在複製,修改呢?

明顯每欄有相同項啊,對不對,一個是內容,一個狀態,一個是錯誤提示語啊,咆哮!!!

這時候,我想起了書本上的一句話: "早重構,常重構"

我已經安奈不住我那顆懶惰的心裡,因為下面還有“狠多狠多”欄,每一行有三個類似的變數,我這依葫蘆畫瓢,這個星期加班,就在複製貼上去了。我是要上進,要每天進步的人,不能這樣!

我為啥不能將這相同的共性抽象出來呢?是不咯,這個時候,我想起了《葵花寶典》的第一頁的第一句:“萬物皆物件”,於是本能的告訴我,每一欄看做一個物件,只是每欄的值不一樣。然後我就寫了一個”雞肋“(基類),程式碼如下:

    /// <summary>
    /// 欄 基類
    /// </summary>
    public class Base: ObservableObject
    {
        private string _content;
        /// <summary>
        /// 內容
        /// </summary>
        public virtual string Content
        {
            get { return _content; }
            set
            {
                _content = value;
                RaisePropertyChanged(() => Content);
            }
        }

        private string _errorTip;
        /// <summary>
        /// 錯誤提示
        /// </summary>
        public virtual string ErrorTip
        {
            get { return _errorTip; }
            set
            {
                _errorTip = value;
                RaisePropertyChanged(() => ErrorTip);
            }
        }

        private bool _isError;
        /// <summary>
        /// 是否錯誤
        /// </summary>
        public virtual bool IsError
        {
            get { return _isError; }
            set
            {
                _isError = value;
                RaisePropertyChanged(() => ErrorTip);
            }
        }
    }
複製程式碼

virtual是為了讓子類能夠重寫get和set(如果有需求的話,為後面擴充套件做準備),然後欄位從3個變到了1個了。

/// <summary>
/// 修改說明欄
/// </summary>
public class EditDescription : Base { }		
private EditDescription _editDescriptions;
/// <summary>
/// 修改說欄繫結變數
/// </summary>
public EditDescription EditDescriptions
{
    get { return _editDescriptions; }
    set
        {
            _editDescriptions = value;
            RaisePropertyChanged(() => EditDescriptions);
        }
}
        
// 其他的一樣,我就不多寫了
複製程式碼

那,我們來算一下賬,原先的變數,每一欄有3個變數,一個變數有6行程式碼的話,假如我這個有100欄,就是:

重構前: 100(欄)x3x6 = 1800 行程式碼 (阿西吧!!!)。

重構後: 100(欄)x1x6 = 600 行程式碼 。

小學算數: 1800 - 600 = 1200 (1200行,你說乾點啥不好啊)

秀兒們算下,你花這麼多時間,在那複製貼上,不敢去動前輩們的程式碼,還是勇敢一點呢?

然後是不是感覺到一個繼承就簡單了很多呢,這只是一個物件導向的簡單運用,就讓我們少寫了這麼多程式碼。

前輩和我說,寫程式碼就像練武功,就像你會了“九陽神功”或者"吸星大法",學其他武功看一眼就會。也就是說,當你理解了物件導向以後呢,你自然而然的就會寫出很精簡的程式碼了。阿西吧,又扯遠了。

變數好了,抬頭一看函式,我的臉便有點抽搐,啊!!這函式有毒!函式是這樣的:

  // 此處函式用來設定每一欄報錯時邊框變紅
    private void SetValidateFlag()
    {
        // 繫結的實體類判斷狀態
        if (tsEntity.EditDescriptionHasValidationError)
        {
            // 控制元件邊框改顏色
            EditDescriptionHsExpander.BorderThickness = new Thickness(1);
            EditDescriptionHsExpander.BorderBrush = _readBrush;
        }

        if (tsEntity.TestSuggestionHasValidationError)
        {
            TestSuggestionHsExpander.BorderThickness = new Thickness(1);
            TestSuggestionHsExpander.BorderBrush = _readBrush;
        }

        if (tsEntity.IntegratedNoteHasValidationError)
        {
            IntegratedNoteHsExpander.BorderThickness = new Thickness(1);
            IntegratedNoteHsExpander.BorderBrush = _readBrush;
        }
        // 此處省略一萬個if,每個欄都有一個if
    }
複製程式碼

然後大家懂的嘛,在我改了兩欄以後,我又耐不住性子了。再一看,感覺似曾相識燕歸來的感覺啊!有沒有,每個if中都有三個類似的東西。我那個心啊,又忍不住悸動了起來,像是等初戀的感覺,想她來,來了又不知道要幹哈。然後我發現其實if中判斷的就是每欄的狀態,括號裡面是對控制元件欄的設定。然後想嘛,能不能搞個類似迴圈的東西,只要寫一個for就好了呢。

思考以後,是這樣的,我把控制元件也變成了欄的一個屬性了,這樣if判斷裡面就都是判斷一個類了。程式碼就變成了這樣:

    public class Base: ObservableObject
    {
        /// <summary>
        /// 模組控制元件(新增)
        /// </summary>
        public object Control { get; set; }
        
        //其他的和上面的Base一樣
    }
複製程式碼

然後 SetValidateFlag() 函式就變成了這樣:

    /// <summary>
    /// 設定校驗介面效果
    /// </summary>
    private void SetValidateFlag()
    {
        // listBase 所有欄實體集合
        foreach (var item in listBase)
        {
            if (item.IsError)
            {
                var control = item.Control as HsExpander;
                if (control == null)
                    continue;
                // 將控制元件的邊框變成紅色
                control.BorderThickness = new Thickness(1);
                control.BorderBrush = _readBrush;
            }
        }
    }
複製程式碼

好了嘞,好好的一個if的葫蘆瓢給我們給整沒了。這時候我們來算算我們這個重構,省了多少事。老樣子:

原先的一個if算5行程式碼,我這個有100欄,就是:

重構前: 100(欄)x 5 = 500 行程式碼 (全是if啊)。

重構後: 我數了一下,沒有錯的話應該是:14行程式碼 。

重構一下以後,變數得到了減少且對外統一。感覺一顆心得到了小小滿足,感覺靈魂得到了昇華,不知道是自己太容易滿足,還是程式碼世界裡給我的成就感。感覺“乾坤大挪移”瞬間上了兩層。

這就是我在寫程式碼的時候一個小小的重構思路,希望能夠幫助到大家一點。然後這個是我的Github,如果有幫助到大家的專案,可以給我點個star, 小生在這邊謝謝啦!!!

相關文章