寒假作业(2/2)——疫情统计

寒假作业(2/2)——疫情统计

1 作业描述

这个作业属于哪个课程https://edu.cnblogs.com/campus/fzu/2020SpringW/
这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/2020SpringW/homework/10281
这个作业的目标 最近新型冠状病毒疫情严重,全国人民都感到担忧,迫切希望能够及时了解到病毒最新的情况,作为IT学子,大家请你帮忙开发一个疫情统计程序。
作业正文 https://github.com/cunkoulaocai/InfectStatistic-main
其他参考文献 菜鸟教程,廖雪峰的博客,bilibili相关课程,github,学长参考博文

2 PSP表格

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

3 解题思路描述

在看到题目之后,我首先阅读了一遍要求,又看了一下助教给的相关提示,我首先需要梳理一下大致的流程:

读取命令->解析参数->根据参数执行命令->读取日志文件获得要存储的内容->保存日志到文件

在这个过程中命令要是可扩展的,参数最好也是也扩展的,用正则表达式匹配字符串最好也比较容易扩展,与此同时要有一些简单的办法来处理这些问题,根据助教的提示我准备尝试使用命令模式和责任链模式.

在完成以上简单的思考之后我准备先学习额外的知识包括设计模式,git的使用,对于git的使用我准备在知乎上寻找对应入门教程,对于设计模式我准备在bilibili上寻找对应教程.除此之外我准备使用idea作为我的开发工具,当然idea的一些高级技巧我也要学习使用,有很多博客提供参考.

经过以上准备之后我开始思考如何做出这道作业,我刚开始思考的时候是传进来的参数必须要改变它的结构封装成为一个map的形式,方便根据参数名找到参数值,并且这个类不对参数做过多的操作,更进一步的操作由具体的命令来执行,这里只起到一个简单的封装的功能.在一开始我还没想好怎么处理除了log,out之外的参数,所以我暂时先放一边,准备先完成从从日志中读取内容并处理之后写入文件上(参数先固定住),这样一来以便完成简单的功能.

根据助教的提示,对于匹配每一行我选择使用正则表达式,并使用责任链模式,以便减少if-else结构的使用,对于写入文件,我希望我最终得到的可以写入文件的信息是规整的只需要根据命令的参数就可以写入文件,所以我选择了map结构存放我的结果,我选择map的键为省份名,map的值为一个Integer数组,全部初始化为0,代表了感染人数,疑似人数,治愈人数,死亡人数,这样上面正则表达式模块也可以返回这种格式,处理起来就比较方便.

最后对于命令的执行和处理日志文件之间的衔接我当时还没想好,准备先写完这两个模块之后在衔接.具体的细节可能得等到实现的时候才发现有些错误.

4 设计实现过程

整个流程为 main->ArgParser->ListCommand->AllInformation->RegXXX->AllInformation

4.1 参数解析器ArgParser

这个类就是将String[] args类型的数组变换为Map<String,List<String>>类型的数据结构,这样就可以简单地通过参数名得到参数值,里面还有一个打印参数信息的方法,能够进行简单的调试.关键函数流程图为,

 

 

4.2 命令执行ListCommand

这个模块是整个程序中第二个重要的类,这个类的设计采用了设计模式中的命令模式,有一个接口Command,两个类实现了这个接口一个是,NoCommand,另一个是ListCommand,还有一个类为CommandReceiver,里面有命令对应的方法,例如本程序中有list命令,那么该类中就有一个list方法用来执行list命令,这样以后有其他命令需要扩展的时候只需要实现Command接口,并在CommandReceiver里面定义对应的方法即可,流程图如下

 

 

4.3 正则表达式RegXXX

这个模块主要是用来实现对于日志文件中的每一行进行处理,通过把日志文件中的每一行交给正则表达式模块处理,返回一个可以被AllInformation处理的数据格式,在这里由于由8个正则表达式,如果使用if-else模块会比较繁琐,所以我决定使用责任链模式,由于有8个正则表达式类,并且每一个类的内容都大同小异,因此这里只展示一个类的内容

虽然我已经实现了责任链模式来处理但是我发现处理了8个正则表达式之后会出现一个栈溢出的现象,在查找了资料发现有可能是正则表达式是采用递归实现的,所以出现了栈溢出,我尝试了一些方法但是无法解决,因此暂时放弃了使用责任链模式,使用简单的if-else模式,逻辑图如下:

 

 

4.4 写入文件AllInformation

这个类应该是最重要的一个类了里面至少有以下几个方法,能够读取日志文件内容并对其进行处理,能够判断每一行应该有哪一个正则表达式处理,将处理后的信息写入到文件中

他有私有变量,以及8个正则表达式

Map<String,Integer[]> info;
String REGEX1 = "(\\S+) 新增 感染患者 (\\d+)人";
String REGEX2 = "(\\S+) 新增 疑似患者 (\\d+)人";
String REGEX3 = "(\\S+) 感染患者 流入 (\\S+) (\\d+)人";
String REGEX4 = "(\\S+) 疑似患者 流入 (\\S+) (\\d+)人";
String REGEX5 = "(\\S+) 死亡 (\\d+)人";
String REGEX6 = "(\\S+) 治愈 (\\d+)人";
String REGEX7 = "(\\S+) 疑似患者 确诊感染 (\\d+)人";
String REGEX8 = "(\\S+) 排除 疑似患者 (\\d+)人";

整个类的逻辑如下

 

 

5 代码说明

运行方法:下载代码到本地之后进入221701139/src目录之下,打开命令行,执行命令即可

java InfectStatistic list -log 221701139/log -out D:/output.txt

注意目录必须是221701139/log,输出目录可以随便,其他参数根据自己需要设置即可,系统会按照下面语句执行函数

public static void main(String[] args) throws IOException {
   Lib lib = new Lib(args);
   lib.execute();
}

我们按上面提到的整体流程和程序的运行过程来展示关键代码:

5.1 参数解析器ArgParser

参数解析器里面关键的就是它的构造函数,将String[] args,变换为Map类型,代码如下

public ArgParser(String[] args) {
   this.command = args[0];
   this.arguments = new HashMap<>();
   // 根据参数列表创建参数映射
   for (int i = 1;i<args.length;i++) {
       // 如果以-开头代表是参数名
       if (args[i].startsWith("-")) {
           String key = args[i];
           int j = i+1;
           List<String> vals = new ArrayList<>();

           while (!args[j].startsWith("-")) {
               vals.add(args[j]);
               j++;
               if (j==args.length) {
                   break;
              }
          }
           arguments.put(key,vals);
      }
  }
}

程序很简单,举个例子即可明白,对于命令

list -log xxx -out aaa -type ip sp

会变为以下格式的map

-log -> [xxx]
-out -> [aaa]
-type -> [ip,sp]

5.2 命令执行ListCommand

这个模块包括了一个接口Command,两个类实现了这个接口一个是,NoCommand,另一个是ListCommand,还有一个类为CommandReceiver,里面有命令对应的方法,例如本程序中有list命令,那么该类中就有一个list方法用来执行list命令,Command接口只有一个函数execute()所以就不介绍了,主要介绍ListCommand,和CommandReceiver

ListCommand的构造函数为

public ListCommand(String[] args,CommandReceiver receiver) {
   this.args = args;
   this.receiver = receiver;
}

由于listCommand方法实现了Command接口一次复写方法

@Override
public void execute() {
   receiver.list(args);
}

除此之外它还有一个重要的方法judgeType用来判断type参数并进行转换

public static List<Integer> judgeType(ArgParser argParser) {
   List<String> types = argParser.getVals("-type");
   List<Integer> newTypes = new ArrayList<>();

   // 如果没有给type参数就按顺序输出
   if (types==null) {
       newTypes.add(0);
       newTypes.add(1);
       newTypes.add(2);
       newTypes.add(3);
       return newTypes;
  }
   // 如果有给type参数就按type参数值的顺序输出
   for (String type:types) {
       if (type.equals("ip")) {
           newTypes.add(0);
      }
       else if (type.equals("sp")) {
           newTypes.add(1);
      }
       else if (type.equals("cure")) {
           newTypes.add(2);
      }
       else if (type.equals("dead")){
           newTypes.add(3);
      }
  }
   return newTypes;
}

例如type参数值为 ip cure,那么会被替换为[0,2]这个被替换的值就会被之后的程序利用

CommandReceiver目前里面只有一个函数就是list函数它用来执行具体的命令,实现如下

public void list(String[] args) {
   // 封装参数
   ArgParser argParser = new ArgParser(args);
   argParser.printArg();
   // 根据参数调用不同的类
   String root = argParser.getVals("-log").get(0);
   String outFileName = argParser.getVals("-out").get(0);
   List<String> dates = argParser.getVals("-date");

   //执行完成,输出提示信息
   File path = new File(root);
   // 读取文件
   File[] files = path.listFiles();
   String filePath = null;

   if (dates!=null) {
       // 如果提供了要处理的文件的日期名
       for (File file:files) {
           if (file.getName().contains(dates.get(0))) {
               filePath = path + "/" + file.getName();
          }
           break;
      }
  }
   else {
       // 如果没有给定日期就使用文件的最后一个
       File lastFile = files[files.length-1];
       filePath = path+"/"+lastFile.getName();
  }

   AllInformation allInformation = new AllInformation();
   allInformation.processInfo(filePath);
   allInformation.writeIntoLog(outFileName,argParser);

   allInformation.printInfo();
}

这个函数里面主要做了两件事一个是根据提供的date参数修改相关的文件名信息,第二个就是将参数传递给AllInformation类,由该类执行最后的工作

修改date相关参数主要是看命令执行时有没有给date参数,如果有给date参数就拼接处指定的路径,如果没有给date参数读取目标文件夹下最后一个文件。

5.3 正则表达式RegXXX

这个类主要的函数为process,由于这个相关的类有8个并且都很简单,因此我只展示其中的一个,具体实现请看下面

public class RegEight {
   // 处理的正则表达式为
   // (\\S+) 排除 疑似患者 (\\d+)人
   // 返回一个省份对应信息的映射
   public Map<String, Integer[]> process(String line) {
       Map<String,Integer[]> map = new HashMap<>();
       String[] result = line.split(" ");
       // 获取省份
       String province = result[0];
       // 获取人数
     Integer population = Integer.parseInt(result[3].substring(0,result[3].length()-1));
       // 封装成map
       Integer[] num = {0,-population,0,0};
       map.put(province,num);
       map.put("全国",new Integer[]{0,-population,0,0});
       return map;
  }
}

对于输入语句福建 排除 疑似患者 2人,返回的数据为

全国 -> {0,-2,0,0}
福建 -> {0,-2,0,0}

写入文件AllInformation

这个类是最重要的一个类,我们按照程序中的处理流程来讲解这个类,首先是构造函数,暴力构造

public AllInformation() {
   info = new LinkedHashMap<>();
   info.put("全国",new Integer[]{0,0,0,0});
   info.put("安徽",new Integer[]{0,0,0,0});
   info.put("北京",new Integer[]{0,0,0,0});
   info.put("重庆",new Integer[]{0,0,0,0});
   info.put("福建",new Integer[]{0,0,0,0});
   info.put("甘肃",new Integer[]{0,0,0,0});
   info.put("广东",new Integer[]{0,0,0,0});
   info.put("广西",new Integer[]{0,0,0,0});
   info.put("贵州",new Integer[]{0,0,0,0});
   info.put("海南",new Integer[]{0,0,0,0});
   info.put("河北",new Integer[]{0,0,0,0});
   info.put("河南",new Integer[]{0,0,0,0});
   info.put("黑龙",new Integer[]{0,0,0,0});
   info.put("湖北",new Integer[]{0,0,0,0});
   info.put("湖南",new Integer[]{0,0,0,0});
   info.put("吉林",new Integer[]{0,0,0,0});
   info.put("江苏",new Integer[]{0,0,0,0});
   info.put("江西",new Integer[]{0,0,0,0});
   info.put("辽宁",new Integer[]{0,0,0,0});
   info.put("内蒙",new Integer[]{0,0,0,0});
   info.put("宁夏",new Integer[]{0,0,0,0});
   info.put("青海",new Integer[]{0,0,0,0});
   info.put("山东",new Integer[]{0,0,0,0});
   info.put("山西",new Integer[]{0,0,0,0});
   info.put("陕西",new Integer[]{0,0,0,0});
   info.put("上海",new Integer[]{0,0,0,0});
   info.put("四川",new Integer[]{0,0,0,0});
   info.put("天津",new Integer[]{0,0,0,0});
   info.put("西藏",new Integer[]{0,0,0,0});
   info.put("新疆",new Integer[]{0,0,0,0});
   info.put("云南",new Integer[]{0,0,0,0});
   info.put("浙江",new Integer[]{0,0,0,0});
}

这个我不知道怎么能更加简洁的构造,虽然可以用循环但是也差不了多少

第二个流程就是根据文件名处理文件processInfo

public void processInfo(String filePath) {
   try {
       // 准备输入流
       FileInputStream fis = new FileInputStream(filePath);
       InputStreamReader inputStreamReader = new InputStreamReader(fis);
       BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
       String line;
       Map<String,Integer[]> lineInfo;
       // 读取文件的每一行
       while((line = bufferedReader.readLine())!=null && !line.startsWith("//")) {
           System.out.println(line);
           lineInfo = judgeReg(line);

           // 设置信息
           Set<String> keys = lineInfo.keySet();
           this.setting(lineInfo);
      }
  }
   catch (IOException e) {
       e.printStackTrace();
  }
}

这个函数以一行一行的读取日志文件,在通过judgeReg函数判断之后(下面会介绍)会转交给RegXXX模块,之后通过setting函数给这个类的私有变量info设置处理完的信息

接下来就是上面用到的,judgeReg函数

 public Map<String,Integer[]> judgeReg(String line) {
    int regNo = 0;
    Map<String,Integer[]> lineInfo = new HashMap<>();
    Pattern pattern1 = Pattern.compile(REGEX1);
    Pattern pattern2 = Pattern.compile(REGEX2);
    Pattern pattern3 = Pattern.compile(REGEX3);
    Pattern pattern4 = Pattern.compile(REGEX4);
    Pattern pattern5 = Pattern.compile(REGEX5);
    Pattern pattern6 = Pattern.compile(REGEX6);
    Pattern pattern7 = Pattern.compile(REGEX7);
    Pattern pattern8 = Pattern.compile(REGEX8);

    Matcher matcher1 = pattern1.matcher(line);
    Matcher matcher2 = pattern2.matcher(line);
    Matcher matcher3 = pattern3.matcher(line);
    Matcher matcher4 = pattern4.matcher(line);
    Matcher matcher5 = pattern5.matcher(line);
    Matcher matcher6 = pattern6.matcher(line);
    Matcher matcher7 = pattern7.matcher(line);
    Matcher matcher8 = pattern8.matcher(line);

    if(matcher1.find()) {
        lineInfo = new RegOne().process(line);
    }
    else if (matcher2.find()) {
        lineInfo = new RegTwo().process(line);

    }
    else if (matcher3.find()) {
        lineInfo = new RegThree().process(line);

    }
    else if (matcher4.find()) {
        lineInfo = new RegFour().process(line);

    }
    else if (matcher5.find()) {
        lineInfo = new RegFive().process(line);
    }
    else if (matcher6.find()) {
        lineInfo = new RegSix().process(line);
    }
    else if (matcher7.find()) {
        lineInfo = new RegSeven().process(line);
    }
    else if (matcher8.find()) {
        lineInfo = new RegEight().process(line);
    }
    return lineInfo;
}

逻辑很简单就不做过多介绍,之所以没用责任链是因为,责任链模式使用的时候出现了我暂时解决不了的bug,因此先放弃使用.

接下来是setting函数

// 通过reg类返回的map信息设置要写入的文件信息
public void setting(Map<String,Integer[]> lineInfo) {
   // 得到要写入的键
   Set<String> keys = lineInfo.keySet();
   for (String key:keys) {
       // 得到要写入的整数信息
       Integer[] newInfo = lineInfo.get(key);
       // 得到原始的整数信息
       Integer[] oriInfo = info.get(key);
       for (int i = 0;i<newInfo.length;i++) {
           oriInfo[i] = oriInfo[i]+newInfo[i];
      }
       info.put(key,oriInfo);
  }
}

这个函数通过在processInfo函数中获得的信息来设置自己的私有变量info,如果传入的数据为

全国 -> {0,-2,0,0}
福建 -> {0,-2,0,0}

原本的数据格式为

全国 -> {1,2,0,0}
福建 -> {1,2,3,4}

那么结果就为

全国 -> {1,0,0,0}
福建 -> {1,0,3,4}

最后就是将得到的信息写入文件的函数writeIntoLog

public void writeIntoLog(String logPath, ArgParser argParser) {
   // 经过上面处理已经获得了全部的要写入日志的信息
   // 需要输出的类型
   List<Integer> indexTypes = ListCommand.judgeType(argParser);
   List<String>  types = new ArrayList<>();
   types.add("感染患者");
   types.add("疑似患者");
   types.add("治愈");
   types.add("死亡");
   // 需要输出的省份名
   List<String> outProvinces = argParser.getVals("-province");
   try {
       FileOutputStream fos = new FileOutputStream(logPath);
       // 键为省份信息,第一个为全国,其他为按照拼音顺序
       Set<String> provinces = info.keySet();
       for (String province:provinces) {
           Integer[] populations = info.get(province);
           StringBuilder line = new StringBuilder(province);
           if (outProvinces!=null) {
               if (outProvinces!=null && outProvinces.contains(province)) {
                   for (Integer index:indexTypes) {
                       line.append(" ").append(types.get(index)).append(populations[index]).append("人");
                  }
                   line.append("\n");
                   fos.write(line.toString().getBytes());
              };
          }
           else {
               if (populations[0]!=0 || populations[1]!=0 || populations[2]!=0 || populations[3]!=0) {
                   for (Integer index:indexTypes) {
                       line.append(" ").append(types.get(index)).append(populations[index]).append("人");
                  }
                   line.append("\n");
                   
                   // 写入信息
                   fos.write(line.toString().getBytes());
              }
          }
      }
       String endLine = "// 该文档并非真实数据,仅供测试使用\n";
       fos.write(endLine.getBytes());
  }
   catch (IOException e) {
       e.printStackTrace();
  }
}

整个函数有两个大的分支,一个是如果参数有提供province选项,那么即便要输出的信息全为0也会输出,如果没有提供province选项那么全为0的信息就不会输出

由于每一行对应的信息都为类似

{"福建",[0,1,0,0]}

配合StringBuilder就可生成要写入的语句,只不过这个我还没有想到更好的通过参数来处理信息的办法,最后需要的信息就会输出。整个比较主要的流程就到此为止

6 单元测试截图和描述。

6.1 ArgParser类测试

这个类需要测试的方法就构造函数,测试它是否能够正常将数组变为映射

测试方法如下我们先来测试一组简单的


@Test
public void testArgParser() {
   String[] arguments = {"list","-log","D:/InfectStatistic-main/221701139/log", "-out", "D:/output.txt", "-date", "2020-01-23","-province","福建","湖北","四川"};
   ArgParser argParser = new ArgParser(arguments);
   argParser.printArg();
   List<String> results = new ArrayList<>();
   results.add("福建");
   results.add("湖北");
   results.add("四川");
   List<String> vals = argParser.getVals("-province");
   for (int i = 0;i<2;i++) {
       String val = vals.get(i);
       String result = results.get(i);
       assertEquals("值不相等",val,result);
  }
}

 

 

第二个测试案例

@Test
public void testArgParser() {
   String[] arguments = {"list","-log","D:/InfectStatistic-main/221701139/log", "-out", "D:/output.txt", "-date", "2020-01-23",
                         "-province","福建","湖北","四川","-type","sp","cure"};
   ArgParser argParser = new ArgParser(arguments);
   argParser.printArg();
   List<String> results = new ArrayList<>();
   results.add("sp");
   results.add("cure");
   List<String> vals = argParser.getVals("-type");
   for (int i = 0;i<2;i++) {
       String val = vals.get(i);
       String result = results.get(i);
       assertEquals("值不相等",val,result);
  }
}

 

 

 

ListCommand类测试

测试judgeType类,这个类用来将argParser类的type参数附带的参数值改变为汉字描述的有顺序的值

代码为

@Test
public void testJudgeType() {
   String[] arguments = {"list","-log","D:/InfectStatistic-main/221701139/log",
                         "-out", "D:/output.txt",
                         "-date", "2020-01-23",
                         "-province","福建","湖北","四川",
                         "-type","sp","cure","ip"};
   ArgParser argParser = new ArgParser(arguments);
   argParser.printArg();
   CommandReceiver receiver = new CommandReceiver();

   ListCommand listCommand = new ListCommand(arguments,receiver);
   List<String> list = listCommand.judgeType(argParser);
   System.out.println(list);
}

6.2 CommandReceiver测试

该类是命令模式中的接收者用来执行不同的命令,在这个程序中我们暂时只有一个命令list,所以我们只需要测试这个方法,不过由于这个函数就是实际上整个函数的调用入口,所以测试这个函数相当于对于整个程序测试:

我们主要测试不同的参数

@Test
public void testList() {
   String[] arguments = {"list","-log","221701139/log", "-out", "D:/output.txt"};
   CommandReceiver commandReceiver = new CommandReceiver();
   commandReceiver.list(arguments);
}
@Test
public void testList() {
   String[] arguments = {"list","-log","221701139/log", "-out", "D:/output.txt", "-date", "2020-01-22","-province","福建","湖北","四川","-type","sp","cure"};
   CommandReceiver commandReceiver = new CommandReceiver();
   commandReceiver.list(arguments);
}
@Test
public void testList() {
   String[] arguments = {"list","-log","221701139/log", "-out", "D:/output.txt","-province","福建","湖北","四川","-type","sp","cure"};
   CommandReceiver commandReceiver = new CommandReceiver();
   commandReceiver.list(arguments);
}
@Test
public void testList() {
   String[] arguments = {"list","-log","221701139/log", "-out", "D:/output.txt","-type","sp","cure"};
   CommandReceiver commandReceiver = new CommandReceiver();
   commandReceiver.list(arguments);
}

6.3 RegXXX类测试

该类将每一行日志信息,经过处理之后会返回Map<String,Integer[]>格式的map,该map会在后续使用到,由于有8个正则表达式,因此相关类也有8个,由于每一个类里面只有一个方法process,并且都很简单,因此只测试其中容易出错的类

第一个是测试RegThree类,主要测试返回值,测试函数如下

public class RegThreeTest extends TestCase {
   @Test
   public void testProcess() {
       String line = "湖北 感染患者 流入 福建 2人";
       RegThree regThree = new RegThree();
       Map<String, Integer[]> lineInfo = regThree.process(line);
       Integer[] integers = lineInfo.get("湖北");
       Integer[] results = {-2,0,0,0};
       for (int i = 0;i<integers.length;i++) {
           assertEquals("不相等",integers[i],results[i]);
      }
  }
}

测试结果正确

第二个是RegSeven类,测试函数如下

public class RegSevenTest extends TestCase {
   @Test
   public void testProcess() {
       String line = "福建 疑似患者 确诊感染 23人";
       RegSeven regThree = new RegSeven();
       Map<String, Integer[]> lineInfo = regThree.process(line);
       Integer[] integers = lineInfo.get("福建");
       Integer[] results = {23,-23,0,0};
       for (int i = 0;i<integers.length;i++) {
           assertEquals("不相等",integers[i],results[i]);
      }
  }
}

经过测试结果正确.

6.4 AllInformation类测试

AllInformation是个很重要的类,几乎每个方法都需要测试,我们首先需要测试构造函数,该构造函数不需要参数

初始化一个Map,每个键为省份,值为长度为4的数组,对应感染,疑似,治愈和死亡人数并全部初始化为0,测试函数如下

@Test
public void testAllInformation() {
   AllInformation allInformation = new AllInformation();
   allInformation.printInfo();
}

结果为

全国 0 0 0 0
安徽 0 0 0 0
北京 0 0 0 0
重庆 0 0 0 0
福建 0 0 0 0
甘肃 0 0 0 0
广东 0 0 0 0
广西 0 0 0 0
贵州 0 0 0 0
海南 0 0 0 0
河北 0 0 0 0
河南 0 0 0 0
黑龙 0 0 0 0
湖北 0 0 0 0
湖南 0 0 0 0
吉林 0 0 0 0
江苏 0 0 0 0
江西 0 0 0 0
辽宁 0 0 0 0
内蒙 0 0 0 0
宁夏 0 0 0 0
青海 0 0 0 0
山东 0 0 0 0
山西 0 0 0 0
陕西 0 0 0 0
上海 0 0 0 0
四川 0 0 0 0
天津 0 0 0 0
西藏 0 0 0 0
新疆 0 0 0 0
云南 0 0 0 0
浙江 0 0 0 0

 

setting方法,该方法用来设置AllInformation里面的info信息,该信息将来会被写入到输出文件中,测试方法为

@Test
public void testSetting() {
   AllInformation allInformation = new AllInformation();
   Map<String,Integer[]> lineInfo = new HashMap<>();
   lineInfo.put("福建",new Integer[]{1,2,3,4});
   allInformation.setting(lineInfo);
   allInformation.printInfo();
}

 

 

processInfo方法:该方法会将传入的文件名的每一行进行处理,得到要写入文件的数据之后通过调用上面的setting设置信息,这个方法根据传入的文件路径来修改info信息,测试方法如下

@Test
public void testProcessInfo() {
   AllInformation allInformation = new AllInformation();
   allInformation.processInfo("221701139/log/2020-01-22.log.txt");
   allInformation.printInfo();
}

测试结果如下

全国 15 22 2 1
安徽 0 0 0 0
北京 0 0 0 0
重庆 0 0 0 0
福建 5 7 0 0
甘肃 0 0 0 0
广东 0 0 0 0
广西 0 0 0 0
贵州 0 0 0 0
海南 0 0 0 0
河北 0 0 0 0
河南 0 0 0 0
黑龙 0 0 0 0
湖北 10 15 2 1
湖南 0 0 0 0
吉林 0 0 0 0
江苏 0 0 0 0
江西 0 0 0 0
辽宁 0 0 0 0
内蒙 0 0 0 0
宁夏 0 0 0 0
青海 0 0 0 0
山东 0 0 0 0
山西 0 0 0 0
陕西 0 0 0 0
上海 0 0 0 0
四川 0 0 0 0
天津 0 0 0 0
西藏 0 0 0 0
新疆 0 0 0 0
云南 0 0 0 0
浙江 0 0 0 0

judgeReg方法:该方法用来判断每一行对应哪个正则表达式,原本来说是不用这个方法改用责任链模式的,但是在责任链模式中出现了正则表达式栈溢出,并且我还无法解决,因此不得不放弃,值得采用普通的if-else方法,希望以后能找到解决的办法,由于这个方法的返回值跟RegXXX类中的process是一致的,所以测试方法也差不多

@Test
public void testJudgeReg() {
   String line5 = "湖北 感染患者 流入 福建 2人";

   AllInformation allInformation = new AllInformation();
   Map<String, Integer[]> map = allInformation.judgeReg(line5);

   Integer[] integers = map.get("福建");
   Integer[] results = {2,0,0,0};
   for (int i = 0;i<integers.length;i++) {
       assertEquals("不相等",integers[i],results[i]);
  }
}

成功交由第三个正则表达式处理

writeIntoLog方法:将上述两步骤处理的信息根据参数条件写入到文件中是整个程序的最后一步,测试函数如下

@Test
public void testWriteIntoLog() {
   String out = "D:/out.txt";
   String[] arguments = {"list","-log","D:/InfectStatistic-main/221701139/log", "-out", "D:/output.txt","-province","福建","湖北","四川","-type","sp","cure"};
   ArgParser argParser = new ArgParser(arguments);
   AllInformation allInformation = new AllInformation();
   allInformation.writeIntoLog(out,argParser);
}

测试结果如下

 

 

@Test
public void testWriteIntoLog() {
   String out = "D:/out.txt";
   String[] arguments2 = {"list","-log","221701139/log","-out","D:/ListOut2.txt","-date","2020-01-22","-province","福建","河北"};
   ArgParser argParser = new ArgParser(arguments2);
   AllInformation allInformation = new AllInformation();
   allInformation.writeIntoLog(out,argParser);
}

测试结果如下

 

 

@Test
public void testWriteIntoLog() {
   String out = "D:/out.txt";
   String[] arguments3 = {"list","-log","221701139/log","-out","D:/ListOut3.txt","-date","2020-01-27","-province","全国","福建","湖北"};
   ArgParser argParser = new ArgParser(arguments3);
   AllInformation allInformation = new AllInformation();
   allInformation.writeIntoLog(out,argParser);
}

测试结果如下

 

 

7 单元测试覆盖率优化和性能测试,性能优化截图和描述。

代码覆盖率

 

 

性能测试截图

 

 

我尝试优化了一下但是并没有什么用,甚至结果还更糟了,看来学习如何优化还有很长的路要走.

8 代码规范

我的代码规范链接:代码规范

9 技术路线图相关仓库

用java实现的数据结构与算法,涵盖了如排序,搜索,动态规划等算法,以及用java实现的数据结构,帮助你复习数据结构与算法

包括了springboot1.x和springboot2.0的基础教程,项目中提供了实例用以操作,是spring boot入门的选择之一

用java实现的微信小程序开发后端程序,是非常好的练习项目,在学习了spring boot基础之后就需要实现写一些项目练练手

若想在java的路上走的更远,那么就必须了解jvm虚拟机,学习了之后能够让你了解java的底层,写出更好的代码

当你想要找到java工作却又不知道从何复习知识点,那么这份java面试指南涵盖了大部分java面试中的知识点,帮助你更全面的复习java

10 心路历程与收获

本次开发过程中花的时间最长的并不是编码的过程,而是github的使用,构建项目模块使其更合理,单元测试等步骤.不得不承认,测试过程中帮我发现了的在开发过程中的简单测试没有发现的bug,整个耗时很长,但是整个程序的bug明显跟少了.事实上由于git的相关概念还不熟练是的我代码还重构了一次,浪费了很多编码时间和测试的时间,除此之外构建项目模块我也想了很久的时间,包括如果解耦合,如何使用设计模式,各个模块之间如何结合,这些耗费了我多时间去思考以及优化,虽然整个过程不免遇到了许多的问题,有些解决了有些没解决,但总是收获良多.特别是设计模式的使用让我见识到了软件设计中的奥妙.

在阅读完构建之法之后,我才意识到,写出高质量的仅仅是一个软件工程师的入门而已,软件的设计,软件的测试,软件的维护,文档的撰写,代码的优化,查找资料,团队合作这些在某种意义上都要比写出及高质量的代码还要重要,仅仅是个人开发,是基本不可能开发出大型的软件的,利用github进行团队合作是重中之重.我也希望能够通过这门课程的锻炼来增加自己的能力.

本次作业也出现了许多意想不到的问题,例如需求文档的理解偏差导致的进度被拖慢,可以见到认真阅读需求,明确相关问题,能大幅的提高开发速度,减少bug,写出更高质量的软件.

 

 

posted @ 2020-02-17 20:34  cybersa  阅读(251)  评论(1编辑  收藏  举报