逐步提升程式質量的演變過程示例

發表於2016-03-03

如何編寫高質量的程式呢? 在《Web服務端軟體的的服務品質概要》闡述了程式的常見質量屬性及實現策略方法,本文將通過一個 Python 實現的圖片檔案批量重新命名工具來演示如何逐步提升程式質量。

圖片檔案批量重新命名工具實現的功能是:將指定目錄 /home/user/path/to/photos/(xxx.png,yyy.png) 下的圖片批量重新命名為 prefix0001.png, prefix0002.png, …

 

雛形

首先,可以編寫出一個基本可用的程式 batchrename_basic.py 。這個程式並不完美,但是可以完成最初的任務。注意到 生成編號使用了閉包,這是為了將生成編號的過程抽離出來成為一個可複用的過程,而這個過程無法預知需要生成怎樣的列表,因此每次僅返回一個編號;程式如下:

 

健壯性

健壯性體現了程式應對錯誤的能力。一個需要網路連線的 APP 在網路正常的情況下執行流暢,如果沒有網路呢? 就必須告知使用者先連線到網路才行。或者採用輸入自動糾錯。比如在搜尋引擎裡搜尋 jquery, 不小心寫成了 jqeury 。搜尋引擎會提示是否需要搜尋的是 jquery。在此例中,當路徑不存在時,就會報錯。

解決方法很簡單: 將  names = os.listdir(dir_path) 抽離出來,寫成一個函式並進行異常捕獲,然後該行改寫成 names = getDirFiles(dir_path):

 

可定製性

如果使用者想指定路徑和字首,就必須在程式裡修改並重新部署,顯然是比較“僵硬”的。控制檯程式通常要加上命令列引數,而實際應用則使用配置檔案。下面通過使用 argparse 模組給該程式新增命令列引數,使之具備可定製性。新增一個 parseArgs 方法, 並修改 main 即可。注意到,使用了元組來清晰表達所希望返回的引數格式,便於主程式使用; 魔數均用字串常量來表達,保證可維護性。

使用方式: $ python batchrename_robust_customized.py /home/lovesqcc/setupdir/scitools/pic/fuzhuang/fz2/ -p fz2  -m NUM 1 5

-p, -m 都是可選的。預設只需要指定目錄路徑。

 

可追蹤性

可追蹤性體現了程式執行過程的可知性和可監控性。記錄程式執行中的關鍵狀態和關鍵路徑,也非常有利於出現錯誤時進行除錯。在此例中,要將檔案重新命名的具體資訊記錄下來,簡便起見,程式中只是列印一下:

安全性

安全性通常表達兩層含義: 1. 程式絕對不能破壞使用者的資料; 2. 程式必須防止其它程式破壞使用者資料或窺探使用者隱私。其中第一條是不可觸犯的。當我們重複執行  $ python batchrename_robust_customized.py /home/lovesqcc/setupdir/scitools/pic/fuzhuang/fz2/ 時,會驚訝地發現,重新命名後檔案變少了!當執行足夠次後,檔案可能只剩下一個! 這是怎麼回事呢? 執行若干次之後,擷取一次結果如下:

稍作分析即可知道, Python os.rename 在 UnixSystem 上會預設覆蓋已存在的檔案,而 os.listdir 輸出的結果是無序的! 解決方案也很簡單:先將 os.listdir 輸出的結果排序後再重新命名,即要修改 getDirFiles:

 

可複用性

可複用性的關鍵是單一職責原則和介面定義正交。單一職責原則指一個函式或方法僅做一件小事,望名知義;介面定義正交是說每個函式、類介面定義的事情沒有重疊,可以組合實現非常靈活的功能。如果程式具備較好的可複用性,那麼,在擴充套件程式時也會獲得益處,將改動影響區域性化。在編寫程式時應時時考慮抽離出可複用的過程和方法。可複用性也有助於編寫更有效的單元測試。此例中正是遵循可複用性原則來編寫程式,使得每次改動僅涉及一小部分。

 

可移植性

寫程式是為了更好更廣泛地使用。可移植性需要:1. 檢測操作平臺; 2. 將特定作業系統的符號和特定作業系統的行為替換成平臺無關的。在本例中,要將路徑分隔符 / 修改為 os.sep. Windows下的使用方式: D:>python batchrename.py -d F:picfuzhuangfz2 -p fz2 -m NUM 1 6

可擴充套件性

可擴充套件性體現了程式應對需求變化的能力。對於此例,可擴充套件性體現在四點: 1. 要對目錄的子目錄遞迴重新命名; 2. 要對多個目錄使用不同字首進行批量重新命名;3. 支援不同的編號生成方式;4. 對於非圖片檔案的批量重新命名。 對於第一點,只需要修改 batchrename 方法即可,檢測到如果是目錄,則遞迴呼叫 batchrename ; 對於第二點,則需要修改命令列引數格式,增加 -d 引數,引數個數至少一個;修改 -p 引數,引數可為零到多個。如果給定目錄數大於給定字首,則使用最後一個字首將字首數補足;若給定目錄數小於字首數,則將從後數多餘的字首忽略。要修改 parseArgs 和 main;對於第三點,則要將生成編號的方式抽離成可複用的過程,使得每次僅返回一個編號;對於第四點,由於沒有對檔案型別做判斷,因此也是適合於非圖片檔案的。最終的程式如下所示, 使用方式:

$ python batchrename_robust_customized_extended.py -d /home/lovesqcc/setupdir/scitools/pic/fuzhuang/fz2/ /home/lovesqcc/setupdir/scitools/pic/fuzhuang/fz1  -p fz2 fz1 -m NUM 1 5

 

效能成本

程式設計師有追求高效的強迫症。想象這是一個 web 服務, 效能成本通常體現在響應速度和吞吐量。響應速度是使用者可感知的,影響到使用者體驗;吞吐量是使用者不可感知的,影響到服務成本。此例中可以考慮百萬個檔案的重新命名;影響效率的因素有兩個: 1. 檔名排序時間; 2.  rename 系統呼叫時間。對於前者,使用快速排序,或者使用更精細的方法在 batchrename 函式中解決 os.rename 預設覆蓋已存在檔案的問題(這樣會降低可維護性); 對於後者,如果程式設計平臺或系統呼叫提供了更高效的批量重新命名介面,則可批量呼叫該介面來完成任務。

結語

提高程式質量並非一蹴而就,而是可以通過漸進的方式來實現。當實現了一個基本可用的程式時,還處於一個起點,有必要問問自己:

1.  健壯性: 程式需要怎樣的執行環境和輸入引數? 如果執行環境不滿足或輸入引數不合法,程式該如何應對?

2.  可定製性: 程式有哪些引數或特性是可定製的? 切忌在程式碼裡寫死;

3.  可追蹤性: 程式有哪些關鍵執行狀態和關鍵執行路徑? 使用 info 日誌記錄下來;

4.  安全性: 程式在何種情況下可能破壞使用者的資料? 程式如何禁止非法程式破壞或窺探使用者資料?

5.  可擴充套件性: 程式可能有哪些變化的潛在合理的需求?

6.  可複用性: 函式方法是否臃腫,可以從中抽離出可複用的子過程?

7.  可測試性: 關鍵函式和方法是否有充分的單元測試?

8.  效能成本: 響應速度是否在使用者接受範圍內?是否可以在不降低可維護性的前提下優化區域性,提高整體吞吐量? 對於大資料量,程式是否可以應對? 程式的吞吐量極限是多少?

相關文章