這個作業屬於哪個課程 | 軟件工程 |
---|---|
這個作業要求在哪里 | 在這 |
這個作業的目標 | 學習github、制定代碼規范、撰寫程序讀取日志,列出全國和各省在某日的感染情況,單元測試、自我評測、尋找五個倉庫 |
作業正文 | 我的寒假作業 |
其他參考文獻 | 無 |
一、前置要求
1. github初始用
我的github倉庫地址:https://github.com/heng1999/InfectStatistic-main
2. PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
---|---|---|---|
Planning | 計划 | 60 | 40 |
Estimate | 估計這個任務需要多少時間 | 1440 | 1680 |
Development | 開發 | 1090 | 1120 |
Analysis | 需求分析 (包括學習新技術) | 150 | 180 |
Design Spec | 生成設計文檔 | 60 | 40 |
Design Review | 設計復審 | 30 | 10 |
Coding Standard | 代碼規范 (為目前的開發制定合適的規范) | 60 | 40 |
Design | 具體設計 | 60 | 60 |
Coding | 具體編碼 | 600 | 540 |
Code Review | 代碼復審 | 30 | 40 |
Test | 測試(自我測試,修改代碼,提交修改) | 100 | 210 |
Reporting | 報告 | 210 | 250 |
Test Repor | 測試報告 | 60 | 30 |
Size Measurement | 計算工作量 | 30 | 40 |
Postmortem & Process Improvement Plan | 事后總結, 並提出過程改進計划 | 120 | 180 |
合計 | 1360 | 1410 |
二、疫情統計程序
1.解題思路
-
思考
這個程序的要求看起來很復雜繁瑣,着實有點頭大。所以我認真看了好幾遍(不過對於一小部分要求的理解還是模模糊糊的),大概分析出了幾個完成作業的關鍵點:
1.GitHub學習
我去GitHub官網注冊了一個賬號,耗費了很久的時間,原因就是網站跳轉的實在是太慢了,加上我 家的網不是很好,斷斷續續的,慢到沒脾氣了。所以我就去搜索了一個讓GitHub變快的方法,修改了我主機的hosts文件,這才跳轉的速度稍微提高了一點(也有可能是我的心理作用)。
按照要求將老師提供的InfectStatistic-main倉庫fork到我自己的賬號下,然后添加同結構的學號文件 夾,因為不熟悉操作就反復commit了好幾次。
2.Git命令學習
我之前是下載過git的,但是從來沒用過。這次剛好讓它派上用場了。
3.代碼規范制定
這個作業要求是最簡單並且淺顯易懂的,就連制定規范的幾個內容都有給出,也提供了參考資料。在我的 印象里在大一就做過類似的代碼規范制定,所以寫起來就很得心應手了,就按照自己的一直以來編碼習慣制定。
4.在本地創建log文件夾
將所給log文件夾復制到本機方便操作。
5.通過檢驗命令行參數決定功能
程序的編寫從args入手
-
查找資料
1.GitHub&Git
參考多來自本題作業中給定的附件。先是注冊的方法,再是fork,clone,push,commit等的使用方法。按照要求我在本機上下載了GitHub桌面版,但是除了登錄了我的GitHub賬號以外就沒有再使用過了,因為用git命令就可以很方便地push新改動到我的倉庫了,還不太知道桌面版的便捷之處(
也因為我不太會用)。Git的各樣命令的學習是從百度得到的。
2.代碼規范
代碼規范多是我自己的日常習慣來制定的,部分有參考於作業附件中所給的《騰訊C++代碼規范》。
3.程序編寫
有部分java自帶的方法的查找來自於百度,例如:省份按照拼音排序的Collator類,用正則表達式檢驗文件路徑的正確性等。
-
解題思路
2. 設計實現過程
-
類的設置
- InfectStatistic總類
- cmdArgs內部類:包含命令行參數成員變量和具有命令參數類型檢驗功能的方法。
- line內部類:對應統計后最終輸出的每一條信息,相當於一個結構。包含有成員變量地理位置信息、感染患者個數、疑似患者個數、治愈人數、死亡人數。按要求結構組織信息返回該條信息的方法。
-
全局變量的設置
- 各類結果的個數(總的數據、篩選后的數據)
- 各類結果line數組(總的數據、篩選后的數據)
- 輸入出文檔路徑
- 控制標志(日期正確性檢驗)
-
功能方法
- 判斷正確性(日期、文檔路徑)
- 按照拼音排序(挑選省份后的line排序、統計后總數據line排序)
- 日志管理(讀取、截取讀取):將每條信息傳送到統計方法進行整合
- 計算(統計、全國):整合同一省份的各個症狀人數,包括全國的總和
- 篩選(省份、類型):返回命令行中設置的省份或類型數組
- 獲取(指定地址的記錄、路徑、最新日志時間)
- 輸出(總體、篩選之后)
-
關鍵函數
-
流程圖
3.代碼說明
(1)主函數片段
public static void main(String[] args) throws IOException {
if(args.length==0) {
System.out.println("輸入命令行為空,請重新輸入!");
return;
}
if(!args[0].equals("list")) {//命令錯誤
System.out.println("未輸入命令‘list’,則不可以帶參數,請重新輸入!");
return;
}
cmdArgs cmd=new cmdArgs(args);
int hasDate=cmd.hasParam("-date");//存命令的索引
int hasPro=cmd.hasParam("-province");//檢查是否有province命令
int hasType=cmd.hasParam("-type");//檢查是否有類型命令
int hasPath=cmd.hasParam("-out");//獲取輸出路徑索引
int hasLog=cmd.hasParam("-log");//獲取log路徑索引
getTopath(args,hasPath);
getFrompath(args,hasLog);
if(!isCorformpath(frompath)) {//輸入日志所在文件夾有錯
return;
}
if(hasDate!=-1) {//有指定日期
readLog(args[hasDate+1],true);
if(index!=-2&&isWrong==0&&hasPro!=-1) {//有指定省份
String[] province=selectPro(args,hasPro);
line[] a=selectMes(province);
line[] b=sortline1(a,selcount);
printSel(b);
if(hasType!=-1) {//有指定類型
String[] type=selectType(args,hasType);
printSelpart(proresult,type,selcount);
}
}
else if(index!=-2&&isWrong==0&&hasType!=-1) {//有指定類型未指定省份
String[] type=selectType(args,hasType);
addAll();
printSelpart(result,type,count);
}
}
else {//未指定日期
readLog(args[hasDate+1],false);
if(hasPro!=-1) {//有指定省份
String[] province=selectPro(args,hasPro);
line[] a=selectMes(province);
line[] b=sortline1(a,selcount);
printSel(b);
if(hasType!=-1) {//有指定類型和省份
String[] type=selectType(args,hasType);
printSelpart(proresult,type,selcount);
}
}
else if(hasType!=-1) {//有指定類型未指定省份
String[] type=selectType(args,hasType);
addAll();
printSelpart(result,type,count);
}
}
}
主函數是程序的入口函數。
- 剛開始進行命令行參數的正確性檢驗,如果為空或者不存在“list”命令則會報錯並終止程序。
- 接着判斷-date、-province、-type的存在情況並通過-log和-out參數得到輸入輸出路徑並存為全局變量。
- 檢驗路徑的正確性,分為格式檢驗、文件夾不存在、文件夾內無內容。
- 分為有-date和無-date兩種情況,兩種情況又分別對應是否有省份是否有類型,有不同的方法調用方式。
- 流程為先讀取滿足日期要求的日志,然后根據省份要求選取省份並排序獲得輸出的line結構,最后根據類型要求配對按需求輸出到文檔。如果沒有省份或類型要求則跳過該步驟方法按默認輸出。
(2)統計statistics
static void statistics(String[] sp,line[] all) {
String location="";
location=sp[0];
line line1;
if(!isExistlocation(location,all)) {//不存在對應該省的記錄
line1=new line(location,0,0,0,0);//新建數據條
all[count]=line1;
count++;
}
else {
line1=getLine(location,all);//獲得原有的數據條
}
if(sp[1].equals("新增")) {
if(sp[2].equals("感染患者")) {//獲得感染人數
line1.grhz+=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));
}
else {//疑似患者
line1.yshz+=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));
}
}
else if(sp[1].equals("死亡")) {
line1.dead+=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
line1.grhz-=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
}
else if(sp[1].equals("治愈")) {
line1.recover+=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
line1.grhz-=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
}
else if(sp[1].equals("疑似患者")) {
if(sp[2].equals("確診感染")){
int change=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));//改變人數
line1.grhz+=change;
line1.yshz-=change;
}
else {//流入情況
String tolocation=sp[3];//流入省
int change=Integer.valueOf(sp[4].substring(0,sp[4].length()-1));//改變人數
line line2;
if(!isExistlocation(tolocation,all)) {//不存在對應該省的記錄
line2=new line(tolocation,0,0,0,0);//新建數據條
all[count]=line2;
count++;
}
else {
line2=getLine(tolocation,all);//獲得原有的數據條
}
line1.yshz-=change;
line2.yshz+=change;
}
}
else if(sp[1].equals("排除")) {
line1.yshz-=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));
}
else {//感染患者流入情況
String tolocation=sp[3];//流入省
//System.out.print(sp[0]);
int change=Integer.valueOf(sp[4].substring(0,sp[4].length()-1));//改變人數
line line2;
if(!isExistlocation(tolocation,all)) {//不存在對應該省的記錄
line2=new line(tolocation,0,0,0,0);//新建數據條
all[count]=line2;
count++;
}
else {
line2=getLine(tolocation,all);//獲得原有的數據條
}
line1.grhz-=change;
line2.grhz+=change;
}
}
統計各地的情況,創建最后會輸出的數組結構line,接受原有log文件中的每行的數據,總和統計每個省份的情況。具體解釋有上部分流程圖顯明。
(3)line結構
static class line{//統計之后的病例每條的結構
String location;//地理位置
int grhz;//感染患者人數
int yshz;//疑似患者人數
int recover;//治愈人數
int dead;//死亡人數
line(String plocation,int pgrhz,int pyshz,int precover,int pdead){
location=plocation;
grhz=pgrhz;
yshz=pyshz;
recover=precover;
dead=pdead;
}
line(){
}
/*
* 功能:打印一條統計疫情信息
* 輸入參數:無
*返回值:信息字符串
*/
String printline() {
return(location+" 感染患者"+grhz+"人 疑似患者"+yshz+"人 治愈"+recover+"人 死亡"+dead+"人");
}
}
最核心的部分,將每個省份對應的一條信息存入,方便增刪改查。
(4)篩選省份后的排序
static line[] sortline1(line[] wannasort,int num) {
String[] location=new String[num];
int i=0;
line[] aa=new line[num];
for(i=0;i<num;i++) {
aa[i]=new line();
}
for(i=0;i<num;i++) {
location[i]=wannasort[i].location;
}
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
Arrays.sort(location, cmp);
i=0;
int j=0;//控制省份拼音順序索引
while(i<num) {
if(wannasort[i].location.equals(location[j])) {
aa[j]=wannasort[i];
j++;
if(j>=num) {
break;
}
i=-1;//重新開始循環
}
i++;
}
return aa;
}
- 先將所要排序的line數組的省份提出存入String數組。
- 用Collator類的方法按照拼音排序省份。
- 按照排序后省份的順序循環對比未排序的line數組的location。若匹配到了則重新開始循環對比。
- 設置空的結果line數組存放排序結束后的line結構。
(5)日志讀取片段
while(i<=index) {
FileInputStream fs=new FileInputStream(frompath+filename[i]);
InputStreamReader is=new InputStreamReader(fs,"UTF-8");
BufferedReader br=new BufferedReader(is);
String s="";
while((s=br.readLine())!=null){//一行一行讀
if(s.charAt(0)=='/'&&s.charAt(1)=='/') {//排除注釋掉的內容
continue;
}
else {
String[] sp =s.split(" ");//分隔開的字符串
statistics(sp,all);
}
}
br.close();
i++;
}
按行讀取,跳過注釋的部分,將每行發送給統計函數。
4.單元測試截圖和描述
單元測試我是分方法來測試的,因為學的皮毛,只是測試了幾個函數,大多數為檢驗函數。我代碼里核心的統計函數和日志讀取方法是沒有返回值的,並且參數的設置也具有較大整篇代碼關聯性,所以並沒有測試。很多功能函數的參數和返回值都與line數組相關,相應賦值也過於繁瑣,所以並沒有測試。
1.檢驗日志文件路徑正確性
根據isCorformpath方法,日志文件路徑的正確性取決於格式、存在、為空三種情況。
格式用正則表達式[1]:\\\\(.+?\\\\)*$檢驗。
2.檢驗最后一個日志日期


獲取最后一個日期來決定讀取的范圍。
3.輸入日期檢驗




日期檢驗分為兩個方法。一個是檢驗兩個日期的早晚判斷,一個是正確性判斷。正確性判斷基於早晚判斷。
4.查找日志位置索引




查找日志位置的索引,用於指定-date的情況,讀取時控制循環變量小於索引,循環讀取。若輸入的日期正確且不存在於log文件夾中,則返回比該日期小的最近的一個日志的索引。
5. 性能優化
1.覆蓋率
所有的命令包括-date、-province和-type全部按規則輸入后得到覆蓋率81.0%(左)
加上所有錯誤處理的情況即盡可能走遍所有分支得覆蓋率95.9%(右)
點開詳情發現沒有達到利用率百分百的方法如圖

我選擇了isBefore函數優化,因為我發現這個方法的返回值太過於雜亂,很浪費空間(左)
后續選擇更好用的字符串自帶函數isCompareto進行優化(右)
得到滿意的覆蓋率

2.監控
第一次使用jdk自帶的jvisualvm工具,但是還不怎么會具體分析。我是在大約22:16左右運行了我的程序。以下四個實時監控圖記錄了相應參數的變化過程。
1.CPU
2.類
3.線程
4.堆
三、代碼規范
我的代碼規范:codestyle.md
四、心路歷程與收獲
1. 心路歷程
這個作業實話說比第一次作業難多了,還是我比較菜的原因,第一次看這個“長篇大論”心情很崩潰。剛開始的要求是學習github,之前我略有耳聞,對github的印象就是難。因為是全英文的網站,正常的閱讀都較為困難,加上加載頁面的時間特別特別長,注冊都耗費了蠻久的時間,所以我就等到第二天再開始其他操作,以防止我心態爆炸。
第二天我又把作業需求看了幾遍,終於理解了80%,並且大致有了打代碼的思路,就是對於命令行的輸入產生不同結果。因為對於github中繁瑣功能按鈕的不熟悉,連建立一個同結構的文件夾都commit了好多次。等到終於整完文件夾后,我舒服多了,終於可以開始我比較熟悉的代碼編寫了。
我先把主要的函數都寫出來,最后再匯總成main函數,經常是函數套函數,所以在后來的單元測試上比較麻煩,因為內部耦合性比較高。最關鍵的就是形成了line數據結構存放每條數據,這省去了很多麻煩,也比較好調用。代碼編寫的過程比較平常,就是按部就班,都是有一定思路的,沒有遇到不會實現的情況,頂多就是寫出來有bug。令我印象比較深刻的就是寫完揀選省份的函數並排序后,程序總是會自己無限循環,必須要我自己手動結束調試。這個邏輯錯誤還不好發現,導致我寫代碼一時爽,Debug火葬場。這是我所有模塊里面找錯時間最長的一次,重新編碼了一遍也沒什么結果,好嘛,世上無難事只要肯放棄,我明天再做。最后是成功了,在找到配對的省份后我又把索引設為0重新循環就可以了。
每天最怕的就是看群,一看大家的問題,再對比我的程序,哦,我少了個路徑檢驗,哦,文件輸入路徑和輸出路徑要是命令行自己輸入的,然后我就要加加加改改改,害。還好這些工作量不是很大。
單元測試部分蠻難的,我參考了作業中的附錄教程,就只做了一個JUnit的簡易測試。之前不知道單元測試的時候我一直是用輸出函數來實時調整我的代碼的,我還覺得這樣也挺方便的。因為程序量不是很大,所以單元測試還沒有發揮它最大的作用。
剛開始覆蓋率僅為80%左右,是因為我寫了很多分支並沒有用到,所以又花了很長的時間去遍歷盡可能多的分支,就輸入了很多次可能的命令,最后總和終於達到了96%左右,檢查覆蓋率低的函數,並修改,效果還不錯。
2. 收獲
-
GitHub賬號
- fork別人的倉庫
- 建立我自己的分支
- ...
-
Git命令並與GitHub綁定
- git add .
- git commit -m ''
- git push
- ...
-
單元測試的方法
- JUnit4
-
覆蓋率的使用
-
jvisualvm監控
五、有關倉庫
1.Flutter:https://github.com/flutter/flutter
flutter庫有多達534位貢獻者。flutter可通過單一代碼庫為移動,Web和桌面提供精美,快速的用戶體驗。Flutter可與現有代碼一起使用,並為世界各地的開發人員和組織所使用。里面有基本函數可以在開發App時直接使用。
2.Dart初學者編程指南:https://github.com/smartherd/DartTutorial
從零開始學習Dart編程。從安裝開始,包括數據類型、控制循環語句、功能方法、異常處理、面向對象編程、函數式編程、飛鏢收藏和可調用類。
3. Flutter-go: https://github.com/alibaba/flutter-go
flutter 開發者幫助 APP,包含 flutter 常用 140+ 組件的demo 演示與中文文檔。帶有用戶中心,專屬個人的widget案例。可在該App上收藏組件。
4. flutter-examples:https://github.com/nisrulz/flutter-examples
其中包含所有示例應用程序,示例應用程序展示了Flutter應用程序開發中的功能/集成。
5. awesome-flutter:https://github.com/Solido/awesome-flutter/
很棒的清單,精選了最好的Flutter庫,工具,教程,文章等。有很多部件內容,例如:影片、導航、模板、手勢系統、圖片、驗證碼等。
A-z ↩︎