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

这个作业属于哪个课程
这个作业要求在哪里
这个作业的目标 GitHub 初使用、代码规范制定、需求分析、单元测试、覆盖率分析、性能分析
作业正文 就是本文
其他参考文献 JUnit5、JProfiler

一、GitHub 仓库地址

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

二、PSP 表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 45
Estimate 估计这个任务需要多少时间 40 30
Development 开发 40 45
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 报告 100 45
Test Report 测试报告 100 120
Size Measurement 计算工作量 40 50
Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划 50 50
合计 865 985

三、解题思路描述

解析命令行参数

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

统计日志文件

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

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

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

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

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

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

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

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

四、设计实现过程

程序模块设计

软工实践寒假作业(2/2)_第1张图片

类图设计

软工实践寒假作业(2/2)_第2张图片

算法流程

软工实践寒假作业(2/2)_第3张图片

五、代码说明

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

    class CommandArgs {
    
        private List commandArgs = new ArrayList<>();
    
        private Map> 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 regexGroup2(String soap, String regex) {
        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        final Matcher matcher = pattern.matcher(soap);
    
        if (matcher.find()) {
            List result = new ArrayList<>();
            result.add(matcher.group(1));
            result.add(matcher.group(2));
            return result;
        }
        return null;
    }
    
    public static List regexGroup3(String soap, String regex) {
        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
        final Matcher matcher = pattern.matcher(soap);
        if (matcher.find()) {
            List 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 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

软工实践寒假作业(2/2)_第4张图片

@Test01: args parser for log

软工实践寒假作业(2/2)_第5张图片

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

@Test02: args parser for out

软工实践寒假作业(2/2)_第6张图片

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

@Test03: args parse for province

软工实践寒假作业(2/2)_第7张图片

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

@Test04: args parse for type

软工实践寒假作业(2/2)_第8张图片

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

@Test05: get older log file list

软工实践寒假作业(2/2)_第9张图片

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

@Test06: decrease province IP Exception

软工实践寒假作业(2/2)_第10张图片

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

@Test07: increase IP of province

软工实践寒假作业(2/2)_第11张图片

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

@Test08: province migrate IP

软工实践寒假作业(2/2)_第12张图片

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

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

@Test09: province migrate SP

软工实践寒假作业(2/2)_第13张图片

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

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

@Test10: regex with 2 groups

软工实践寒假作业(2/2)_第14张图片

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

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

软工实践寒假作业(2/2)_第15张图片

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

软工实践寒假作业(2/2)_第16张图片

JProfiler 性能报告总览:

软工实践寒假作业(2/2)_第17张图片

内存使用情况:

软工实践寒假作业(2/2)_第18张图片

八、我的代码规范的链接

代码规范

九、心路历程与收获

这次寒假正好在家里好好沉下心完成了这次寒假作业,在还没有认真阅读完需求之前,我就尝试动手完成了这个作业的代码。
之后才看到要学习一下构建之法,于是我认真地把前三章都阅读了一遍,顺便把学习笔记录在一篇博客 构建之法学习笔记(第一章~第三章)里。
在重新编写这次作业的代码前,我先写好了 PSP 规划表,说实话,很多部分当时还是云里雾里的,只能凭自己的感觉就定下了一个时间。编码前先设计好所有类图花了我不少时间。
实践证明,软件工程需要清晰的代码逻辑思维,如何在编码前设计好各个模块之间的关联?编码时如何做好单测?单测应该什么时候做?这些问题我之前都没有考虑过,以至于我最后单独拿出一段时间来重新设计需要单测的模块。经历了这次惨痛的教训,我认识到事前做好规划的重要性,无论是生活还是学习,是从事编程还是其他行业,这一点我觉得都是需要事前考虑清楚的。
同时,软件工程不像 ACM 之类的需要打比赛,拼编码速度(打字员),它是一门工程学科,需要的是像科学家一样思考,做好系统分析和设计,不是 copy and paste 那样简单的事情。
麻雀虽小,五脏俱全,虽然这次作业仍然只是个人的一个小作业,但是它也用到了许多技术,如单元测试,版本控制等,这些是游离于语言之外的技能,也是需要掌握的。
将来可能我们很多人会从事程序员这个行业,但是,我们大部分人都不是天才,不会去做那种很高深的项目,尽管我们将来接触的只是一些简单的项目,但还是需要一些扩展技能的。
很多时候我们可能会停滞不前,认为自己到达了自己的天花板,但是程序员这个行业是需要不断学习的,永远有值得我们学习的东西,只有怀着学徒的心态,才能越走越远。

十、第一次作业中技术路线图相关的 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)

你可能感兴趣的:(软工实践寒假作业(2/2))