重構學習筆記(1)-大話重構第一章筆記

重構

大話重構第一章筆記

筆者認為從【商業】和【價值】和【個人職業發展】,三個角度來看待【系統重構】這一塊會比較好…

數十年的遺留系統,常見改或不改?

  • 不改會遇到越來越多需求變更,維護成本越高,更改越困難,面對不斷出現新技術,系統越來越落後,面對競爭者會被市場淘汰風險。
  • 遺留系統湊者用可以運行數年,一不小心改出問題,後面就完蛋,面對一堆客訴,四處救火,對手趁火打劫,這是軟體企業不能承受風險。

     

筆者見解過去擔任相關企業IT部門單位或是專案公司,很多成員或是Member欠缺從【整體思維】來看,系統是公司的命脈。

只要自己接管了系統,系統就會跟自己是息息相關,已經逃脫不了跟公司營運和策略,因為公司營運策略方向會持續的調整,相對的系統和程式,也是要隨者能夠持續調整和優化,甚至是重構。
 

只要一但需求變更或是追加需求,系統的調整命運就會跟我們自身跑不掉,最好的方式就是要【持續學習】,這其實也是和精實也有關係,如果用【持續學習】和【承擔責任】及【持續改善】來落實系統重構,會來得更洽當。

 

何謂系統重構?

它是一套嚴謹而安全的過程方法,他透過一系列【行之有效】的方法和措施,保證軟體在改善同時,不會引入BUG,也保證軟體改造品質。

就是在【不改變軟體外部行為】的基礎上,改變軟體內部結構,使更易於閱讀、易於維護、易於變更。

 

系統重構的前提是【不改變軟體外部行為】,這保證了不會引入bug. 例如,把顯示的日期表示從'2021-12-12’ 變成'2021-12-12 00:00: 00’,在開發看來不是bug,但在客戶看來就是bug。

 

系統重構的總結

  • 系統重構不是為那種冒極大危險進行的程式修改,保證前後輸入輸出的一致性,這就是我們提的【不改變軟體外部行為】。
  • 保證前後輸入輸出的一致性,透過不斷的測試,起初這樣做法是【手工測試】,隨後逐漸轉變為【自動化測試】。
  • 每修改一點,進行一個測試,然後再修改一點點。
  • 測試就是系統重構的保險索。
  • 系統重構應避免大設計,盡量採用一個一個不斷的小設計,這就是【小步快跑】模式,特性可以簡單與快速回饋。
  • 不可能預知未來,做今天的設計,解決今天的事情,完成今天的重構,讓明天見鬼,主要是簡單化去處理系統。

     

何謂兩頂帽子

  • 透過安全重構方式先重構系統,可以應付需求,在追加代碼,實現新需求。
  • 一頂只重構而不新增功能,一頂時增加新功能而實現新需求,設計時候,只思考當下。


    重構的週期在10分鐘-一小時,重構週期越長,考慮問題越複雜,出錯問題越大。

     

修改軟體四個動機

  • 增加新功能
  • 原有程式含有BUG
  • 改善原有程式的結構
  • 改善原有系統效能

這邊有提到外部品質是【客戶對軟體功能需求與非功能需求的滿意度】,涉及一個軟體的信譽和生命力,把軟體外部品質高於一切高度同時,軟體內部品質長期不重視,公司沒有保證軟體內部品質的措施,甚至趕工方式降低內部程式品質,程式愈寫愈爛,效率愈來愈低。

這邊筆者解讀的是,透過趕工的方式,交付出軟體,得到客戶的成果,降低軟體的品質,例如一坨式寫法,沒有採用一些工程方式進行軟體開發,來快速結案之類。

要提高軟體內部的品質則採用第三點,改善原有程式的結構。

這邊筆者補充,改善原有程式結構以外,筆者認為還要追加如下

  • 透過看書,經典書,原文書
  • 上課用錢買時間(吸收一流大師或是高手的知識與經驗)
  • 持續改善
  • 控制風險
  • 為自己和為接手的人設想,方便維護與調整程式

接者來重構Hello World例子,其實也是按照書裡面轉換成C#的調整。
文中有提到系統重構所有方法,都是一個程式的等量變換。

何謂等量變換?

等量變換的意思是在台灣用語來說,也可以用【等量公理】來看,代數中的一個公理,可用於解方程式。
舉個例子: a, b, c三數中,若a = b,則:

  • a + c = b + c
  • a - c = b - c
  • ac = bc

 

註記:等量變換不等於原地踏步。

以下的Hello World程式就是符合了,遺留系統中特性

  • 沒有註釋
  • 循序的程式設計
  • 沒有層次
  • 耦合度低
    public class HelloWorld
    {
        public string sayHello(DateTime now, string user)
        {
            Calendar c = new Calendar();
            int h;
            string s = string.Empty;
            c.setTime(now);
            h = c.get(c.HOUR_OF_DAY);
            if (h >= 6 && h < 12)
            {
                s = "Good morning!";
            }
            else if (h > 12 && h < 19)
            {
                s = "Good afternoon!";
            }
            else
            {
                s = "Good night!";
            }
            return "Hi, " + user + "." + s;
        }
    }

    internal class Calendar
    {
        public DateTime HOUR_OF_DAY;

        internal void setTime(DateTime now)
        {
            HOUR_OF_DAY = now;
        }
        public int get(DateTime hour_of_day)
        {
            return HOUR_OF_DAY.Hour;
        }
    }

以下我們進行初步重構增加註釋、調整次序、重新命名變數、進行分段

    /// <summary>
    /// The Refactoring's Hello-world program
    /// author eddie
    /// </summary>
    public class HelloWorld
    {
        /// <summary>
        /// Say hello to everyone
        /// </summary>
        /// <param name="now"></param>
        /// <param name="user"></param>
        /// <returns>
        /// the words what to say
        /// </returns>
        public string sayHello(DateTime now, string user)
        {
            //Get current hour of day
            Calendar c = new Calendar();
            int h;
            c.setTime(now);
            h = c.get(c.HOUR_OF_DAY);
            //Get the right words to say hello
            string s = string.Empty;
            if (h >= 6 && h < 12)
            {
                s = "Good morning!";
            }
            else if (h > 12 && h < 19)
            {
                s = "Good afternoon!";
            }
            else
            {
                s = "Good night!";
            }
            return "Hi, " + user + "." + s;
        }
    }

    internal class Calendar
    {
        public DateTime HOUR_OF_DAY;

        internal void setTime(DateTime now)
        {
            HOUR_OF_DAY = now;
        }
        public int get(DateTime hour_of_day)
        {
            return HOUR_OF_DAY.Hour;
        }
    }

最後再提取出來getHour()和getSecond

    /// <summary>
    /// The Refactoring's Hello-world program
    /// author eddie
    /// </summary>
    public class HelloWorld
    {
        /// <summary>
        /// Say hello to everyone
        /// </summary>
        /// <param name="now"></param>
        /// <param name="user"></param>
        /// <returns>
        /// the words what to say
        /// </returns>
        public string sayHello(DateTime now, string user)
        {
            int hour = getHours(now);
            return "Hi, " + user + "." + getSecondGreeting(hour); ;
        }
        /// <summary>
        /// Get current hour of day
        /// </summary>
        /// <param name="now"></param>
        /// <returns>
        /// current hour of day
        /// </returns>
        private int getHours(DateTime now)
        {
            Calendar calendar = new Calendar();
            calendar.setTime(now);
            return calendar.get(calendar.HOUR_OF_DAY);
        }
        /// <summary>
        /// Get the second greeting.
        /// </summary>
        /// <param name="hour"></param>
        /// <returns>
        /// the second greeting
        /// </returns>
        private  string getSecondGreeting(int hour)
        {
            if (hour >= 6 && hour < 12)
                return "Good morning!";
            else if (hour > 12 && hour < 19)
                return "Good afternoon!";
            else
                return  "Good night!";
        }

    
    }

    internal class Calendar
    {
        public DateTime HOUR_OF_DAY;

        internal void setTime(DateTime now)
        {
            HOUR_OF_DAY = now;
        }
        public int get(DateTime hour_of_day)
        {
            return HOUR_OF_DAY.Hour;
        }
    }


這例子加入增加註釋、調整次序、重新命名變數、進行分段…等操作,這是前期準備的常用技巧,最後再把透過註釋將分段存放的程式片段提取出來單獨函數,這方法叫做【提取方法】(Extract Method)。

 

 

元哥的筆記