Python 入門指南 5.0

單元 28 - 重構

~~學習進度表~~

重構 (refactoring) 是指不改變軟體 (software) 外部行為下,對程式原始碼 (source code) 進行整理、修改,不外是希望提升原始碼的可讀性及更易於維護

Source code → Refactoring
Readability
Maintainability
Extensibility

簡單說,只要感覺原始碼有些不對勁就該重構,尤其在一個大的開發團隊中,每個小組負責的部份可能都不同,因而常常會發生識別字 (identifier) 命名不一致或是太多類別 (class) 有共通特性的情況。所以需要適時的重構原始程式碼,這也是軟體開發過程中一項重要的工作。

常見的重構技術如下

有許多專書討論重構技術,由於本書篇幅有限,不可能塞太多東西,所以這裡僅是初步介紹而已。

重構通常需要搭配單元測試 (unit testing) ,確保軟體各部份的功能沒有改變。

我們的 Encrypt 類別的 __init__() 方法其實有點問題

 7
 9
    def __init__(self):
        self.set_code()

這樣的做法是建立 Encrypt 物件 (object) ,同時建立隨機的密碼表,雖說目前 Encrypt 類別符合我們開發的需求,倒是當我們想要擴充功能的時候就會發生問題。怎麼說呢?例如有次編碼結果不錯,我們想要下次繼續沿用相同的密碼表,這時候就需要把密碼表儲存起來了,存檔有兩種選擇,一種是儲存整個物件,另一種則是儲存密碼表的字串就好。

儲存整個物件比較麻煩,相對儲存密碼表字串 (string) 比較簡單,原因不外是字串太常用了,所以 Python 程式庫 (library) 就提供了簡單的方式來儲存字串。話說感覺到 encrypt07.pyEncrypt 類別的問題出在哪裡的嗎?是的,如果我們把密碼表字串儲存到某一的純文字檔案中,重新讀取回來就得設定 code 屬性,可是我們目前的版本無法替我們做到這一點。

還有就是我們利用數學公式設計一種演算法 (algorithm) ,利用這種演算法來建立密碼表,然而這個演算法並不是最理想的方式,因為這個公式建立的密碼表只有

5 × 10 = 50

依據 a 可能有 5 種數字, b 可能有 10 種數字,因此這個演算法最多就只能產生 50 種密碼表。

可是 26 個英文字母的排列會有

1 × 2 × 3 × ⋯⋯ × 25 × 26 = 4.0329 × 1026

由上可見有非常非常多種。

有沒有更理想的方式呢?有的,事實上我們可以直接攪亂字串中字母的順序,這需要另一個攪亂順序的演算法,這在標準程式庫 (standard library) 中有,另外編碼 toEncode() 跟 toDecode() 兩個方法都按照 Unicode 原始數值來做計算也太不 Python 啦! Python 是以簡潔、豐富程式庫而著稱的程式語言 (programming language) ,我們連續用了 chr()ord()str()len() 等,雖說是內建函數 (function) 沒錯,可是卻是拿 Unicode 編碼來思考轉換,一點也沒用 Python 思考說。

下面我們直接介紹 Encrypt 類別的最終版本,並且說明重構的原因。

完整程式請參考「範例程式篇」的 encrypt.py

首先刪去 setcode() ,替 __init__() 新增一個參數 (parameter) new_code ,同時把 __code 屬性改為串列 (list) ,另外新增一個串列屬性 __alph

 7
 9
10
11
12
13
16
    def __init__(self, new_code=None):
        if new_code == None:
            self.__code = [chr(i) for i in range(97, 123)]
            shuffle(self.__code)
        else:
            self.__code = list(new_code)
        self.__alph = [chr(i) for i in range(97, 123)]

__code 屬性改成串列的原因很簡單,因為串列是 Python 的工作馬,幾乎程式裡大大小小所有的工作都交給串列比較方面,另外串列是可變的,這給了我們很大的方便,留意這一行

10
            self.__code = [chr(i) for i in range(97, 123)]

這是串列的綜合運算 (comprehension) ,直接在中括弧中寫運算式建立串列元素值,由於 range() 回傳參數指定範圍的整數序列,因此 ifor 迴圈取得的第一個值為 97chr(i) 回傳單一字元的字串 "a" ,因此 code 會得到 26 個英文小寫字母的串列。

下面一行

11
            shuffle(self.__code)

shuffle() 直接攪亂串列裡元素的順序,同樣在標準程式庫的 random 中,所以要先 import 進來

 2
from random import shuffle

參數 new_code 用來直接設定 code 屬性,注意這裡用內建函數 list()new_code 轉換成串列,至於另一個屬性 __alph 則是英文小寫字母表,這在重構 toEncode()toDecode() 的地方會用到。

然後我們也新增 __str__() 來代替原本的 code 屬性

19
20
21
    def __str__(self):
        code = "".join(self.__code)
        return "code: " + code

類別預設的 __str__() 為物件的字串形式,留意這裡是用空字串加上 join() 方法連結 code 中的所有元素。

之所以把 code 屬性刪除,主要原因是重構後內部處理的 __code 為串列,之後到 GUI 或瀏覽器都只需要密碼表字串,雖然說也可以將 code 屬性設定為密碼表字串,但是這樣就跟內建的 __str__() 在功能上重複了。

toEncode() 重構版本的編碼迴圈 (loop) 如下

29
32
33
34
35
36
        for i in original_string:
            if i in self.__code:
                j = self.__alph.index(i)
                result += self.__code[j]
            else:
                result += i

之前的版本是利用計算編碼值,重構後的版本則是用串列的 index() 方法找索引值,也就是簡單用兩個串列的對應索引值來作替換,這點同樣用在解碼的迴圈中

47
50
51
52
53
54
        for i in original_string:
            if i in self.__code:
                j = self.__code.index(i)
                result += self.__alph[j]
            else:
                result += i

兩個迴圈幾乎一樣,除了把 self.__alph 換成 self.__code 以外,至於原本的巢狀迴圈 (nested loop) 也不需要了。

重新仔細看一下 encrypt.py 是不是更容易理解了呢?接下來我們就要進入 GUI 的部份了,在此之前先來認識一下標準程式庫中的 Tk 吧!

中英文術語對照
演算法algorithm
屬性attribute
類別class
綜合運算comprehension
封裝encapsulation
函數function
識別字identifier
程式庫library
串列list
迴圈loop
方法method
巢狀迴圈nested loop
物件object
參數parameter
程式語言programming language
重構refactoring
軟體software
原始碼source code
標準程式庫standard library
字串string
子類別subclass
父類別superclass
型態type
單元測試unit testing
重點整理
1. 重構是指重新整理程式碼,讓程式更易於維護。
2. 常見的重構技術包括整理屬性、方法,挑出類別的共通特性定義父類別等等。
3. Encrypt 類別的重構包括刪去 setcode() ,重新定義 __init__() ,增加 __str__() ,修改 toEncode()toDecode() 等。
問題與討論
1. 什麼是重構?為什麼要替開發好的程式進行重構?
2. Encrppt 類別進行了哪些重構?為什麼要作這些重構?
練習
1. 將單元 22 的練習 10 重構到程式 exercise2801.py 中。 參考程式碼
2. 將單元 23 的練習 9 重構到程式 exercise2802.py 中。 參考程式碼
3. 將單元 23 的練習 10 重構到程式 exercise2803.py 中。 參考程式碼
4. 將單元 25 的練習 3 重構到程式 exercise2804.py 中。 參考程式碼
5. 將單元 25 的練習 4 重構到程式 exercise2805.py 中。 參考程式碼
6. 將單元 25 的練習 6 重構到程式 exercise2806.py 中。 參考程式碼
7. 將單元 26 的練習 4 重構到程式 exercise2807.py 中。 參考程式碼
8. 將單元 26 的練習 5 重構到程式 exercise2808.py 中。 參考程式碼
9. 將單元 26 的練習 8 重構到程式 exercise2809.py 中。 參考程式碼
10. 將單元 27 的練習 9 重構到程式 exercise2810.py 中。 參考程式碼
11. 將單元 27 的練習 10 重構到程式 exercise2811.py 中。 參考程式碼

上一頁 單元 27 - 解碼
回 Python 入門指南 5.0 首頁
下一頁 單元 29 - 認識 Tk
回 Python 教材首頁
回程式語言教材首頁