第四次博客做業——結隊編程

做業要求地址 http://www.javashuo.com/article/p-zywsxnlu-cu.html 
GitHub項目地址  https://github.com/anranbixin/WordCount
結對夥伴的博客  http://www.javashuo.com/article/p-kalozehf-e.html

 

一、PSP表格

PSP2.1html

Personal Software Process Stagesgit

預估耗時(分鐘)程序員

實際耗時(分鐘)github

Planning正則表達式

計劃編程

 30ide

 40函數

· Estimate工具

· 估計這個任務須要多少時間性能

 1200

 1440

Development

開發

 1080

 1200

· Analysis

· 需求分析 (包括學習新技術)

 40

 50

· Design Spec

· 生成設計文檔

 20

 20

· Design Review

· 設計複審 (和同事審覈設計文檔)

 20

 30

· Coding Standard

· 代碼規範 (爲目前的開發制定合適的規範)

 30

 30

· Design

· 具體設計

 60

 50

· Coding

· 具體編碼

 700

 960

· Code Review

· 代碼複審

 60

 50

· Test

· 測試(自我測試,修改代碼,提交修改)

 120

 150

Reporting

報告

 30

 20

· Test Report

· 測試報告

 60

 40

· Size Measurement

· 計算工做量

 30

 20

· Postmortem & Process Improvement Plan

· 過後總結, 並提出過程改進計劃

 30

 20

 

合計

 1230

 1480

 

二、計算模塊接口的設計與實現過程

2.1項目分析(項目需求)

(1)應具有的功能

  • 統計文件的字符數

      要求:一、漢字不考慮,空格、空格,水平製表符,換行符,均算字符,

                 二、輸出的單詞統一爲小寫格式

  • 統計文件的單詞總數

      要求:一、至少以4個英文字母開頭,且能夠跟上字母數字符號

                 二、單詞以分隔符分割,且不區分大小寫

  • 統計文件的有效行數

     要求:任何包含非空白字符的行都須要統計

  • 統計文件中各單詞的頻數

       要求:一、最終只輸出頻率最高的10個

                  二、頻率相同的單詞,優先輸出字典序靠前的單詞。

  • 按照字典序輸出到文件txt(默認爲output.txt)

       要求:一、字典裏面的單詞已通過頻數排序

                  二、輸出的文件可任意指定

                  三、輸出的量可控

(2)新功能

  • 詞組統計

       要求:能統計文件夾中指定長度的詞組的詞頻 

  • 自定義輸出

       要求:能輸出用戶指定的前n多的單詞與其數量

  • 多參數的混合使用

      要求:格式爲   -i input路徑 -o output路徑 -n 參數 -m 參數

(3)附加功能(這個功能咱們兩個決定選擇性的實現)

  • 用戶交互界面繪製

2.2項目思路

-- 首先假設給出的路徑已知,分別爲(便於咱們以後要進行指令讀入形成的代碼修改):

(1)讀入文件路徑:D:\VS_practice\wordCount\wordCount\bin\Debug\input.txt

(2)寫入文件路徑:D:\VS_practice\wordCount\wordCount\bin\Debug\output.txt

注意:一、這裏咱們程序運行的當前路徑爲:D:\VS_practice\wordCount\wordCount\bin\Debug

           二、咱們只須要調用語句,便可得到當前程序路徑,以後加上咱們的文件名便可

//獲取當前文件路徑
string currentpath = Directory.GetCurrentDirectory();

(3)數據的讀取上,咱們考慮到文件內容若是很大會影響數據的讀取,咱們採用的是content.ReadLine()進行單行的讀取,這裏也是會出現問題的,值得注意的一點是:在讀入的input.txt文件裏面,每一行英文必須是要用空格或者其餘符號結尾,否則在後面使用Regex(Regex我會在後面單獨提到)進行單詞的提取上會出現錯誤

-- 程序如何才能實現:

(1)咱們預計設計兩個.cs文件,一個爲主函數.cs文件(program.cs),一個爲函數.cs文件(port.cs)

(2)數據的存儲:使用string 變量來存儲字符數,list<> 來存儲提取出來的單詞,dictionary<string,int> 來存儲單詞以及其的頻率(這裏要使用到dictionary的兩個參數的使用,key和)

(3)頭文件的調用:文件的輸入輸出須要使用到StreamWriter,所以須要調用IO頭文件;提取單詞須要使用到System.Text.RegularExpressions.Regex,所以須要調用Text.RegularExpressions頭文件

//文件流的輸入輸出
using System.IO;
//正則表達式
using System.Text.RegularExpressions;

補充:

這裏知識點的我參考了其餘的博客:https://www.cnblogs.com/liangsetian/archive/2011/07/05/2098280.html

(4)獲取字符數、單詞數、頻數等等,直接使用函數返回便可

(5)指定輸出和指定單詞組的輸出都是涉及函數涉及,在以後會進行詳解,這裏輸出會使用到遍歷foreach ( element in list or dictionary)

(6)多參數的混合使用裏面涉及到cmd命令,此時須要使用到args[i],經過Main方法中的string[]  args參數來獲取

補充:這裏我進行了知識點的搜索(args),博客源爲:https://blog.csdn.net/eric_k1m/article/details/37518579

-- 程序的流程大體爲下圖所示:

基礎功能模塊和新功能模塊:

 

2.3接口的設計與代碼的實現過程

--  首先進行基礎功能的接口設計與實現

這裏我使用的是五個函數,首先必須依次調用Getcharacters(path);Withdraword();Tolower();Wordfrequency();這四函數,以實現初始化,中途涉及函數的調用,函數須要進行依次的設計,這四個函數須要依次進行實現,不然出來的結果是不正確的。例如:尚未進行字符的錄入,可是你要輸出單詞這就是沒法實現的。如下依次爲5個函數的設計(4個基本+1個輸出):

(1)進行字符的讀取,這裏咱們必需要將待讀取的文件的路徑傳進來,咱們將函數設計爲:public void Getcharacters(string path),在裏面咱們使用了ReadLine()來對path裏面的文檔進行一行一行的讀取,而後將讀取的字符直接存儲於account_chara字符串裏面

//讀取文件,string account_chara用於存儲字符
        public void Getcharacters(string path)
        {  
            StreamReader content = new StreamReader(path);
            //定義字符臨時變量
            string temp = content.ReadLine();
            //讀取
            while (temp != null)
            {
                account_chara = account_chara + temp;
                account_line++;
                temp = content.ReadLine();
            }

            //最後一行讀入無效,將其刪去
            account_line -= 1;
        }
字符讀取

(2)接着就是將單詞提取出來,這裏咱們採用的是之間說到的正則表達式,也就是使用Regex來對單詞進行提取。因爲咱們提取單詞是有要求的,須要咱們(1)至少以4個英文字母開頭,且能夠跟上字母數字符號,因此咱們採用的是:@"([a-zA-Z]{4}\w*)" (2)單詞以分隔符分割,且不區分大小寫,這裏就須要把單詞所有小寫化,咱們採用的是ToLower()

//正則表達式匹配英文單詞
        public void Withdraword()
        {
            //以字母開頭,數字結尾,單詞至少4個字符
            MatchCollection mc_word = Regex.Matches(account_chara, @"([a-zA-Z]{4}\w*)");
            //臨時變量
            int i = 0;
            while (i < mc_word.Count)
            {
                //存儲單詞
                word.Add(Convert.ToString(mc_word[i]));
                i++;
            }
        }
正則表達式,提取單詞
//單詞小寫    
       foreach (string element in word)
            {
                element.ToLower();
            }
小寫化

注意:這裏咱們是弄的時間比較長,後來在參考博客發現可使用正則表達式(Regex.Matches),博客的來源:http://www.javashuo.com/article/p-hbppmzua-bd.html

(3)計算字符的數目,這裏須要注意的是咱們的字符 (string  account_chara)不包括中文,因此咱們首先須要瞭解中文怎麼表示,"[\u4e00-\u9fa5]" 表示中文。既然咱們是把咱們的字符存儲在一個字符串temp裏面(這裏是出去中文字符),而後直接輸出temp的長度便可獲得咱們想要的字符數

//字符總數
        public int Characternum()
        {
            //區分是否爲中文,中文是@"[\u4e00-\u9fa5]"     \u4E00-\u9FA5
            MatchCollection mc_chara = Regex.Matches(account_chara, @"[^\u4e00-\u9fa5]*");
            string temp = null;
            int i = 0;
            while (i < mc_chara.Count)
            {
                temp = temp + Convert.ToString(mc_chara[i]);
                i++;
            }
            return temp.Length;
        }
字符總數

(4)獲取單詞以及單詞的頻數,首先經過d_word.ContainsKey(word[i])得到單詞以及單詞的頻數,而後經過dictionary的key和value進行排序(看你須要如何排序來進行設計)

//單詞頻數(dictionary,sort)
        public void Wordfrequency()
        {
            //排序以前,將單詞存入dictionary
            Dictionary<string, int> d_word = new Dictionary<string, int>();
            int i = 0;
            while (i < word.Count)
            {
                //ContainsKey判斷是否存在
                if (d_word.ContainsKey(word[i]))
                {
                    d_word[word[i]]++;
                }
                else
                {
                    d_word[word[i]] = 1;
                }
                i++;
            }
            //經過dictionary的key和value進行排序
            word_num = d_word.OrderByDescending(p => p.Value).ToDictionary(p => p.Key, o => o.Value);
        }
單詞頻數(dictionary,sort)

注意:這裏個人參考博客是:http://www.360doc.com/content/18/0425/18/54584204_748693269.shtml

 (5)寫入文檔,這裏須要使用到字節流的寫入,經過FileStream,StreamWriter行文件讀寫

//寫入文件
        public void Writetofile(string path,string outpath)
        {
            //準備(讀入文檔,單詞提取,以及詞頻的排序)
            Prep(path);
            FileInfo file = null;
            if (outpath == null)
            {
                file = new FileInfo(@"D:\VS_practice\wordCount\wordCount\bin\Debug\output.txt");
            }
            else
            {
                file = new FileInfo(outpath);
            }
            StreamWriter sw = file.AppendText();
            sw.WriteLine("字符數:" + Characternum());
            sw.WriteLine("單詞數:" + Wordnum());
            sw.WriteLine("行數:" + Wordlinenum());
            Console.WriteLine("字符數爲:" + Characternum());
            Console.WriteLine("單詞數爲:" + Wordnum());
            Console.WriteLine("行數爲:" + Wordlinenum());
            //統計前10個高頻單詞
            Writeword(sw,10);
            //關閉文件
            sw.Close();
        }
寫入文件

注意:這裏文件的寫入參考的博客(內含有知識點)是:https://blog.csdn.net/u010159842/article/details/51785613

--  而後是新功能的接口設計與實現

詞組統計、自定義輸出採用的是2個函數,多參數是在主函數中實現,這裏咱們使用到了args[i],我只展現多參數的實現:

//篩選出前10個高頻單詞 
public void Writeword(StreamWriter sw,int n)
//輸出指定數量的單詞數,並寫入文件
public void Wordgroupp(StreamWriter sw, int m)
 for (int i = 0; i < args.Length; i++)
            {
                switch (args[i])
                {
                    case "-i":
                        path = args[i + 1];//輸入路徑
                        break;
                    case "-o"://-o輸出路徑
                        outpath = args[i + 1];
                        break;
                    case "-m"://-m輸出幾個高頻詞
                        m = args[i + 1];
                        break;
                    case "-n"://-n輸出幾個單詞的個數
                        n = args[i + 1];
                        break;
                    
                }
            }
參數指令

--  運行結果

(1)程序運行:

 (2)cmd輸入命令

 

文件的截圖不完整,因爲數據太大了。

 

2.4接口的封裝

因爲每次都須要調用函數是容易出錯的,因此須要進行接口的封裝

例如:個人小夥伴在看個人程序的時候,不知道前後順序,沒有進行單詞的排序就直接輸出含有單詞頻數的dictionary變量,結果輸出爲空。

爲此咱們進行了函數接口的封裝,以下所示:

public void Prep(string path)
        {
            Getcharacters(path);
            Withdraword();
            Tolower();
            Wordfrequency();

        }

這是一個準備函數,直接將須要進行的函數進行封裝,在咱們開始咱們的主程序的時候,只須要先調用這個函數便可完成文件的預處理。

再者,咱們將函數寫在了一個.cs文件裏面,將主函數寫在一個.cs文件裏面,實現了函數的封裝,下次直接調用便可。

三、代碼互審過程

代碼規範(由咱們本身制定的,咱們兩個的博客中相同)

通過參考C#代碼規範,咱們制定了咱們的代碼規範:
咱們本着「保持簡明,讓代碼更容易讀」的原則,讓咱們更好地理解和維護程序。
代碼風格的原則是:簡明,易讀,無二義性。
1.縮進:4個空格,在VS2017和其餘的一些編輯工具中均可以定義Tab鍵擴展成爲幾個空格鍵。不用 Tab鍵的理由是Tab鍵在不一樣的狀況下會顯示不一樣的長度。4個空格的距離從可讀性來講正好。
2.括號:在複雜的條件表達式中,用括號清楚地表示邏輯優先級。
3.斷行與空白的{ }行:每一個「{」和「}」都獨佔一行
如:

if ( condition)
{
    DoSomething();
}
else { DoSomethingElse(); }

4.分行:不要把多行語句放在一行上。
5.命名:命名方法使用「匈牙利命名法」,在變量面前加上有意義的前綴,就可讓程序員一眼看出變量的類型及相應的語義
例如:
    fFileExist,代表是一個bool值,表示文件是否存在;
    szPath,代表是一個以0結束的字符串,表示一個路徑。
6.下劃線問題:下劃線用來分隔變量名字中的做用域標註和變量的語義
7.大小寫問題:由多個單詞組成的變量名,若是所有都是小寫,很不易讀,一個簡單的解決方案就是用大小寫區分它們 

Pascal——全部單詞的第一個字母都大寫;
Camel——第一個單詞所有小寫,隨後單詞隨Pascal格式,這種方式也叫lowerCamel。
一個通用的作法是:全部的類型/類/函數名都用Pascal形式,全部的變量都用Camel形式。
類/類型/變量:名詞或組合名詞,如Member、ProductInfo等。
函數則用動詞或動賓組合詞來表示,如gett; RenderPage()。
8.註釋:複雜的註釋應該放在函數頭,養成邊寫代碼邊寫註釋的好習慣

總結:

真的在兩我的的項目中我真切的感覺到了這個的重要性,剛開始我覺得個人代碼真的算是規範的了,可是個人小夥伴在看個人代碼的時候卻仍是出現了問題了。

應該是每一個人的命名都是會有區別的,我喜歡把關鍵字寫在後面,雖然採用的也是英文命名,可是一個意思的英文單詞確實太多了,每次都會有點小差錯。

而後咱們就本身制定了一個命名規範,並用於咱們的小項目當中。

四、接口部分的性能改進

4.1效能分析與改進

首先能夠看到咱們測出來的代碼的性能圖,第一個是內存使用率,第二個爲CPU使用率:

 

 總結(綜合我和個人結對小夥伴):

能夠看到咱們調用主函數的頻率是高的,能夠看出Main函數中是CPU佔比最多的函數,這樣子是不太好的,通過我兩的一致討論結果是把主函數簡化,確實相比以後是要優化不少的。

因而咱們看到main函數,其中在該函數中,咱們讀取文件、將最後結果放在文件中所花時間、判斷命令行中出現的命令選項、判斷並打印結果等所花時間都不少,特別是文件的讀取過程很慢,所以咱們針對文件讀取作了不少分析,最後減小了程序對文件的讀取的個數使得性能加快。咱們就儘可能將主函數在調用port.cs裏面的函數都統一在一個函數裏面,這樣就下降 了他的調用頻率,其實對於主函數過於龐大咱們兩個仍是有意見的,她但願將指令單獨列成一個函數,我堅持寫在主函數裏面,可能將其列成函數是有必定的優化效果的。
在另外一個類port中,WordFrequency()函數佔用率最高,所以咱們針對WordFrequency()函數進行了分析,WordFrequency()函數中主要拖垮性能的緣由是有大量的判斷語句,判斷d_word字典中的key值,即單詞word[i]是否存在,若存在則執行value值加1的操做,若不存在,則將其做爲key存入字典並將其value值令爲1,所以咱們在函數外將咱們須要判斷的值優先判斷,最後再根據其判斷值進行value的值操做,加快了程序性能。

4.2接口性能的改進

將主要的準備工做作成一個函數,到時候直接進行一次調用,就可實現初始化效果,這樣子還減小了主函數對於其餘函數的調用。可是能夠看出將其封裝爲一個函數的時候(Prep),Prep()的調用率是很高的。

五、異常(錯誤)處理說明

使用try {} catch {} 來進行異常處理:

首先咱們須要瞭解到哪裏是須要進行異常處理的,當咱們進行輸入的時候,或者是須要進行傳參的時候,須要咱們來檢驗那些輸入和傳入的參數是否有效,若是無效則拋出異常。

(1)在文件進行路徑讀入的時候(判斷路徑中是否含有文檔,-i 與 -o相似):

try
    {
          path.Contains(".txt");
     }
catch
    {
          Console.WriteLine("輸入的路徑不含有txt文件!");
     }

(2)指令的輸入的時候(判斷指令後面的數據是否有效-m 與 -n 的是相似的,這裏只列舉一個):

try
    {
         int test = int.Parse(args[i + 1]);
    }
catch
    {
         Console.WriteLine("輸入的指令無效");
    }

六、單元測試展現

將代碼轉移到本身的電腦上的時候,出現了一些錯誤,而後經過調試以後能夠運行且無錯誤。

根據以前的經驗咱們開始進行單元測試,剛開始的時候還有一些錯誤(並修改了一些源代碼):

 而後經過寫單元測試代碼,能夠看到測試所有都經過了:

 在圖片中咱們能夠看到部分的測試代碼,這裏就不一一進行解釋了。

七、提交項目到GitHub上

將項目git到本身的倉庫,再將文件下載下來,再裏面建立一個咱們兩個其中一個的學號的文檔(建立的個人),而後將項目放在裏面。

經過git bush提交項目(提交命令以下圖所示,注意要修改了代碼才能夠進行提交,每次提交都相似):

 

 以後能夠看到咱們的提交記錄,以下圖所示:

三次提交項目:

 

八、結對過程的描述

項目在咱們兩我的的努力下就這樣完成了,剛開始咱們最難的也就是寫代碼。最初的時候不知道怎麼開始,在設計好PSP表格以後,就開始設計函數 。設計好基本函數以後,咱們就直接輸入輸入路徑來進行測試 (此時發現了一些代碼錯誤,而後 進行了修改),測試良好。而後就進行命令的輸入的設計,經過args來讀取獲得路徑(兩我的一塊兒查資料,很快就解決了~)。

八、總結

這次的結對編程感受還不錯,兩個的代碼能力都通常,剛開始的時候還很無措,可是兩我的的解決辦法就多了,不少問題在短期就獲得瞭解決。

整體而言兩我的的編程1+1>2的,編代碼的時間獲得了大大的縮短,兩我的的代碼測試使得代碼的結構更加的穩定,並且代碼的編寫更爲規範了。

在實踐的過程當中還發現了不少本身的不足之處,以爲本身仍是有不少須要改進的地方。

相關文章
相關標籤/搜索