一、我们Fork仓库的Github项目地址
结对使用的Github项目地址:https://github.com/xuyixiaowoaini/WordCount.git
结对伙伴的作业地址:https://www.cnblogs.com/CHIQING123/p/10659152.html
二、PSP表格
PSP2.1 |
Personal Software Process Stages |
预估耗时(分钟) |
实际耗时(分钟) |
Planning |
计划 |
5 | 5 |
· Estimate |
· 估计这个任务需要多少时间 |
5 | 5 |
Development |
开发 |
720 | 810 |
· Analysis |
· 需求分析 (包括学习新技术) |
60 | 120 |
· Design Spec |
· 生成设计文档 |
10 | 10 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
10 | 10 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10 | 10 |
· Design |
· 具体设计 |
30 | 60 |
· Coding |
· 具体编码 |
360 | 360 |
· Code Review |
· 代码复审 |
60 | 60 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
180 | 180 |
Reporting |
报告 |
130 | 190 |
· Test Report |
· 测试报告 |
60 | 120 |
· Size Measurement |
· 计算工作量 |
10 | 10 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
60 | 60 |
合计 |
855 | 1005 |
三、计算模块接口的设计与实现过程。
1、设计
这个项目有五个基本功能点:计算Input文件的字符数、单词总数、有效行数、单词出现次数并排序,以及输出到Result文件。
这些功能总体由三个类来完成:
(1)WordIO(实现输入文件以及输出文件的功能)
(2)WordTrie(实现计算单词出现次数并排序的功能)
(3)WordCalculate(实现计算字符数、单词总数、有效行数的功能)
大致结构如下:
2、如何体现“Design by Contract”、“Information Hiding”、 “Interface Design”、 “Loose Coupling”等原则的
(1)Design By Contract(契约式设计):
按照某种规定对一些数据等作出约定,如果超出约定,程序将不再运行。
如何体现:一个契约设计,就是约束了某个方法调用的要求、以及返回的承诺。那么,设计在正确的输入下,能够得到正确的输出,否则程序将报错。
(2)Information Hiding(信息隐藏):
在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。
如何体现:类与类之间通过接口类访问。
(3)Interface Design(接口设计):
对接口的名字,功能,接口与接口间的继承关系进行设计;好的接口设计可以增强代码可读性,易用性,可更改性。
如何体现:设计接口,规范接口名字,注重接口逻辑。
(4)Loose Coupling 松耦合:
软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
如何体现:增加接口。
四、代码复审过程。
1、代码规范
参考https://wenku.baidu.com/view/b5be911b6bd97f192279e9bd.html。
2、代码互审情况、发现的问题
我们完成这个项目时基本是在一起讨论编程的,因此最后没有发现什么大的问题,只是合并时改了不同的参数,以及对代码规范的问题进行了一些修改。
五、计算模块接口部分的性能改进。
1、性能改进
在整体编程结束后,进行性能分析与改进是非常有必要的。虽然在小规模的数据下,功能的实现是不太会涉及到优化算法的。但是,个别特例或者是大规模的数据却需要对程序进行性能的优化改进。我查询资料找到这些方法:
(1)消除循环的低效率
(2)减少过程调用
(3)消除不必要的存储器引用
(4)循环展开
(5)提高并行性
在性能改进方面则运用了这些方法来改进以提高性能。
2、性能分析图
六、代码说明。
在关键部分已加入注释说明。
(1)WordIO类,实现输入文件以及输出文件的功能
1 public class WordIO 2 { 3 public string pathIn; 4 public string pathOut; 5 6 //按行读取输入文件并统计 7 public WordCalculate Input(WordCalculate datanumber, WordTrie wtrie) 8 { 9 FileStream fs = null; 10 StreamReader sr = null; 11 String dataline = String.Empty; 12 try 13 { 14 fs = new FileStream(this.pathIn, FileMode.Open); 15 sr = new StreamReader(fs); 16 while ((dataline = sr.ReadLine()) != null) 17 { 18 datanumber.Calculate(dataline, wtrie); //按行统计数据 19 } 20 } 21 catch { Console.WriteLine("文档读取失败!"); } 22 finally 23 { 24 if (sr != null) { sr.Close(); } 25 if (fs != null) { fs.Close(); } 26 } 27 return datanumber; 28 } 29 30 //将统计数据输出并写到输出文件 31 public void Output(WordCalculate datanumber, WordTrie wtrie) 32 { 33 FileStream fs = null; 34 StreamWriter sw = null; 35 List<WordTrie.ListUnit> WordList = new List<WordTrie.ListUnit>(); 36 try 37 { 38 fs = new FileStream(this.pathOut, FileMode.Create); 39 sw = new StreamWriter(fs); 40 WordList = wtrie.Sort(); 41 sw.WriteLine("字符总数为:{0}", datanumber.charactersnumber); 42 sw.WriteLine("单词总数为:{0}", datanumber.wordsnumber); 43 sw.WriteLine("有效行数为:{0}", datanumber.linesnumber); 44 sw.WriteLine("\n词频\t单词\n"); 45 Console.WriteLine("字符总数为:{0}", datanumber.charactersnumber); 46 Console.WriteLine("单词总数为:{0}", datanumber.wordsnumber); 47 Console.WriteLine("有效行数为:{0}", datanumber.linesnumber); 48 Console.WriteLine("\n词频\t单词\n"); 49 for (int i = 0; (i < 10 && i < WordList.Count); i++) 50 { 51 sw.WriteLine("{0}\t{1}",WordList[i].WordNum, WordList[i].Word); 52 Console.WriteLine("{0}\t{1}",WordList[i].WordNum, WordList[i].Word); 53 } 54 } 55 catch { Console.WriteLine("文档写入失败!"); } 56 finally 57 { 58 if (sw != null) 59 { 60 sw.Close(); 61 } 62 if (fs != null) 63 { 64 fs.Close(); 65 } 66 } 67 } 68 }
(2)WordTrie类,利用Trie树节点实现计算单词出现次数并排序的功能(只展示关键函数)
1 //获取单词词频 2 public int WordCount(string word) 3 { 4 return GetCount(word, true); 5 } 6 7 private int GetCount(string str, bool isword) 8 { 9 if (string.IsNullOrEmpty(str)) 10 { 11 return -1; 12 } 13 TrieNode node = _Root; 14 for (int i = 0, len = str.Length; i < len; i++) 15 { 16 char pos = str[i]; 17 if (!node.Sons.ContainsKey(pos)) return 0; 18 else node = node.Sons[pos]; 19 } 20 return isword ? node.WordNum : node.PrefixNum; 21 }
1 //词频排序 2 public List<ListUnit> Sort() 3 { 4 TrieNode node = _Root; 5 List<ListUnit> WordList = new List<ListUnit>(); 6 WordList = WordPreOrder(node, WordList); 7 //按词频降序排列,若词频相等按字典序排列 8 WordList.Sort((a, b) => 9 { 10 if (a.WordNum.CompareTo(b.WordNum) != 0) 11 return -a.WordNum.CompareTo(b.WordNum); 12 else 13 return a.Word.CompareTo(b.Word); 14 }); 15 return WordList; 16 }
(3)WordCalculate(实现计算字符数、单词总数、有效行数的功能)
1 public class WordCalculate 2 { 3 public long charactersnumber = 0; //统计数据:字符数 4 public long wordsnumber = 0; //统计数据:单词数 5 public long linesnumber = 0; //统计数据:行数 6 //数据统计 7 public void Calculate(string dataline, WordTrie wtrie) 8 { 9 if (string.IsNullOrEmpty(dataline)) return; 10 string word = null; 11 for (int i = 0, len = dataline.Length; i < len; i++) 12 { 13 char unit = dataline[i]; 14 if (unit >= 65 && unit <= 90) 15 { 16 unit = (char)(unit + 32); 17 } //大写字母转换成小写 18 if ((unit >= 48 && unit <= 57) || (unit >= 97 && unit <= 122)) 19 { 20 word = String.Concat(word, unit); 21 } 22 else 23 { 24 if (!string.IsNullOrEmpty(word)) 25 { 26 if ((word[0] >= 97 && word[0] <= 122) ) 27 { 28 wtrie.Insert(word); 29 } 30 word = null; 31 } 32 } 33 } 34 if (!string.IsNullOrEmpty(word)) 35 { 36 if ((word[0] >= 97 && word[0] <= 122) ) 37 { 38 wtrie.Insert(word); 39 } 40 word = null; 41 } 42 this.linesnumber++; //统计行数 43 this.wordsnumber = wtrie.CountSum; //统计单词数 44 this.charactersnumber += dataline.Length; //统计字符数 45 } 46 }
运行结果:
七、计算模块部分单元测试展示。
单元测试代码是根据可能出现的异常而设计出的,(部分单元测试代码见第八)。
单元测试得到的测试覆盖率截图如下:
八、计算模块部分异常处理说明。
1、单词以英文字母开头
1 [TestMethod()] 2 public void MainTest01() 3 { 4 string test; 5 Program.Result trueres = new Program.Result(); 6 string reason; 7 test = "123abcd"; 8 trueres.charactersnumber = 7; 9 trueres.wordsnumber = 0; 10 trueres.linesnumber = 1; 11 reason = "测试用例1"; 12 UnitTest(test, trueres, reason); 13 14 }
123abcd不是一个单词。若错误会计算出单词数为1。
2、单词以分隔符分割
1 [TestMethod()] 2 public void MainTest02() 3 { 4 string test; 5 Program.Result trueres = new Program.Result(); 6 string reason; 7 test = "abcd abcd@abcd"; 8 trueres.charactersnumber = 14; 9 trueres.wordsnumber = 3; 10 trueres.linesnumber = 1; 11 reason = "测试用例2"; 12 UnitTest(test, trueres, reason); 13 }
单词以空格和非字母数字符号作分隔符分隔。若错误会计算出单词数为1。
3、统计有效行数
1 TestMethod()] 2 public void MainTest03() 3 { 4 string test; 5 Program.Result trueres = new Program.Result(); 6 string reason; 7 test = "abcd\n\nabcd"; 8 trueres.charactersnumber = 8; 9 trueres.wordsnumber = 2; 10 trueres.linesnumber = 2; 11 reason = "测试用例3"; 12 UnitTest(test, trueres, reason); 13 }
有效行数指任何包含非空白字符的行。测试设计了一个空白行,若错误会计算出行数为3。
4、单词:字母后可跟数字
1 [TestMethod()] 2 public void MainTest04() 3 { 4 string test; 5 Program.Result trueres = new Program.Result(); 6 string reason; 7 test = "abcd123"; 8 trueres.charactersnumber = 7; 9 trueres.wordsnumber = 1; 10 trueres.linesnumber = 1; 11 reason = "测试用例4"; 12 UnitTest(test, trueres, reason); 13 }
若错误则计算出单词个数不为1。
九、结对的过程及结对照片。
1、结对过程:
我和与我结对的黄欣同学,先把项目需要完成的功能划分成几个模块,再一起填写psp表格并进行分工。我们分别完成各自分配的模块,中途遇到的问题一起讨论查资料解决,最后我们将代码合并起来。
2、结对照片:
十、解决项目的心路历程与收获,以及结对感受
1、由于编程不熟悉,以及在实际的开发过程中遇到种种问题的阻挠,使得完成这个项目实际花费的时间远远大于预估计划的时间,并且一些功能也没有完善的很好,我感受到以后的学习还有很长的路需要去努力。
2、结对感受:第一次结对完成一个项目,我的感受是1+1>2。因为在编程中,两个人除了独立完成自己那部分的功能任务,还能够在互审阶段会更容易发现对方自己不易察觉的问题,不仅如此,两人合作会使遇到的困难更容易被解决,学习中得到的进步也更大。而在这一次结对的实际操作中,让我对结对编程的理解更加深入了。