1.概述
項目 | 內容 |
---|---|
這個作業屬於哪個課程 | <班級的鏈接> |
這個作業要求在哪里 | <作業要求的鏈接> |
這個作業的目標 | Git、GitHub使用,代碼規范意識,一定的程序設計能力(基於命令行),PSP,以及單元測試和性能分析改進。 |
作業正文 | 見正文 |
其他參考文獻 | 無 |
2.Git Hub倉庫連接
https:// github.com/ maouou /InfectStatistic-main -> 點擊進入
3.PSP
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 60 | 25 |
Estimate | 估計這個任務需要多少時間 | 910 | 1125 |
Development | 開發 | 480 | 720 |
Analysis | 需求分析 (包括學習新技術) | 120 | 120 |
Design Spec | 生成設計文檔 | 30 | 40 |
Design Review | 設計復審 | 30 | 15 |
Coding Standard | 代碼規范 (為目前的開發制定合適的規范) | 10 | 15 |
Design | 具體設計 | 30 | 300 |
Coding | 具體編碼 | 420 | 360 |
Code Review | 代碼復審 | 30 | 60 |
Test | 測試(自我測試,修改代碼,提交修改) | 120 | 50 |
Reporting | 報告 | 60 | 90 |
Test Repor | 測試報告 | 30 | 20 |
Size Measurement | 計算工作量 | 10 | 10 |
Postmortem & Process Improvement Plan | 事后總結, 並提出過程改進計划 | 20 | 45 |
合計 | 970 | 1150 |
4.解題思路
-
實話實說,第二次作業看到以后我是蒙圈的
然后下定決心開始做的時候,我是按照作業要求上一步一步開始下手的。
之前沒有接觸過github,所以在這方面花了蠻久的時間去熟悉學習他,剛開始很痛苦,無從下手。之后干脆就根據github提供的學習手冊->https://guides.github.com/activities/hello-world/來學習,用了很大一部分時間,才解決了Git和Github的使用問題。 -
之后開始理需求,這個過程還是比較輕松加愉快的,整體文檔要求比較明確,讀完之后,最初的設計思路是這樣的:
5.設計實現過程
在實際編碼設計過程中,對很多類進行了調整,也新增了很多需要的實體類和工具類。
例:
- 添加統計各省人數的數據結構 -> PeopleType
- 添加Command接口、CommandGet類、InfectSatisticCommandSet類、CommandList類用於實現命令行控制以及方便之后功能擴展
工作流程:
6.代碼說明
關鍵函數
-
CommandGet. GetCommand(String[] CommandSource)
用於提取命令行中的參數並存儲在該類的數據結構中,返回值為void
public void GetCommand(String[] CommandSource) { int CommandLength = CommandSource.length; if(CommandSource[0].equals("list")) this.Command = 1; else System.out.println(CommandSource[0] + " 不能被解析為合法命令"); for(int i = 1 ; i < CommandLength ; i++) { if(CommandSource[i].equals("-log")) { this.Log = CommandSource[i+1]; i++; } else if(CommandSource[i].equals("-out")) { this.Out = CommandSource[i+1]; i++; } else if(CommandSource[i].equals("-date")) { this.IsDate = true; this.Date = CommandSource[i+1]; i++; } else if(CommandSource[i].equals("-province")) { this.IsProvince = true; int j = 0; while(i+1<CommandSource.length&&!Pattern.matches("-.*", CommandSource[i+1])) { this.Province[j] = CommandSource[i+1]; i++; j++; } } else if(CommandSource[i].equals("-type")) { this.IsType = true; int j = 0; while(i+1<CommandSource.length && !Pattern.matches("-.*", CommandSource[i+1])) { this.Type[j] = CommandSource[i+1]; i++; j++; } } else System.out.println("命令中包含不合法參數" + CommandSource[i]); }
-
DateCompareTool.getFileName(String Date,String LogLocation)
根據log參數和date參數提供的信息,篩選出需要處理的文件路徑集合,返回值為List
public static List<String> getFileName(String Date,String LogLocation) { List<String> FileName = new ArrayList<String>(); String[] FileList=new File(LogLocation).list(); int MaxMonth = 0; int MaxDay = 0; //下述判斷用於解決是否帶 -date參數問題 if(Date.equals("")) { for(int i = 0; i < FileList.length; i++) FileName.add(LogLocation + "\\" + FileList[i]); return FileName; } else { boolean DateOutbound = false; String[] SplitDate = Date.split("-"); String MonthDate = SplitDate[1]; String DayDate = SplitDate[2]; int TargetMonth = Integer.valueOf(MonthDate); int TargetDay = Integer.valueOf(DayDate); for(int i = 0;i < FileList.length ;i++) { String[] SplitFileName = FileList[i].split("-"); String Month = SplitFileName[1]; String Day = SplitFileName[2].split(".log")[0]; int FileMonth = Integer.valueOf(Month); int FileDay = Integer.valueOf(Day); if(FileMonth > MaxMonth) MaxMonth = FileMonth; if(FileDay > MaxDay) MaxDay = FileDay; if(FileMonth < TargetMonth) FileName.add(LogLocation + "\\" + FileList[i]); if(FileMonth == TargetMonth && FileDay <= TargetDay) FileName.add(LogLocation + "\\" + FileList[i]); } if(TargetMonth > MaxMonth || (TargetMonth == MaxMonth && TargetDay > MaxDay)) DateOutbound = true; if(DateOutbound) { System.out.println("日期超過范圍"); System.exit(1); } return FileName; } }
-
DataGet.getData(String line)
根據每一行的信息,匹配8種信息模式,切割字符串,獲得原始數據存儲在該類的數據結構中,返回值void
public void getData(String line) { String Type1 = "\\W+ 新增 感染患者 \\d+人"; String Type2 = "\\W+ 新增 疑似患者 \\d+人"; String Type3 = "\\W+ 感染患者 流入 \\W+ \\d+人"; String Type4 = "\\W+ 疑似患者 流入 \\W+ \\d+人"; String Type5 = "\\W+ 死亡 \\d+人"; String Type6 = "\\W+ 治愈 \\d+人"; String Type7 = "\\W+ 疑似患者 確診感染 \\d+人"; String Type8 = "\\W+ 排除 疑似患者 \\d+人"; if(Pattern.matches(Type1, line)) this.Type = 1; if(Pattern.matches(Type2, line)) this.Type = 2; if(Pattern.matches(Type3, line)) this.Type = 3; if(Pattern.matches(Type4, line)) this.Type = 4; if(Pattern.matches(Type5, line)) this.Type = 5; if(Pattern.matches(Type6, line)) this.Type = 6; if(Pattern.matches(Type7, line)) this.Type = 7; if(Pattern.matches(Type8, line)) this.Type = 8; String[] SplitLine = line.split(" "); if(this.Type == 1 || this.Type == 2) { this.Province1 = SplitLine[0]; this.Number = Integer.valueOf(SplitLine[3].split("人")[0]); } if(this.Type == 3 || this.Type == 4) { this.Province1 = SplitLine[0]; this.Province2 = SplitLine[3]; this.Number = Integer.valueOf(SplitLine[4].split("人")[0]); } if(this.Type == 5 || this.Type == 6) { this.Province1 = SplitLine[0]; this.Number = Integer.valueOf(SplitLine[2].split("人")[0]); } if(this.Type == 7 || this.Type == 8) { this.Province1 = SplitLine[0]; this.Number = Integer.valueOf(SplitLine[3].split("人")[0]); } }
-
StatisticResult.Statistic(DataGet data)
根據傳入的原始數據進行數據統計,獲得每個省的人數信息,存儲在該類的數據結構中,返回值void
public void Statistic(DataGet data) { if(data.Type == 1) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); StatisticLink.get(ProvinceIndex_1).Comfirmed += data.Number; StatisticLink.get(0).Comfirmed += data.Number; ProvinceOccur[ProvinceIndex_1] = true; } if(data.Type == 2) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); StatisticLink.get(ProvinceIndex_1).Suspected += data.Number; StatisticLink.get(0).Suspected += data.Number; ProvinceOccur[ProvinceIndex_1] = true; } if(data.Type == 3) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); int ProvinceIndex_2 = ProvinceIndex.indexOf(data.Province2); StatisticLink.get(ProvinceIndex_1).Comfirmed -= data.Number; StatisticLink.get(ProvinceIndex_2).Comfirmed += data.Number; ProvinceOccur[ProvinceIndex_1] = true; ProvinceOccur[ProvinceIndex_2] = true; } if(data.Type == 4) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); int ProvinceIndex_2 = ProvinceIndex.indexOf(data.Province2); StatisticLink.get(ProvinceIndex_1).Suspected -= data.Number; StatisticLink.get(ProvinceIndex_2).Suspected += data.Number; ProvinceOccur[ProvinceIndex_1] = true; ProvinceOccur[ProvinceIndex_2] = true; } if(data.Type == 5) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); StatisticLink.get(ProvinceIndex_1).Dead += data.Number; StatisticLink.get(0).Dead += data.Number; StatisticLink.get(ProvinceIndex_1).Comfirmed -= data.Number; StatisticLink.get(0).Comfirmed -= data.Number; ProvinceOccur[ProvinceIndex_1] = true; } if(data.Type == 6) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); StatisticLink.get(ProvinceIndex_1).Healed += data.Number; StatisticLink.get(0).Healed += data.Number; StatisticLink.get(ProvinceIndex_1).Comfirmed -= data.Number; StatisticLink.get(0).Comfirmed -= data.Number; ProvinceOccur[ProvinceIndex_1] = true; } if(data.Type == 7) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); StatisticLink.get(ProvinceIndex_1).Comfirmed += data.Number; StatisticLink.get(0).Comfirmed += data.Number; StatisticLink.get(ProvinceIndex_1).Suspected -= data.Number; StatisticLink.get(0).Suspected -= data.Number; ProvinceOccur[ProvinceIndex_1] = true; } if(data.Type == 8) { int ProvinceIndex_1 = ProvinceIndex.indexOf(data.Province1); StatisticLink.get(ProvinceIndex_1).Suspected -= data.Number; StatisticLink.get(0).Suspected -= data.Number; ProvinceOccur[ProvinceIndex_1] = true; } }
-
FileHandleTool.HandleFile(String DeadLine,String LogLocation,String[] Province, String[] Type)
根據log的值和deadline的值調用getFileName獲取待處理文件后,按行讀入,並將每行信息調用getData和Statistic函數統計結果,再按照province和type參數的值生成輸出結果。返回值List
public static List<String> HandleFile(String DeadLine,String LogLocation,String[] Province, String[] Type) { List<String> FileNames = DateCompareTool.getFileName(DeadLine,LogLocation); StatisticResult Result = new StatisticResult(); for(int i = 0 ; i < FileNames.size() ; i ++) { String FilePath = FileNames.get(i); try { FileInputStream Is = new FileInputStream(FilePath); InputStreamReader Isr = new InputStreamReader(Is,"utf-8"); BufferedReader Br = new BufferedReader(Isr); String line; try { while((line = Br.readLine()) != null) { if(line.equals("")) continue; if(Pattern.matches("//.*", line)) continue; else { DataGet Use = new DataGet(); Use.getData(line); Result.Statistic(Use); } } } catch(IOException e) { e.printStackTrace(); System.err.println("讀取一行文件錯誤"); } } catch(FileNotFoundException e) { e.printStackTrace(); System.err.println("文件路徑錯誤,找不到指定文件"); } catch (UnsupportedEncodingException e1) { // TODO 自動生成的 catch 塊 e1.printStackTrace(); } } if(Province[0].equals("")&&Type[0].equals("")) { for(int i = 0 ; i < Result.ProvinceOccur.length; i++) { if(Result.ProvinceOccur[i]) { OutResult.add(Result.ProvinceIndex.get(i) + " 感染患者" + Result.StatisticLink.get(i).Comfirmed + "人" + " 疑似患者" + Result.StatisticLink.get(i).Suspected + "人" + " 治愈" + Result.StatisticLink.get(i).Healed + "人" + " 死亡" + Result.StatisticLink.get(i).Dead + "人"); } } OutResult.add("// 該文檔並非真實數據,僅供測試使用"); } if(Province[0].equals("") && !Type[0].equals("")) { for(int i = 0 ; i < Result.ProvinceOccur.length; i++) { if(Result.ProvinceOccur[i]) { String Data = Result.ProvinceIndex.get(i); for(int j = 0 ; j < 4 ; j ++) { if(Type[j].equals("")) break; else if(Type[j].equals("ip")) Data += " 感染患者" + Result.StatisticLink.get(i).Comfirmed + "人"; else if(Type[j].equals("sp")) Data += " 疑似患者" + Result.StatisticLink.get(i).Suspected + "人"; else if(Type[j].equals("cure")) Data += " 治愈" + Result.StatisticLink.get(i).Healed + "人"; else if(Type[j].equals("dead")) Data += " 死亡" + Result.StatisticLink.get(i).Dead + "人"; } OutResult.add(Data); } } OutResult.add("// 該文檔並非真實數據,僅供測試使用"); } if(!Province[0].equals("") && Type[0].equals("")) { for(int i = 0 ; i < 32; i++) Result.ProvinceOccur[i] = false; for(int i = 0 ; i < 32; i++) { if(Province[i].equals("")) break; Result.ProvinceOccur[Result.ProvinceIndex.indexOf(Province[i])] = true; } for(int i = 0 ; i < Result.ProvinceOccur.length; i++) { if(Result.ProvinceOccur[i]) { OutResult.add(Result.ProvinceIndex.get(i) + " 感染患者" + Result.StatisticLink.get(i).Comfirmed + "人" + " 疑似患者" + Result.StatisticLink.get(i).Suspected + "人" + " 治愈" + Result.StatisticLink.get(i).Healed + "人" + " 死亡" + Result.StatisticLink.get(i).Dead + "人"); } } OutResult.add("// 該文檔並非真實數據,僅供測試使用"); } else { for(int i = 0 ; i < 32; i++) Result.ProvinceOccur[i] = false; for(int i = 0 ; i < 32; i++) { if(Province[i].equals("")) break; Result.ProvinceOccur[Result.ProvinceIndex.indexOf(Province[i])] = true; } for(int i = 0 ; i < Result.ProvinceOccur.length; i++) { if(Result.ProvinceOccur[i]) { String Data = Result.ProvinceIndex.get(i); for(int j = 0 ; j < 4 ; j ++) { if(Type[j].equals("")) break; else if(Type[j].equals("ip")) Data += " 感染患者" + Result.StatisticLink.get(i).Comfirmed + "人"; else if(Type[j].equals("sp")) Data += " 疑似患者" + Result.StatisticLink.get(i).Suspected + "人"; else if(Type[j].equals("cure")) Data += " 治愈" + Result.StatisticLink.get(i).Healed + "人"; else if(Type[j].equals("dead")) Data += " 死亡" + Result.StatisticLink.get(i).Dead + "人"; } OutResult.add(Data); } } OutResult.add("// 該文檔並非真實數據,僅供測試使用"); } return OutResult; }
-
OutputControlTool.ProductInfectStatistic(String DeadLine, String LogLocation,String ResultLocation,String[] Province,String[] Type)
調用HandleFile來獲取結果,並將結果逐條存儲到out參數所指定的目標文件中。返回值void
public static void ProductInfectStatistic(String DeadLine, String LogLocation,String ResultLocation,String[] Province,String[] Type) { File TargetFile = new File(ResultLocation); if(TargetFile.exists()) { System.out.println("該日志文檔已存在,請修改目標文件名"); return ; } else { try { TargetFile.createNewFile(); } catch (IOException e) { System.err.println("該目標文件無法新建,請重試"); e.printStackTrace(); } } List<String> Result = FileHandleTool.HandleFile(DeadLine,LogLocation,Province,Type); try { OutputStream out = new FileOutputStream(TargetFile); BufferedWriter rd = new BufferedWriter(new OutputStreamWriter(out,"utf-8")); for(int i = 0; i < Result.size() ; i ++) rd.write(Result.get(i) + "\n"); rd.close(); out.close(); } catch(IOException e){ System.err.println("文件寫入錯誤"); e.printStackTrace(); } System.out.println(ResultLocation + "文件已生成"); }
7.單元測試截圖和描述
-
缺少必要的out參數
java InfectStatistic list -log D:
-
缺少必要的log參數
java InfectStatistic list -out D:\
-
測試-date、-log、-out參數
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log\ -out D:\Java\InfectStatistic-main\example\result\Out1.txt -date 2020-01-22
-
測試-province、-log、-out參數
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\Out2.txt -date 2020-01-22 -province 福建 河北
-
測試-type、-province、-log、-out參數
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\Out3.txt -date 2020-01-23 -type cure dead ip -province 全國 浙江 福建
-
日期超出范圍
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\Out5.txt -date 2020-02-23
-
測試只有-log、-out參數,統計全部數據
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\Out4.txt
-
測試相同-out參數即目標文件已存在
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\Out4.txt
-
測試一個合法但不存在日志的日期
java InfectStatistic list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\Out5.txt -date 2020-01-25
8.單元測試覆蓋率優化和性能測試,性能優化截圖和描述。
-
覆蓋率測試
list -log D:\Java\InfectStatistic-main\example\log -out D:\Java\InfectStatistic-main\example\result\out6.txt -date 2020-01-27
分析:
- FileHandleTool覆蓋較少主要是因為產生輸出的時候,受province和type參數影響,產生了四個判斷分支,因此覆蓋較少
- CommandGet覆蓋率不高主要是因為缺少province和type參數,因此提取參數時很多判斷沒有覆蓋到
- DateCompareTool主要是因為獲取待處理文件時,非法處理較多,因此影響了覆蓋率
-
性能測試
- 程序中,較多地用到了ArrayList,其底層用數組實現,故char[]使用頻率很高。
- 程序中嵌套較多,性能會比較差一些
- 頻繁使用到切割字符串存貯比較,會影響耗時
-
優化
暫時沒有什么好的優化方案。
9.代碼規范的鏈接
10.心路歷程與收獲
真的剛要開始准備做這次作業的時候,內心世界是崩潰的。開始在心里覺得這次作業涉及面很廣,感覺知識點又很多,整體很雜亂。再加之自己之前也沒有github的使用經驗。所以一開始從在哪里着手都不知道。之后好在助教發了一篇關於此次作業的一些提示和引導。於是我就從github的學習着手,一步一步學習使用各項功能,之后看懂了示例文件的結構,並成功地fork進了我的倉庫和本地。至於之后的編碼工作其實算法上沒有什么難度,只是功能比較繁瑣。
其實當我真正沉下心來開始一步步分析學習這次作業,也只是用了4個小時左右,就從毫無思緒的狀態,到了感覺思路很明確各項功能門清的境地了。感覺心里已經有底了,之后就開始沉下心來設計、編碼、測試、改bug。
通過這次作業我感覺我的收獲有一個是復習了Java設計,當初學Java的時候,個人還是很喜歡Java語言的編程風格的,只是后來疏於聯系,很多語法都不是那么熟練,這次作業讓我將忘掉的語法又撿了起來。
其次就是學會了github進行代碼管理,這真的是個很棒也很方便的平台,剛開始沒有使用過的時候感覺很多功能都很繁雜,但熟悉之后就覺得他很便利也很簡明。
再次就是了解學會了Eclipse的覆蓋率測試和利用JProfiler進行性能測試。
最后,最重要的還是樹立了信心吧。雖然我在軟件工程和計算機方面真的不是很有靈性,但是所有的事情只要靜下心去做,還是可以摸到門路,最后基本完成任務的!!!
11.相關的倉庫
-
yii2-ueditor
基於yii2.0的百度編輯器擴展
-
JD
基於Yii2.0框架模仿打造京東電商平台
-
mzcx
使用YII2.0基於web端開發的打車軟件
-
yii2_rbac_admin
Yii2.0 Rbac 示例教程
-
WebComponent
前端常用組件