一、Fork仓库的Github项目地址和伙伴的地址
Github项目地址:https://github.com/WHYNOTEN/WordCount.git
合作同学作业地址:https://www.cnblogs.com/SW-P-WY/p/10657136.html
合作人:wnagyang、zhourong
二、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 |
绘制PSP表格总结:以前编写项目的时候并未有设计和预算的过程,个人感觉编写表格和事先设计好进程,更有利于完成项目。因为在完成的过程中对自己进行到那个地方心里有个数。
三.计算接口模块的设计与实现过程
1.需求分析(由两人共同商议)
基础功能:
1.统计文件字符数(统计字符这一块,对提取文本中字符问题,出现了乱码,后来商讨了下解决不同文本转码的问题)
2.统计单词总数(单词必须四个英文字母开头,后面可以为数字,不区分大小写)
3.统计文件中有效行数
4.统计文件中单词单词总数,按频率排序,对于相同频率单词按字典顺序排列
5.按照字典顺序输出到文件中
6.调用程序时,通过命令行传入文件读取和写入路径
新功能:
1.输出前n个频率最高的单词(直接新增加一个列表保存输出的前n个单词)
2.指定一个词组长度,输出词组及其出现频率
3.多参数混合使用(封装)
实现所有需求思路:
- 需求主要是对于文本中内容的提取,这里就要用到正则表达,之前没有涉及到过就看了下群里同学分享的链接。(用到正则的部分是伙伴编写的,个人不是很会使用正则)
- 对于行数提取则可以通过读取文件(按行提取内容)时,判断提取内容是否为null来解决。
- 统计频率可以通过字典这种Key,Value这种存储方式来进行统计,排序则通过字典排序实现。
- 对于命令行传入参数可以通过Main(string[] args)中对args进行解析来实现。
- 新功能输出前n个频率最高可以直接输出字典内容即可。
- 伙伴询问助教老师后确定同样可以利用字典统计词组并输出(-m的功能是和伙伴“吵了一架的”,因为他觉得构架和基础功能无异,由我编写)
- 第三个功能主要是对于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() { //建立临时字典保存单词以及出现次数 Dictionary<string, int> words = new Dictionary<string, int>(); 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()(PS:之前和伙伴在-m新增功能中有争议,他说会影响性能,硬是要删除,被本人留了下来,后来代码复审的过程中我发现,确实功能和基础功能用法无异)
//将内容写入文件中 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(KeyValuePair<string,int> kvp in this.words_sort) { sw.WriteLine("{0,-10}:{1,-3}", kvp.Key, kvp.Value); } //关闭文件 sw.Close(); Console.WriteLine("结果文件保存于:{0}", filepath); }
分析代码发现在ToFile()函数中进行了当前文件路径读取,读取文件,并多次写入文件(特别是遍历字典同时进行写入文件),由此可见循环写入文件是导致性能变差的原因。改进方法是将需要写入的内容保存至一个临时字符串中,遍历字典后再对整个字符串进行写入文件操作。ExtractChar()函数使用了正则进行匹配,由于对Regex类具体实现不清楚,暂时无法进行性能优化。(PS:在合作过程中,伙伴普及很多知识于本人,正则很耗费内存,还有封装,本人在重新加入-m功能的时候被伙伴吐槽,代码复用太多,难以封装)
六、单元测试
百度了下静态测试,前一次作业单元测试是无效的,通过伙伴的知识普及,了解到一个有效的单元测试是要有断言的。(PS:伙伴将封装后的代码文件发送给我,然后在自己电脑上测试并不能通过,因为电脑配置问题,还在其他同学的帮助下百度了问题。修改了配置文件,还是不行,最后用伙伴的电脑进行的测试)
七、异常处理
异常处理主要针对命令行传入参数的判定:
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表格,进行编程。其实本人很清楚,此次编程中,伙伴花费了很多精力,最然本人也在参与,但是编程能力远远不如他,有趣的是在第一次商讨的时候我们在一起编写基础功能,然后他永远比本人快。(附上基础功能完成后,一起改进的照片)
十、总结
本来和伙伴的编程能力就相差甚远,在这次编程中,个人是羞愧的,觉得1+1<2,没有自己的参与伙伴也许会更快的完成项目,在这次过程中于本人而言,1+1>2,因为伙伴普及了很多知识,包容我的过失等。为大学前两年没有好好学习感到懊悔,为自己拖后退感到抱歉,经过此次,我找到了好好学习的另一个理由,没有人为你的过失买单,想要好好的与人合作,更要提升自己,加油!期待下次和他合作的自己更好!