• 2020寒假作业(2/2)


    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzu/2020SpringW
    这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/2020SpringW/homework/10281
    这个作业的目标 考察需求分析,学习git、github
    作业正文 https://www.cnblogs.com/herokilito/p/12264891.html
    其他参考文献 ...

    1、GitHub

    • GitHub用户名

      herokilito

    • 本次作业仓库地址

      https://github.com/herokilito/InfectStatistic-main

    • 第一次作业相关仓库

      • Spring
        简介:MyBatis-Spting适配器,会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
      • Spring-framework
        简介:Spring框架,Spring提供了Java编程语言以外的所有所需内容,可用于为各种场景和体系结构创建企业应用程序。
      • mybatis-3
        简介:对象关系映射工具,简单性是MyBatis数据映射器的最大优势。
      • Spring-boot
        简介:Spring Boot使创建具有Spring动力的生产级应用程序和服务变得非常容易。
      • SpringCloudLearning
        简介:SpringCloud教程

    2、PSP表格

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划
    Estimate 估计这个任务需要多少时间 1200 1330
    Development 开发
    Analysis 需求分析 (包括学习新技术) 300 200
    Design Spec 生成设计文档 60 80
    Design Review 设计复审 10 20
    Coding Standard 代码规范 (为目前的开发制定合适的规范) 30 20
    Design 具体设计 150 100
    Coding 具体编码 420 540
    Code Review 代码复审 30 60
    Test 测试(自我测试,修改代码,提交修改) 120 200
    Reporting 报告
    Test Report 测试报告 20 20
    Size Measurement 计算工作量 10 10
    Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 30 20
    合计 1200

    3、分析与设计

    • 思路描述

      • 首先分离参数,判断传入了什么参数,记录各个参数的参数值。
      • 每种参数都设计一个方法来实现相应的功能。
      • 根据记录的参数按特定的顺序一个个执行相应的方法。
      • 在执行方法的过程发生未预期的错误直接结束进程并显示错误信息。
      • 数据可以用键值对的方式保存,如java中的map。
    • 程序设计

      • 代码组织
        • 方法划分
          有五种参数,根据参数类型划分成五个方法
          -log参数:dolog()
          -out参数:doOut()
          -date参数:doDate()
          -type参数:doType()
          -province参数:doProvince()
          日志中可能出现八种情况,根据不同情况划分不同的计算方法
          <省> 新增 感染患者 n人:increaseInf()
          <省> 新增 疑似患者 n人:increaseSus()
          <省1> 感染患者 流入 <省2> n人:infInflow()
          <省1> 疑似患者 流入 <省2> n人:susInflow()
          <省> 死亡 n人:dead()
          <省> 治愈 n人:cure()
          <省> 疑似患者 确诊感染 n人:diagnose()
          <省> 排除 疑似患者 n人:exclude()
        • 数据结构
          观察预期输出数据可知,使用某种数据结构即能保存省份名,又能保存省份数据便于简化编程,于是使用了java的Map接口。
          因为输出数据需要按拼音数据排序,于是选择了LinkedHashMap实例。
          LinkedHashMap<String,List> String为省份名,List为数据列表保存该省份四种数据,每个数据项用整型存储。
      • 主要函数流程图

        流程图

    4、主要代码

    public void execute(String[] args) throws Lib.Exit {
     if(args.length == 1){
         Lib.helpList();    //显示提示信息
         throw new Lib.Exit("请按照提示输入命令");
     }
     /*分离参数*/
     int i = 1;
     while (i < args.length) {
         switch (args[i]) {
             case "-log":
                 hasLog = true;
                 if (++i >= args.length) {  //如果-log后面没有给参数值
                     throw new Lib.Exit("-log参数缺少参数值");
                 }
                 logParam = args[i++];      //-log后面跟着的参数为-log的参数值
                 break;
             case "-out":
                 hasOut = true;
                 if (++i >= args.length) {  //如果-out后面没有给参数值
                     throw new Lib.Exit("-out参数缺少参数值");
                 }
                 outParam = args[i++];      //-out后面跟着的参数为-out的参数值
                 break;
             case "-date":
                 hasDate = true;
                 if (++i >= args.length) {  //如果-date后面没有给参数值
                     throw new Lib.Exit("-date参数缺少参数值");
                 }
                 dateParam = args[i++];     //-date后面跟着的参数为-date的参数值
                 break;
             case "-type":
                 hasType = true;
                 while (++i < args.length && !args[i].equals("-log") && !args[i].equals("-out") && !args[i].equals("-date")
                         && !args[i].equals("-province")) {   //-type的参数值范围
                     typeParams.add(args[i]);
                 }
                 break;
             case "-province":
                 hasProvince = true;
                 while (++i < args.length && !args[i].equals("-log") && !args[i].equals("-out") && !args[i].equals("-date")
                         && !args[i].equals("-type")) {       //-province的参数值范围
                     provinceParams.add(args[i]);
                 }
                 break;
             default:
                 throw new Lib.Exit(""" + args[i] + ""无法解析的参数");
         }
     }
     /*执行相应的方法*/
     if(!hasLog){  //log必须有
         throw new Lib.Exit("缺少-log参数");
     }
     if(!hasOut){  //out必须有
         throw new Lib.Exit("缺少-out参数");
     }
     if(!hasDate){  //如果没有data参数
         dateParam=new SimpleDateFormat("yyyy-MM-dd").format(new Date()); //当前日期
     }
     doLog(logParam);    //读取日志路径
     doDate(dateParam);   //读取日志路径下相应日期的日志
     if(hasType){
         doType(typeParams);   //需要输出的信息类型
     }
     if(hasProvince){
         doProvince(provinceParams);   //需要输出的省份疫情信息
     }
     doOut(outParam);  //输出到指定的路径
    }
    

    说明:判断传入的参数、参数值,调用相应的方法,具体请看程序注释。

    private void doDate(String date) throws Lib.Exit {
     List<File> logList = Lib.getLogFiles(logDirectory);
     DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
     Date paramDate;
     BufferedReader reader = null;
     try {
         paramDate = dateFormat.parse(date);
         List<Integer> nationalData = statistics.get("全国"); //全国数据
         for (File log : logList) {
             Date logDate = dateFormat.parse(log.getName().substring(0, log.getName().indexOf('.')));
             if(logDate.compareTo(paramDate) > 0) {  //判断日志文件的日期是否小于等于给定日期
                 continue;
             }
             reader = new BufferedReader(new InputStreamReader(new FileInputStream(log), StandardCharsets.UTF_8));
             String dataRow;
             while((dataRow = reader.readLine()) != null){
                 if(dataRow.startsWith("//")) { //忽略注释行
                     continue;
                 }
                 String[] data = dataRow.split(" ");  //分割数据行
                 if(!outProvince.contains(data[0])){
                     outProvince.add(data[0]);
                 }
                 List<Integer> provinceData = statistics.get(data[0]);   //当前行的省份数据
                 List<Integer> destProvince;   //用于处理流入
                 switch (data[1]) {
                     case INCREMENT:  //处理新增
                         if (data[2].equals(INFECTION_PATIENT)) {  //新增感染
                             increaseInf(nationalData, provinceData, Lib.parseData(data[3]));
                         } else {                                  //新增疑似
                             increaseSus(nationalData, provinceData, Lib.parseData(data[3]));
                         }
                         break;
                     case EXCLUDE:  //处理排除疑似
                         excludeSus(nationalData, provinceData, Lib.parseData(data[3]));
                         break;
                     case CURE:  //处理治愈
                         cure(nationalData,provinceData,Lib.parseData(data[2]));
                         break;
                     case DEAD:  //处理死亡
                         dead(nationalData,provinceData,Lib.parseData(data[2]));
                         break;
                     case INFECTION_PATIENT:  //处理感染患者流入
                         destProvince = statistics.get(data[3]);
                         infInflow(provinceData,destProvince,Lib.parseData(data[4]));
                         break;
                     case SUSPECTED_PATIENT:
                         if(data[2].equals(INFLOW)){   //处理疑似患者流入
                             destProvince = statistics.get(data[3]);
                             susInflow(provinceData,destProvince,Lib.parseData(data[4]));
                         } else if(data[2].equals(DIAGNOSE)) {  //处理确诊
                             diagnose(nationalData,provinceData,Lib.parseData(data[3]));
                         }
                         break;
                 }
             }
         }
     }catch (Exception e){
         throw new Lib.Exit(e.getMessage());
     }finally {
         try{
             if (reader != null) {
                 reader.close();
             }
         }catch (Exception e){
             e.printStackTrace();
         }
     }
    }
    

    说明:读取日志信息,判断日志内容属于哪一类,并调用相应的计算方法,具体请看程序注释。

    5、单元测试,单元测试覆盖率

    • 单元测试说明

      单元测试所用工具为IDEA的Junit5插件,测试所用日志数据为作业模板给的三个日志文件

    • 单元测试展示及测试结果

    @Test
    void testLogOut1() {   
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut1.txt" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut1
    说明:基本参数的测试,只有-log和-out参数

    @Test
    void testLogOut2() {
     String[] args = {
             "list" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut2.txt" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut2
    说明:基本参数的测试,测试参数顺序是否会影响结果

    @Test
    void testDate1() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut3.txt" ,
             "-date" , "2020-01-22"
     };
     InfectStatistic.main(args);
    }
    

    listOut3
    说明:-date参数的测试,测试日期在日志文件之内

    @Test
    void testDate2() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut4.txt" ,
             "-date" , "2020-2-1"
     };
     InfectStatistic.main(args);
    }
    

    listOut4
    说明:-date参数的测试,测试日期在日志文件之外

    @Test
    void testType1() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut5.txt" ,
             "-type" , "ip" , "sp" , "cure" , "dead" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut5
    说明:测试-type参数,包含全部-type参数值

    @Test
    void testType2() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut6.txt" ,
             "-type" , "cure" , "dead" , "ip" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut6
    说明:测试-type参数,包含部分-type参数值且参数值顺序改变

    @Test
    void testProvince1() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut7.txt" ,
             "-province" , "全国" , "福建" , "湖北" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut7
    说明:测试-province参数

    @Test
    void testProvince2() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut8.txt" ,
             "-province" , "全国" , "浙江" , "福建" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut8
    说明:测试-province参数

    @Test
    void testAll1() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut9.txt" ,
             "-date" , "2020-01-27" ,
             "-type" , "ip" , "sp" , "dead" , "cure" ,
             "-province" , "福建" , "浙江" , "河北" , "湖北"
     };
     InfectStatistic.main(args);
    }
    

    listOut9
    说明:测试全部参数

    @Test
    void testAll2() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut10.txt" ,
             "-date" , "2020-01-23" ,
             "-type" , "ip" , "dead" ,"cure" ,
             "-province" , "福建" , "浙江" , "河北" , "湖北"
     };
     InfectStatistic.main(args);
    }
    

    listOut10
    说明:测试全部参数

    @Test
    void testHelp() {
     String[] args = {
     };
     InfectStatistic.main(args);
    }
    

    listOut11
    说明:测试提示信息

    @Test
    void testListHelp() {
     String[] args = {
             "list" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut12
    说明:测试list命令的提示信息

    @Test
    void testUnknownCmdError() {
     String[] args = {
             "listt" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut11.txt" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut13
    说明:测试错误命令

    @Test
    void testUnknownParamError() {
     String[] args = {
             "list" ,
             "-loge" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut12.txt" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut14
    说明:测试list命令的错误参数

    @Test
    void testLackParamError1() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut15
    说明:测试必要参数缺少参数值

    @Test
    void testLackParamError2() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut16
    说明:测试缺少必要参数

    @Test
    void testUnknownType() {
     String[] args = {
             "list" ,
             "-log" , "D:/Java/InfectStatistic-main/test/log" ,
             "-out" , "D:/Java/InfectStatistic-main/test/result/listOut13.txt" ,
             "-type" , "确诊" , "sp" , "cure" ,
     };
     InfectStatistic.main(args);
    }
    

    listOut17
    说明:测试-type参数的错误参数值

    • 单元测试覆盖率

    覆盖率

    6、性能测试,性能优化

    • 性能测试截图及说明

      • 说明

        性能测试用了2020-01-20到2010-01-31共十二个日志文件,每个日志文件近万条数据,共约十二万条日志数据
        使用Jprofiler11进行分析

      • 测试结果

        1
        2

    • 性能优化

      • 分析

        由测试结果可以看出程序再doDate()这个方法耗时最久,于是优先优化这个方法。
        发现我在处理每一条日志数据的时候都计算了全国数据,这没有必要,只需要输出的时候计算全国数据就可以。于是我修改了计算方法,并增加了一个用于计算全国数据的方法。

      private void countNational() {
       	List<Integer> national = statistics.get("全国");
       	for (List<Integer> data : statistics.values()) {
              for (int i = 0 ; i < national.size() ; i ++){
              national.set(i,national.get(i) + data.get(i));
      	}
      }
      

      这个方法在doOut()输出之前调用。
      doDate()是一个个读取log文件,计算完一个文件再读取下一个,我想为何不能用多线程来完成这一步骤呢?把读取文件内容并计算的任务交给线程去处理,充分发挥多核处理器的并发性能。

      threadPool.submit(() -> { //创建新线程,加入线程池
          BufferedReader reader = null;
          try {
              reader = new BufferedReader(new InputStreamReader(new FileInputStream(log), StandardCharsets.UTF_8));
              String dataRow;
              while ((dataRow = reader.readLine()) != null) {
                  if (dataRow.startsWith("//")) { //忽略注释行
                      continue;
                  }
                  String[] data = dataRow.split(" ");  //分割数据行
                  if (!outProvince.contains(data[0])) {
                      outProvince.add(data[0]);
                  }
                  synchronized (statistics) {   //线程同步,给数据加锁
                      List<Integer> provinceData = statistics.get(data[0]);   //当前行的省份数据
                      List<Integer> destProvince;   //用于处理流入
                      switch (data[1]) {
                          case INCREMENT:  //处理新增
                             if (data[2].equals(INFECTION_PATIENT)) {  //新增感染
                                  increaseInf(provinceData, Lib.parseData(data[3]));
                              } else {                                  //新增疑似
                                  increaseSus(provinceData, Lib.parseData(data[3]));
                              }
                              break;
                          case EXCLUDE:  //处理排除疑似
                              excludeSus(provinceData, Lib.parseData(data[3]));
                              break;
                          case CURE:  //处理治愈
                              cure(provinceData, Lib.parseData(data[2]));
                              break;
                          case DEAD:  //处理死亡
                              dead(provinceData, Lib.parseData(data[2]));
                              break;
                          case INFECTION_PATIENT:  //处理感染患者流入
                              destProvince = statistics.get(data[3]);
                              infInflow(provinceData, destProvince, Lib.parseData(data[4]));
                              break;
                          case SUSPECTED_PATIENT:
                              if (data[2].equals(INFLOW)) {   //处理疑似患者流入
                                  destProvince = statistics.get(data[3]);
                                  susInflow(provinceData, destProvince, Lib.parseData(data[4]));
                              } else if (data[2].equals(DIAGNOSE)) {  //处理确诊
                                  diagnose(provinceData, Lib.parseData(data[3]));
                              }
                              break;
                      }
                  }
              }
          } catch (Exception e) {
              e.printStackTrace();
          } finally {
              if(reader != null) {
                  try {
                      reader.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      });
      threadPool.shutdown();
      while (!threadPool.isTerminated());   //等待所有线程执行完
      
      • 优化结果

        1
        2
        可以看到程序执行时间从1142ms优化到了972ms,文件数量越多的话优化效果越明显。由于要兼顾线程同步,耗费的时间并没有太大的改善,或许换成其他的线程加锁方式能改善许多。

    7、心路历程与收获

    刚开始看到作业的时候人是懵的,毕竟有太多知识是第一次接触到,比如PSP表格,单元测试等。后来去查资料,去认真的学习新的知识,我也对软件工程和项目开发过程也有了更多的了解。按照作业要求分析需求,搭建好了程序基本结构,发现看似复杂的任务也简单了起来。然后就是写代码,做测试,写代码,做测试。。。直到最终完成所有需求。开发中遇到了许多的bug,都在测试过程中很容易的解决掉了,也让我感受到在编程中测试发现bug解决起来比编程完成后的测试发现bug解决起来容易得多。最后性能测试的过程中使用了专门的性能分析工具,也让我更能看到代码中需要优化的部分,优化起来也方便的多。
    总的来说这次作业收获还是很大的,学到了更多项目开发的知识,学到了单元测试和性能测试工具的使用,学到了GitHub和简单git指令的使用。学无止境,希望下次作业能收获更多。

  • 相关阅读:
    linux下小知识点积累
    马斯洛需求层次理论
    tar命令的小经验
    shell 和c语言的区别
    使用vue实现的品牌列表简单小例子
    vue的基本代码以及常见指令
    MVC和MVVM
    CSS3幽灵
    Web版App,原生App,混合App的区别以及优缺点
    常见的sql操作
  • 原文地址:https://www.cnblogs.com/herokilito/p/12264891.html
Copyright © 2020-2023  润新知