天天看點

軟工實踐寒假作業(2/2)

這個作業屬于哪個課程 <班級的連結>
這個作業要求在哪裡 <作業要求的連結>
這個作業的目标 GitHub 初使用、代碼規範制定、需求分析、單元測試、覆寫率分析、性能分析
作業正文 https://www.cnblogs.com/gnulxj/p/12322336.html
其他參考文獻 JUnit5、JProfiler

一、GitHub 倉庫位址

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

二、PSP 表格

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 40 30
Estimate 估計這個任務需要多少時間
Development 開發 465 590
Analysis 需求分析 (包括學習新技術) 60 45
Design Spec 生成設計文檔
Design Review 設計複審
Coding Standard 代碼規範 (為目前的開發制定合适的規範)
Design 具體設計
Coding 具體編碼 120 200
Code Review 代碼複審
Test 測試(自我測試,修改代碼,送出修改) 150
Reporting 報告 190 220
Test Report 測試報告 100
Size Measurement 計算工作量 50
Postmortem & Process Improvement Plan 事後總結,并提出過程改進計劃
合計 695 850

三、解題思路描述

1. 解析指令行參數

首先分析了一下指令行參數的結構,參數是鍵值對的形式,自然想到用 Map 映射;由于一個參數後可以跟多個參數值,就把 List 作為映射中的值。程式後續需要再使用到指令行參數,是以将用一個資料結構将這裡的 Map 包裝起來,再提供一些接口供後續程式的使用,如判斷參數是否存在,根據參數名擷取參數值等必需接口。

2. 統計日志檔案

讀取檔案使用了檔案的輸入輸出,我專門設計了一個類用于讀取指定的日志檔案。

統計日志檔案前需根據給定的日期過濾掉該日期之後的日志,在将剩下的日志檔案清單交給日志處理器處理。

日志處理器依次讀取每個日志檔案,我首先想到的是直接 if else if esse 判斷每行特殊的 token,但後來考慮到這種方式不易擴充,後續如果在增加一些新的情況,可能要修改不止一處代碼,擴充性較差。于是我開始考慮通過正規表達式比對,盡管在性能上相對較差,但擴充起來相當友善,隻需要一行正則就可搞定一種情況。

比對指定情況後,需要将該結果存儲起來,供後續輸出使用,于是我又設計了一個統計類,用于存儲各個省份包含了4 種人群的人數,使用 Map<String, Stat> 将省份名與統計結果映射起來。

3. 根據指令行參數輸出結果到指定檔案

輸出前先通過參數判斷是否有指定輸出省份,擷取待輸出省份的清單。

如果待輸出省份種有包含“全國”則需要将把所有省份的統計結果累加,統計出相應結果。

最後輸出前判斷是否指定了輸出患病人群的類别,無指定直接輸出所有類别的患病情況;有指定需要按指定順序輸出。

四、設計實作過程

程式子產品設計

軟工實踐寒假作業(2/2)

類圖設計

軟工實踐寒假作業(2/2)

算法流程

軟工實踐寒假作業(2/2)

五、代碼說明

  • 存儲指令行參數的資料結構如下:
    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

軟工實踐寒假作業(2/2)

@Test01: args parser for log

軟工實踐寒假作業(2/2)

用于測試對指令行參數

log

的解析是否正确。

@Test02: args parser for out

軟工實踐寒假作業(2/2)

out

的解析是否正确

@Test03: args parse for province

軟工實踐寒假作業(2/2)

province

的解析是否正确 (可以包含多個值)

@Test04: args parse for type

軟工實踐寒假作業(2/2)

type

@Test05: get older log file list

軟工實踐寒假作業(2/2)

用于擷取日期不超過指定日期的日志檔案清單。

@Test06: decrease province IP Exception

軟工實踐寒假作業(2/2)

用于測試減少患病人員數量越界異常(OutOfBoundException):0 + 3 - 6 = -3 < 0。

@Test07: increase IP of province

軟工實踐寒假作業(2/2)

用于測試增加某個省份的患病數量:0 + 3 + 2 = 5。

@Test08: province migrate IP

軟工實踐寒假作業(2/2)

用于測試省份患病人員遷移:

省份統計 遷移前 遷移後
source 0 + 3 = 3 3 - 2 = 1
target 0 + 2 = 2 2 + 2 = 4

@Test09: province migrate SP

軟工實踐寒假作業(2/2)

用于測試省份疑似病人員遷移:

@Test10: regex with 2 groups

軟工實踐寒假作業(2/2)

用于測試解析帶兩個組的正規表達式。

七、單元測試覆寫率優化和性能測試

軟工實踐寒假作業(2/2)

可以看到 ProvinceStat 覆寫率還有替身空間,于是我清理了一些不可能用到的接口,如死亡患者減少、治愈患者減少等接口,之後再進行測試,結果如下:

軟工實踐寒假作業(2/2)

JProfiler 性能報告總覽:

軟工實踐寒假作業(2/2)

記憶體使用情況:

軟工實踐寒假作業(2/2)

八、我的代碼規範的連結

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)

繼續閱讀