天天看點

2020寒假作業(2/2)

這個作業屬于哪個課程 軟體工程
這個作業要求在哪裡 在這
這個作業的目标 學習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 生成設計文檔
Design Review 設計複審 30 10
Coding Standard 代碼規範 (為目前的開發制定合适的規範)
Design 具體設計
Coding 具體編碼 600 540
Code Review 代碼複審
Test 測試(自我測試,修改代碼,送出修改) 100 210
Reporting 報告 250
Test Repor 測試報告
Size Measurement 計算工作量
Postmortem & Process Improvement Plan 事後總結, 并提出過程改進計劃 120
合計 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類,用正規表達式檢驗檔案路徑的正确性等。
  • 解題思路
    2020寒假作業(2/2)

2. 設計實作過程

  1. 類的設定
    • InfectStatistic總類
    • cmdArgs内部類:包含指令行參數成員變量和具有指令參數類型檢驗功能的方法。
    • line内部類:對應統計後最終輸出的每一條資訊,相當于一個結構。包含有成員變量地理位置資訊、感染患者個數、疑似患者個數、治愈人數、死亡人數。按要求結構組織資訊傳回該條資訊的方法。
  2. 全局變量的設定
    • 各類結果的個數(總的資料、篩選後的資料)
    • 各類結果line數組(總的資料、篩選後的資料)
    • 輸入出文檔路徑
    • 控制标志(日期正确性檢驗)
  3. 功能方法
    • 判斷正确性(日期、文檔路徑)
    • 按照拼音排序(挑選省份後的line排序、統計後總資料line排序)
    • 日志管理(讀取、截取讀取):将每條資訊傳送到統計方法進行整合
    • 計算(統計、全國):整合同一省份的各個症狀人數,包括全國的總和
    • 篩選(省份、類型):傳回指令行中設定的省份或類型數組
    • 擷取(指定位址的記錄、路徑、最新日志時間)
    • 輸出(總體、篩選之後)
  4. 關鍵函數
    2020寒假作業(2/2)
    2020寒假作業(2/2)
    2020寒假作業(2/2)
  5. 流程圖
    2020寒假作業(2/2)

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);
		}
    }
}    	
           

主函數是程式的入口函數。

  1. 剛開始進行指令行參數的正确性檢驗,如果為空或者不存在“list”指令則會報錯并終止程式。
  2. 接着判斷-date、-province、-type的存在情況并通過-log和-out參數得到輸入輸出路徑并存為全局變量。
  3. 檢驗路徑的正确性,分為格式檢驗、檔案夾不存在、檔案夾内無内容。
  4. 分為有-date和無-date兩種情況,兩種情況又分别對應是否有省份是否有類型,有不同的方法調用方式。
  5. 流程為先讀取滿足日期要求的日志,然後根據省份要求選取省份并排序獲得輸出的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;
    }
           
  1. 先将所要排序的line數組的省份提出存入String數組。
  2. 用Collator類的方法按照拼音排序省份。
  3. 按照排序後省份的順序循環對比未排序的line數組的location。若比對到了則重新開始循環對比。
  4. 設定空的結果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.檢驗日志檔案路徑正确性

2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)

根據isCorformpath方法,日志檔案路徑的正确性取決于格式、存在、為空三種情況。

格式用正規表達式[1]:\\\\(.+?\\\\)*$檢驗。

2.檢驗最後一個日志日期

2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)

擷取最後一個日期來決定讀取的範圍。

3.輸入日期檢驗

2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)

日期檢驗分為兩個方法。一個是檢驗兩個日期的早晚判斷,一個是正确性判斷。正确性判斷基于早晚判斷。

4.查找日志位置索引

2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)
2020寒假作業(2/2)

查找日志位置的索引,用于指定-date的情況,讀取時控制循環變量小于索引,循環讀取。若輸入的日期正确且不存在于log檔案夾中,則傳回比該日期小的最近的一個日志的索引。

5. 性能優化

1.覆寫率

所有的指令包括-date、-province和-type全部按規則輸入後得到覆寫率81.0%(左)

加上所有錯誤處理的情況即盡可能走遍所有分支得覆寫率95.9%(右)

2020寒假作業(2/2)
2020寒假作業(2/2)

點開詳情發現沒有達到使用率百分百的方法如圖

2020寒假作業(2/2)

我選擇了isBefore函數優化,因為我發現這個方法的傳回值太過于雜亂,很浪費空間(左)

後續選擇更好用的字元串自帶函數isCompareto進行優化(右)

2020寒假作業(2/2)
2020寒假作業(2/2)

得到滿意的覆寫率

2020寒假作業(2/2)

2.監控

第一次使用jdk自帶的jvisualvm工具,但是還不怎麼會具體分析。我是在大約22:16左右運作了我的程式。以下四個實時監控圖記錄了相應參數的變化過程。

1.CPU

2020寒假作業(2/2)

2.類

2020寒假作業(2/2)

3.線程

2020寒假作業(2/2)

4.堆

2020寒假作業(2/2)

三、代碼規範

我的代碼規範:codestyle.md

四、心路曆程與收獲

1. 心路曆程

這個作業實話說比第一次作業難多了,還是我比較菜的原因,第一次看這個“長篇大論”心情很崩潰。剛開始的要求是學習github,之前我略有耳聞,對github的印象就是難。因為是全英文的網站,正常的閱讀都較為困難,加上加載頁面的時間特别特别長,注冊都耗費了蠻久的時間,是以我就等到第二天再開始其他操作,以防止我心态爆炸。

第二天我又把作業需求看了幾遍,終于了解了80%,并且大緻有了打代碼的思路,就是對于指令行的輸入産生不同結果。因為對于github中繁瑣功能按鈕的不熟悉,連建立一個同結構的檔案夾都commit了好多次。等到終于整完檔案夾後,我舒服多了,終于可以開始我比較熟悉的代碼編寫了。

我先把主要的函數都寫出來,最後再彙總成main函數,經常是函數套函數,是以在後來的單元測試上比較麻煩,因為内部耦合性比較高。最關鍵的就是形成了line資料結構存放每條資料,這省去了很多麻煩,也比較好調用。代碼編寫的過程比較平常,就是按部就班,都是有一定思路的,沒有遇到不會實作的情況,頂多就是寫出來有bug。令我印象比較深刻的就是寫完揀選省份的函數并排序後,程式總是會自己無限循環,必須要我自己手動結束調試。這個邏輯錯誤還不好發現,導緻我寫代碼一時爽,Debug火葬場。這是我所有子產品裡面找錯時間最長的一次,重新編碼了一遍也沒什麼結果,好嘛,世上無難事隻要肯放棄,我明天再做。最後是成功了,在找到配對的省份後我又把索引設為0重新循環就可以了。

每天最怕的就是看群,一看大家的問題,再對比我的程式,哦,我少了個路徑檢驗,哦,檔案輸入路徑和輸出路徑要是指令行自己輸入的,然後我就要加加加改改改,害。還好這些工作量不是很大。

單元測試部分蠻難的,我參考了作業中的附錄教程,就隻做了一個JUnit的簡易測試。之前不知道單元測試的時候我一直是用輸出函數來實時調整我的代碼的,我還覺得這樣也挺友善的。因為程式量不是很大,是以單元測試還沒有發揮它最大的作用。

剛開始覆寫率僅為80%左右,是因為我寫了很多分支并沒有用到,是以又花了很長的時間去周遊盡可能多的分支,就輸入了很多次可能的指令,最後總和終于達到了96%左右,檢查覆寫率低的函數,并修改,效果還不錯。

2. 收獲

  1. GitHub賬号
    • fork别人的倉庫
    • 建立我自己的分支
    • ...
  2. Git指令并與GitHub綁定
    • git add .
    • git commit -m ''
    • git push
  3. 單元測試的方法
    • JUnit4
  4. 覆寫率的使用
  5. 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庫,工具,教程,文章等。有很多部件内容,例如:影片、導航、模闆、手勢系統、圖檔、驗證碼等。

  1. A-z ↩︎