1. 程式人生 > >ASE第三次作業——結對程式設計

ASE第三次作業——結對程式設計

成員:張賀 楊濤 石恩升

github地址:https://github.com/ThomasMrY/ASE-project-MSRA

 

題目簡介:

  此次程式設計的題目是——統計文字檔案中英語單詞的頻率,作業要求我們不僅要完成題目功能,還要對我們的程式做單元測試、迴歸測試以及效能分析。

       題目的源地址:https://www.cnblogs.com/xinz/archive/2011/11/27/2265000.html

使用者需求:英語的26 個字母的頻率在一本小說中是如何分佈的?某型別文章中常出現的單詞是什麼?某作家最常用的詞彙是什麼?《哈利波特》 中最常用的短語是什麼,等等。 我們就寫一些程式來解決這個問題,滿足一下我們的好奇心。

程式要具有的基本功能:

第0步:輸出某個英文文字檔案中 26 字母出現的頻率;

第一步:輸出單個檔案中的前 N 個最常出現的英語單詞;

第二步:  支援 stop words,即略過停用詞檔案裡的單詞;

第三步:  統計常用的短語頻率,短語包含的單詞數目由人為規定; 

第四步:把動詞形態都統一之後再計數;

第五步:統計“動詞-介詞”短語的頻率。

 前期準備:

  專案前期,我們小組在一起討論了作業的實現思路,並最終決定了我們工作流程和程式碼風格。

       工作流程:首先,每個人根據自己的想法實現作業中的幾個功能;然後,比較每個人的演算法在各個任務上的效能,選擇最優的版本繼續開發;最後,通過pair programming的方式將最終程式碼彙總,並作效能分析和功能優化。之所以選擇這種方式,一是為了產生更多的想法,避免思路的侷限性;另一方面也是為了讓不熟悉python語言的同學儘快掌握程式設計方法。事實證明,這種方式確實增加了我們的工作效率和討論深度,還是很有效的。

       程式碼風格:我們投票決定使用python作為專案的開發語言,為了提高我們的合作效率,我們規定了程式碼中變數、函式的命名風格以及註釋的格式。
  變數、函式命名風格:駝峰命名法

  變數舉例

wordsCount    #普通變數
g_letterDict    #全域性變數

  函式/類名舉例

def CountLetters(fileName,n,stopName,verbName):

  註釋風格:

##########################################
#Name:CountLetters
#Inputs:fileName # n : output the top N items in letters # stopName: the file of stopwords skipped # verbName: the file of verb dict #outputs:None #Author: ThomasY #Date:2018.10.24 ##########################################

開發階段:

       1、專案開發過程中,因為小組成員線下交流比較方便,因此,針對各個階段功能的實現,我們遵守了先前的約定,各自完成功能核心程式碼後便進行了交流。經過比較程式碼效能,選擇較好的版本併入專案,此過程我們通過結對程式設計來完成。

       2、每增加一個功能,我們用“單元測試”驗證程式碼的正確性和覆蓋率,以step2:支援 stop words為例說明,我們在用測試用例測試該模組程式碼時,發現雖然可以100%實現預期功能,但其中兩行程式碼總是無法覆蓋,如下圖所示:

  紅色陰影處是我們在刪除單詞字典“tempc”中的元素時,為防止刪除字典中不存在的元素時會報錯而寫的異常處理機制。分析後可知,上一步得到的“tempc”變數是“Counter”型別的,這一型別的結構在刪去本身不存在的元素時會自動跳過,因此我們的異常處理是多餘的。刪去try...except...語句後覆蓋率達到100%,如下圖所示。

  但是這種異常處理機制並非完全沒有必要,在後續的模組中我們不得已引入了這種機制,負責整合程式碼的同學以為前面也需要這種處理,便將這種機制也加入了前面的模組。儘管迴歸測試不能發現問題,但重新對這塊程式碼進行單元測試,還是可以找出一些不完美的地方,單元測試確實在我們持續開發程序中發揮著至關重要的作用。

  其他階段的單元測試,可以參見這篇部落格:https://blog.csdn.net/qq_35001962/article/details/83627235

  3.每次在原有工程上擴充一個新功能後,我們都在做單元測試的同時,測試以往功能,避免新引入的模組對以前的功能造成破壞,從而完成迴歸測試。

我們最終完成所有功能時的迴歸測試程式碼如下:

 

from CountLetters import CountLetters
from CountPhrase import CountPhrases
from CountPrephrase import CountVerbPre
from CountWords import CountWords
from CountDir import OperateInDir

if (__name__ == '__main__'):
    # test letters 
    CountLetters('gone_with_the_wind.txt', 10, None, None)
    CountLetters('gone_with_the_wind.txt', -10, None, None)
    CountLetters('gone_with_the_wind.txt', 10, 'stopwords.txt', 'verbs.txt')
    CountLetters('empty.txt', 10, None, None)
    CountLetters('blanks.txt', 10, None, None)
    # test words 
    CountWords('gone_with_the_wind.txt', 10, None, None)
    CountWords('gone_with_the_wind.txt', 10, 'stopwords.txt', None)
    CountWords('gone_with_the_wind.txt', 10, None, 'verbs.txt')
    CountWords('gone_with_the_wind.txt', 10, 'stopwords.txt', 'verbs.txt')
    CountWords('empty.txt', 10, 'stopwords.txt', 'verbs.txt')
    # test phrase 
    CountPhrases('gone_with_the_wind.txt', 10, None, None, 2)
    CountPhrases('gone_with_the_wind.txt', 10, None, 'verbs.txt', 2)
    CountPhrases('gone_with_the_wind.txt', 10, 'stopphrase.txt', 'verbs.txt', 2)
    CountPhrases('blanks.txt', 10, 'stopphrase.txt', 'verbs.txt', 2)
    # test dir 
    OperateInDir(CountWords, 'examples', 10, 'stopwords.txt', 'verbs.txt', True)
    OperateInDir(CountPhrases, 'examples', 10, 'stopwords.txt', 'verbs.txt', None, 2)
    # test verbpre 
    CountVerbPre('empty.txt', 10, None, 'verbs.txt', 'prepositions.txt')
    CountVerbPre('empty.txt', 10, 'stopverbpre.txt', 'verbs.txt', 'prepositions.txt')
    CountVerbPre('gone_with_the_wind.txt', 10, None, 'verbs.txt', 'prepositions.txt')
    CountVerbPre('gone_with_the_wind.txt', 10, 'stopverbpre.txt', 'verbs.txt', 'prepositions.txt')

 

  4.完成題目要求的所有功能後,我們用python中自帶的cProfile模組和視覺化圖形模組graphviz對工程做效能分析。以step1:輸出單個檔案中的前 N 個最常出現的英語單詞為例說明,起初我的想法是用正則表示式找出文字中的所有單詞,然後遍歷找到的單詞列表,構建單詞的詞典。效能分析時,我發現自己函式主體模組耗時較為嚴重,應該是實現“遍歷”的迴圈結構導致的。

  楊濤同學想到了用collections模組中的Counter函式來統計單詞列表裡不同元素的個數,只需呼叫一次該函式即可統計所有單詞頻率,效能比我實現遍歷的迴圈要高出不少。程式碼改良後,耗時0.50s,明顯優於先前的0.628s,而且函式主體的耗時和其他模組的耗時已經不在一個量級上了,而引入的Counter函式耗時顯然遠遠優於迴圈的模組,說明優化的方向是對的。

重點關注改良後效能分析圖的兩個耗時佔比較大的模組:

  A模組是呼叫一次的findall函式,用來找出文字中的所有單詞,很難再繼續優化;B模組是呼叫一次的Counter函式,速度比上萬次的遍歷要好出很多。其他階段的優化情況有很多,此處不一一列舉,詳細的優化過程可以參看這篇部落格:https://blog.csdn.net/qq_35001962/article/details/83627235

隊友評價:

  隊友甲:1.對python語言十分熟悉,不僅是我們的小老師,還經常有一些十分精彩的操作和處理;

      2.執行力強,程式設計能力過硬,可以很快的完成預期目標;

      3.做事有熱情有追求,可以很好的激發隊友潛力。

  隊友乙:1.認真負責,積極地提出工作中的疑惑和不足,並組織大家討論解決;

      2.善於交流,很好的統籌了我們的討論計劃和工作安排;

      3.學習能力很強,可以很快地接受消化新知識。

  隊友不足:隊友甲有時候想法實現的太快,有時候會忘了與我們溝通分享,他的某些暗中的騷操作在專案後期有時候會帶來一點點麻煩;

       隊友乙在專案前期對python語言不太熟悉,程式設計速度較慢。

個人收穫:

  1.這是我第一次按照軟體開發的流程寫程式碼,體會到了很多新東西。在以前的程式設計中,我通常不會過分注重程式碼的格式和效率,只關心結果是否是我想要的,功能是否能實現。現在深刻感受到,一份結構簡潔、註釋清晰的程式碼,對於別人理解自己的思想以及自己的除錯和優化都大有裨益。

  2.這也是我第一次參與結對程式設計。在程式設計的過程中,有可能是因為工作量較小,並沒有感覺到程式設計效率有明顯提高,但是自己的知識廣度和思維方式真的是大開眼界。在審視別人的程式碼時,我往往不會糾結於功能的實現過程,反而對隊友的語言邏輯和異常處理格外上心,這對我養成良好的程式設計習慣也很有幫助。

  3.我從隊友身上學到很多。這次結對過程中,兩位隊友在某個功能中對我實現的效果並不滿意,決定實現另一個較為棘手的演算法。一開始我對那個演算法並不看好,在“勉為其難”地參與了他們的討論後,我發現隊友地想法還是很有可行性的,最終我們一起解決了那個問題。這樣的“插曲”讓我感受頗深,一是體會到結對的好處,二是反思了自己不敢於嘗試新演算法的弱點。

  4.在互相交流程式碼的時候,我們很自然的收集了彼此的使用者體驗。我開始以為這是一個並不複雜的專案,但在互相嫌棄和挑bug的過程中,我深刻感受到,想把一個看似簡單的專案做到毫無瑕疵、人人接受也是一件很傷腦筋的事,可能這就是軟體工程的魅力所在吧。