第三次
WordCount
1.Github项目地址:https://github.com/dckejds/WordCount
结对代码地址:https://www.cnblogs.com/tianhaoqi/p/10638696.html
2.结对过程:
我们的项目于4月1日正式开始,我们首先对如何完成项目以及对作业要求的分析等进行了商定和讨论,在综合考虑了上课的时间以及项目外的因素,约定了各自的分工以及进行结对编程的时间和地点。最后一起完成了PSP表格的填写。照片如下:
3.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 35 | 40 |
Estimate | 估计这个任务需要多少时间 | 1770 | 1950 |
Development | 开发 | 1620 | 1780 |
Analysis | 需求分析 (包括学习新技术) | 50 | 60 |
Design Spec | 生成设计文档 | 45 | 35 |
Design Review | 设计复审 (和同事审核设计文档) | 30 | 35 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 25 | 30 |
Design | 具体设计 | 70 | 80 |
Coding | 具体编码 | 1200 | 1320 |
Code Review | 代码复审 | 140 | 150 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 70 |
Reporting | 报告 | 150 | 170 |
Test Report | 测试报告 | 60 | 70 |
Size Measurement | 计算工作量 | 60 | 50 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 50 |
合计 | 1805 |
1990 |
4.解题思路描述:
在拿到题目的一开始,本人一头雾水,不知道应该如何下手,因为对c#文件操作的不熟悉,在编写代码前查阅了大量资料,并且对cmd命令行操作进行了研究。
对字符数,行数及单词总数的统计,使用循环和ReadLine结合,逐行读取文本文档操作,这样能更为精确的进行统计。考虑到统计字符数后面的要求只统计AscII码,故在逐行读取文本文件内容时,应将内容利用StreamReader(path, Encoding.ASCII)将其中的每个字符转化为其相应的AscII值。
对单词的判断,使用正则表达式匹配法,精确匹配符合要求的单词,为此,上网查询了许多与正则表达式有关的资料,其中这篇资料对这次编程提供了很大的帮助,链接:https://www.cnblogs.com/hehehehehe/p/6043710.html
之后就是对词组的判断了,选择利用双重循环来判断词组,最后利用排序等方法输出指定长度的词组及其词频,并按照字典序排序。
5.设计实现过程:
由于编程经验欠缺,这次设计参考了网络上的许多代码,并且因为搭档水平也不够,所以这次设计大多是由本人进行设计。搭档完成的部分为统计字符数,单词总数,有效行数和对单词进行判断。这部分代码和设计在搭档博客中。
之后,在将两人的代码合并后,将判断单词的方法IsWords(),统计字符的方法CountChar(),统计单词总数的方法CountSumofWords()和统计词频的方法CountWord()这几个方法封装在了一个类Words中,以便能将这些功能作为一个独立的模块运行以满足不同的需求,并将其在WordCount中进行调用。这就是“Information Hiding”原则和“Loose Coupling“原则的体现。在主函数中,通过switchcase语句,增强程序的人机交互性能和接口的稳定性,这体现的是“Interface Design”原则。
在实现输出词频和输出词组词频的方法前,判断单词的方法IsWords()在这里是很重要,在这两个方法中都需要对这一方法进行引用。
本人设计的是输出文本文件中频率前n位的单词及其词频输出和用户给定长度词组词频按字典序输出的方法,这两个算法的独到之处就是利用linq语句进行排序,比起一般的冒泡排序等排序方法效率更高。而且由于利用了c#中的字典操作,在排序前将单词存入字典中,查找速度更快。
设计输出词频的方法,首先需要创建从单词到频率的新映射,使用dictionary<string,int>创建从string到int的空白映射,将用它统计每个单词在一段给定文本中的频率,然后,利用IsWords方法找出文本中的所有单词,将这些单词一同存入一个链表当中,最后遍历链表,对每个单词都检查它是否已经在映射中。如果是,就增加现有计数,否则,就为单词赋予一个初始计数1 ,负责递增的代码不需要执行到 int的强制类型转换,就可以执行加法运算:取回的值在编译时是int类型。统计完后输出各个单词的词频,使用linq语句进行排序,输出按照字典序排序的单词词频。
设计输出词组频率的方法与输出词频的方法类似,只需要增加判断词组的语句即可,先通过传入参数确定词组的长度,同样对文本文件中每行进行遍历操作,将长度符合要求的词组组成字符串,之后进行与输出词频方法类似的循环遍历每行,计算出各个长度符合要求的词组的词频即可。
6.代码互审细节:
6.1代码规范
代码规范参考:http://www.cnblogs.com/wulinfeng/archive/2012/08/31/2664720.html
1、大括号需令起一行,注释要和代码对齐
2、 If、switch 代码段都需要使用{}括起,即使只有一句代码
3、 字符串的比较需要使用 Equals,不能直接使用“==”
4、内部使用的全局字段,需要以 g_打头,后面第一个单词字母大写,即后面按照函数名的命名方式进行
5、单个函数的代码行数不能超过 100 行(从函数定义开始到结束的大括弧)。
6、函数缩进等级不能超过 5 级。
7、明确方法功能,精确(而不是近似)地实现方法设计。
8、如果一个功能将在多处实现,即使只有两行代码,也应该编写方法实现。
9、IO操作等需要使用结束close()的对象必须在try -catch-finally 的finally中close()
10、避免使用不易理解的数字,用有意义的标识来替代。
11、代码缩进用tab。方法、属性的命名使用Pascal大小写形式,一般将其命名为动宾短语.如
ShowDialog()CreateFile()变量、参数使用Camel 大小写形式。例:int totalCount
6.2代码互审的情况
在代码互审中,我的搭档帮助我解决了代码中的错误,从而顺利的完成了代码的合并以及运行,并且修改了部分繁复和无用的代码,使得我们的合并代码更加简洁和高效。
7.程序改进
7.1在改进程序性能上所花费的时间:大约四小时左右,其中主要花费在对两人代码的同意以及规范的处理,同时对代码中出现的繁杂以及不必要的代码进行了删除和修改,增加了代码的可读性以及高效性。
7.2性能分析图(在另一个人的博客中)
8.代码说明(改进记录以及单元测试在另一个人的博客中)
/// <summary> /// 判断是否为单词 /// </summary> /// <param name="word"></param> /// <returns></returns> public bool IsWords(string word) { char[] ch = word.ToCharArray();//将单词转换成字符数组 if (ch.Length < 4) { return false; } else { for (int i = 0; i < word.Length; i++) { if (i < 4) { //如果前四个字符不为字母,则其不是单词 if (!((ch[i] >= 'a' && ch[i] <= 'z') || (ch[i] >= 'A' && ch[i] <= 'Z'))) { return false; } } else { //若从第五个字符开始出现非字母,则其不是单词 if (!((ch[i] >= 'a' && ch[i] <= 'z') || (ch[i] >= 'A' && ch[i] <= 'Z'))) { if (i == word.Length - 1) { if (ch[i] == ',' || ch[i] == '.' || ch[i] == '!')//若最后一个字符为","、"."、"!"前面也应是单词 { return true; } } return false; } } } return true; } } /// <summary> /// 统计文本文件中单词总数并将其写入文件 /// </summary> /// <returns></returns> public int CountSumofWords() { reader.BaseStream.Seek(0, SeekOrigin.Begin);//将文件重新读取 string temp;//临时变量 int words=0; while ((temp = reader.ReadLine()) != null) { string[] wordstemp = temp.Split(' ');//将每一行拆分成字符串数组 for (int i = 0; i < wordstemp.Length; i++) { if (IsWords(wordstemp[i])) { words++; } } } //writeFile.WriteLine("words: " + words); //writeFile.Flush();//写入文件 return words; } /// <summary> /// 统计文本文件中的字符数并将其写入文件 /// </summary> /// <returns></returns> public int CountChar() { string Line = reader.ReadLine(); int chars = Line.Length; //writeFile.WriteLine("characters: " + chars); //writeFile.Flush();//写入文件 return chars; } /// <summary> /// 统计有效行数并将结果输出到文件中 /// </summary> /// <returns></returns> public int CountEffectiveLine() { reader.BaseStream.Seek(0, SeekOrigin.Begin); Stopwatch sw = new Stopwatch(); int lines = 0; sw.Restart(); using (var sr = this.reader) { var ls = ""; while ((ls = sr.ReadLine()) != null) { if (ls.Length != 0) { lines++; } } } sw.Stop(); //writeFile.WriteLine("lines: " + lines); //writeFile.Flush();//写入文件 return lines; }
9.心路历程、收获以及结对感受
在结对编程整体完成后,我们发现预估时间和实际耗时有一定的差距,主要表现在开发阶段两个人的习惯以及思路的不同上,由于我们在代码自审和互审阶段都发现了编码的不规范,所以相比编写代码而言花了更多的时间去修改代码。在编码的过程中我们发现我们要完成的内容比我们想象得要困难,有些部分也从一个小的类改为更为复杂的程序,所以在编码的时候花了比预期更多的时间。在最后测试中,由于我们之间采用的方式不同而导致花费更多的时间。在这次的结对编程中,我的许多小的不良习惯都得到了改正,同时我的搭档也给予了我相当多的帮助来完成这次任务,也使我学到了许多在独立编程时很少注意到的问题的解决方法,总的来说这次的结对编程让我的收获非常大!