项目地址
https://github.com/cosensible/WordCountPlus
PSP表格
PSP2.1 | PSP阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 190 | 270 |
· Analysis | · 需求分析(包括学习新技术) | 25 | 40 |
· Design Spec | · 生成设计文档 | 0 | 0 |
· Design Review | · 设计复审 | 0 | 0 |
· Coding Standard | · 代码规范(为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 45 | 60 |
· Code Review | · 代码复审 | 20 | 20 |
· Test | · 测试 | 60 | 60 |
Reporting | 报告 | 50 | 80 |
· Test Report | · 测试报告 | 30 | 45 |
· Size Measurement | · 计算工作量 | 5 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 15 | 25 |
合计 | 250 | 360 |
接口实现
我负责的模块功能是输出控制。它将countWordFrequency()函数统计得到的单词词频结果写入到指定文件夹中。该接口主要处理以下问题:
- 当得到的单词词频统计结果中没有记录时,程序没有记录可以写,该函数可以直接返回。
- 当文件已经存在但是不可写时,程序无法写入结果,该函数直接返回。
- 当文件不存在时,根据提供的文件名参数创建新的文件用以记录结果。
- 初始化FileWriter和BufferedWriter。
- 循环读取单词词频统计结果,写入指定文件流,并且统计写入记录的数量。
- 当数量达到100,停止写入并返回。
- 最后,关闭文件流。
以下是该函数的头部:
public static int write(
final String filePath,
final String[] contents,
final boolean append);
接口流程图:
上图中最后那个条件应该是:"num>=100或读到contents末尾"。
测试用例设计
白盒测试
为了对接口进行白盒测试,将以上给出的流程图改画为程序图如下:
图中有四个判定节点,只需设计五个测试用例。
路径 | 测试用例 | 预期输出 | 实际输出 |
---|---|---|---|
A->B->D->E->F->G->H->C | contents有一条记录,文件不存在 | 创建文件,返回1 | 创建文件,返回1 |
A->B->D->E->F->G->H->G->H->C | contents有两条记录,文件不存在 | 创建文件,返回2 | 创建文件,返回2 |
A->B->D->E->G->H->C | contents有一条记录,文件存在且可写 | 返回1 | 返回1 |
A->B->D->C | contents!=null,文件存在但不可写 | 返回0 | 返回0 |
A->B->C | contents==null | 返回0 | 返回0 |
黑盒测试
该接口结构如下:
public static int write(
final String filePath,
final String[] contents,
final boolean append);
据此,可以划分以下几个等价类测试:
等价类 | 说明 |
---|---|
等价类1 | 结果记录集 contents 为空,返回0; |
等价类2 | contents!=null,路径对应的文件存在但不可写,返回0; |
等价类3 | contents!=null,路径对应的文件存在且可写,记录结果并返回num; |
等价类4 | contents!=null,路径对应的文件不存在,创建新文件记录结果并返回num; |
等价类5 | append=true,向记录结果的文件以追加方式写入记录。 |
单元测试结果
单元测试脚本
以下是部分测试脚本代码:
该测试脚本采用Junit4测试框架编写。
运行截图
分析以上单元测试结果,可以看出整个测试过程用了251ms,测试的效率比较高。另外,该测试覆盖度很高,考虑到了各种可能发生的情况。因此,该接口的可靠性得到了保证。
要是还想要提高测试类的运行速度,可以将全部的测试用例写在一个函数里面,经过测试,这种方法的运行速度为78ms,速度有很大提升。
扩展任务:静态测试
参考规范
对组员17139的代码规范分析
public class WordFrequencyCountUtil {
public static String[] countWordFrequency(String[] resultContents){
Map<String,Integer> resultMap=new TreeMap<String,Integer>();
for(String content:resultContents){
//按照规则,找出每行中,形如abc(-ab)*这样的单词
String regex="[a-zA-Z]+(-[a-zA-Z]+)*";
Pattern pattern=Pattern.compile(regex);
Matcher matcher=pattern.matcher(content);
while (matcher.find()){
input(content.substring(matcher.start(),matcher.end()),resultMap);
}
}
//对resultMap按照指定的规则进行排序
List<Map.Entry<String,Integer>> list=new ArrayList<>(resultMap.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
if(o1.getValue()<o2.getValue())
return 1;
if(o1.getValue()>o2.getValue())
return -1;
return o1.getKey().compareTo(o2.getKey());
}
});
//将排好序的结果存入results中
String[] results=new String[list.size()];
int index=0;
for(Map.Entry<String,Integer> entry:list){
results[index++]=entry.getKey()+" "+entry.getValue();
}
return results;
}
private static void input(String s,Map<String,Integer> map){
s=s.toLowerCase();
if(map.containsKey(s)){
map.put(s,map.get(s)+1);
}else
map.put(s,1);
}
public static void main(String[] args){
String[] result = WordFrequencyCountUtil.countWordFrequency(new String[]{"int", "int-main ni int'int"});
for(String i : result)
System.out.println(i);
}
}
1.《阿里巴巴Java开发手册》中指出:【强制】if/for/while/switch/do 等保留字与左右括号之间都必须加空格。这样的风格让关键字更加突出,便于程序的阅读。显然,这位同学没有注意到这方面,暂且相信他以后是会改正的。
2.《阿里巴巴Java开发手册》中指出:【强制】任何运算符左右必须加一个空格。说明:运算符包括赋值运算符=、逻辑运算符&&、加减乘除符号、三目运行符等。同样的,这样的代码风格更便于阅读。显然,这位同学对这种规范还不知道,或者知道了但是不愿遵循,希望他以后能坚持下来。
3.《阿里巴巴Java开发手册》中指出:【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
这位同学在大多数情况下做得很好,但是在某些地方仍然做的不够好,应该是借助了IDE的帮助。希望他以后好好改进。
4.《阿里巴巴Java开发手册》中指出:【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。这位同学先定义了一个public方法,后定义了一个private方法,又定义了一个public方法,显然没有注意到这个问题。
5.另外我觉得他的测试方法取名太随意了,直接使用test1,test2...这样的测试方法命名会给代码使用者带来很大的阅读障碍,因为他们很难知道代码编写者的意图,所以很难理解,会给测试造成较大影响。
@Test
public void test1() throws Exception {
String[] result = WordFrequencyCountUtil.countWordFrequency(new String[]{"int"});
assertEquals("int 1", result[0]);
}
@Test
public void test2() throws Exception {
String[] result = WordFrequencyCountUtil.countWordFrequency(new String[]{"int int"});
assertEquals("int 2", result[0]);
}
对自己代码静态检查
使用工具
静态扫描截图
可以看到,这个工具实在是太强大了,所有违背规范的地方都能够找出来,并显示链接让我们精确定位并且提供一键修改,非常方便。可以看出,代码主要的问题在于注释、命名规则以及符号前后的空格没有遵循规范,还有一些变量应该加上对应的关键字修饰符。
代码风格改进
public class FileUtilWriteTest {
private FileUtil fileUtil;
@org.junit.Before
public void setUp() throws Exception {
fileUtil=new FileUtil();
}
@org.junit.After
public void tearDown() throws Exception {
}
@Test
public void testNullContents() {
//测试第二个参数 contents==null 时程序返回0
final String filePath = "";
final String[] contents = null;
assertEquals(0, fileUtil.write(filePath, contents, false));
}
@Test
public void testFileCanNotWrite() {
//测试当文件存在但不可写时返回0
final String filePath1 = "E:\\JavaProjects\\WordCountPlus\\src\\test\\test.txt";
final String[] contents1 = {"import 3", "java 3"};
File file = new File(filePath1);
file.setWritable(false);
assertEquals(0, fileUtil.write(filePath1, contents1, false));
}
...
}
这里主要将一些错误的命名规则改正过来,将仅在类内部使用的变量添加private关键词修饰。这样一看,代码的命名显得异常统一,更加方便阅读。最重要的是,在小组内部达成了一致的意见,方便以后的合作。
重新运行单元测试
发现整个模块测试的运行时间变为126ms,相比之前提高了很多。这也许是因为程序运行了很多遍的原因,因为根据Java语言的特性,相同的代码,运行次数越多,执行速度就会越来越快。
结论
组内主要问题在于注释、命名规则以及符号前后的空格没有遵循规范,还有一些变量应该加上对应的关键字修饰符。另外,还有一些数据结构的使用方法不够规范,某些设计模式的运用不够熟练。经过这次代码规范的分析,整个小组人员认识到了自己在代码规范方面不够重视,以及这样做带来的代码缺陷,以后会尽最大努力编写满足规范的代码!