一、作业地址
Github项目地址:https://github.com/gentlemanzq/WordCount.git(用的是同伴的)
作业链接:https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/homework/2882
结对同伴博客地址:https://www.cnblogs.com/gentlemanzq/
我的博客地址:https://www.cnblogs.com/Ysml/
二、结对过程描述
2.1 总体情况分析
首先,根据题目要求,将代码分解为几个模块,确立好每个模块的具体函数内容。其次,根据个人的编程能力,将不同模块分配给
相应的人 进行完成。分配任务后,两人一起完成,其中一人完成自己相应模块的编程,另外一个便审查代码,指出错误之处或者不同
的思路与见解,最后两人相互讨论,选取最优的编程方法。
2.2 存在的问题
由于两人的编程水平存在差异,有时存在无法理解同伴编写代码的具体意思,又因为思路的不同,存在代码具体位置的争论。
2.3 解决方法
对存在的问题,单独拉出来,由编程的人对不理解的人进行讲解,其次对相应调用函数的不认,通过百度进行了解认识。
附结对照片:
三、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 30 | 45 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 60 |
Development | 开发 | 45 | 60 |
· Analysis | · 需求分析 (包括学习新技术) | 10 | 15 |
· Design Spec | · 生成设计文档 | 5 | 8 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
· Design | · 具体设计 | 20 | 30 |
Coding | · 具体编码 | 90 | 120 |
· Code Review | · 代码复审 | 45 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 80 |
Reporting | 报告 | 100 | 120 |
· Test Report | · 测试报告 | 45 | 50 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 15 | 10 |
合计 | 560 | 693 |
四、解题思路
拿到题目后,先仔细阅读题目,我们根据题目要求,构建了一个大概的框架,将题目要求分解成不同的模块。然后根据模块的内容
找出学过相对应的代码内容。随后思考代码的可行性,是否存在漏洞。遇到不会编写的代码,上网查找资料,图书馆借阅书籍查找相关问题。
五、设计实现过程
通过审读题目要求,可以将整个程序分为如下部分:
1.统计文件字符数函数
2.统计有效行数函数
3.统计文件中各单词的出现次数函数,并只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。
4.将程序输出结果写入txt文件函数 (几个函数相互独立)
(当对代码进行封装时,就可以将以上每个函数封装成类,需要用时,可以直接调用。)
统计文件中各单词的出现次数函数,并只输出频率最高的10个。频率相同的单词,优先输出字典序靠前的单词。为关键函数,流程图如下:
(算法关键、独特之处:提取单词的时候,一并统计了频率。并且在输出排序时,调用Orderby算法,可以直接按频率和字典序输出结果)
PS:增加客服需求新功能时,只需将一些变量变成由客户输入即可。
六、代码规范与互审
代码规范:
2、不变的值,尽量写个常量类。
3、尽量使用if{}else,不要一直if去判断。
4、减少循环调用方法;减少IO流的消耗资源。
5. 当一行代码太长时,将其截断成两行写。
6. 常用缩进和换行,使代码层次清晰,明了。
7. 注释的量不应该少于代码量的三分之一。ps(变量统一使用例如/// <param name="s">文件读入路径</param>的注释方式)
8. 定义变量名字和方法名字的时候尽量使用英文缩写,或者拼音缩写,便于识别。
9. 对泛型进行循环时,都采用foreach而不使用for。
11. 对于功能函数写入一个function文件夹中,便于以后功能升级。
12. 一屏原则:一个方法体的代码幅应该在一屏比较和合理;逻辑复杂的代码可以抽离出方法体;
(这只是一部分,当然还有很多,就不一一列举了)
代码互审:
在编写统计字符个数时,最初代码如下:
public static int agefile()//打开文件并统计字符个数 { string fileName = @"D:\新建文本文档 (3).txt"; FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); byte[] buf = new byte[fs.Length]; fs.Read(buf, 0, buf.Length); fs.Close(); return buf.Length; }
经过代码复审后,发现打开文件和统计字符个数时过于繁杂,便有了以下代码:这样一来,代码精简很多,占用空间更少。
public static int agefile()//打开文件并统计字符个数 { string str = File.ReadAllText(@"C:\Users\LiuQi\Desktop\新建本文档案(3).txt"); int num = Regex.Matches(str, @".").Count; return num + lines() - 1; }
其次,在统计单词出现频率和按要求输出时时,最开始傻傻的认为按需求一步步编写代码就能解决。但是实际操作起来却
发现存在很多问题,许多简单情况下能够操作,在复杂情况下,存在很多很多的限制,不能轻易解决。
例如:在按字典表输出时,按原思路就是按顺序判断然后输出,做到一半都发现太麻烦了,就去百度了一下有没有简单算法,
便发现C#自带字典数排序,只需调用即可。
七、模块接口部分的性能改进
1、改进计算模块性能上所花费的时间:45分钟
2.、改进思路:将统计行数,字符数的函数封装成一个类,到时候可以根据用户需求只需改变参数即可。
3、耗时最多的函数:(运用字典数统计字符总数和其出现的频率)
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; }
八、单元测试与部分异常处理
在没有对函数进行封装时,我们对各自对对方写的代码进行了单元测试
1、首先针对逻辑上第一个调用的计算行数的功能模块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行时,测试失败。此处出现问题,当没有输入文本时,行数没有进行判断,所以出现错误。
2.测试asccount类(统计有多少个字符)封装的类如下:
public class asccountTests { [TestMethod()] public void asccountsTest() { path.s = @"D:\se.txt"; int num = 8; Assert.AreEqual(num, asccount.asccounts()); //Assert.Fail(); } }
测试结果:当文本不输入字符时,num=0,出现错误。思考后,发现是没有判断为0的情况,出现错误。
3.其余类经过测试并未发生什么问题。
4.功能改进后的主函数:
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(); }
九、心路历程与收获
首先这次的作业由于已经有了上次Github的操作经验,再一次使用时,觉得不在陌生,基础操作都能够完成。在说说代码,初一看感觉并不是很难,但是在
实际操作过程中,却是发现存在各种各样的小问题。(结对编程时,两人对换行符所占字符数发生了奇异,改来改去,发现最开始的代码是对的,而重新写的
代码也是对的,白白的纠结了很多时间。)有些小问题是编写代码时发生的,有些却是知识点漏区产生的。随后就在这些小问题中,浪费了打把的时间,但是
个人看来,效率还是远远大于单人操作。编写代码时,同伴会在一旁发表自己的见解,也会帮忙指出忽略的错误之处,大大提高了效率,因此我觉得1+1>2。