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

来源:http://www.cnblogs.com/lovesqcc/archive/2016/02/28/5224511.html
-Advertisement-
Play Games

本文將通過一個 Python 實現的圖片文件批量重命名工具來闡述如何逐步提升程式質量。


 

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

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

 

      雛形

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

# -*- coding: cp936 -*- 
import os 
import os.path as PathUtil


def createDesignator(num, bits):
    return str(num).zfill(bits)

def number_generator(start_num=0, bits=4):
    start = []
    start.append(start_num)
    def inner():
        start[0] = start[0] + 1
        return createDesignator(start[0], bits)
    return inner

def batchrename(dir_path, prefix="IMG_",generator_func=number_generator()):
    '''
    rename files (such as xxx.[jpg, png, etc]) in the directory specified by dir_path to [prefix][designator].[jpg, png, etc], designator is generated by generator_fuc
    '''
    names = os.listdir(dir_path)
    for filename in names:
        old_filename = PathUtil.join(dir_path,filename)
        if PathUtil.isfile(old_filename)==True: 
            newname=prefix.upper() + generator_func() + '.' + getFileSuffix(filename)
            os.rename(old_filename,PathUtil.join(dir_path,newname))

def getFileSuffix(filename):
    try:
        sep_ind = filename.index('.')
        return filename[sep_ind+1:]
    except ValueError:
        return None


def testGetFileSuffix():
    assert getFileSuffix("good.jpg") == "jpg"
    assert getFileSuffix("good") is None
    print "testGetFileSuffix Passed."

def testNumberGenerator():
    geneNums = []
    generator = number_generator()
    for i in range(10):
        geneNums.append(generator()) 
    assert geneNums[0] == '0001'
    assert geneNums[1] == '0002'
    assert geneNums[9] == '0010'
    print 'testNumberGenerator Passed.'

if __name__ == '__main__':

    testGetFileSuffix()
    testNumberGenerator()
    
    dir_path = '/home/lovesqcc/setupdir/scitools/pic/mmnet/beauty'
    batchrename(dir_path, prefix="beauty_")

 

  健壯性

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

Traceback (most recent call last):
  File "batchrename_robust.py", line 57, in <module>
    batchrename(dir_path, prefix="beauty_")
  File "batchrename_robust.py", line 21, in batchrename
    names = os.listdir(dir_path)
OSError: [Errno 2] No such file or directory: '/home/lovesqcc/setupdir/scitools/pic/mmnet/beauty'

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

def getDirFiles(dir_path):
    try:
        return os.listdir(dir_path)
    except OSError, err:
        print 'No Such Directory: %s, exit.' % dir_path
        os._exit(1)

 

      可定製性

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

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

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

import argparse

DEFAULT_PREFIX = 'IMG_'
DEFAULT_START_NUM = 1
DEFAULT_BITS = 4
NUM_METHOD = 'NUM'


def parseArgs():
    description = 'This program is used to batch rename files in the given DIRECTORY to PREFIX_GeneratedDesignator. GeneratedDesignator is a BITS number counting from START_NUM to the number of files (etc. PREFIX0001,PREFIX0002,...) in the given DIRECTORY with leading zero if necessary.'
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument('DIRECTORY', help='Given directory name is required')
    parser.add_argument('-p','--prefix',nargs='?', default="IMG_", help='Given renamed prefix')
    parser.add_argument('-m','--method',nargs='*',help='method to generate designator, etc --m [NUM [START_NUM [BITS]]]')
    args = parser.parse_args()
    dir_path = args.DIRECTORY

    if args.prefix:
        prefix = args.prefix
    else:
        prefix = DEFAULT_PREFIX

    if not args.method:
       method = NUM_METHOD
       start_num = DEFAULT_START_NUM
       bits = DEFAULT_BITS
       return (dir_path, prefix, (method, start_num, bits))

    if type(args.method) == list:
        if len(args.method) == 0:
            method = NUM_METHOD
            start_num = DEFAULT_START_NUM
            bits = DEFAULT_BITS
        elif args.method[0] ==NUM_METHOD:
            method = NUM_METHOD
            if len(args.method) == 1:
                start_num = DEFAULT_START_NUM
                bits = DEFAULT_BITS
            elif len(args.method) == 2:
                start_num = int(args.method[1])
                bits = DEFAULT_BITS
            elif len(args.method) == 3:
                start_num = int(args.method[1])
                bits = int(args.method[2])
    
    return (dir_path, prefix, (method, start_num-1, bits))
if __name__ == '__main__':

    testGetFileSuffix()
    testNumberGenerator()
    
    (dir_path, prefix, (method, start_num, bits)) = parseArgs()
    if method == NUM_METHOD:
        number_generator = number_generator(start_num, bits)
    batchrename(dir_path, prefix, number_generator)

 

  可追蹤性

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

os.rename(old_filename,PathUtil.join(dir_path,newname))
print '%s rename to %s.' % (filename, newname)  # should be info log

   

    安全性

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

IMG_0006.png rename to IMG_0002.png.
IMG_0003.jpg rename to IMG_0003.jpg.
IMG_0002.png rename to IMG_0004.png.
IMG_0005.jpg rename to IMG_0005.jpg.
IMG_0007.png rename to IMG_0006.png.

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

def getDirFiles(dir_path):
    try:
        filenames = os.listdir(dir_path)
        filenames.sort()
        return filenames
    except OSError:
        print 'No Such Directory: %s, exit.' % dir_path
        os._exit(1)

 

    可復用性

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

  

   可擴展性

   可擴展性體現了程式應對需求變化的能力。對於此例,可擴展性體現在兩點: 1. 要對目錄的子目錄遞歸重命名; 2. 要對多個目錄使用不同首碼進行批量重命名;3. 支持不同的編號生成方式。 對於第一點,只需要修改 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

# -*- coding: cp936 -*- 
import os 
import os.path as PathUtil
import argparse

DEFAULT_PREFIX = 'IMG_'
DEFAULT_START_NUM = 1
DEFAULT_BITS = 4
NUM_METHOD = 'NUM'


def parseArgs():
    description = 'This program is used to batch rename files in the given DIRECTORY to PREFIX_GeneratedDesignator. GeneratedDesignator is a BITS number counting from START_NUM to the number of files (etc. PREFIX0001,PREFIX0002,...) in the given DIRECTORY with leading zero if necessary.'
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument('-d','--directories', nargs='+', help='Given directory name is at least one required')
    parser.add_argument('-p','--prefix',nargs='*', help='Given renamed prefix')
    parser.add_argument('-m','--method',nargs='*',help='method to generate designator, etc --m [NUM [START_NUM [BITS]]]')
    args = parser.parse_args()
    dir_path_list = args.directories
    dir_num = len(args.directories)

    if not args.prefix or len(args.prefix) == 0:
        prefix_list = [DEFAULT_PREFIX] * dir_num
        prefix_num = dir_num
    else:
        prefix_list = args.prefix
        prefix_num = len(args.prefix)

    if prefix_num > dir_num:
        prefix_list = prefix_list[0:dir_num]
    else:
        prefix_list.extend([prefix_list[prefix_num-1]]*(dir_num-prefix_num))

    if not args.method:
       method = NUM_METHOD
       start_num = DEFAULT_START_NUM
       bits = DEFAULT_BITS
       return (dir_path_list, prefix_list, (method, start_num, bits))

    if type(args.method) == list:
        if len(args.method) == 0:
            method = NUM_METHOD
            start_num = DEFAULT_START_NUM
            bits = DEFAULT_BITS
        elif args.method[0] ==NUM_METHOD:
            method = NUM_METHOD
            if len(args.method) == 1:
                start_num = DEFAULT_START_NUM
                bits = DEFAULT_BITS
            elif len(args.method) == 2:
                start_num = int(args.method[1])
                bits = DEFAULT_BITS
            elif len(args.method) == 3:
                start_num = int(args.method[1])
                bits = int(args.method[2])
    
    return (dir_path_list, prefix_list, (method, start_num-1, bits))

def createDesignator(num, bits):
    return str(num).zfill(bits)

def number_generator(start_num=0, bits=4):
    start = []
    start.append(start_num)
    def inner():
        start[0] = start[0] + 1
        return createDesignator(start[0], bits)
    return inner

def getDirFiles(dir_path):
    try:
        filenames = os.listdir(dir_path)
        filenames.sort()
        return filenames
    except OSError:
        print 'No Such Directory: %s, exit.' % dir_path
        os._exit(1)
  

def batchrename(dir_path, prefix=DEFAULT_PREFIX ,generator_func=number_generator()):
    '''
    rename files (such as xxx.[jpg, png, etc]) in the directory specified by dir_path to [prefix][designator].[jpg, png, etc], designator is generated by generator_fuc
    '''
    names = getDirFiles(dir_path)
    for filename in names:
        old_filename = PathUtil.join(dir_path,filename)
        if PathUtil.isfile(old_filename)==True: 
            newname=prefix.upper() + generator_func() + '.' + getFileSuffix(filename)
            os.rename(old_filename,PathUtil.join(dir_path,newname))
            print '%s rename to %s.' % (filename, newname)  # should be info log
        else:
            batchrename(dir_path+'/'+filename, prefix, generator_func)

def getFileSuffix(filename):
    try:
        sep_ind = filename.index('.')
        return filename[sep_ind+1:]
    except ValueError:
        return None


def testGetFileSuffix():
    assert getFileSuffix("good.jpg") == "jpg"
    assert getFileSuffix("good") is None
    print "testGetFileSuffix Passed."

def testNumberGenerator():
    geneNums = []
    generator = number_generator()
    for i in range(10):
        geneNums.append(generator()) 
    assert geneNums[0] == '0001'
    assert geneNums[1] == '0002'
    assert geneNums[9] == '0010'
    print 'testNumberGenerator Passed.'

if __name__ == '__main__':

    testGetFileSuffix()
    testNumberGenerator()
    
    (dir_path_list, prefix_list, (method, start_num, bits)) = parseArgs()
    
    dir_num = len(dir_path_list)
    for i in range(dir_num):
        if method == NUM_METHOD:
            number_generator_func = number_generator(start_num, bits)
        batchrename(dir_path_list[i], prefix_list[i], number_generator_func)

 

    性能成本

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

 

  結語

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

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

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

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

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

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

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

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

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

 

  轉載請註明出處。謝謝 :)

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 玩轉Windows服務系列彙總 [創建Windows服務][1] [Debug、Release版本的註冊和卸載及其原理][2] [無COM介面Windows服務啟動失敗原因及解決方案][3] [服務運行、停止流程淺析][4] [Windows服務小技巧][5] [命令行管理Windows服務][6]
  • Hadoop、Redis、Memcached、MongoDB、Spark、Storm、雲計算、R語言、機器學習、Nginx、Linux、MySQL、Java EE、.NET、PHP Memcached 視頻教程 大數據 高性能 集群 NoSQL 安裝 命令 使用
  • Hadoop、Redis、Memcached、MongoDB、Spark、Storm、雲計算、R語言、機器學習、Nginx、Linux、MySQL、Java EE、.NET、PHP MongoDB 權威 實戰 入門應用 視頻教程 大數據 高性能 集群 NoSQL
  • Hadoop、Redis、Memcached、MongoDB、Spark、Storm、雲計算、R語言、機器學習、Nginx、Linux、MySQL、Java EE、.NET、PHP R語言 視頻教程 實戰 數據分析 數據挖掘 入門編程 培訓 核心技術
  • 1.什麼叫做會話控制 允許伺服器根據客戶端做出的連續請求。 2.為什麼需要會話控制? 因為當你打開一個網站,並想訪問該網站的其他頁面的時候,如果沒有會話控制,當跳轉到其他頁面的 時候,就需要再次輸入賬戶和密碼。 3.Cookie的原理和作用 將客戶端的簡單信息保存在個人PC中,其他程式獲取PC的Co
  • java同步容器 在Java的集合容器框架中,主要有四大類別:List、Set、Queue、Map。List、Set、Queue介面分別繼承了Collection介面,Map本身是一個介面。註意Collection和Map是一個頂層介面,而List、Set、Queue則繼承了Collection介面
  • 1 YUV2RGB的模塊如下: 1 module yuv2rgb( 2 clk, //時鐘輸入 3 rstn, //複位輸入,低電平複位 4 5 y_in, //變換前Y分量輸出 6 cb_in, //變換前Cb分量輸出 7 cr_in, //變換前Cr分量輸出 8 ena_in, //待變換數據使
  • spring沒有採用約定優於配置的策略,spring要求顯示指定搜索哪些路徑下的Java文件。spring將會把合適的java類全部註冊成spring Bean。 問題:spring怎麼知道把哪些Java類當初bean類處理? 這就需要使用annotation,spring使用一些特殊的annota
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...