《构建之法》——第四次个人作业
GitHub项目地址 | 这里 |
---|---|
合作同学作业地址 | 这里 |
一、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 1510 | 1925 |
Development | 开发 | 990 | 1325 |
Analysis | 需求分析 (包括学习新技术) | 90 | 100 |
Design Spec | 生成设计文档 | 60 | 60 |
Design Review | 设计复审 (和同事审核设计文档) | 20 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 20 |
Design | 具体设计 | 30 | 25 |
Coding | 具体编码 | 900 | 1200 |
Code Review | 代码复审 | 60 | 100 |
Test | 测试(自我测试,修改代码,提交修改) | 240 | 300 |
Reporting | 报告 | 30 | 25 |
Test Report | 测试报告 | 10 | 5 |
Size Measurement | 计算工作量 | 30 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 40 |
合计 | 1510 | 1925 |
二、计算模块接口的设计与实现过程
1.项目实现的基本思路
1)首先我们需要读取指定的文件,并且将文本文件中的内容提出出来,进行操作。这里使用C#的IO流来进行文件操作。
2)根据要求以及难易程度,我们首先解决文本的行数以及字符数的统计。这里使用正则表达式进行处理。
3)对于单词的统计我们在完成行数和字符数统计之后,决定使用集合来处理单词,但是发现单词是能够处理了,但是得不到单词出现的次数,所以我们转而使用字典集(Dictionary)来进行处理,字典集为我们提供了很多方便的功能。
先用ArrayList集合存储所有单词,包括重复的单词,都存进去,但是是按照要求存储单词,也就是说必须四个英文字母开头的单词我们才存储,这里就用到正则表达式来解决。
将存储好的单词经过遍历放进字典集,这样我们就得到了符合要求的所有单词的一个字典集,并且也得到了它们出现的次数,然后在对它们进行排序,就可以得到最终符合要求的前10个单词了。
4)基础功能实现后,又开始实现新添加的功能,命令行操作以及输出指定长度的词组。
由于使用条件判断语句不能够很好的满足命令行操作的要求,我们经过查找资料,发现了可以使用一个第三方的工具包来帮助我们。这里@命令行解析,通过这个工具包,我们实现了命令行操作。
对于指定长度的词组,实现方式和单词的存储大致相同。
类图
整体程序流程图如下图所示
2.部分代码以及代码规范
-
通过正则表达式存储符合要求单词
/* * 按要求存储可用单词 */ public ArrayList Splitwords(string text) { ArrayList al = new ArrayList(); MatchCollection matchs = Regex.Matches(text, @"\b[a-zA-Z]{4,}\w*"); foreach (Match match in matchs) { al.Add(match.Value); } return al; } public ArrayList Splitlenth(int lenth, string text) { string b = lenth.ToString(); string pattern = "\\b\\w{"+b+"}\\s"; ArrayList al = new ArrayList(); MatchCollection matchs = Regex.Matches(text, pattern); foreach (Match match in matchs) { al.Add(match.Value); } return al; }
-
统计每个单词出现的次数
/* * 统计每个单词出现的次数 */ public Dictionary<string, int> countWords(ArrayList arrayList) { Dictionary<string, int> nary = new Dictionary<string, int>(); foreach (string word in arrayList) { if (nary.ContainsKey(word)) { nary[word]++; } else { nary.Add(word, 1); } } return nary; }
-
按值排序
/* * 按值排序 */ public Dictionary<string, int> sort(Dictionary<string, int> nary) { var result = from pair in nary orderby pair.Value descending, pair.Key ascending select pair; Dictionary<string, int> bronary = new Dictionary<string, int>(); foreach (KeyValuePair<string, int> pair in result) { bronary.Add(pair.Key, pair.Value); } return bronary; }
-
指定输出词组长度
/* * 指定词组长度 */ public Dictionary<string,int> msort(ArrayList al,int size) { Dictionary<string, int> nary = new Dictionary<string, int>(); ArrayList bl = new ArrayList(); int i = 0; while(i<=al.Count-size) { string str = null; var result = al.GetRange(i, size); foreach (var n in result) { str += n.ToString()+" "; } bl.Add(str); i++; } foreach (string word in bl) { if (nary.ContainsKey(word)) { nary[word]++; } else { nary.Add(word, 1); } } return nary; }
-
代码规范
代码规范是程序员的一种编程习惯,良好的编程习惯,不仅能自然地产生几乎没有bug的代码,而且在代码交接时,也方便继任者的阅读.这是我们的代码规范
3.接口设计以及封装
1)接口设计:将三个基本功能,分为三个类,降低耦合度,便于进行单元测试,以及代码的修改,这样就可以在修改其中一个功能的时候,不影响其他功能。但是也并没有把所有的功能都分离开,统计单词里面就有很多个功能,类多了会增加整个程序的繁琐程度,也会降低代码的可读性。
2)功能封装:按照要求将三个基本功能进行封装,封装成一个ClassLibrary,并且让它生成dll文件,这样程序就可以直接引用封装好的功能,更能减少代码量,降低耦合度。
在我们的Winform程序当中,我们引用了封装好的统计功能,减少了重复代码量。
接口的封装让我们的功能的实现更加的隐蔽,用户在引用我们的dll时,不会看见我们的代码。
三、代码复审
代码复审过程是一个相互学习的过程,这次复审了项目的每个模块,对代码的写法进行优化,这里只举了一个比较严重的问题。
索引超界问题
在实现输出指定长度的词组功能时,发生了“偏移量和长度超出数组界限”的问题。
在查看ArrayList相关资料时,发现了Capacity与count的区别,我们所使用的的capacity是ArrayList可以包含的元素个数,而count是ArrayList实际包含的元素个数,这就导致了越界问题。
将capacity换为count
四、模块接口性能改进
在完成所有要求之后,对代码进行效能分析如下。
-
对效能分析结果发现characSum函数占用较多CPU,修改前后效能分析对比如下
-
发现的问题以及解决措施
内存溢出问题
在进行小文本测试时,代码可以正常实现预定功能,并输出;当我们使用一个大小为28MB的txt文本时,经过进2分钟的运行,抛出内存溢出错误。
在多方查找资料以及严密分析之后,发现了我们代码中存在的问题。在characSum函数中,对大量数据进行处理时,需要连续不断的实例化非常多个对象,并且它们一直会在缓存区,直到函数执行完毕才会被释放,这样就会导致内存一直被挤压,最后就会导致内存不够用,进而抛出内存溢出的错误。
在了解了问题触发的机制后,在处理数据时,我们不在使用实例化对象的方式,而是通过统计字符串的整个长度来得到字符数的大小,并且经过实验,达到了预期目的。
五、单元测试
- 测试规则
测试三个基本功能。
对于行数以及字符数测试,我们进行简单的数据构造,测试数据要能够靠人工数出来。
对于单词统计测试,我们同样进行简单的数据构造。
1)大小写同时出现。
2)数据中要有file123和123file这类词。
3)数据中心要有小于4个字符的词。
- 测试截图如下(本次测试共进行10组测试,由于本机安装社区版VS2017,不能查看测试代码覆盖率)
六、模块异常处理
- 命令行输入异常
命令行输入不符合要求的话,会提示出错,需要重新输入命令行。
- 内存溢出异常
根据代码效能分析的结果,设置内存溢出异常处理。
七、结对过程
这是结对过程,共同完成了代码的编写以及博客共同部分的编写。
结对照片:
用户交互界面绘制
- 导入单词文本(-i命令行)
- 直接输入单词
- 自定义词组长度(-m)
- 自定义输出高频单词数量(-n)
- 导出统计结果
总结
结对感受:第一次进行结对编程,只能说感受良多,同时也用亲身的体验回答了自己对第一篇个人博客里面提出的疑问
现在我的回答是两个人坐在一起,确实会有人力资源浪费的情况,也会存在着偷闲的借口。再说效率问题,对于这次作业,如果一个人会比两个人快吗,我觉得会快,因为一个人写的话效率会高很多,两个人的话,有适应过程,有协商的过程,这个过程会存在什么,偷闲的借口。那么结对编程的优势体现在哪里呢?那就是互相学习,从对方身上看到优点,见贤思齐焉,见不贤而内自省也,还有就是两个人看一个东西,实现的方式会更多一些,思路更多一些。1+1等于多少?1+1等于1,两个人融为一体,步调一致,才能够完美发挥结对编程的目的。
队友辛苦了。