GIT地址 | https://github.com/gentlemanzq/WordCount.git |
GIT用戶名 | gentlemanzq |
結對伙伴博客地址 | |
博客地址 | https://www.cnblogs.com/gentlemanzq/ |
作業鏈接 | https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/homework/2882 |
這一次結對編程,怎么說呢。帶來了一次不一樣的編程體驗,很難說清楚。具體后面再說,先看作業
- 一.結對過程
照片如下:(一直很疑惑為什么后台更改的照片,前端不會更改,百度了也沒有解決辦法。將就看吧)
- 二.PSP表格
PSP2.1 |
Personal Software Process Stages |
預估耗時(分鍾) |
實際耗時(分鍾) |
Planning |
計划 |
30 | 20 |
· Estimate |
· 估計這個任務需要多少時間 |
30 | 20 |
Development |
開發 |
670 | 740 |
· Analysis |
· 需求分析 (包括學習新技術) |
180 | 180 |
· Design Spec |
· 生成設計文檔 |
20 | 10 |
· Design Review |
· 設計復審 (和同事審核設計文檔) |
20 | 20 |
· Coding Standard |
· 代碼規范 (為目前的開發制定合適的規范) |
30 | 30 |
· Design |
· 具體設計 |
60 | 60 |
· Coding |
· 具體編碼 |
180 | 200 |
· Code Review |
· 代碼復審 |
60 | 80 |
· Test |
· 測試(自我測試,修改代碼,提交修改) |
120 | 160 |
Reporting |
報告 |
85 | 105 |
· Test Report |
· 測試報告 |
45 | 60 |
· Size Measurement |
· 計算工作量 |
20 | 15 |
· Postmortem & Process Improvement Plan |
· 事后總結, 並提出過程改進計划 |
20 | 30 |
合計 |
785 | 865 |
- 三.解題思路
1.首先拿到題目仔細閱讀,理清題意。大致需要完成的功能都是關於字符操作和計數排序的操作。根據這幾點需求,我們決定使用泛型Dictionary來完成這一系列操作。
2.關於如何解題,是根據作業要求來的,首先需要將文件讀入成一個字符串,然后對其進行操作,如果是統計字符數量和行數,需要借助Regex函數通過正則表達式 ‘’.‘’來統計除換行符之外的字符,最后加上換行符。(此處要注意換行符是兩個字符)
3.判斷是否是單詞的時候,先使用for循環將大小寫統一轉換為小寫,重新定義一個新的Dictionary,然后使用笨辦法if語句進行判斷單詞長度是否超過4個並且前四個是否是英文,如果滿足條件則重新賦給新的dictionary。
4.輸出頻率最高的10個詞,如果頻率最高則按字典序輸出這個地方。將新的dictionary先用frequencies函數進行統計判斷,然后將結果首先按照value值進行排序,然后再按照key值進行排序
5.對每一個功能項進行封裝,方便后面增加功能。
6.考慮到以上涉及的知識,所以我們在編碼前,首先參考了字典的用法,如何進行單詞判斷,如何按照key值和value值進行排序(參考這兩篇博客【1】【2】)。其次是關於regex,正則表達式的使用(參考這篇博客【3】)。
【1】:https://www.cnblogs.com/wt-vip/p/5997094.html
【2】:https://blog.csdn.net/ybhjx/article/details/69668442
【3】:https://blog.csdn.net/u012102536/article/details/85160138
- 四.代碼設計及接口封裝設計
1.首先對於每一個功能大致設置一個類,初步設計六個類,將除了program類其余放入function文件夾中,具體關系后面說明(PS:增加功能之后再添加)
2.類與類之間的調用關系具體為:program類中調用path,linescount,asccount類。在asccount類中會調用linescount參與部分計算。wordcount調用ynword進行判斷
3.啟動主函數在program類里面,如果要計算有多少個字符就調用ascount里面的agelife方法,如果要統計有多少行,就調用linescount中的lines方法。同理其余都是一樣。
4.基礎功能的難點在於判斷是否是單詞,並且需要按照次數,字典序排序,在wordcount函數中先進行是否單詞判斷,此處調用ynword。具體設計見流程圖
5.單元測試設計,主要測試function文件夾中的功能函數,類圖如下 (PS:單元測試代碼后見代碼復審)
6.接口設計及特色,由於功能都具有各自特色,相互影響性不高,故將每個功能都單獨成塊,抽離出來。功能都單獨返回值,不會在功能中輸出。並將有用的參數通過ref傳出。
7.算法設計關鍵:行數 總字符數都是通過正則表達式的方式進行統計,判斷單詞出現頻率,調用字典的封裝好的函數。在判斷是否是單詞時,將文本讀成字符串,字符串再通過正則表達式拆分成字符串數組。
- 五.代碼規范
1. 不要冗余無用代碼,過於冗余的代碼可以清理一下,一些已經注釋掉的代碼可以刪除
2、不變的值,盡量寫個常量類。
3、盡量使用if{}else,不要一直if去判斷。
4、減少循環調用方法;減少IO流的消耗資源。
5. 當一行代碼太長時,將其截斷成兩行寫。
6. 常用縮進和換行,使代碼層次清晰,明了。
7. 注釋的量不應該少於代碼量的三分之一。ps(變量統一使用例如/// <param name="s">文件讀入路徑</param>的注釋方式)
8. 定義變量名字和方法名字的時候盡量使用英文縮寫,或者拼音縮寫,便於識別。
9. 對泛型進行循環時,都采用foreach而不使用for。
11. 對於功能函數寫入一個function文件夾中,便於以后功能升級。
12. 一屏原則:一個方法體的代碼幅應該在一屏比較和合理;邏輯復雜的代碼可以抽離出方法體。
- 六.代碼復審及部分單元測試
1.在沒有封裝功能前,我們各自對對方寫的代碼進行第一次互審。耗時:20min
2.在進行封裝之后,我們一起針對幾個模塊功能進行審查,首先針對邏輯上第一個調用的計算行數的功能模塊linescount。經過二人的審查,覺得代碼沒有問題。為了測試正確,此處進行單元測試。
public class linescountTests { [TestMethod()] public void linesTest() { path.s = @"D:\se.txt"; int x = 0;//第一次測試時輸入5,第二次輸入0 Assert.AreEqual(x, linescount.lines()); // Assert.Fail(); } }
測試結果如下:此處第一次在記事本中輸入兩行測試成功,但是在輸入0行時測試失敗,此處出現大問題,當沒有輸入文本時,行數沒有進行判斷,所以出現錯誤。
3.審查asccount類(ps:功能為統計有多少字符),經過檢查之后,並未發現問題,於是進行單元測試。
public class asccountTests { [TestMethod()] public void asccountsTest() { path.s = @"D:\se.txt"; int num = 8; Assert.AreEqual(num, asccount.asccounts()); //Assert.Fail(); } }
測試結果如下:當定義num=0,文本不輸入字符時,出現測試錯誤。反應過來依舊是沒有判斷為零情況,所以才會出現錯誤。
4.根據邏輯思維,由於想要審查countword類必須要先審查ynword,保證其正確性,故先審查ynword功能模塊,經過前兩次錯誤,此次審查小心謹慎,依舊沒有發現問題。故接着進行單元測試
public void ynword1Test() { int w = 1; string[] n = { "word1" }; string[] newword = { "word1" }; string[] test = ynword.ynword1(n, ref w); Assert.AreEqual(newword[0],test[0] ); }
5.使用10個不同測試樣例,重復以上操作
6.代碼測試覆蓋率,由於這個是社區版,沒有測試覆蓋率。
- 七.異常處理
1.關於輸入路徑,輸出路徑異常處理(暫時只想到路徑異常)
try { for (int i = 0; i < args.Length; i++) { if (args[i] == "-i") path.s = args[++i];//-i 命令行 else if (args[i] == "-n") max = Convert.ToInt32(args[++i]);//-n 命令行 else if (args[i] == "-o") path.outputpath = args[++i];//-o 命令行 else if (args[i] == "-m") { len = Convert.ToInt32(args[++i]); } } } catch { Console.WriteLine("輸入或者輸出的路徑有誤"); } //--------------------------------------------------------- try { Console.WriteLine("不輸入參數,請手動輸入讀入文件路徑"); string s = Console.ReadLine(); path.s = s; max = 10; Console.WriteLine("請手動輸入輸出路徑"); string s1 = Console.ReadLine(); path.outputpath = s1; } catch { Console.WriteLine("輸入或者輸出的路徑有誤"); }
未處理:
處理后:
- 八.改進代碼
1.改進所用時間:45min+。
2.剛開始結對編程的時候,第一時間想用數組,字符串數組來寫的,但是在進行一定的編碼后,覺得數組太麻煩,實在是不適合,故最后決定使用字典泛型。
3.使用字典編程之后,剛開始所有功能模塊都寫在一起,代碼耦合性太差了,后由於要增加功能,並且要進行封裝接口,故把所有的功能模塊都抽離出來,寫入function文件夾
4.剛開始統計行數和字符總數時,用的另外的方法,后面改進使用正則表達式的方式,提高效率。
5.見效率分析圖及最耗時函數
- 九.部分代碼展示
1.統計行數展示:ps:需要注意0行的情況
public static int lines()//統計文件中的行數 { string str = File.ReadAllText(@path.s); int nr = Regex.Matches(str, @"\r").Count ; if (nr != 0) nr = nr + 1; return nr; }
2.統計總字符個數展示:ps:需要注意換行符是/r/n兩個字符。“.”只能統計/r不能統計/n故加上行數。需注意0個字符的情況。
public static int asccounts()//打開文件並統計字符個數 { string str = File.ReadAllText(@path.s); int num = Regex.Matches(str, @".").Count; if (linescount.lines() == 0) return num + linescount.lines(); else return num + linescount.lines() - 1; }
3.統計單詞個數展示:ps:在這里統計單詞數需要對每一個進行判斷,調用了ynword,此處不展示,用的笨辦法if多次判斷
public static Dictionary<string, int> Countword() { string str = File.ReadAllText(@path.s); Dictionary<string, int> frequencies = new Dictionary<string, int>(); string[] words = Regex.Split(str, @"\W+"); int k = 0; string[] newwords = ynword.ynword1(words,ref k); string[] newwords1 = new string[k]; for (int i = 0; i < k; i++) { newwords1[i] = newwords[i]; } foreach (string word in newwords1) { if (frequencies.ContainsKey(word)) { frequencies[word]++; } else { frequencies[word] = 1; } } return frequencies; }
4.主函數展示: ps:此處由於增加功能,通過查閱資料,知道了命令行輸入是存入args中的,故通過這種方式進行操作。
static void Main(string[] args) { int temp = 0; int max = 0; int len = 0;
//如果命令行有參數執行 if (args.Count() != 0) { for (int i = 0; i < args.Length; i++) { if (args[i] == "-i") path.s = args[++i];//-i 命令行 else if (args[i] == "-n") max = Convert.ToInt32(args[++i]);//-n 命令行 else if (args[i] == "-o") path.outputpath = args[++i];//-o 命令行 else if (args[i] == "-m") { len = Convert.ToInt32(args[++i]); } } if (path.s == null || path.outputpath == null)//路徑為空則不存在 { Console.WriteLine("路徑不正確,文件不存在"); } }
//命令行無參數,執行 else { Console.WriteLine("不輸入參數,請手動輸入讀入文件路徑"); string s= Console.ReadLine(); path.s = s; max = 10; Console.WriteLine("請手動輸入輸出路徑"); string s1 = Console.ReadLine(); path.outputpath = s1; } Dictionary<string, int> frequencies = function.wordcount.Countword();//調用wordcount中方法統計單詞 Dictionary<string, int> dic1Asc = frequencies.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);//按照字典序進行排序 int sum = function.wordcount.sum1(dic1Asc);//計算出單詞總數量 Console.WriteLine("字符數:"+asccount. asccounts());//計算出字符數量 Console.WriteLine("單詞總數:" + sum); Console.WriteLine("行數:"+linescount. lines());//計算出行數 //先按照出現次數排序,如果次數相同按照字典序排序 Dictionary<string, int> dic1Asc1 = frequencies.OrderByDescending(o => o.Value).ThenBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value); foreach (KeyValuePair<string, int> entry in dic1Asc1) { if (temp == max) break; string word = entry.Key; int frequency = entry.Value; temp++; Console.WriteLine("{0}:{1}", word, frequency); } Console.ReadKey(); }
- 十.總結
剛開始覺得結對編程沒有太大的用處,但是通過這次結對編程的經歷之后,感覺了結對編程的好處,1+1還真是大於2的。由於剛開始陷入了誤區,糾結了許多小問題,通過搭檔的提醒,一下子豁然開朗,一個人的主觀思維是不完美的,只有通過和他人的合作,才能使得代碼趨緊無錯,滿足所有情況。有了一個實時搭檔,在編碼時在旁邊指出自己的不足和思維漏洞,這樣提高了效率,降低了檢查時重來的時間。此次收獲良多。