Python 入門指南

單元 20 - 重構

~~學習進度表~~

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

Source code → Refactoring
Readability
Maintainability
Extensibility

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

常見的重構技術如下

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

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

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

def __init__(self):
  self.setcode()

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

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

還有就是我們承接好幾個單元發展出的 encrypt07.py 也太不 Python 啦! Python 是以簡潔、豐富程式庫而著稱的程式語言 (programming language) ,我們連續用了 chr()ord()str()len() 等,雖說是內建函數 (built-in function) 沒錯,可是卻是拿 Unicode 編碼來思考轉換,一點也沒用 Python 思考說。

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

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

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

# 建立 Encrypt 物件同時建立密碼表
def __init__(self, str=None):
  # 設定 code
  if str == None:
    self.code = [chr(i) for i in
                 range(97, 123)]
    shuffle(self.code)
  else:
    self.code = list(str)
      
  # 設定 alph
  self.alph = [chr(i) for i in
               range(97, 123)]

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

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

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

下面一行

shuffle(self.code)

shuffle() 直接攪亂串列裡元素的順序,同樣在 random 模組 (module) 中,所以要先 import 進來

from random import shuffle

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

然後我們也新增 __str__() 來代替 getcode()

# 回傳密碼表字串
def __str__(self):
  code = "".join(self.code)
  return "code: " + code

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

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

# 利用迴圈走完參數字串的所有字元
for i in str:
  # 判斷該字元是否為英文小寫字母
  # 若是英文小寫字母就進行編碼轉換
  if i in self.code:
    j = self.alph.index(i)
    result += self.code[j]
  else:
    result += i

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

# 利用迴圈走完參數字串的所有字元
for i in str:
  # 判斷該字元是否為英文小寫字母
  # 若是英文小寫字母就進行解碼轉換
  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 的部份了,在此之前先來認識一下標準程式庫 (standard library) 吧!

中英文術語對照
類別class
綜合運算comprehension
封裝encapsulation
屬性attribute
函數function
識別字identifier
程式庫library
串列list
迴圈loop
方法method
模組module
巢狀迴圈nested loop
物件object
參數parameter
程式語言programming language
重構refactoring
軟體software
原始碼source code
標準程式庫standard library
子類別subclass
父類別superclass
型態type
單元測試unit testing
重點整理
1. 重構是指重新整理程式碼,讓程式更易於維護。
2. 常見的重構技術包括整理屬性、方法,挑出類別的共通特性定義父類別等等。
3. Encrypt 類別的重構包括刪去 setcode() ,重新定義 __init__() ,增加 __str__() ,修改 toEncode()toDecode() 等。
問題與討論
1. 什麼是重構?為什麼要替開發好的程式進行重構?
2. Encrppt 類別進行了哪些重構?為什麼要作這些重構?
練習
1. 承接上一個單元的猜數字遊戲,將新程式寫在 exercise2001.py 中,替 __init__() 新增一個實體屬性 length ,並用參數 digit 來設定, digit 預設為 None ,當 digitNone 或小於 3 、大於 6 之時, length 就設定為 4 ,不然就設定為 digit
2. 承上題,將新程式寫在 exercise2002.py 中,修正 set_code() 裡的一個潛在錯誤,讓 GuessGame 能符合用 digit 設定猜測長度的設定。

相關教學影片

上一頁 單元 19 - 解碼
回 Python 入門指南首頁
下一頁 單元 21 - 認識標準程式庫及 Tk
回 Python 教材首頁
回程式語言教材首頁