這個作業屬於哪個課程 | <班級的鏈接> |
---|---|
這個作業要求在哪里 | <作業要求的鏈接> |
這個作業的目標 | 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 | 估計這個任務需要多少時間 | 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
@Test01: args parser for log
用於測試對命令行參數 log
的解析是否正確。
@Test02: args parser for out
用於測試對命令行參數 out
的解析是否正確
@Test03: args parse for province
用於測試對命令行參數 province
的解析是否正確 (可以包含多個值)
@Test04: args parse for type
用於測試對命令行參數 type
的解析是否正確 (可以包含多個值)
@Test05: get older log file list
用於獲取日期不超過指定日期的日志文件列表。
@Test06: decrease province IP Exception
用於測試減少患病人員數量越界異常(OutOfBoundException):0 + 3 - 6 = -3 < 0。
@Test07: increase IP of province
用於測試增加某個省份的患病數量:0 + 3 + 2 = 5。
@Test08: province migrate IP
用於測試省份患病人員遷移:
省份統計 | 遷移前 | 遷移后 |
---|---|---|
source | 0 + 3 = 3 | 3 - 2 = 1 |
target | 0 + 2 = 2 | 2 + 2 = 4 |
@Test09: province migrate SP
用於測試省份疑似病人員遷移:
省份統計 | 遷移前 | 遷移后 |
---|---|---|
source | 0 + 3 = 3 | 3 - 2 = 1 |
target | 0 + 2 = 2 | 2 + 2 = 4 |
@Test10: regex with 2 groups
用於測試解析帶兩個組的正則表達式。
七、單元測試覆蓋率優化和性能測試
可以看到 ProvinceStat 覆蓋率還有替身空間,於是我清理了一些不可能用到的接口,如死亡患者減少、治愈患者減少等接口,之后再進行測試,結果如下:
JProfiler 性能報告總覽:
內存使用情況:
八、我的代碼規范的鏈接
https://github.com/xjliang/InfectStatistic-main/blob/master/221701107/codestyle.md
九、心路歷程與收獲
博客連接 https://www.cnblogs.com/gnulxj/p/12322126.html
十、第一次作業中技術路線圖相關的 5 個倉庫
-
Spring Boot 使用的各種示例,以最簡單、最實用為標准,此開源項目中的每個示例都以最小依賴,最簡單為標准,幫助初學者快速掌握 Spring Boot 各組件的使用。
-
V 部落是一個多用戶博客管理平台,采用 Vue+SpringBoot 開發。項目演示地址: http://45.77.146.32:8081/index.html
-
Spring MVC Showcase
通過小而簡單示例演示 Spring MVC Web 框架的功能。在回顧了這個展示之后,您應該對 Spring MVC 可以做什么有一個很好的了解,並了解它的易用性。 -
歡迎使用 Spring Integration Samples 存儲庫,該存儲庫提供了 50 多個示例來幫助您學習 Spring Integration。為了簡化您的體驗,Spring Integration 示例分為 4 個不同的類別:basic,intermediate, advanced, applications。
-
📚 技術面試必備基礎知識、Leetcode、計算機操作系統、計算機網絡、系統設計、Java、Python、C++
(Done)