2011年11月30日 星期三

阿班私房菜-重構(Refactoring)的小技巧(1)


最近在看一本書"The Pragmatic Programmer",裡有不少與重構相關的範例,值得參考,有與趣人可以買來看看。因此書,筆者心血來潮,分享一下自己的開發經驗。


在專案開發程式的過程,常會因時間壓力或市場考量,不得不加入很多非計畫中的程式碼。
開發時間越久,就漸漸難以維護,增加新功能更為秏時;當人力不足以達成出貨時程,便開始增加人力,最後日積月累,專案程式變成一個大怪物;直至某一日砍掉重練。


而筆者近年來,使用重構方法,使得自己不用一直為解專案bug而加班。

重構(Refactoring) 英文是一個現在進行式,表示這個動作需要一直不斷發生。


其中,"重構"的最重要的基石就是自動化測試程式,如每當重構程式碼中重覆的部分時,執行一下自動化測試程式,便可很快的確認程式修改後的正確性。

接下來我們來看一些筆者常用的小技巧,以下範例使用Python語言。

def show_animal_info0(animal):
    if animal == 'dog':
        print("The dog has four legs")
    elif animal == 'chick':
        print("The chick has two legs")
    else:
        print("There is an unknown animal")
show_animal_info0("dog")
show_animal_info0("chick")
show_animal_info0("cat")


重構if-else

通常if-else會越加越長,慢慢變得難以除錯。
animal_info_dict = {
        "dog":   "four legs",
        "chick": "two legs"
        }
def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        print("The %s has %s." % (animal, info))
    except:
        print("There is unknown animal")
用dict結構表格取代if-else, 也可用來取代switch


重構重覆呼叫函式

這種函式大部分由copy and paste而來。
show_animal_info0("dog")
show_animal_info0("chick")
show_animal_info0("cat")
def main():
    animals = ["dog", "chick", "cat"]
    for name in animals:
        show_animal_info1(name)
if __name__== "__main__":
    main()

重構屬性
紅色legs部分也是重覆程式碼的狀況之一
animal_info_dict = {
        "dog":   "four legs",
        "chick": "two legs"
        }
def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        print("The %s has %s." % (animal, info))
    except:
        print("There is unknown animal")
animal_info_dict = {
        "dog":   {"leg": "four"},
        "chick": {"leg": "two"},
        "cat":   {"leg": "four"}
        }
def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        legs = info['leg']
        print("The %s has %s legs." % (animal, legs))
    except:
        print("There is unknown animal.")


自動化測試程式

要進行自動化測試,以本範例為例:

1. 將邏輯與資料顯示動作分離

def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        legs = info['leg']
        print("The %s has %s legs." % (animal, legs))
    except:
        print("There is unknown animal.")
def get_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        legs = info['leg']
        msg = "The %s has %s legs." % (animal, legs)
    except:
        msg = "There is unknown animal."
    return msg

2. 比對預期函式回傳值結果

使用pyunit 來實作自動測試
import unittest
from code_for_automation import *
class DefaultTestCase(unittest.TestCase):
    def testTestDog(self):
        sample="The dog has four legs."
        msg = get_animal_info1('dog')
        assert msg == sample , 'test dog message'
    def testTestChick(self):
        sample="The chick has two legs."
        msg = get_animal_info1('chick')
        assert msg == sample , 'test chick message'
    def testTestCat(self):
        sample="The cat has four legs."
        msg = get_animal_info1('cat')
        assert msg == sample , 'test cat message'
if __name__ == '__main__':
    myTestSuite = unittest.makeSuite(DefaultTestCase,'test')
    runner = unittest.TextTestRunner()
    runner.run(myTestSuite)

3. 統計測試結果
$ python 05_automation.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
以上範例程式均可由tip1下載

結論

當有了自動化測試程式之後,可提高自行重構的信心,又不用花費手動一個個輸入測試的時間。
本文雖以python為例,但觀念是相通的,其他語言也有其自動測試函式庫可供使用,歡迎不吝指教。


沒有留言: