軟工實踐寒假作業(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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM