zoukankan      html  css  js  c++  java
  • WordCount优化

    1.基本任务

    1.1本项目Github地址

    https://github.com/ReWr1te/WcPro

    1.2PSP表格分析

    PSP2.1 PSP阶段 预估耗时
    (分钟)
    实际耗时
    (分钟)
    Planning 计划 60 60
    ·Estimate ·估计这个任务需要多上时间 60 60
    Development 开发 680 890
    ·Analysis ·需求分析(包括学习新技术) 30 30
    ·Design Spec ·生成设计文档 30 30
    ·Design Review ·设计复审(和同事审核设计文档) 20 30
    ·Coding Standard ·代码规范(为目前的开发制定合适的规范) 20 30
    ·Design ·具体设计 60 60
    ·Coding ·具体编码 240 300
    ·Code Review ·代码复审 40 50
    ·Testing ·测试(自我测试,修改代码,提交修改) 240 360
    Reporting 报告 240 300
    ·Test Report ·测试报告 100 120
    ·Size Measurement ·计算工作量 40 60
    ·Postmortem & Process Improvement Plan ·事后总结,并提出过程改进计划 100 120
    合计 980 1250

    这次作业的具体编码用时相对减少,但测试所用时间大大增加。

    1.3代码设计思路

    本人所在小组对WordCount优化项目定义了4个接口,分别为

    String parseCommand(String[] args)//输入命令行命令,返回文件内容
    
    HashMap<String, Integer> parseContent(String str)//输入文件内容,返回单词统计信息
    
    void writeResult(HashMap<String, Integer> result)//输入单词统计信息,排序并将其写入文件
    
    GUI//GUI界面完成人机交互
    

    我要设计实现的接口是void writeResult(HashMap<String, Integer> result),输入的参数为单词统计信息,存放在一个HashMap里面,key是字符串类型,存放的是单词内容,value是整型,存放的是词频。首先,我要对单词词频进行降序排列,对单词词频相同的情况,按照单词所包含的每个字母从a到z的次序依次排列,若单词包含连字符-,则连字符优先。这涉及到对HashMap的排序,我参考了博客 如何将HashMap,按照value值排序[1]和Java Map 按Key排序和按Value排序[2]实现对单词词频的降序排列。下面贴出重写实现Comparator接口的compare方法的代码。

    /**  
     * 排序类  
      */  
    private static class ValueComparator implements Comparator<Map.Entry<String,Integer>> {  
        public int compare(Map.Entry<String,Integer> m,Map.Entry<String,Integer> n){  
            if(n.getValue()-m.getValue()==0)  
                return m.getKey().compareTo(n.getKey());  
            else 
                return n.getValue()-m.getValue();  
      }  
    }
    

    使用的时候需要调用Collections工具类的sort方法,传入我们自己写的这个实现类的对象,作为参数就可以了。

    接下来是输出词频排序的结果,排序之后得到的是一个List,里面存放的是Map.Entry<String,Integer>,输出里面的内容要用到Iterator,为了实现输出的单词和词频空一格,需要获取List里的每一个Map.Entry<String,Integer>的key和value赋给一个字符串然后中间用空格隔开,为了仅输出单词词频从高到低排序的前100个(从1到100),需要一个初值为0的计数变量amount,每取一个Map.Entry<>amount加一,amount大于等于100停止取Map.Entry<>,为了去除输出文件末尾多余的换行符,需截取输出字符串的第一个字符到倒数第二个字符,最后再用覆盖写入的方法(参考博客Java读取txt文件和覆盖写入txt文件和追加写入txt[3])写到result.txt。下面贴出部分实现代码。

    /**  
     * 输出  
     */  
    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()+"
    ";  
        }  
        if(outContent.length()>0) {  
            outContent = outContent.substring(0, outContent.length() - 1);//去除输出文件末尾多余的换行符  
        }  
        writeFile(pathOfOutput,outContent);  
        return outContent;  
    }
    

    关键方法流程图

    1.4测试设计过程

    我对ResultWriter类的排序方法sort()和输出方法output()进行了单元测试,采用黑盒测试和白盒测试方法设计了22个测试用例,进行了等价类划分,得到以下分类:

    • 各单词词频不重复;
    • 单词词频重复;
    • 词频重复基础上单词首字母重复;
    • 词频重复基础上单词首字母、二字母重复;
    • 词频重复基础上单词首字母、二字母、三字母重复;
    • 词频重复基础上字母和连字符的比较;
    • 单词数不过百(50个);
    • 单词数过百(113个)。

    边界情况:

    • 单词数为0;
    • 单词数为100;
    • 调用排序方法之前单词词频已有序;
    • 调用排序方法前单词词频倒序排列。
      测试用例由于内容过多,下面贴出第一个测试用例的输入和预期输出:

    测试用例详情请见本项目Github的test文件夹上的测试用例清单result_writer_testcase.txt

    下面贴出ResultWriterTestCaseList.xlsx的截图:

    1.5测试运行和评价

    我在IntelliJ IDEA上用JUnit4单元测试框架(需要安装插件JUnitGenerator V2.0,JUnit插件已被默认安装)对自己负责实现的接口进行单元测试,编写单元测试脚本ResultWriterTest.java,下面给出关键部分代码和运行截图:

        /**
         * test method sort
         * @throws Exception
         */
        @Test
        public void testSort1() throws Exception {
            System.out.println("sort1");
            List<Map.Entry<String, Integer>> list = resultWriter.sort(result);
            String sortedContent="";
            Map.Entry<String,Integer> sortedMap;
            for(Iterator<Map.Entry<String,Integer>> it=list.iterator();it.hasNext();){
                sortedMap=it.next();
                sortedContent+=sortedMap.getKey()+" "+sortedMap.getValue()+"
    ";
            }
            sortedContent=sortedContent.substring(0,sortedContent.length()-1);
            assertEquals(sortedStr,sortedContent);
        }
    
        /**
         * test method output
         * @throws Exception
         */
        @Test
        public void testOutput1() throws Exception {
            System.out.println("output1");
            List<Map.Entry<String, Integer>> sortedList = resultWriter.sort(result);
            String string=resultWriter.output(sortedList);
            assertEquals(outContent,string);
        }
    

    运行截图:

    评价:自己的单元测试的等价类划分还不够完美,可能没覆盖所有情况,边界测试也可能没覆盖所有边界,对输入内容为空的测试没有覆盖,测试覆盖了常见的情况,能满足基本需求。通过测试我觉得被测模块的质量水平较高,该模块被其他模块调用不会出现问题。

    2.拓展任务

    2.1开发规范说明

    我选定的开发规范是《阿里巴巴Java开发手册》,选择了对命名风格、常量定义、代码格式、控制语句、注释规约方面的编程规约,用于检查代码的内容是本人(学号后5位:17121)写的ResultWriter.java。

    2.2交叉代码评审

    我选择的代码评审对象是组长田诗园(学号后5位:17122)写的而后经组员沃锦文(学号后5位:17103)修改的CommandParser.java,按照开发规范,评审结果如下:

    • 第9行,该类缺少@Author的注释信息,应添加作者的注释信息;
    • 第10、49、91、104、114行,方法开头不应该用单行注释,应该用javadoc形式的注释;
    • 第32、36、40行,未经定义的常量不应该直接出现在代码中,应该对常量进行定义;
    • 第69、71、80、98、100、124行,不应该使用行尾注释,应在前一行单独作为注释行注释;
    • 第97、99、106、123行,if没有大括号,应在if的判断条件后加大括号;
    • 第74行,及时清理不再使用的代码段或配置信息。

    2.3静态代码扫描

    我使用的扫描工具是Alibaba Java Coding Guidelines插件,下载地址为 https://plugins.jetbrains.com/plugin/10046-alibaba-java-coding-guidelines ,我在IntelliJ IDEA 2017.2.5使用该工具进行静态代码扫描,按照 https://github.com/alibaba/p3c/tree/master/idea-plugin [4]的教程安装使用该插件对自己写的ResultWriter.java进行静态代码检查。

    扫描结果:

    • 在if/else/for/while/do语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if (condition) statements;

    • 所有的覆写方法,必须加@Override注解。 反例:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

    • 不允许任何魔法值(即未经定义的常量)直接出现在代码中。

    • 所有的类都必须添加创建者信息。 说明:在设置模板时,注意IDEA的@author为${USER},而eclipse的@author为${user},大小写有区别,而日期的设置统一为yyyy/MM/dd的格式。

    • 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释。注意与代码对齐。

    • 类、类属性、类方法的注释必须使用javadoc规范,使用/**内容*/格式,不得使用//xxx方式和/*xxx*/方式。 说明:在IDE编辑窗口中,javadoc方式会提示相关注释,生成javadoc可以正确输出相应注释;在IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。

    运行截图:

    按照扫描结果并参考《阿里巴巴java开发手册》进行代码改进后的静态代码检查运行截图:

    2.4组内代码分析

    整个小组的代码存在一些共同的问题:

    • 代码没加作者信息;
    • 方法开头没使用javadoc形式的注释;
    • 常量未定义直接在代码中使用;
    • 大量使用行尾注释;
    • 未及时清理不用的代码段;
    • if判定条件后没写大括号。

    下面贴出静态代码检查工具运行截图:

    我们按照扫描的结果并参考《阿里巴巴java开发手册》改进代码,实现了比较好的代码规范。

    3.高级任务

    3.1测试数据集

    经过试验,我们发现当txt文件的字符数达到千万级别时能给程序带来压力,故我们小组创建了jQuery.txt作为测试数据集,大小:22.9 MB (24,025,398 字节),占用空间:22.9 MB (24,027,136 字节),包含24,025,398个字符,307,981行。

    我在自己的电脑(操作系统:64位 win10,处理器:Intel(R) Celeron(R) CPU N2920 @ 1.86GHz 1.86GHz,已安装的内存(RAM):8.00GB(7.87 GB 可用))的IntelliJ IDEA 2017.2.5对测试数据集进行测试,运行程序得到处理时长为4957ms。

    3.2同行评审过程描述

    1. 角色分工:
    • 主持人&记录员:田诗园
    • 讲解员:邱利光 沃锦文 王启萌 田诗园
    • 评审员:邱利光 沃锦文 王启萌 田诗园
    • 作者:邱利光 沃锦文 王启萌 田诗园
    1. 评审目的:
    • 评定代码对于性能需求的满足程度。
    • 对程序进行性能优化。
    1. 评审意见收集:
    • 代码并无明显缺陷。
    • 代码性能可以继续优化。
    1. 评审结果
    • 正则表达式效率较慢,可以用状态机来提取单词。
    • 考虑到程序对查找的性能要求比较高,单词统计我们采用HashMap来存储。当数据容量较少时其内部实现为一个链表,当数据量较大时,自动改用二叉树进行存储,有效提高了查询效率。
    • 输出单词时,改变原来的每个单词打开一次文件的做法,只打开一次文件,全部输出后关闭文件流,提高了IO速度。

    3.3性能分析

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

    3.4性能优化

    • 使用状态机代替正则式分词提高效率。
    • 把多次输出改为一次输出提高IO速度。

    3.5作业小结

    在软件开发中,软件测试很重要,特别是在团队合作中,自己写的代码要交给他人使用,应保证代码质量,要对自己写的代码进行单元测试,确保没有缺陷,项目出现问题也不会出现在自己写的代码上,保证代码质量能加快项目开发进度。

    参考文献链接

    [1] https://blog.csdn.net/u012730840/article/details/19699179
    [2] https://www.cnblogs.com/zhujiabin/p/6164826.html
    [3] https://www.cnblogs.com/Simeonwu/p/7565005.html
    [4] https://github.com/alibaba/p3c/tree/master/idea-plugin

  • 相关阅读:
    RESTful API 设计指南【转】
    一款 .NET 下的轻量级 REST 和 HTTP API 客户端
    Windows 服务开发框架介绍
    实例演示 kino.razor (前端 Javascript 模板工具,Razor 风格)的使用
    深入研究 蒋金楠(Artech)老师的 MiniMvc(迷你 MVC),看看 MVC 内部到底是如何运行的
    扩展 IEnumerable<T>,让它根据另一个集合的顺序来排列
    谷歌正式发布Google APIs Client Library for .NET
    身为码农,为12306说两句公道话
    12306外包给阿里巴巴/IBM到底是否可行?
    自定义一个叫 ReadOnlyXmlMembershipProvider 的 MembershipProvider,用 XML 作为用户储藏室
  • 原文地址:https://www.cnblogs.com/SSSGLQ/p/8748002.html
Copyright © 2011-2022 走看看