做業要求地址 | http://www.javashuo.com/article/p-zywsxnlu-cu.html |
GitHub項目地址 | https://github.com/anranbixin/WordCount |
結對夥伴的博客 | http://www.javashuo.com/article/p-kalozehf-e.html |
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個
二、頻率相同的單詞,優先輸出字典序靠前的單詞。
要求:一、字典裏面的單詞已通過頻數排序
二、輸出的文件可任意指定
三、輸出的量可控
(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); }
注意:這裏個人參考博客是: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.註釋:複雜的註釋應該放在函數頭,養成邊寫代碼邊寫註釋的好習慣
總結:
真的在兩我的的項目中我真切的感覺到了這個的重要性,剛開始我覺得個人代碼真的算是規範的了,可是個人小夥伴在看個人代碼的時候卻仍是出現了問題了。
應該是每一個人的命名都是會有區別的,我喜歡把關鍵字寫在後面,雖然採用的也是英文命名,可是一個意思的英文單詞確實太多了,每次都會有點小差錯。
而後咱們就本身制定了一個命名規範,並用於咱們的小項目當中。
首先能夠看到咱們測出來的代碼的性能圖,第一個是內存使用率,第二個爲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("輸入的指令無效"); }
將代碼轉移到本身的電腦上的時候,出現了一些錯誤,而後經過調試以後能夠運行且無錯誤。
根據以前的經驗咱們開始進行單元測試,剛開始的時候還有一些錯誤(並修改了一些源代碼):
而後經過寫單元測試代碼,能夠看到測試所有都經過了:
在圖片中咱們能夠看到部分的測試代碼,這裏就不一一進行解釋了。
將項目git到本身的倉庫,再將文件下載下來,再裏面建立一個咱們兩個其中一個的學號的文檔(建立的個人),而後將項目放在裏面。
經過git bush提交項目(提交命令以下圖所示,注意要修改了代碼才能夠進行提交,每次提交都相似):
以後能夠看到咱們的提交記錄,以下圖所示:
三次提交項目:
項目在咱們兩我的的努力下就這樣完成了,剛開始咱們最難的也就是寫代碼。最初的時候不知道怎麼開始,在設計好PSP表格以後,就開始設計函數 。設計好基本函數以後,咱們就直接輸入輸入路徑來進行測試 (此時發現了一些代碼錯誤,而後 進行了修改),測試良好。而後就進行命令的輸入的設計,經過args來讀取獲得路徑(兩我的一塊兒查資料,很快就解決了~)。
這次的結對編程感受還不錯,兩個的代碼能力都通常,剛開始的時候還很無措,可是兩我的的解決辦法就多了,不少問題在短期就獲得瞭解決。
整體而言兩我的的編程1+1>2的,編代碼的時間獲得了大大的縮短,兩我的的代碼測試使得代碼的結構更加的穩定,並且代碼的編寫更爲規範了。
在實踐的過程當中還發現了不少本身的不足之處,以爲本身仍是有不少須要改進的地方。