WcPro项目(WordCount优化)

Posted PaulTian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WcPro项目(WordCount优化)相关的知识,希望对你有一定的参考价值。

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 本次测试用例的设计采用黑盒测试和白盒测试相结合的方式

黑盒测试通过不同的输入类型(包含输入参数对应的文本文件内容类型)来测试功能的正确性;白盒测试在尽量覆盖所有路径(函数结构比较简单,所有路径大致与下列测试用例相符)的情况下对每个路径进行参数与返回值的匹配正确性测试:

Unit Test Case

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 单个测试用例的运行截图(代码参照上述代码):

One Unit Test

1.5.2 所有用例的运行截图:

All Unit Test

可以看出,每个测试用例都通过了,意味着实际输出与预期输出相一致,测试用例的质量和源代码质量都符合要求。

(所有测试用例见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<Map.Entry<String,Integer>> list){
    final String pathOfOutput="result.txt";
    String outContent="";
    Map.Entry<String,Integer> oMap;
    int amount=0;//仅输出单词词频从高到低排序的前100个(从1到100)
    for(Iterator<Map.Entry<String,Integer>> 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. 第1~3行:注释,采用了/**内容*/的格式,符合规范(编程规约-注释规约-1-强制);
  2. 第4行:类方法定义,接口类中方法和属性不要加任何修饰符号(public也不要),需要改进;左大括号前加空格,需要改进(编程规约-命名风格-13-推荐,编程规约-代码格式-5-强制);
  3. 第5行:变量定义、赋值,名称:pathOfOutput,符合命名规范且语义明确;但双目运算符"="前后都需要添加一个空格,需要改进(编程规约-命名风格-4-强制,编程规约-代码格式-4-强制);
  4. 第6行:变量定义、赋值,名称:outContent,符合命名规范且语义明确;但双目运算符"="前后都需要添加一个空格,需要改进(编程规约-命名风格-4-强制,编程规约-代码格式-4-强制);
  5. 第7行:变量定义,名称:oMap,符合命名规范且语义较为明确(编程规约-命名风格-4-强制);
  6. 第8行:变量定义、赋值、注释,名称:amount,语义不够明确(例如改为wordAmount可能更好一些);双目运算符"="前后都需要添加一个空格,需要改进;方法内部单行注释应该在被注释语句上方另起一行,需要改进;注释的双斜线与注释内容之间有且仅有一个空格,需要改进(编程规约-命名风格-4-强制,编程规约-代码格式-4-强制,编程规约-注释规约-4-强制,编程规约-代码格式-6-强制);
  7. 第9~10行:for循环语句,for保留字与括号之间必须加空格,需要改进;双目运算符"&&"、"<"前后都需要添加一个空格,需要改进;左大括号前加空格,需要改进(编程规约-代码格式-3-强制,编程规约-代码格式-4-强制,编程规约-代码格式-5-强制);
  8. 第11行:赋值表达式,双目运算符"="前后都需要添加空格,需要改进(编程规约-代码格式-4-强制);
  9. 第12行:赋值表达式,双目运算符"+="和"+"前后都需要添加空格,需要改进(编程规约-代码格式-4-强制);
  10. 第13行:右大括号前后换行,符合规范(编程规约-代码格式-1-强制);
  11. 第14行:赋值表达式,双目运算符"="和"-"前后都需要添加空格,需要改进;定义或传递多个参数时,需要在逗号后加空格,需要改进;方法内部单行注释应该在被注释语句上方另起一行,需要改进;注释的双斜线与注释内容之间有且仅有一个空格,需要改进(编程规约-代码规范-4-强制,编程规约-代码规范-8-强制,编程规约-注释规约-4-强制,编程规约-代码格式-6-强制);
  12. 第15行:方法调用,定义或传递多个参数时,需要在逗号后加空格,需要改进(编程规约-代码规范-8-强制);
  13. 第16行:return语句,符合规范;
  14. 第17行:右大括号前换行,符合规范(编程规约-代码格式-1-强制)。

2.3 静态代码检查工具

此次选择的静态代码检查工具是阿里的 Alibaba Java Coding Guidelines,与之前使用的《阿里巴巴Java开发手册》高度吻合,因此比较统一。

我使用该工具的方法是下载IDEA插件,GitHub地址为:

https://github.com/alibaba/p3c

同时也可以在IDEA的如下界面——Preferences(MacOSX)配置(Windows在File-Settings里配置):

One Unit Test

2.4 静态检查结果

截图如下,可见,自己编写的代码有很多不符合规范的地方,还需要慢慢学习和改正。具体不符合规范之处就不一一列举,感兴趣的读者可以参见《阿里巴巴Java开发手册》。

One Unit Test One Unit Test

存在的问题以及改进方法:

  1. if/else后没有加大括号,就算是只有一行也应该加上;
  2. 出现魔法值,应该先定义常量;
  3. 没有添加类创建者信息,每一个类都应该添加创建者信息;
  4. 方法内部的单行注释都必须在原语句上另起一行;
  5. 类、类属性、类方法的注释必须符合形如/**注释*/的规范。

2.5 小组代码整体分析

通过之前的组员互评以及后续的讨论,我们发现我们的代码规范问题主要集中在注释和if/else等控制语句后的大括号上面,通过与《阿里巴巴Java开发手册》对比,我们将我们的代码进行了改进,并最终呈现为最后提交到GitHub上的代码。

我自己修改后的部分代码及检测结果如下:

One Unit Test

可见所有代码都符合了对应的编程规范。

3 高级任务:性能测试和优化

3.1 测试数据集设计思路

  1. 单元测试数据集:单元测试数据及也应采用黑盒测试和白盒测试相结合的方式,完整覆盖每一个功能,如果采用路径覆盖的白盒测试,就应做到对所有路径的完全覆盖;
  2. 集成测试数据集:基于黑盒测试设计思想,对整个程序的功能进行测试,选用内容足够大且复杂的文件进行测试。

3.2 优化前的程序性能指标

由于各用户机器类型和性能有所差异,这里给出预期的优化率代替性能指标,预期优化率:50%

附性能检测部分代码:

//获取开始时间
long startTime = System.currentTimeMillis();
//获取结束时间
long endTime = System.currentTimeMillis();
//输出程序运行时间
System.out.println("程序运行时间:" + (endTime - startTime) + "ms");

3.3 同行评审过程

  1. 参与者:17122 17121 17098 17103;
  2. 主持人&记录员:17122;
  3. 评审员:17122 17121 17098 17103(交叉互审);
  4. 讲解员:17098(代码深入分析、知识普及);
  5. 评审内容:各功能模块;
  6. 意见小结:代码规范可以改进、代码可以持续优化。

3.4 评审结论

影响性能的主要因素是循环的低效率和正则式自身的可优化特性,与同行评审大致相符。

  • 正则表达式效率较慢,可以用状态机来提取单词。
  • 考虑到程序对查找的性能要求比较高,单词统计我们采用HashMap来存储。当数据容量较少时其内部实现为一个链表,当数据量较大时,自动改用二叉树进行存储,有效提高了查询效率。
  • 输出单词时,改变原来的每个单词打开一次文件的做法,只打开一次文件,全部输出后关闭文件流,提高了IO速度。

3.5 优化设计思路

去掉不必要的循环,同时采用比正则式更优的分词方式,性能指标同比增长100%(耗时为50%)。

3.6 附加功能

我们小组设计实现了GUI设计。

3.7 项目小结

个人认为它们的大致联系为:

基本任务——软件开发;

扩展任务——软件测试;

高级任务——软件质量;

当然,不可否认的是,基本任务中也包含了单元测试,但我认为单元测试也算是开发工作的一部分,同时也肯定是整个测试过程中的一环;值得注意的是,测试工作其实贯穿从基本任务到高级功能实现的整个过程,从始至终为高质量的软件开发提供支持和保证。

软件开发是手段,软件测试是工具,软件质量是目的。在产品开发的整个过程中我们都需要兼顾三者,权衡比重,保证软件过程的顺利、高效进行。

以上是关于WcPro项目(WordCount优化)的主要内容,如果未能解决你的问题,请参考以下文章

第四周作业WordCount优化

第4周小组作业:WordCount优化

WordCount 优化版测试小程序实现

第4周小组作业:WordCount优化

WordCount优化

第4周作业:WordCount优化