小组github项目地址:https://github.com/Wegnery/New_WordCount
基础功能:
一.PSP表格
PSP2.1 |
PSP阶段 |
预估耗时 (分钟) |
实际耗时 (分钟) |
Planning |
计划 |
||
· Estimate |
· 估计这个任务需要多少时间 |
30 | 25 |
Development |
开发 |
||
· Analysis |
· 需求分析 (包括学习新技术) |
10 | 5 |
· Design Spec |
· 生成设计文档 |
20 | 25 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
15 | 15 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
5 | 8 |
· Design |
· 具体设计 |
10 | 10 |
· Coding |
· 具体编码 |
30 | 15 |
· Code Review |
· 代码复审 |
15 | 10 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
20 | 10 |
Reporting |
报告 |
||
· Test Report |
· 测试报告 |
15 | 10 |
· Size Measurement |
· 计算工作量 |
5 | 5 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
10 | 10 |
合计 |
185 | 148 |
二.代码实现
在小组讨论过程中,我们根据老师的建议,将代码分成四个模块,分别是输入控制:读入文件并正确识别单词,核心处理:统计单词词频,输出控制:对统计好词频的单词进行排序并输出到文件里,其他:写main函数并整体架构。首先我对整体进行了搭建,并进行了分工:
周雨贝:input.class
输入模块实现功能:1.识别.txt文件,读入 2.判定合法输入与不合法输入
输出:一个Vector,包含文件中的所有单词。
宁宁:calc.class
实现统计功能:1.对周雨贝传下来的Vector进行统计词频,并用map存储
输出:map类型,map<string,int> string对应的是单词,int对应的是次数。
朱全:output.class
实现输出功能:1.对宁宁传下来的map类型的数据进行排序,就是按map里的int进行整体排序。
2.将统计好的数组进行输出,输出到一个txt文件中。
输出:无输出,string和int写到.txt文件里即可。
易成龙:构架整体以及main函数
我负责的是核心处理模块,因为之前使用过HashMap,所以我一开始将参数类型定义成了HashMap,后来看到词频相同的单词需要根据首字母进行排序,又将HashMap改成了TreeMap。具体思路如下:
代码内容中注释很详尽,都是按照上面的流程图来设计的,因为我们小组将功能分为好几个类,所以编写类的时候需要自己测试是否通过,所以代码中多了注释掉的main和print函数,在此只列举核心函数account
public static TreeMap<String , Integer>account(Vector<String> strs) { //产生一个容器 TreeMap<String , Integer>data=new TreeMap<String ,Integer>(); for (int i = 0; i < strs.size(); i++) {//通过下标来取出字符串中的一个个元素 String str=strs.get(i);//先把第一个字符串取出来 //判断一下hashmap中的key有没有,如果没有则收录 hashmap中 if (data.get(str)==null) {//第一次统计到字符串 data.put(str, 1);//把这个字符串作为键,值为第一次收入 }else { //取出key所对应的值加一 data.put(str, (data.get(str)+1));//累加 } } return data;//最终返回hashmap里面的值 }
三.测试用例的设计
核心模块代码的用例只有一个分支,从黑盒和白盒两个角度去考虑,黑盒角度,输入一个vector测试功能是否能运行成功,白盒角度,首先用例应保证能覆盖所有语句,一个包含{a,b,b}的vector能满足此要求,判断覆盖,只有一个判断,所以跟语句覆盖的用例相同,条件覆盖,考虑了一个形如{a,b,c}的用例,这样else分支不会被覆盖到,但是不存在if条件永远不满足的用例。还有一个空vector的用例,一开始应该在函数中多一个空vector的判断,但是由于在main函数中已经进行过判断文件是否为空,该部分属于冗余代码,所以删掉了。【对博客园表格的使用不是很熟练,所有的用例设计见表格https://github.com/Wegnery/New_WordCount/blob/master/%E6%B5%8B%E8%AF%95%E7%94%A8%E4%BE%8B.xlsx】
Input 输入 |
针对我自己的代码,能想出的用例有限,这部分问过老师,老师说可以针对小组其他成员的代码想一些用例。于是针对其他成员的代码,我又想了一些用例,以input.java为例,根据等价类划分,针对不同情况的输入,我想了一些用例:
输入 |
四.单元测试运行结果
testaccount为我写的核心模块的测试。测试质量良好,被测代码质量良好,在较小的vector下,代码可以快速跑完测试。测试代码:
package New_WordCountTest; import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; import New_WordCount.calc; import java.util.Arrays; import java.util.Collection; import java.util.TreeMap; import java.util.Vector; @RunWith(Parameterized.class) public class calcTest2 { private static final Object[] Object = null; // (2)步骤二:为测试类声明几个变量,分别用于存放期望值和测试所用数据 private Vector<String> inputVector; private TreeMap<String, Integer> expectMap; // (3)步骤三:为测试类声明一个带有参数的公共构造函数,并在其中为第二个环节中声明的几个变量赋值。 public calcTest2(Vector<String> inputVector, TreeMap<String, Integer> expectMap) { this.inputVector = inputVector; this.expectMap = expectMap; } // (4)步骤四:为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 // java.util.Collection 的公共静态方法,并在此方法中初始化所有需要测试的参数对。 @Parameters @SuppressWarnings("rawtypes") public static Collection testData() { //测试用例定义 String[][] inputArr = new String[][]{ {"a","b"}, {"a","b","b"}, {"a","a","a","a","d","d","x","string","a-b","c-num"}, }; Vector<String>[] inputVector = new Vector[inputArr.length]; for(int i=0;i<inputArr.length;i++){ inputVector[i]=new Vector<String>(); for(int j=0;j<inputArr[i].length;j++){ inputVector[i].add(inputArr[i][j]); } } //结果定义 String[][] key=new String[][]{ {"a","b"}, {"a","b"}, {"a","d","x","string","a-b","c-num"}, }; int[][] value=new int[][]{ {1,1}, {1,2}, {4,2,1,1,1,1} }; TreeMap<String, Integer>[] expectMap = new TreeMap[key.length]; for(int i=0;i<key.length;i++) { expectMap[i]=new TreeMap<String,Integer>(); for(int j=0;j<key[i].length;j++) { expectMap[i].put(key[i][j],value[i][j]); } } Object[][]data= new Object[][] { {inputVector[0],expectMap[0]}, {inputVector[1],expectMap[1]}, {inputVector[2],expectMap[2]}, }; return Arrays.asList(data); } // (5)步骤五:编写测试方法,使用定义的变量作为参数进行测试。 @Test public void testAccount() { TreeMap<String , Integer> actualMap = calc.account(inputVector);//获得函数返回值 assertEquals(expectMap,actualMap);//进行对比 } }
扩展功能:
本小组成员均完成扩展功能。
一.评价小组成员代码
选择了现代软件工程讲义 3 代码规范与代码复审为参考,评价了小组成员17011的代码,结合代码说明对该文档内容的理解。
1.文档中指出:“10.1.3 括号 在复杂的条件表达式中,用括号清楚地表示逻辑优先级。”,结合该同学的代码
while((line = br.readLine()) != null)
清晰的表达了前后的逻辑关系。
2.文档中指出:“每个“{”和“}”都独占一行”,在该同学代码中,
if(args.length >= 2) { System.out.println("输入了不止一个文件,仅处理第一个文件"); }
每一个if条件的大括号都独占一行,值得提倡。但是该同学while分支后有的有tab,有的没有tab,代码可读性下降。
while((line = br.readLine()) != null) { line = line.toLowerCase();//大写转换成小写 String regex = "[A-Za-z]+-?[A-Za-z]+|[A-Za-z]";//提取含-或者不含的单词的正则表达式 Pattern pattern=Pattern.compile(regex);//将正则表达式转为pattern Matcher ma=pattern.matcher(line);//与每行文本进行匹配
3.文档中:“由多个单词组成的变量名,如果全部都是小写,很不易读,一个简单的解决方案就是用大小写区分它们。”,但是该同学许多变量采用了br,isr,ma这种命名,可读性下降,并且不易区分。
4.注释问题,文档中提到“复杂的注释应该放在函数头,很多函数头的注释都是解释参数的类型等的,如果程序正文已经能够说明参数的类型in/out等,就不要重复!",该同学为了小组合作方便,确实将复杂注释放在了开头位置,并且注释掉了为了自己测试而写的main函数,但是在扩展阶段,基础功能已经实现,不再需要改方面的注释,应当删掉,避免冗余。除去开头的注释,没有冗余注释,体现了良好的注释规范。
5.“现代程序设计语言中的绝大部分功能,都在程序的函数(Function, Method)中实现,关于函数最重要的原则是:只做一件事,但是要做好。”整体代码中只有一个函数,实现了识别输入文件并识别合法单词的功能。整体是对输入的处理,但是核心还可以划分为两个模块,一个处理输入的文件,一个找文件中的合法单词。
二.静态代码检查工具
使用的是findbugs工具,下载地址:http://findbugs.sourceforge.net/downloads.html
运行截图:
该截图说明了我的代码中类名首字母应当大写,我没有大写。并且经过小组其他成员的审查,我的代码中注释存在太多冗余现象,整体显得非常乱。并且将hashmap改为treemap之后,注释并没有跟着修改。而且if,else的大括号也没有独占一行。非常的乱,贴一段修改前代码:
public static TreeMap<String , Integer>account(Vector<String> strs) { //产生一个容器 TreeMap<String , Integer>data=new TreeMap<String ,Integer>(); for (int i = 0; i < strs.size(); i++) {//通过下标来取出字符串中的一个个元素 String str=strs.get(i);//先把第一个字符串取出来 //判断一下hashmap中的key有没有,如果没有则收录 hashmap中 if (data.get(str)==null) {//第一次统计到字符串 data.put(str, 1);//把这个字符串作为键,值为第一次收入 }else { //取出key所对应的值加一 data.put(str, (data.get(str)+1));//累加 } } return data;//最终返回hashmap里面的值 }
修改后:
public static TreeMap<String , Integer>account(Vector<String> strs) { TreeMap<String , Integer>data=new TreeMap<String ,Integer>(); for (int i = 0; i < strs.size(); i++) { String str=strs.get(i); //判断一下treemap中的key有没有,如果没有则收录 treemap中 if (data.get(str)==null) { //第一次统计到字符串 data.put(str, 1); } else { //取出key所对应的值加一 data.put(str, (data.get(str)+1)); } } return data;//最终返回treemap里面的值 }
三.小组整体存在问题
小组整体存在问题是都不太熟悉github,对合作开发也不是很理解,所以框架搭的不是很完善。每个人的代码中多了很多冗余的注释信息。并且由于各个模块独立开发,有些交流做的不够,如在cal.java中已经返回一个treemap类型了,但是在output中不知道是不是treemap,所以又加了个强制类型转换treemap(data)。改进方法,删掉多余注释,并整体重新运行一次单元测试。
运行截图如下:
一开始遇到了一点错误,但是修改了过来,错误截图如下:
高级功能
本小组成员均完成高级功能
一.测试数据集的构建
构建测试集由我来负责,一开始只是想找网上的几个英文小说来测一下,首先想到的长小说就是权力的游戏,文件大小为5000+K。后来我认为程序中许多特殊字符,数字等都没有得到很好的体现,于是我编写了一个随机生成包含a-zA-Z0-9以及老师所提到的所有特殊字符文件的java代码,代码在RandomFileCreate文件夹中,用IDEA写的java程序,没有打包成.exe,但是可以运行,将生成的文件作为输入文件,生成不同的result.txt。该随机生成程序,可以通过str字符串中字符所占比重来调整特殊字符,空格所占比重,也可以生成不同长度的文件。首先生成了一个100000个字符的文件,然后生成了个1000000个字符的文件,又用了个权力的游戏小说文档来测试。生成文件核心代码如下:
public static String getRandomString(int length){ String str="abcdefghijklmnopqrstuvwxyzABCDEFGH IJKLMNOPQRSTUVWXYZ012 3456789~`!#%^&*_...( )[]+=-:;“‘<> ,./?|"; Random random=new Random(); StringBuffer sb=new StringBuffer(); for(int i=0;i<length;i++){ int number=random.nextInt(str.length()); sb.append(str.charAt(number)); } return sb.toString(); }
中间的str控制生成字符所占比例,大量空格就是空格所占比例。length控制生成文件所占长度。
二.同行评审
小组分工:主持人:宁宁17009 评审员:宁宁17009、朱全17031、周雨贝17011 作者:宁宁17009,朱全17031,周雨贝17011,易成龙17020 讲解员:易成龙17020 记录员:易成龙17020
我们对每个人代码规范,代码中可以提出的优化进行评审,最终认为影响性能指标的主要因素如下:
1.输入文件大小
2.输入文件中包含特殊字符比例
3.输入文件中包含由空格隔开的字符串数量
4.排序时所采用的算法是否高效。
5.硬件制约因素如:磁盘I/O,CPU性能等。
6.单词重复率,TreeMap中是否有该单词,如果一个单词重复率很高,那每次在Treemap中找单词的时间并加一的时间要比直接添加到TreeMap中的时间长。
三.测试结果
我们小组采用的性能指标是时间:
1.采用Gamesofthrones.txt测试,测试集特点:文件大,是三者中最大的文件;文件中单词结构单一,重复率大,基本无特殊字符与字母。测试结果:
2.采用第一个file.txt测试,测试集特点:文件小,长度为100000个字符,文件中含特殊字符,数字多。测试结果:
3.采用另外一个file.txt测试,测试集特点:文件大一点,长度为1000000个字符,文件中特殊字符,数字多。测试结果:
【注:第2,3个文件是我自己写的随机文件生成的。】
结论:然后我们得出的结论是,文件越大,时间越长,但是也不一定,如果文件中特殊字符比较多,那么一个小文件可能跑出来的时间比一个只有英文单词的大文件时间还长。测试集1比测试集3耗时短的原因主要是特殊字符少与单词重复率大。测试集3比2用时长的主要原因是单词多,文件大。在小组成员的电脑中运行出的结果与我的电脑不一致,说明了与硬件也有关。(由于疏忽,忘记截图了)
四.作业小结
我认为软件开发首先要遵循良好的代码风格习惯,尤其在小组合作的时候,一定要统一注释风格,代码风格等。并且在github使用的时候,我遇到了许多麻烦,其中有一次记录是把所有代码都删掉了,因为merge的时候出现了错误,又回退不了,所以直接全删了。测试是对程序正确性的体现,同时也是对整体代码是否有正确理解的体现。能测试出许多意想不到的bug,比如输入文件不能有空格这种,我认为这也是对逻辑思维是否缜密的一种锻炼。在静态评审阶段,学会了使用findbugs对程序进行找错,提醒我以后在编写代码的过程中应当遵循良好的规范。我认为给我启发最大的就是高级功能模块,一开始并没有打算做,后来觉得能为难自己的程序是个有意思的事情,而且能考验自己程序的质量到底如何。所以我想到了写文件随机生成程序的方法,针对小组想的几个制约因素,构建不同的测试集来进行测试,结果与预期一致,但是我没有想到会差这么多时间(权游文档和测试集3)。我认为软件开发,软件测试,软件质量三者相辅相成,测试决定开发结果是否正确,质量决定开发结果是否优秀,只有建立在正确的前提下才能开发出优秀的程序。
小组贡献:0.3
参考链接:
1.github如何加tag:https://blog.csdn.net/zjws23786/article/details/71159805
2.FindBugs参数代表含义:https://blog.csdn.net/fancy_xty/article/details/51718687
3.代码规范与代码复审:http://www.cnblogs.com/xinz/archive/2011/11/20/2255971.html
4.分支切换出错:https://blog.csdn.net/tongxinxiao/article/details/43988773
5.treemap的使用:http://www.cnblogs.com/skywang12345/p/3310928.html
6.作业地址:http://www.cnblogs.com/ningjing-zhiyuan/p/8654132.html