1 基本任务:代码编写+单元测试
1.1 项目GitHub地址
https://github.com/ReWr1te/WcPro
1.2 项目PSP表格
PSP2.1 | PSP阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 870 | 1160 |
· Analysis | · 需求分析 (包括学习新技术) | 60 | 80 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 180 | 240 |
· Coding | · 具体编码 | 180 | 240 |
· Code Review | · 代码复审 | 120 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 240 | 360 |
Reporting | 报告 | 100 | 70 |
· Test Report | · 测试报告 | 30 | 30 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 30 |
合计 | 990 | 1250 |
1.3 个人接口及其实现
本次项目基本任务部分共分为输入控制、核心处理、输出控制和其他(本组计划添加GUI)组成,我所负责的部分为输入控制。(备注:由于负责优化的同学参与了代码的后期编写,所以最终结果可能和我的基本功能有些许出入,最终实现以GitHub代码为准)
1.3.1 任务需求
任务需求为:对输入进行有效性校验,识别和处理无效输入,并针对有效输入,从中提取所需数据。
根据需求定义了如下接口:
外部接口负责和其他模块通信,调用内部子接口实现功能:
/**
* get the content in a file and return the result
*/
public String parseCommand(String[] args)
子接口分别完成参数处理、获取地址、获取文本内容以及对于文本内容是否为半角字符的判断:
/**
* judge the input parameters
*/
public int paraHandling(String[] args)
/**
* get the address
*/
public String getAddress(String fileName)
/**
* get the content
*/
public String getContent(FileReader fr, BufferedReader br, StringBuffer sb, String address)
/**
* check half-width characters
*/
public int halfWidthChar(String content)
下面取两个典型代码段分析(全部代码请参见GitHub源码):
1.3.2 参与外部调用的接口,主函数通过调用该接口来读取文件并获得文件内容:
/**
* get the content in a file and return the result
*/
public String parseCommand(String[] args) {
// initiate content file, each line, filename and address to store related info
String address, content;
// initiate file reader, buffered reader and string buffer to store file medially
FileReader fileReader = null;
BufferedReader bufferedReader = null;
StringBuffer stringBuffer = null;
// call the functions to get address and then content
if (paraHandling(args) != 1) {
// to return a void result
return null;
} else {
address = getAddress(args[0]);
content = getContent(fileReader, bufferedReader, stringBuffer, address);
if (halfWidthChar(content) == 0) {
return null;
} else {
return content;
}
}
}
该接口结构简单清晰,因为代码已经很好地封装在了其它函数中。
在完成了必要变量的初始化之后,直接使用简单的if结构调用内部函数就能完成相应功能。
调用内部函数的顺序为:
参数判断→获取地址→获取文本→半角字符判断
1.3.3 半角判断的函数,通过对于字符串字节长度的比较来判断文本内容是否全为半角字符:
/**
* check half-width characters
*/
public int halfWidthChar(String content) {
if (content.getBytes().length == content.length()) {
return 1;
} else {
System.out.println("half-width characters only!");
return 0;
}
}
1.4 测试用例的设计以及测试效率的满足
1.4.1 本次测试用例的设计采用黑盒测试和白盒测试相结合的方式
黑盒测试通过不同的输入类型(包含输入参数对应的文本文件内容类型)来测试功能的正确性;白盒测试在尽量覆盖所有路径(函数结构比较简单,所有路径大致与下列测试用例相符)的情况下对每个路径进行参数与返回值的匹配正确性测试:
1.4.2 单元测试代码举例如下:
/**
* #0 zero-parameter test (black-box)
*/
@Test
public void parseCommandTest0() throws Exception {
String[] paras = {};
System.out.println("parseCommand() Test0 started.");
assertEquals("Failed",null, cp.parseCommand(paras));
System.out.println("parseCommand() Test0 succeeded.");
System.out.println("parseCommand() Test0 finished.");
}
所有测试用例请参见GitHub输入测试类。
1.5 测试用例的运行截图
1.5.1 单个测试用例的运行截图(代码参照上述代码):
1.5.2 所有用例的运行截图:
可以看出,每个测试用例都通过了,意味着实际输出与预期输出相一致,测试用例的质量和源代码质量都符合要求。
(所有测试用例见GitHub: UTCaseDesign_Input.xlsx,实现见测试类源代码)
1.6 小组贡献分
见毕博平台。
2 扩展功能:静态测试
2.1 选用的开发规范文档:《阿里巴巴Java开发手册》编程规约部分
该部分从命名风格、常量定义、代码格式、OOP规约、集合处理、并发处理、控制语句和注释规约八个方面详细地阐释了Java编程过程中应当注意和践行的规约,并且给出了恰当而形象的例子。
其中的编程规约主要分为“强制”、“推荐”和“参考”三类,以强制和推荐类型居多;强制的编程规约是要求程序员在Java开发过程中必须遵循的准则,如果不采用这些编程规范,则很有可能引发相当基础的错误,或者造成难以理解的歧义;推荐类型的规约则更像是告诉我们如何编写优雅高效的代码,通过简单的编程方式或者处理,我们可以让代码达到比较高的可读性和运行效果;参考给我们提供了更多可选择的编码方式,同时引发了更多关于编码技巧的思考。
举例理解:
《阿里巴巴Java开发手册》中指出:【强制】在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:if (condition) statements。 说明:即使是最简单的一行语句,在紧接着控制语句时也应当加上大括号,使得代码结构更加清晰完整。
根据我的实践体会举例如下:以前编写代码(特别是C和C++)为了方便,很多时候都会忽略控制语句后面接的单行语句,将它们写在控制语句的同一行上面,而且不加大括号,例如:
if (count == 0) return 1;
现在想来如果这行代码前后结构很复杂,在没有大括号的情况下确实容易和其他代码混淆。因此,在控制语句中必须使用大括号是一种很实用且规范的编码方式。
2.2 组员代码分析
分析代码的作者学号后5位:17121。
为了更好地阐述分析思路,下面先给出我所分析的这一段代码:
/**
* 输出
*/
public String output(List> list){
final String pathOfOutput="result.txt";
String outContent="";
Map.Entry oMap;
int amount=0;//仅输出单词词频从高到低排序的前100个(从1到100)
for(Iterator> it=list.iterator();it.hasNext()&&amount<100;++amount){
oMap=it.next();
outContent+=oMap.getKey()+" "+oMap.getValue()+"\n";
}
outContent=outContent.substring(0,outContent.length()-1);//去除输出文件末尾多余的换行符
writeFile(pathOfOutput,outContent);
return outContent;
}
为了方便描述和理解,这里采用从前到后的顺序分析方式
- 第1~3行:注释,采用了/**内容*/的格式,符合规范(编程规约-注释规约-1-强制);
- 第4行:类方法定义,接口类中方法和属性不要加任何修饰符号(public也不要),需要改进;左大括号前加空格,需要改进(编程规约-命名风格-13-推荐,编程规约-代码格式-5-强制);
- 第5行:变量定义、赋值,名称:pathOfOutput,符合命名规范且语义明确;但双目运算符"="前后都需要添加一个空格,需要改进(编程规约-命名风格-4-强制,编程规约-代码格式-4-强制);
- 第6行:变量定义、赋值,名称:outContent,符合命名规范且语义明确;但双目运算符"="前后都需要添加一个空格,需要改进(编程规约-命名风格-4-强制,编程规约-代码格式-4-强制);
- 第7行:变量定义,名称:oMap,符合命名规范且语义较为明确(编程规约-命名风格-4-强制);
- 第8行:变量定义、赋值、注释,名称:amount,语义不够明确(例如改为wordAmount可能更好一些);双目运算符"="前后都需要添加一个空格,需要改进;方法内部单行注释应该在被注释语句上方另起一行,需要改进;注释的双斜线与注释内容之间有且仅有一个空格,需要改进(编程规约-命名风格-4-强制,编程规约-代码格式-4-强制,编程规约-注释规约-4-强制,编程规约-代码格式-6-强制);
- 第9~10行:for循环语句,for保留字与括号之间必须加空格,需要改进;双目运算符"&&"、"<"前后都需要添加一个空格,需要改进;左大括号前加空格,需要改进(编程规约-代码格式-3-强制,编程规约-代码格式-4-强制,编程规约-代码格式-5-强制);
- 第11行:赋值表达式,双目运算符"="前后都需要添加空格,需要改进(编程规约-代码格式-4-强制);
- 第12行:赋值表达式,双目运算符"+="和"+"前后都需要添加空格,需要改进(编程规约-代码格式-4-强制);
- 第13行:右大括号前后换行,符合规范(编程规约-代码格式-1-强制);
- 第14行:赋值表达式,双目运算符"="和"-"前后都需要添加空格,需要改进;定义或传递多个参数时,需要在逗号后加空格,需要改进;方法内部单行注释应该在被注释语句上方另起一行,需要改进;注释的双斜线与注释内容之间有且仅有一个空格,需要改进(编程规约-代码规范-4-强制,编程规约-代码规范-8-强制,编程规约-注释规约-4-强制,编程规约-代码格式-6-强制);
- 第15行:方法调用,定义或传递多个参数时,需要在逗号后加空格,需要改进(编程规约-代码规范-8-强制);
- 第16行:return语句,符合规范;
- 第17行:右大括号前换行,符合规范(编程规约-代码格式-1-强制)。
2.3 静态代码检查工具
此次选择的静态代码检查工具是阿里的 Alibaba Java Coding Guidelines,与之前使用的《阿里巴巴Java开发手册》高度吻合,因此比较统一。
我使用该工具的方法是下载IDEA插件,GitHub地址为:
https://github.com/alibaba/p3c
同时也可以在IDEA的如下界面——Preferences(MacOSX)配置(Windows在File-Settings里配置):
2.4 静态检查结果
截图如下,可见,自己编写的代码有很多不符合规范的地方,还需要慢慢学习和改正。具体不符合规范之处就不一一列举,感兴趣的读者可以参见《阿里巴巴Java开发手册》。
存在的问题以及改进方法:
- if/else后没有加大括号,就算是只有一行也应该加上;
- 出现魔法值,应该先定义常量;
- 没有添加类创建者信息,每一个类都应该添加创建者信息;
- 方法内部的单行注释都必须在原语句上另起一行;
- 类、类属性、类方法的注释必须符合形如/**注释*/的规范。
2.5 小组代码整体分析
通过之前的组员互评以及后续的讨论,我们发现我们的代码规范问题主要集中在注释和if/else等控制语句后的大括号上面,通过与《阿里巴巴Java开发手册》对比,我们将我们的代码进行了改进,并最终呈现为最后提交到GitHub上的代码。
我自己修改后的部分代码及检测结果如下:
可见所有代码都符合了对应的编程规范。
3 高级任务:性能测试和优化
3.1 测试数据集设计思路
- 单元测试数据集:单元测试数据及也应采用黑盒测试和白盒测试相结合的方式,完整覆盖每一个功能,如果采用路径覆盖的白盒测试,就应做到对所有路径的完全覆盖;
- 集成测试数据集:基于黑盒测试设计思想,对整个程序的功能进行测试,选用内容足够大且复杂的文件进行测试。
3.2 优化前的程序性能指标
由于各用户机器类型和性能有所差异,这里给出预期的优化率代替性能指标,预期优化率:50%
附性能检测部分代码:
//获取开始时间
long startTime = System.currentTimeMillis();
//获取结束时间
long endTime = System.currentTimeMillis();
//输出程序运行时间
System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
3.3 同行评审过程
- 参与者:17122 17121 17098 17103;
- 主持人&记录员:17122;
- 评审员:17122 17121 17098 17103(交叉互审);
- 讲解员:17098(代码深入分析、知识普及);
- 评审内容:各功能模块;
- 意见小结:代码规范可以改进、代码可以持续优化。
3.4 评审结论
影响性能的主要因素是循环的低效率和正则式自身的可优化特性,与同行评审大致相符。
- 正则表达式效率较慢,可以用状态机来提取单词。
- 考虑到程序对查找的性能要求比较高,单词统计我们采用HashMap来存储。当数据容量较少时其内部实现为一个链表,当数据量较大时,自动改用二叉树进行存储,有效提高了查询效率。
- 输出单词时,改变原来的每个单词打开一次文件的做法,只打开一次文件,全部输出后关闭文件流,提高了IO速度。
3.5 优化设计思路
去掉不必要的循环,同时采用比正则式更优的分词方式,性能指标同比增长100%(耗时为50%)。
3.6 附加功能
我们小组设计实现了GUI设计。
3.7 项目小结
个人认为它们的大致联系为:
基本任务——软件开发;
扩展任务——软件测试;
高级任务——软件质量;
当然,不可否认的是,基本任务中也包含了单元测试,但我认为单元测试也算是开发工作的一部分,同时也肯定是整个测试过程中的一环;值得注意的是,测试工作其实贯穿从基本任务到高级功能实现的整个过程,从始至终为高质量的软件开发提供支持和保证。
软件开发是手段,软件测试是工具,软件质量是目的。在产品开发的整个过程中我们都需要兼顾三者,权衡比重,保证软件过程的顺利、高效进行。