Github项目地址:
合作同学作业地址:
一.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 890 | 1435 |
· Estimate | · 估计这个任务需要多少时间 | 890 | 1435 |
Development | 开发 | 770 | 1270 |
· Analysis | · 需求分析 (包括学习新技术) | 40 | 50 |
· Design Spec | · 生成设计文档 | 30 | 35 |
· Design Review | · 设计复审 (和同事审核设计文档) | 50 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 15 |
· Design | · 具体设计 | 30 | 50 |
· Coding | · 具体编码 | 420 | 900 |
· Code Review | · 代码复审 | 60 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 120 | 165 |
· Test Report | · 测试报告 | 60 | 80 |
· Size Measurement | · 计算工作量 | 30 | 60 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 25 |
合计 | 900 | 1435 |
二.计算接口模块的设计与实现过程
1.需求分析(由两人共同商议)
基础功能:
1.统计文件字符数(包括字母/数字/特殊符号/空白符)
2.统计单词总数(单词必须四个英文字母开头,后面可以为数字,不区分大小写)
3.统计文件中有效行数
4.统计文件中单词单词总数,按频率排序,对于相同频率单词按字典顺序排列
5.按照字典顺序输出到文件中
6.调用程序时,通过命令行传入文件读取和写入路径
新功能:
1.输出前n个频率最高的单词
2.指定一个词组长度,输出词组及其出现频率
3.多参数混合使用
实现所有需求思路:
- 需求主要是对于文本中内容的提取,由于之前学习过爬虫,自然想到了利用正则表达式对内容提取,但是由于C#和Python还是存在一些区别同时对于正则的支持不太一样,所以需要学习一下C#中对正则表达式的使用。
- 对于行数提取则可以通过读取文件(按行提取内容)时,判断提取内容是否为null来解决。
- 统计频率可以通过字典这种Key,Value这种存储方式来进行统计,排序则通过字典排序实现。
- 对于命令行传入参数可以通过Main(string[] args)中对args进行解析来实现。
- 新功能输出前n个频率最高可以直接输出字典内容即可。
- 对于第二个新功能最初不是很理解,最后询问助教老师后确定同样可以利用字典统计词组并输出
- 第三个功能主要是对于Main方法args参数的解析,可以利用字典保存参数及其内容
最终确定主要写两个.cs文件,其中一个对功能实现,另外一个进行功能类调用,这样测试时即只需要对功能类函数进行测试即可,基本功能由笔者实现,对于新功能有合作者实现,最终整合在功能中:
Function.cs:主要实现对文本内容的提取筛选实现需求功能;
Program.cs:对命令行参数进行分析(GetMand()),调用Function.cs对文本内容分析,实现功能(Main()).
算法关键在于正则表达式的书写以及将内容写入字典整合:
文件读取代码:
//读取文件中的字符数目并保存文件内容 public void GetChar() { //打开文件 FileInfo file = new FileInfo(this.path); //定义读取文件对象 StreamReader sw = file.OpenText(); //按行进行读取,不为空行记录行数并保存内容,返回null打断循环 while (true) { //对文件读取内容进行判断,如果不为空用变量接收,行数加一 string temp = sw.ReadLine(); if (temp != null) { account++; this.content += temp; } //为空则停止 else { break; } } }
正则匹配函数:
public void ExtractChar() { //利用正则进行匹配,以字母开头,可以数字结尾 MatchCollection rel = Regex.Matches(this.content, @"([a-zA-Z]{4}\w*)"); for (int i = 0; i < rel.Count; i++) { //匹配到的单词进入列表 this.result.Add(Convert.ToString(rel[i])); } }
字典写入代码:
//利用字典统计单词出现次数 public void Statistical() { //建立临时字典保存单词以及出现次数 Dictionarywords = new Dictionary (); for (int i = 0; i < this.result.Count; i++) { if (words.ContainsKey(this.result[i])) { words[this.result[i]]++; } else { words[this.result[i]] = 1; } } //对字典内容排序,并赋值给类变量 this.words_sort = words.OrderByDescending(p => p.Value).ToDictionary(p => p.Key, o => o.Value); //清空临时字典内容 words.Clear(); }
三.代码复审
代码规范:
各自完成代码交换代码,对代码内容进行查看,基本均按照代码规范进行编码,对于不懂部分进行了增添注释,使代码得到可读性提高。同时对命名部分进行了详细检查,修改了部分变量名。
四.性能改进
从性能分析图看出Program.Main()与Function.ToFile()分配最多,但由于这两个函数都是调用了其余函数,所以减去其余调用函数分配,得出Function.ToFile()分配最多,为786,其次是Function.ExtractChar()
//将内容写入文件中 public void ToFile(string path) { //运行先行函数,将需要的文件内容保存至变量中 this.GetChar(); this.ExtractChar(); this.ToLow(); this.Statistical(); //获取当前文件路径 string filepath = Directory.GetCurrentDirectory(); //定义文件输出路径 filepath += path; FileInfo file = new FileInfo(@filepath); StreamWriter sw = file.AppendText(); sw.WriteLine("characters: {0}", this.CharNum()); sw.WriteLine("words: {0}", this.WordsNum()); sw.WriteLine("lines: {0}", this.account); //遍历字典将内容写入文件 foreach(KeyValuePairkvp in this.words_sort) { sw.WriteLine("{0,-10}:{1,-3}", kvp.Key, kvp.Value); } //关闭文件 sw.Close(); Console.WriteLine("结果文件保存于:{0}", filepath); }
分析代码发现在ToFile()函数中进行了当前文件路径读取,读取文件,并多次写入文件(特别是遍历字典同时进行写入文件),由此可见循环写入文件是导致性能变差的原因。改进方法是将需要写入的内容保存至一个临时字符串中,遍历字典后再对整个字符串进行写入文件操作。ExtractChar()函数使用了正则进行匹配,由于对Regex类具体实现不清楚,暂时无法进行性能优化。
五.单元测试
由于项目中函数依赖关系,故只需要对部分函数进行单元测试即可,即对CharNum()和WordsNum()两个函数查看返回结果是否正确即可,若返回字符数与单词数均正确,则单元测试通过。但是有一个缺陷那就是对于命令行输入内容的测试,没有找到方法对于.exe运行时接收的外部参数进行测试判断.同时部分函数是将内容写入文件中,没有找到合适的途径进行测试。另外由于VS2017社区版本无自动输出代码覆盖率功能,无法判断代码覆盖率是否符合要求。下面是部分测试代码:
六.异常处理
异常处理主要针对命令行传入参数的判定:
1.文件传入路径
//对得到内容进行处理 - i参数确定文件是否存在 try { StreamReader sw = new StreamReader(command["-i"]); } catch { Console.WriteLine("需处理的文件不存在!请检查路径重新运行!"); }
2.-m 与-n 参数内容是否为整数
//对-m -n 参数内容进行强制转换看是否为整数if (command.ContainsKey("-n") && command.ContainsKey("-m")) { try { int nums = Convert.ToInt32(command["-n"]); int length = Convert.ToInt32(command["-m"]); } catch { Console.WriteLine("参数输入有误,请重新运行!"); } } else if (command.ContainsKey("-m") && !command.ContainsKey("-n")) { try { int length = Convert.ToInt32(command["-m"]); } catch { Console.WriteLine("参数输入有误,请重新运行!"); } } else if (!command.ContainsKey("-m") && command.ContainsKey("-n")) { try { int length = Convert.ToInt32(command["-n"]); } catch { Console.WriteLine("参数输入有误,请重新运行!"); } } else { }
七.结对过程
在结对之后,选定了两方都有空的时间进行讨论,根据PSP表格预估时间,讨论出项目需求,代码设计,根据各自水平进行分工,完成代码先进性自省,然后交换代码进行复审,最后汇总生成.exe文件进行实例测试,单元测试并不断提交进度,最后撰写博客。
八.总结
这是第一次与人进行真正意义上的结对编程,最大的感觉就是比单独一个人编程轻松许多,同时更容易发现问题所在,在代码注释方面也明显比单独编程时多了许多,代码可读性更高,变量命名更加规范。同时也有一些小问题,两个人的想法会有不同,对于同一个问题的理解有一些差别,而这个意见统一过程花费的时间显然更多。但总的来说,这次的结对编程收获颇丰。