《構建之法》--第四次做業--結對編程

這個做業屬於哪一個課程 課程的連接
這個做業要求在哪裏 做業要求的連接
GIT項目地址 WordCount
結對夥伴做業地址 linls
我的博客主頁 Vchopin

咱們這一次的github項目是從做業指導中Fork下來到本身的項目中的,用的git地址,是個人項目地址。此次結對編程個人結對夥伴是linls,技術牛逼,python機器學習大佬一枚,如下簡稱爲俊老闆html

系統分析

原本還想進行需求需求瞭解的,結果仔細一看做業指導中,北航老師已經清清楚楚寫的明明白白了,因此和俊老闆就決定直接開始分析python

分析

第一步

  做業中須要在第一步完成wordCount的基本功能,也就是在命令行中統計一個txt文本中的所有字符數以及其餘要求。
1.命令行參數。對於命令行參數的處理,C#能夠直接從Main函數中的args中直接獲取git

2.漢字的處理。要實現只處理ascii而不處理漢字,其實能夠經過漢字的unicode對其進行過濾。可是這個時候俊老闆提出,一段文本里面可能不僅是有漢字和英文,也可能有日語或者其餘什麼語言,因此光去除漢字不行,應該是保留全部的ascii去除不是ascii的文字。固然能夠用正則表達式,也能夠直接用.Net自帶的Encoding類過濾。github

3.空格、製表符和換行符的處理。剛開始一看很懵...這咋知道\t是否是四個space啊。後來俊老闆提醒,這些都是電腦判斷,咱們只須要判斷他這個字符是\t仍是space就能夠了。是四個space那也不用當成\t面試

4.對於file123和123file的判斷處理。其實這個要點我和俊老闆發生了意見衝突,我認爲經過C#的字符串方法string.StartWith()這個方法就能夠進行判斷求解的。可是俊老闆堅持認爲,使用正則表達式可以更加快速以及效果顯著的完成統計。由於具體兩種方法都是能夠實現的,因此咱們都仍是各自保留了本身的想法,到編碼的時候在具體選擇。正則表達式

5.單詞的統計。我和俊老闆一看題,這個單詞是按照分割符進行統計的,但是分隔符是包括了全部的非字母數字符號,咱們一致用正則表達式的硬編碼匹配那就很難受。後來我想可不能夠直接用while遍歷所有的字符,將那些ascii碼值大於等於97小於等於122的那些當作分隔符,出現就將其兩邊的字母做爲前一個單詞結尾和後一個單詞開始。算法

6.統計頻率最高出現的10個單詞。俊老闆在看到的時候認爲,這個能夠新增一個類,將單詞的內容和頻次做爲這個類的屬性。可是後來,我以爲不行,要是統計的文章有個一萬多個字,那就要構造一萬多個對象,先不說內存花費,構造對象的時間都有夠嗆。咱們考慮了用數組,泛型等等。發現數組浪費的空間很大,泛型在執行效率方面比較低下。最後想到用Dictionary或者HashSet能夠完美解決效率和空間的問題。編程

7.按照字典序輸出。我想的是,在統計完成以後,若是發現有幾個統計頻率是同樣的單詞,就讓他們在按照升序排一次序就行。可是我初步估算的話,這樣的時間複雜度會比較大,若是按照冒泡排序來講,就多是O(n^4)的時間複雜度...我和俊老闆在這裏對於具體怎麼實現都還不清楚,咱們也仍是決定到時候具體編程的時候在具體解決。windows

第二步

  這個部分主要是將第一步中的統計字符數統計單詞數統計最多的10個單詞及其詞頻這三個功能進行剝離,造成dll,以便於在命令行、圖形界面、網頁程序和手機App上面使用。而且這個dll可以提供相應的api供其餘輔助調用。首先要保證這三個功能的徹底正確性,因此對其進行單元測試保證可用性。
  那麼這個問題我和俊老闆都是決定經過從新寫一個類,裏面的public方法就是分別是上面三個功能的入口,其他的輔助函數都寫成private防止暴露。最後利用VS工具生成動態連接庫(DLL)就能夠完成封裝。當咱們在其餘地方進行調用的時候,將該dll添加到引用,並引入命名空間,便可開始調用裏面的方法完成計算。固然,第二步是須要咱們寫單元測試保證該dll穩定,因此,最主要的仍是在單元測試中引入相關代碼並完成測試。若是有時間,俊老闆打算在移動應用或者ASP.Net上面試試。api

第三步

  這一步主要是在原有基礎上的功能的拓展。對多參數處理詞組統計自定義輸出的實現。

  1. 對於多參數處理的實現。俊老闆認爲直接對獲取到的每個參數進行處理,若是他是以-符號開頭的,能夠認爲是一個標識符。而後獲取到-後面的字母,在對其進行判斷類型,同時,還得保證標識符的空格後面必須有合法的值。好比對於-i,後面就必須是一個存在的文件名,不然提示錯誤。
  2. 使用參數設定統計的詞組長度。我認爲這個是能夠直接從處理事後的單詞詞組中進行選擇,按照序號依次經過for循環輸出,循環次數由輸入的-m後面的參數決定。固然也須要完成對後面的數字的處理。
  3. 設定單詞數量。和上面的思路基本同樣,在第一步中是實現的前10個詞頻最高的單詞,能夠直接輸出10個Console。可是如今經過變量運算來控制顯示單詞的個數,就須要有一個count單詞來計數。當達到相應的數量以後就break

  其實最重要的我認爲仍是最後一個多參數的混合使用,好比某個參數可能不出現,不出現就得有默認參數。而且參數之間的順序也不固定,不能按照順序對其進行取值。俊老闆的想法是將全部輸入參數組成一個字符串,最後經過斷定特定字符是否在字符串中進行參數存在斷定。

第四步

  這一步其實沒有什麼好說的,利用winform或者wpf直接拖控件按照對應的功能和所須要求完成界面設計,計算單詞的算法仍是用第一步中基本核心算法功能,加上第四步中的加強功能改爲GUI的方式完成設計。

第五步

  這一步是進行單元測試。是對前面全部的部分進行單元測試,包括核心算法功能,額外加強功能和GUI的附加功能。咱們認爲對於核心算法功能和額外加強功能都比較好記性測試,但是對於圖形化界面如何進行測試呢?查詢資料後發現網絡上面基本沒用對GUI進行測試的...不是由於很難,由於沒有必要...可是仍是發現有這樣的工具,好比Nunit能夠對winform和wpf這種C#寫的代碼進行圖形化測試。俊老闆查詢的簡單粗暴,直接用Rebot類自動測試。其實我以爲均可以。最重要的仍是對於單詞的計算的功能的測試。咱們決定對如下幾個地方進行測試:

  1. 輸出格式測試
  2. 字母、單詞、行數統計的測試
  3. 前10個頻次最高按照字典排序的單詞測試
  4. 對於非ascii碼的處理測試
  5. 多參數讀入測試
  6. 讀入文件輸出文件非法文件名測試
  7. 詞組長度測試
  8. 輸出單詞數量的測試
  9. 意外狀況處理測試
  10. 輸入錯誤的處理

    第六步

      效能分析咱們決定在代碼寫出來以後再利用vs的效能分析軟件查看他的性能,對嚴重拖慢程序運行進度的進行修改和優化。

    代碼規範

      咱們認爲,要作就要作好。因此,咱們的代碼規範都是按照互聯網上通用規則進行編寫:

    註釋

    1> 若是處理某一個功能須要不少行代碼實現,而且有不少邏輯結構塊,相似此種代碼應該在代碼開始前添加註釋,說明此塊代碼的處理思路及注意事項等
    2> 註釋重新行增長,與代碼開始處左對齊  
    3> 雙斜線與註釋之間以空格分開

    命名規則

    部分參考https://blog.csdn.net/tieshuxianrezhang/article/details/51960039

4> 類和接口命名
  l 類的名字要用名詞;
  l 避免使用單詞的縮寫,除非它的縮寫已經廣爲人知,如HTTP。
  l 接口的名字要以字母I開頭。保證對接口的標準實現名字只相差一個「I」前綴,例如對IComponent接口的標準實現爲Component;
  l 泛型類型參數的命名:命名要爲T或者以T開頭的描述性名字,例如:
    public class List
    public class MyClass
  l 對同一項目的不一樣命名空間中的類,命名避免重複。避免引用時的衝突和混淆;
5> 方法命名
  l 第一個單詞通常是動詞;
  l 若是方法返回一個成員變量的值,方法名通常爲Get+成員變量名,如若返回的值 是bool變量,通常以Is做爲前綴。另外,若是必要,考慮用屬性來替代方法;
  l 若是方法修改一個成員變量的值,方法名通常爲:Set + 成員變量名。同上,考慮 用屬性來替代方法。
6> 變量命名
  l 按照使用範圍來分,咱們代碼中的變量的基本上有如下幾種類型,類的公有變量;類的私有變量(受保護同公有);方法的參數變量;方法內部使用的局部變量。    這些變量的命名規則基本相同,見標識符大小寫對照表。區別以下:
    a) 類的公有變量按一般的方式命名,無特殊要求;
    b) 類的私有變量採用兩種方式都可:採用加「m」前綴,例如mWorkerName;
    c) 方法的參數變量採用camalString,例如workerName;
  l 方法內部的局部變量採用camalString,例如workerName。
  l 不要用_或&做爲第一個字母;
  l 儘可能要使用短並且具備意義的單詞;
  l 單字符的變量名通常只用於生命期很是短暫的變量:i,j,k,m,n通常用於integer;c,d,e 通常用於characters;s用於string
  l 若是變量是集合,則變量名要用複數。例如表格的行數,命名應爲:RowsCount;
  l 命名組件要採用匈牙利命名法,全部前綴均應遵循同一個組件名稱縮寫列表

代碼編寫

思路分析

  我和俊老闆仔細研究題目後,認爲咱們倆的水平仍是不太行,總體來寫的話頗有難度,因而咱們決定按照題目要求一個點一個點來編寫,逐個擊破。因爲做業指導中已經將具體代碼優化步驟已經給出,因此,咱們在第一步的基本功能的實現方面就將所有方法糅雜在一個Main函數中。而後在第二步中根據題目要求和咱們代碼具體實現進行拆分解耦。在第三步在根據第二步拆分的進行拓展。後面的步驟基本就按照做業指導走就好了。附上一塊兒討論編程的合做照片




合做照片

編碼

第一步

  首先實現的是最最基本的利用正則表達式將所有的非字母數字的所有替換成爲#,而後在用字符串的Split('#')方法將一個字符串拆分到數組中,就造成一個一個的單詞。在根據單詞前四個必須是字母完成對不符合的要求的篩選。代碼以下

string regexStr = Regex.Replace(readLine, @"[^a-zA-Z0-9]+", "#");//過濾
string[] wordsArr1 = regexStr.Split('#');  
charactersCount += readLine.Length;//統計每行的字符數 最後只需再加上每行的字符數就是總字符數

foreach (string newWord in wordsArr1)
{
    if (newWord.Length != 0)
    {
        char[] temparr = newWord.ToCharArray();
        if ((newWord.Length >= 4) && (char.IsLetter(temparr[0]) && char.IsLetter(temparr[1]) && char.IsLetter(temparr[2]) && char.IsLetter(temparr[3])))
        {
            lists.Add(newWord.ToLower());
        }
    }
}

  代碼流程圖以下




流程圖

  完成對單詞的提取以後,接下來是對單詞的頻率統計和排序。在需求分析裏面咱們討論瞭如何是實現單詞內容和頻率的關聯,考慮到最後輸出的每一個單詞都是不可能同樣的(惟一性),可是頻率有多是同樣的,這個屬性徹底符合字典的key-value模型,所以咱們決定是使用具備KeyValuePairDictionary類來對其綁定實現。循環迭代上面的lists中的單詞,沒有出如今字典中的,就直接按照頻率爲1加入到字典集合中,出如今字典中的,對該個keyvalue加一操做,這樣就能夠完成單詞的統計。字典的統計單詞頻率代碼以下:

Dictionary<string, int> wordsCount = new Dictionary<string, int>();

//單詞出現頻率統計
foreach (string li in lists)
{
    if (wordsCount.ContainsKey(li))
    {
        wordsCount[li] ++;
    }
    else
    {
        wordsCount.Add(li, 1);
    }

}

  統計頻次完成以後,就須要對其進行排序,按照頻次從大到小,若是頻次相同,就要按照字典序對單詞排序。這裏其實涉及到兩種排序,一開始俊老闆是想將其挨個取出放在List中,排序以後在放回Dictionary裏面。這是能夠實現的,可是空間複雜度和時間複雜度都是至關的高。咱們後面繼續查閱資料(參看博客http://www.javashuo.com/article/p-ekjdzdap-da.html)發現C#的Dictionary類是自帶排序的,屬於鏈式編程正好完美解決降序排一次在升序排一次。代碼以下:

Dictionary<string, int> sortedWord = wordsCount.OrderByDescending(p => p.Value).ThenBy(p => p.Key).ToDictionary(p => p.Key, o => o.Value);
foreach (KeyValuePair<string, int> item in sortedWord)
{
    Console.WriteLine("word:{0} ; count:{1}",item.Key, item.Value);
}

  這樣就基本完成第一步的代碼編寫了。那麼是騾子是馬,上圖溜溜:
首先是咱們的測試文件圖片




測試用例

  而後是代碼操做運行截圖




測試頁面


  新增兩個換行符以及hello字符。預測行數不會增長,字符數增長7個就是98個字符。



修改測試用例


  在此運行wordCount進行單詞統計,獲得:



修改後的運行結果


  與預期不一致,檢測代碼發現是最後的字符數是跟有效行數掛鉤的,致使只有有效行數的 /n換行符被統計。



錯誤代碼


  俊老闆仔細考慮了一下,在計算有效行數的時候不須要空白行,可是在計算字符數的時候就須要空白行了,因此這裏須要分開進行計算。修改代碼爲:



俊老闆修改的代碼

  不看卻是沒什麼,仔細一看,這個代碼問題大得很。ReadToEnd()的這個方法,直接就所有讀完,以前一行一行的判斷直接到了末尾。如今在用這個方法等於沒有讀到任何字符。若是必定要用這個方法,只能用兩個StreamReader分別讀取測試文件。最後咱們決定採起一個折中的辦法,浪費空間,換取對從新讀取文件的時間。新增一個lines表明所有行數,每讀一次就自增1.最後就能夠得出所有行數。試驗效果:




俊老闆修改的修改代碼


  這樣就所有完成基本功能驗證,符合預期。

  第一個版本作出來,就準備開始上傳Git了。仍是按照做業2的步驟上傳Git,放到這裏出現問題了...不可以將要上傳的VS目錄添加到暫存空間。




GIT Error

  屢次查閱資料(參考博客http://www.javashuo.com/article/p-ewxyoaoi-dp.html)以後發現VS目錄中的隱藏文件夾.VS是沒法讀取上傳的。經過輸入git add --ignore-errors .就能夠忽略不能讀取的進行上傳。
使用git commit上傳




GIT Commit

  在使用git remote add origin https://github.com/vchopin/WordCount.git而後使用git push又出現錯誤了




GIT Push Error

  根據英文意思,我猜想是沒有和遠端倉庫合併代碼,因此接下來先執行git pull拉去倉庫到本地合併。




GIT Pull

  完成合並以後,在繼續git push推送到倉庫中




GIT Push


  登陸github查看上傳狀況,已經成功上傳



GIT頁面

第二步

  第二步是對原有代碼進行差分解耦。我和俊老闆決定按照不一樣功能分別用抽象實現頂部封裝,便於往後的升級和代碼規範。最最重要的仍是要將統計字符數統計單詞數統計最多的10個單詞及其詞頻這三個功能進行獨立出來,我和俊老闆想的是若是給每個功能都新增抽象類,那麼類就會很龐大。因此最後採起維護基本功能、抽象核心計算功能。
  首先是對文件輸入和輸出的剝離,將讀取字符功能和打印前十個單詞的功能抽象爲一個接口中的兩個方法以下:

interface IDataIO
{
    /// <summary>
    /// 從文件中讀取所有字符
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    string ReadFromFile(string path);

    /// <summary>
    /// 打印前maxline個排序後的單詞。爲0則所有打印
    /// </summary>
    /// <param name="sortedWord"></param>
    /// <param name="maxline"></param>
    void Print(Dictionary<string,int> sortedWord, int maxline=0);
}

  而後在實現這個接口

class DataIO:IDataIO
    {
        public static string ReadFromLittleFile(string path)
        {
            return File.ReadAllText(path, Encoding.ASCII);
        }
        public static string ReadFromLargeFile(string path)
        {
            return File.ReadAllText(path, Encoding.ASCII);
        }

        /// <summary>
        /// 將文件所有讀成string類型進行傳遞
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public string ReadFromFile(string path)
        {
            return File.ReadAllText(path, Encoding.ASCII);
        }
        /// <summary>
        /// 打印前maxline個排序後的單詞。爲0則所有打印
        /// </summary>
        /// <param name="sortedWord"></param>
        /// <param name="maxline"></param>
        public void Print(Dictionary<string, int> sortedWord, int maxline = 0)
        {
            if (maxline != 0)
            {
                int i = 0;
                foreach (KeyValuePair<string, int> item in sortedWord)
                {

                    Console.WriteLine(item.Key + " " + item.Value);
                    if (i == maxline)
                        break;
                    i++;
                }
            }
            else
            {
                foreach (KeyValuePair<string, int> item in sortedWord)
                {
                    Console.WriteLine(item.Key + " " + item.Value);
                }
            }
            
        }
    }

  這樣就將命令行的輸入輸出剝離開來。

  接下來就是對核心計算功能的剝離了。爲了保證代碼後續的升級,因此經過接口定義三個功能函數:

interface ICore
{
    /// <summary>
    /// 得到所有字母數量
    /// </summary>
    /// <returns></returns>
    int GetCharNum();

    /// <summary>
    /// 得到所有單詞數量
    /// </summary>
    /// <param name="wordsCount"></param>
    /// <returns></returns>
    int GetWordNum(Dictionary<string, int> wordsCount);

    /// <summary>
    /// 獲取排序後的單詞集
    /// </summary>
    /// <param name="wordsCount"></param>
    /// <returns></returns>
    Dictionary<string, int> SortAndGetWord(Dictionary<string, int> wordsCount);
}

  這三個方法就是對須要剝離的三個功能的規範抽象。在這三個方法下面對三個功能進行詳細實現。代碼和第一步徹底同樣,只是從新拆分開了,因此就不在贅述。
俊老闆比我細心,基本都是我敲錯了他一眼就發現了,因此代碼編寫起來比較快速。
而後是對核心功能進行測試,一共三個功能。因此分別寫了三條測試語句來對核心計算進行測試。首先是生成動態連接庫。在VS中的項目屬性修改輸出類型爲類庫,




項目屬性

  在從新生成一次,到Debug文件夾中進行查看,就已經生成DLL了。




WordCount動態連接庫

  在單元測試中添加對這個DLL的引用,在添加using wordCount使用命名空間以後,就能夠調用方法驗證算法是否正確。可是...儘管我對其添加了引用,最後沒法使用命名空間。




沒法引用空間

  查閱資料無果後,俊老闆和我分開嘗試怎麼樣才能使用dll。最後我發現當新建的項目是動態連接庫項目的時候,就可以正常引用dll。最後沒有辦法,只好新建一個動態連接庫項目,而後將類拷貝過去。




項目結構

  最後在wordCounter裏面完成單元測試

  1. 統計字符數。這個是真的深有體會,不作不知道,一作嚇一跳,原來代碼有這麼多錯誤的地方以前沒觀察到。主要出現的錯誤有若是隻有一行會多算一個字符、中文字符也被計算在內。一一改正以後進行測試,測試字符數所用代碼以下:
[TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            string content = File.ReadAllText("test.txt");
            
            ICore core = new Core(content);
            Assert.AreEqual(93, core.GetCharNum());
        }
    }

測試經過

2.統計單詞數。相似於第一個測試,測試代碼以下:

[TestMethod]
public void TestMethod2()
{
    string content = File.ReadAllText("test.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    Assert.AreEqual(6, core.GetWordNum(words));
}

  本覺得也會完美經過,結果出現錯誤。




測試失敗


  調試測試以後發現,是測試一修改的中文字符去除的時候出現問題,修改以後,在此測試,完美經過



測試成功

3.統計前10個單詞的輸出。字符串匹配我門還真不知道怎麼測試,因此對一開始打算直接輸出,俊老闆說那根本就不是測試...最後,我和俊老闆得出一個折中的方案,用StringAssert測試字符串,輸出的字符串使用foreach拼接

[TestMethod]
public void TestMethod3()
{
    string content = File.ReadAllText("test.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    core.GetWordNum(words);
    string test="file1234 2\ndsfdsfsd5421 1\nhello 1\nwindows2000 1\nwindows95 1\nwindows98 1\n";
    words = core.SortAndGetWord(words);
    string actual = "";
    foreach (KeyValuePair<string, int> pair in words)
    {
        actual += pair.Key + " " + pair.Value + "\n";
    }

    StringAssert.Equals(test, actual);
}

測試經過

  測試完成以後,基本的計算功能就有了保障。趕忙提交到Git進行保存。




測試經過

  接着開始對其進行功能上面的拓展。
  新增的-i-m-n-o這四個參數匹配就只有設定單詞詞組和輸出到文件是新的須要實現的功能,首先是對單詞詞組的實現,由於涉及到詞組的頻次,故仍是使用Dictionary<string,int>來保存數據,而對於詞組的構造,就是根據設定的長度,利用雙重for循環進行詞組拼接。代碼以下

/// <summary>
/// 得到指定長度的詞組
/// </summary>
/// <param name="words"></param>
/// <param name="len"></param>
/// <returns></returns>
public static Dictionary<string, int> GetWordGroup(List<string> words, int len = 3)
{
    Dictionary<string, int> wordsGroup = new Dictionary<string, int>();
    for (int j = 0; j<words.Count;j++)
    {
        string wordsRelation = "";
        if (j <= words.Count - len )
        {
            for (int i = j; i < j+len; i++)
            {
                wordsRelation += words[i] + " ";
            }

            if (wordsGroup.ContainsKey(wordsRelation))
            {
                wordsGroup[wordsRelation]++;
            }
            else
            {
                wordsGroup.Add(wordsRelation, 1);
            }
        }
    }
    return wordsGroup;
}

  對於-o的寫出倒沒有什麼問題,讀入在以前就已經實現了,如今寫出到文件和其原理基本類似。寫出的代碼以下:

public void WriteToFile(string path,string content)
{
    using (System.IO.StreamWriter file = new System.IO.StreamWriter(path))
    {
        string line = "";
        using (StringReader sr = new StringReader(content))
        {
            while ((line = sr.ReadLine()) != null)
            {
                file.WriteLine(line);
            }
        }
    }
}

  傳入寫出到文件的content就須要本身構造了。利用字符串拼接,按照做業指導中的格式,構造出符合規範的詞組,構造方法以下:

int charNum = core.GetCharNum();
int wordNum = core.GetWordNum(words);
words = core.SortAndGetWord(words);
wordsGroup=GetWordGroup(((Core)core).Lists,m);
string wordsGroupContent = "";
foreach (KeyValuePair<string, int> wordsPair in wordsGroup)
{
    wordsGroupContent += wordsPair.Key + ": " + wordsPair.Value + "\n";
}
string wordsCountContent = io.Print(words, n);

string fullContent = "characters: " + charNum + "\n" +
    "words: " + wordNum + "\n" +
    "lines: " + ((Core)core).LineCount + "\n\n" +
    wordsGroupContent + "\n"+
    wordsCountContent;

  再次提交到Git完成保存,就準備完成圖形化界面繪製。




Git推送成功


Git推送成功

  接下來進入到第四步,是對圖形化界面的實現。咱們採用WinForm的形式完成對圖形化界面的繪製,這部分主要是由俊老闆實現,我對他代碼進行審覈。
下面是俊老闆繪製的圖形界面




wordCounter GUI

  關於事件的基本就是一個輸入輸出OpenFileDialogSaveFileDialog進行保存,其他的統計都是在前面第三步的wordCount項目中作好了的,直接調用就行了。因此,圖形化界面製做總體比較簡單。可是調試的時候遇到一個有趣的問題。如圖:




wordCounter GUI

  右邊的統計結果很明顯沒有了換行,但是剛剛第三步的代碼中我明明添加了\n換行符,而且在命令行中也可以正常顯示。查閱資料後得知(參考博客http://www.javashuo.com/article/p-gplibcus-du.html),Windows的界面換行符是\r\n,而命令行中是任意的,就是\n\r\n都是能夠的。所以,修改原有代碼,成功解決問題。




wordCounter GUI

  最後上傳Git完成編寫工做。




Git項目界面

兩點分析

總結

  編寫代碼這個部分多是結對編程最大的意義所在了。我和俊老闆從開始的爭爭吵吵,互不相讓慢慢的開始變得有默契。最後一個眼神就知道該換位置了。實際上,剛開始的效率比較低下,後來咱們慢慢的熟悉以後,寫出來的代碼真的是質量高,不多會有二次改動,這也是咱們星期四纔開始寫代碼,星期天就所有作好的根本緣由。在寫代碼的時候也發現結對編程的問題所在,好比編累了,容易一塊兒打遊戲,以及若是一方情緒控制很差,容易撂挑子,另一我的就很被動...

單元測試

  按照需求分析,咱們在單元測試這裏準備了10個測試用例來保證程序健壯性

  1. 輸出格式測試
    測試用例:



    測試用例

測試代碼:

string content = File.ReadAllText("test.txt");
Dictionary<string, int> words = new Dictionary<string, int>();
ICore core = new Core(content);
core.GetCharNum();
core.GetWordNum(words);
string test="file1234 2\ndsfdsfsd5421 1\nhello 1\nwindows2000 1\nwindows95 1\nwindows98 1\n";
words = core.SortAndGetWord(words);
string actual = "";
foreach (KeyValuePair<string, int> pair in words)
{
    actual += pair.Key + " " + pair.Value + "\n";
}

StringAssert.Equals(test, actual);

測試結果:




測試結果

  1. 字母、單詞、行數統計的測試
    測試用例:



    測試用例

測試代碼:

[TestMethod]
public void TestMethod1()
{
    string content = File.ReadAllText("test.txt");
    
    ICore core = new Core(content);
    Assert.AreEqual(93, core.GetCharNum());
}

[TestMethod]
public void TestMethod2()
{
    string content = File.ReadAllText("test.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    Assert.AreEqual(6, core.GetWordNum(words));
}

測試結果:




測試結果

  1. 前10個頻次最高按照字典排序的單詞測試
    測試用例:



    測試用例

測試代碼:

[TestMethod]
public void TestMethod4()
{
    string test = "confidence 3\nyourself 3\nadmiration 1\nahead 1\narrogant 1\nchallenges 1\ndizzy 1\nenergy 1\nextremely 1\nfarewell 1\n";
    string content = File.ReadAllText("test1.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    core.GetWordNum(words);
    words = core.SortAndGetWord(words);
    string actual = "";
    foreach (KeyValuePair<string, int> pair in words)
    {
        actual += pair.Key + " " + pair.Value + "\n";
    }

    StringAssert.Equals(test, actual);
}

測試結果:




測試結果

  1. 對於非ascii碼的處理測試
    測試用例:



    測試用例

測試代碼:

[TestMethod]
public void TestMethod5()
{
    string test = "sdfs 2\ndfdsf 1\ndffs 1\nsdfsf 1";
    string content = File.ReadAllText("test2.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    ICore core = new Core(content);
    core.GetCharNum();
    core.GetWordNum(words);
    words = core.SortAndGetWord(words);
    string actual = "";
    foreach (KeyValuePair<string, int> pair in words)
    {
        actual += pair.Key + " " + pair.Value + "\n";
    }

    StringAssert.Equals(test, actual);
}

測試結果:




測試結果

  1. 讀入文件非法文件名測試
    測試用例:



    測試用例

測試代碼:

[TestMethod]
public void TestMethod6()
{
    string content = "";
    if (File.Exists("test10.txt"))
    {
        content = File.ReadAllText("test10.txt");

    }
    else
    {
        content = "文件名不正確請檢查...";
    }
    StringAssert.Equals("文件名不正確請檢查...", content);
}

測試結果:




測試結果

  1. 輸出文件非法文件名測試

測試用例:




測試用例

測試代碼:

[TestMethod]
public void TestMethod7()
{
    IDataIO data = new DataIO();
    string path = "??T>>Txsd>test.txt";
    data.WriteToFile(path, "hello");
    bool exist = File.Exists(path);
    Assert.AreEqual(false, exist);
}

測試結果:




測試結果

  1. 詞組長度測試

測試用例:




測試用例

測試代碼:

[TestMethod]
public void TestMethod8()
{
    string test = "dffs sdfs sdfsf : 1\nsdfs sdfsf dfdsf: 1\nsdfsf dfdsf sdfs: 1\n";
    string content = File.ReadAllText("test1.txt");
    Dictionary<string, int> words = new Dictionary<string, int>();
    Dictionary<string, int> wordsGroup = new Dictionary<string, int>();
    ICore core = new Core(content);
    IDataIO io = new DataIO();
    int charNum = core.GetCharNum();
    int wordNum = core.GetWordNum(words);
    words = core.SortAndGetWord(words);
    wordsGroup = Program.GetWordGroup(((Core)core).Lists, 3);
    string wordsGroupContent = "";
    foreach (KeyValuePair<string, int> wordsPair in wordsGroup)
    {
        wordsGroupContent += wordsPair.Key + ": " + wordsPair.Value + "\n";
    }
    StringAssert.Equals(test, wordsGroupContent);
}

測試結果:




測試結果

  1. -m -n 後不是數字的處理



    測試用例

測試代碼:

wordCount.exe -i test2.txt -m heelo -n fdsfsf -o output.txt

測試結果:




測試結果

  1. 意外狀況處理測試



    測試用例

測試代碼:

[TestMethod]
public void TestMethod9()
{
    string content = File.ReadAllText("test3.txt");
    ICore core = new Core(content);
    Assert.AreEqual(46, core.GetCharNum());
}

測試結果:




測試結果

  1. 輸入錯誤的處理

    因爲沒法對命令行進行單元測試,這裏使用人工測試

測試用例:




測試用例

測試代碼:
wordCount.exe -i test2.txt -x -xdsdsfd -o output.txt
測試結果:




測試結果

效能分析

  因爲命令行工具在效能分析中沒法使用,因此爲了方便查看效率,就將輸出目錄和輸入目錄直接硬編碼在程序中,本次效能分析使用小說《蘇菲的世界》英中對照版進行測試




測試用例

  在性能查看器中選擇查看CPU效率,分析結果以下圖:




性能消耗分析

  從圖中能夠看到,CPU開銷最大的就是Main()函數,固然這是由於Main()函數中包括了所有的調用方法。雙擊進入Main()函數:




性能消耗分析

  生成詳細報告以後,查看執行單個工做最多的函數:




性能消耗分析

  從上圖咱們能夠看到,調用最多的是字符串拼接函數Concat(),應該是我在輸出到output.txt中的時候,爲了使格式統一,用了大量的字符串拼接。可是雖然調用次數多,效率不必定低。因此繼續查看

  點擊查看消耗最大的Main()函數,查看代碼佔用效率:




代碼消耗

  從圖中能夠看出來,消耗主要是在GetCharNum()wordsGroupContent += wordsPair.Key + ": " + wordsPair.Value + "\n";string wordsCountContent = io.Print(words, n);這三句。咱們挨個進行分析。

  首先進入GetCharNum()函數查看:




代碼消耗

  這裏消耗最大的代碼就是正則表達式...這可咋優化啊。正則表達式的主要消耗在於它的「回溯」匹配,只要減小「回溯」次數,就可以提升效率。可是匹配Ascii之外得字符除了string regexStr1 = Regex.Replace(line, @"[^\u0000-\u007F]+", string.Empty);這一句,也沒有其餘更好的方法了。只能轉而優化其餘。

  查看另外兩個消耗比較大的代碼,得出一個驚人的發現。消耗最大的都是字符拼接處理:




Print函數最高消耗


wordGroupContent消耗

  我和俊老闆想了一下,修改C#的字符串拼接方式應該能夠改進性能。所以,參考博客https://blog.csdn.net/yeshennet/article/details/51435409後決定,將兩個「重災區」代碼改用StringBuilder.Append()函數進行優化。




修改拼接


修改拼接

  從新探查性能以後以後查看分析報告:




性能查看

  性能大大提高,提升了程序效率。

PSP表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20 15
· Estimate · 估計這個任務須要多少時間 20 15
Development 開發 4320 6182
· Analysis · 需求分析 (包括學習新技術) 720 720
· Design Spec · 生成設計文檔 240 120
· Design Review · 設計複審 (和同事審覈設計文檔) 240 120
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範) 240 120
· Design · 具體設計 1080 2880
· Coding · 具體編碼 360 1440
· Code Review · 代碼複審 720 60
· Test · 測試(自我測試,修改代碼,提交修改) 720 720
Reporting 報告 720 660
· Test Report · 測試報告 360 360
· Size Measurement · 計算工做量 180 180
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃 180 120
合計 5060 6917

總結

  結對編程從編程效率上來講,確實不容易出現錯誤和低質量代碼。但是在作需求分析的時候,兩我的作分析容易致使意見分歧,若是沒有第三我的,就很容易互相僵持,走入死衚衕。老師上課講的,兩我的互相監督的效果咱們也沒有達到,應該說不是體制問題,是咱們自身要求沒有達到。致使後面快作完的時候,效率很是低下,都一塊兒打遊戲了,正好雙排上分。因此,我認爲,在編程環節,是1+1>2的,可是項目需求分析和項目收尾的時候,每每會出現1+1<1的效果。

相關文章
相關標籤/搜索