软工实践寒假作业(2/2)

这个作业属于哪个课程 <班级的链接>
这个作业要求在哪里 <作业要求的链接>
这个作业的目标 GitHub 初使用、代码规范制定、需求分析、单元测试、覆盖率分析、性能分析
作业正文 https://www.cnblogs.com/gnulxj/p/12322336.html
其他参考文献 JUnit5JProfiler

一、GitHub 仓库地址

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

二、PSP 表格

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

三、解题思路描述

1. 解析命令行参数

首先分析了一下命令行参数的结构,参数是键值对的形式,自然想到用 Map 映射;由于一个参数后可以跟多个参数值,就把 List 作为映射中的值。程序后续需要再使用到命令行参数,因此将用一个数据结构将这里的 Map 包装起来,再提供一些接口供后续程序的使用,如判断参数是否存在,根据参数名获取参数值等必需接口。

2. 统计日志文件

读取文件使用了文件的输入输出,我专门设计了一个类用于读取指定的日志文件。

统计日志文件前需根据给定的日期过滤掉该日期之后的日志,在将剩下的日志文件列表交给日志处理器处理。

日志处理器依次读取每个日志文件,我首先想到的是直接 if else if esse 判断每行特殊的 token,但后来考虑到这种方式不易扩展,后续如果在增加一些新的情况,可能要修改不止一处代码,扩展性较差。于是我开始考虑通过正则表达式匹配,尽管在性能上相对较差,但扩展起来相当方便,只需要一行正则就可搞定一种情况。

匹配指定情况后,需要将该结果存储起来,供后续输出使用,于是我又设计了一个统计类,用于存储各个省份包含了4 种人群的人数,使用 Map<String, Stat> 将省份名与统计结果映射起来。

3. 根据命令行参数输出结果到指定文件

输出前先通过参数判断是否有指定输出省份,获取待输出省份的列表。

如果待输出省份种有包含“全国”则需要将把所有省份的统计结果累加,统计出相应结果。

最后输出前判断是否指定了输出患病人群的类别,无指定直接输出所有类别的患病情况;有指定需要按指定顺序输出。

四、设计实现过程

程序模块设计

程序模块

类图设计

类图

算法流程

算法流程

五、代码说明

  • 存储命令行参数的数据结构如下:

    class CommandArgs {
    
        private List<String> commandArgs = new ArrayList<>();
    
        private Map<String, List<String>> optionValuesMap = new HashMap<>();
    }
    
  • 代码中使用正则表达式匹配各个省份的统计情况,正则表达式设计如下:

    final static private String regex1 = "(^\\W+) 新增 感染患者 (\\d+)人";
    final static private String regex2 = "(^\\W+) 新增 疑似患者 (\\d+)人";
    final static private String regex3 = "(^\\W+) 感染患者 流入 (\\W+) (\\d+)人";
    final static private String regex4 = "(^\\W+) 疑似患者 流入 (\\W+) (\\d+)人";
    final static private String regex5 = "(^\\W+) 死亡 (\\d+)人";
    final static private String regex6 = "(^\\W+) 治愈 (\\d+)人";
    final static private String regex7 = "(^\\W+) 疑似患者 确诊感染 (\\d+)人";
    final static private String regex8 = "(^\\W+) 排除 疑似患者 (\\d+)人";
    
  • 取得正则表达式中字符串的方法如下,其中 regexGroup2 用于获取两个参数,regexGroup3 用于获取三个参数:

    public static List<String> regexGroup2(String soap, String regex) {
        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        final Matcher matcher = pattern.matcher(soap);
    
        if (matcher.find()) {
            List<String> result = new ArrayList<>();
            result.add(matcher.group(1));
            result.add(matcher.group(2));
            return result;
        }
        return null;
    }
    
    public static List<String> regexGroup3(String soap, String regex) {
        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        final Matcher matcher = pattern.matcher(soap);
        if (matcher.find()) {
            List<String> result = new ArrayList<>();
            result.add(matcher.group(1));
            result.add(matcher.group(2));
            result.add(matcher.group(3));
            return result;
        }
        return null;
    }
    
  • 统计类的数据结构如下:

    class ProvinceStat {
    
        private int numIP; // #infected
        private int numSP; // #suspected
        private int numCure; // #cured
        private int numDead; // #dead
    
        public ProvinceStat() {
            this.numIP = 0;
            this.numSP = 0;
            this.numCure = 0;
            this.numDead = 0;
        }
    }
    
  • Lib.parse(String[] args) 是算法主流程,首先使用 ArgsParser 解析命令行参数,然后根据命令行参数获取需要解析的 log 文件列表,将该列表交给 LogParser 解析得到各个省份的统计结果,最后通过 outputResult 输出。

    public void parse(String[] args) throws IOException, DataFormatException {
        this.args = args;
        this.commandArgs = ArgsParser.parse(args);
    
        String logDir = commandArgs.getOptionValues("log").get(0);
        String logDeadline = "9999-99-99";
        if (commandArgs.containsOption("date")) {
            logDeadline = commandArgs.getOptionValues("date").get(0);
        }
    
        String[] logFiles = getOlderFiles(logDir, logDeadline);
    
        for (int i = 0; i < logFiles.length; i++) {
            logFiles[i] = logDir + '\\' + logFiles[i];
        }
    
        LogParser logParser = new LogParser();
        this.provinceStatMap = logParser.parse(logFiles);
    
        outputResult();
    }
    
  • LogParser.parse 用于解析日志文件,解析完成后返回结果映射:

    for (String logFile : logFiles) {
    
        BufferedReader bufferedReader = new BufferedReader(new FileReader(logFile));
        String line;
        System.out.println("parsing file \"" + logFile + "\"...");
        while ((line = bufferedReader.readLine()) != null) {
            if (line.startsWith("//")) { // 跳过注释行
                continue;
            }
    
            List<String> result;
            if ((result = regexGroup2(line, regex1)) != null) {
                ProvinceStat provinceStat = getProvinceStat(result.get(0));
                provinceStat.incrNumIP(Integer.parseInt(result.get(1)));
            } else if ((result = regexGroup2(line, regex2)) != null) {
                // ...
            } el se if ((result = regexGroup3(line, regex3)) != null) {
                // ...
            } else if ((result = regexGroup3(line, regex4)) != null) {
                // ...
            } else if ((result = regexGroup2(line, regex5)) != null) {
                // ...
            } else if ((result = regexGroup2(line, regex6)) != null) {
                // ...
            } else if ((result = regexGroup2(line, regex7)) != null) {
                // ...
            } else if ((result = regexGroup2(line, regex8)) != null) {
                // ...
            } else {
                // exception
                System.out.println("exception");
                throw new DataFormatException();
            }
        }
        bufferedReader.close();
    }
    return provinceStatMap;
    

六、单元测试截图和描述

All Tests

Lib test

@Test01: args parser for log

args parse for log

用于测试对命令行参数 log 的解析是否正确。

@Test02: args parser for out

args parse for out

用于测试对命令行参数 out 的解析是否正确

@Test03: args parse for province

args parse for province

用于测试对命令行参数 province 的解析是否正确 (可以包含多个值)

@Test04: args parse for type

args parse for type

用于测试对命令行参数 type 的解析是否正确 (可以包含多个值)

@Test05: get older log file list

get older log file list

用于获取日期不超过指定日期的日志文件列表。

@Test06: decrease province IP Exception

decrease province IP exception

用于测试减少患病人员数量越界异常(OutOfBoundException):0 + 3 - 6 = -3 < 0。

@Test07: increase IP of province

increase IP of province

用于测试增加某个省份的患病数量:0 + 3 + 2 = 5。

@Test08: province migrate IP

province migrate IP

用于测试省份患病人员迁移:

省份统计 迁移前 迁移后
source 0 + 3 = 3 3 - 2 = 1
target 0 + 2 = 2 2 + 2 = 4

@Test09: province migrate SP

province migrate SP

用于测试省份疑似病人员迁移:

省份统计 迁移前 迁移后
source 0 + 3 = 3 3 - 2 = 1
target 0 + 2 = 2 2 + 2 = 4

@Test10: regex with 2 groups

regex with 2 groups

用于测试解析带两个组的正则表达式。

七、单元测试覆盖率优化和性能测试

Lib Test Coverage

可以看到 ProvinceStat 覆盖率还有替身空间,于是我清理了一些不可能用到的接口,如死亡患者减少、治愈患者减少等接口,之后再进行测试,结果如下:

Unit Test Coverage Opt

JProfiler 性能报告总览:

telemeries-overview

内存使用情况:

live memory

八、我的代码规范的链接

https://github.com/xjliang/InfectStatistic-main/blob/master/221701107/codestyle.md

九、心路历程与收获

博客连接 https://www.cnblogs.com/gnulxj/p/12322126.html

十、第一次作业中技术路线图相关的 5 个仓库

  1. Spring Boot 学习示例

    Spring Boot 使用的各种示例,以最简单、最实用为标准,此开源项目中的每个示例都以最小依赖,最简单为标准,帮助初学者快速掌握 Spring Boot 各组件的使用。

  2. VBlog

    V 部落是一个多用户博客管理平台,采用 Vue+SpringBoot 开发。项目演示地址: http://45.77.146.32:8081/index.html

  3. Spring MVC Showcase
    通过小而简单示例演示 Spring MVC Web 框架的功能。在回顾了这个展示之后,您应该对 Spring MVC 可以做什么有一个很好的了解,并了解它的易用性。

  4. Spring Integration Samples

    欢迎使用 Spring Integration Samples 存储库,该存储库提供了 50 多个示例来帮助您学习 Spring Integration。为了简化您的体验,Spring Integration 示例分为 4 个不同的类别:basic,intermediate, advanced, applications。

  5. CS-Notes

    📚 技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计、Java、Python、C++


(Done)

posted @ 2020-02-17 16:26  gnulxj  阅读(283)  评论(1编辑  收藏  举报