這個作業屬于哪個課程 | 福大20春軟工S班 |
---|---|
這個作業要求在哪裡 | 軟工實踐寒假作業(2/2) |
這個作業的目标 | 新型冠狀病毒疫情統計 |
作業正文 | 221701135王澤宇作業(2/2) |
其他參考文獻 | 《Java程式設計》 |
1. Github倉庫
個人代碼見Github倉庫:2217wang/Infectstatistic-main
2. PSP表格
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 45min | 30min |
Estimate | 估計這個任務需要多少時間 | 15min | |
Development | 開發 | 60min | |
Analysis | 需求分析 (包括學習新技術) | 180min | 240min |
Design Spec | 生成設計文檔 | ||
Design Review | 設計複審 | 600min | |
Coding Standard | 代碼規範 (為目前的開發制定合适的規範) | 120min | |
Design | 具體設計 | ||
Coding | 具體編碼 | 1200min | |
Code Review | 代碼複審 | ||
Test | 測試(自我測試,修改代碼,送出修改) | 300min | |
Reporting | 報告 | ||
Test Repor | 測試報告 | ||
Size Measurement | 計算工作量 | ||
Postmortem & Process Improvement Plan | 事後總結, 并提出過程改進計劃 | ||
合計 | 3990min |
3. 解題思路描述
由作業要求可知:先讀取日志檔案統計疫情情況,再根據指令行參數輸入的選項要求,輸出相應的文檔。

- 程式語言選擇Java實作。
- 在輸入的args中,比對字元串,擷取不同指令的參數情況。
- 讀取日志檔案,通過循環按行擷取日志内容,分割比對相關資訊,通過判斷字元串,處理增減人數。
- 需要有一個省份結構存儲讀取的資訊。
- 根據選項,篩選輸出内容。
-
儲存到相應檔案中。
日志檔案的内容根據以下情況劃分,然後根據劃分出來的數組對相應的省份進行操作。
4. 設計實作過程
根據思路解決相應的功能。
5. 關鍵代碼與思路
- Province類詳細情況
public static class Province {
private String name;//省份名稱
private long ip;//感染患者
private long sp;//疑似患者
private long cure;//治愈患者
private long dead;//死亡患者
/////////////////初始化相關變量///////////////////
Province(String name, long ip, long sp, long cure, long dead) {
this.name = name;
this.ip = ip;
this.sp = sp;
this.cure = cure;
this.dead = dead;
}
////////////////數量變化函數//////////////////////
public void ipadd(long add) {this.ip += add;}//感染增加
public void spadd(long add) {this.sp += add;}//疑似增加
public void cureadd(long add) {this.cure += add;}//治愈增加
public void deadadd(long add) {this.dead += add;}//死亡增加
public void ipsub(long sub) {this.ip -= sub;}//感染減少
public void spsub(long sub) {this.sp -= sub;}//疑似減少
////////////////擷取省份資料的函數////////////////
public String getname() {return name;}
public long getip() {return ip;}
public long getsp() {return sp;}
public long getcure() {return cure;}
public long getdead() {return dead;}
}
-
對指令行參數進行處理
在readArgs函數内通過for循環一次對args參數進行判斷。單詞循環當中用switch對各個參數進行篩選,比對上的參數就對相應的全局變量進行指派。如果沒有參數的情況,全局變量會保留最初的指派。
public static void readArgs(String[] args) {//讀取指令行參數
for (int i = 1; i<args.length; i++) {
switch (args[i]) {
case "-log":
log = args[i+1];
i++;//跳過log後跟的參數
break;//擷取日志目錄路徑
case "-out":
outpath = args[i+1];//擷取輸出路徑
i++;//跳過out後面的參數
break;
case "-date"://擷取截止日期
date = args[i+1];
i++;//跳過date後面跟的參數
break;
case "-type"://擷取相關輸出類型
for (int j = i+1; j<i+1+4 && j<args.length; j++) {
switch (args[j]) {
case "ip":
type.add("ip"); break;
case "sp":
type.add("sp"); break;
case "cure":
type.add("cure"); break;
case "dead":
type.add("dead"); break;
}//使用type數組之後記得要初始化
}
break;
case "-province"://擷取輸出省份
for (int j = i+1; j<args.length && j<args.length; j++) {
if (checkprovince(args[j]))
outputprovince.add(args[j]);
else break;
}
break;
}
}
return;
}
-
讀取指定目錄下的檔案
指定目錄下對檔案清單的内容按行讀取,每行按空格分割根據不同的情況進行比對,然後存入哈希表相應的省份當中。分割方式如下圖:
public static void getLogFile(String log) {//擷取指定目錄下的檔案
File logfile = new File(log);
List<String> fileprovince = new ArrayList<String>();//指定省份涉及的省份
if (logfile.isDirectory()) {
String list[] = logfile.list();//擷取檔案清單
if (!date.equals("") && !lastDateCheck(list)) {//如果date有參數,且超過最晚日期
System.out.println("超出最晚日期");
return ;
} else {
for (int i=0; i<list.length; i++) {
File file = new File(log + "/" +list[i]);//合成目标檔案路徑
if (file.isFile() && checkDate(file)) {//判斷指定目錄的檔案下是否為标準檔案,且日期是否不大于指定日期
try {
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String str = null;
while((str = br.readLine())!=null){
// System.out.println(str);
String[] arr = str.split(" ");
if (arr[0].equals("//"))
break;
if (outputprovince.size() == 1) {//如果沒有outputprovince參數則擷取日志中涉及的省作參數
fileprovince.add(arr[0]);
}
if (arr.length == 5) {//湖北 感染患者 流入 福建 2人//湖北 疑似患者 流入 福建 5人
if (outputprovince.size() == 1) {//如果沒有outputprovince參數則擷取日志中涉及的省作參數
fileprovince.add(arr[3]);
}
switch (arr[1]) {
case "感染患者" :
map.get(arr[3]).ipadd(getLongFromStr(arr[4]));//流入省增加
map.get(arr[0]).ipsub(getLongFromStr(arr[4]));//流出省減少
break;
case "疑似患者" :
map.get(arr[3]).spadd(getLongFromStr(arr[4]));//流入省增加
map.get(arr[0]).spsub(getLongFromStr(arr[4]));//流出省減少
break;
}
} else if (arr.length == 4) {//福建 新增 感染患者 2人//福建 新增 疑似患者 5人//湖北 排除 疑似患者 2人//湖北 疑似患者 确診感染 3人
switch (arr[1]) {
case "新增" :
if (arr[2].equals("感染患者")) {
map.get(arr[0]).ipadd(getLongFromStr(arr[3]));//感染患者增加
} else {
map.get(arr[0]).spadd(getLongFromStr(arr[3]));//疑似患者增加
}
break;
case "排除" :
map.get(arr[0]).spsub(getLongFromStr(arr[3]));//疑似患者減少
break;
case "疑似患者" :
map.get(arr[0]).spsub(getLongFromStr(arr[3]));//疑似患者減少
map.get(arr[0]).ipadd(getLongFromStr(arr[3]));//感染患者增加
break;
}
} else if (arr.length == 3) {//湖北 死亡 1人//湖北 治愈 2人
switch (arr[1]) {
case "治愈" :
map.get(arr[0]).ipsub(getLongFromStr(arr[2]));//感染患者減少
map.get(arr[0]).cureadd(getLongFromStr(arr[2]));//治愈患者增加
break;
case "死亡" :
map.get(arr[0]).ipsub(getLongFromStr(arr[2]));//感染患者減少
map.get(arr[0]).deadadd(getLongFromStr(arr[2]));//死亡患者增加
break;
}
}
}
br.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
} //else { System.out.println(log + "/" +list[i] + "不為标準檔案,或者日期超過最晚日期。"); }
}
... ...
... ...
}
} else { System.out.println("輸入的log位址不為目錄。"); }
}
-
結果輸出
全局變量outputprovince在并沒有按照拼音排序過,是以在output之前先對需要輸出的省份進行排序。然後再根據排完序的provinceout進行指定省份結合type的情況進行輸出
public static void outPut(String[] args) {
// for (int i = 0; i<outputprovince.size(); i++)
// System.out.println("output開始:" + outputprovince.get(i));
String str = new String("");
List<String> provinceout = new ArrayList<String>();
int[] index = {
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0,0,0,0,
0,0
};
// for (int i = 0; i<outputprovince.size(); i++) {
// System.out.println(outputprovince.get(i));
// }
for (int i = 0; i<outputprovince.size(); i++) {
for (int n = 0; n<provincename.length; n++) {
if (outputprovince.get(i).equals(provincename[n])) {
index[n] = 1;
// System.out.println("n = " + n);
break;
}
}
}
for (int i = 0; i<index.length; i++) {
if (index[i] == 1)
provinceout.add(provincename[i]);
}
// for (int i=0; i<provinceout.size(); i++) {
// System.out.println("provinceout[" + i + "] = " + provinceout.get(i));
// }
if (type.size() == 1) {
type.add("ip");
type.add("sp");
type.add("cure");
type.add("dead");
}
try {
for (int i = 0; i<provinceout.size(); i++) {
// System.out.println("1");
str += map.get(provinceout.get(i)).getname();
// System.out.println("name");
for (int n = 0; n<type.size(); n++) {
switch (type.get(n)) {
case "ip":
// System.out.println("ip");
str += " 感染患者" + map.get(provinceout.get(i)).getip() + "人"; break;
case "sp":
// System.out.println("sp");
str += " 疑似患者" + map.get(provinceout.get(i)).getsp() + "人"; break;
case "cure":
// System.out.println("cure");
str += " 治愈" + map.get(provinceout.get(i)).getcure() + "人"; break;
case "dead":
// System.out.println("dead");
str += " 死亡" + map.get(provinceout.get(i)).getdead() + "人"; break;
}
}
// System.out.println(str);
str += "\r\n";
}
}catch (Exception e) {
// TODO: handle exception
}
str += "// 該文檔并非真實資料,僅供測試使用\r\n//指令:";
for (int i = 0; i<args.length; i++) {
str += args[i] + " ";
}
try {
FileOutputStream fos = new FileOutputStream(outpath);
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write(str);
bw.newLine();
bw.close();
osw.close();
fos.close();
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(str);
str = "";
}
6. 單元測試截圖和描述
(1)測試ListOut1.txt
測試result檔案ListOut1.txt中的指令。
(2)測試ListOut2.txt
測試result檔案ListOut2.txt中的指令。
(3)測試ListOut3.txt
測試result檔案ListOut3.txt中的指令。
(4)測試超出最晚日志日期
要求輸入的date不能超出最新的日志日期。
(5)測試log路徑出錯問題
考慮log路徑出錯問題。
(6)測試預設日期、省份、類型
預設不存在province、type、date選項的輸出結果。
(7)測試預設類型的輸出順序
type預設選項時的輸出。
(8)測試指定類型的輸出順序
type選項指定時的輸出。
(9)測試隻會列出指定省份
(10)測試不列出全國,隻會列出指定省份
測試不列出全國。
(11)測試邊界問題:剛好是最晚日期
考慮日期邊界問題。
7. 單元測試覆寫率優化和性能測試
單元測試覆寫率測試
* 圖中的checkprovince用于判斷輸入參數的省份名稱是否存在,其覆寫率與讀入的-province帶的參數相關。
public static boolean checkprovince(String str) {
for (int i = 0; i<provincename.length; i++) {
if (provincename[i].equals(str))
return true;
}
return false;
}
8. 代碼規範
我的代碼規範->Github倉庫:codestyle.md
9. 心路曆程與收獲
心路曆程:起初看到題目有點長就大概看了一下題目的要求,沒有仔細的閱讀。但是看完之後就有點懵了,
我已經有段時間沒有去寫Java代碼了,十分生疏再加上本來的底子就不是很厚,我就去在看了一下Java的相關知識點。
但看并沒有什麼用,最重要的還是要去打代碼,多打多練自然而然就熟悉了,就像國小生剛開始學習寫字一樣,練就了自然就熟悉了,也自然就會了。
開始看到開發的軟體要求并不覺得很難,最初的思路還是比較清晰的:讀檔案,然後再結合參數進行輸出。
但是迫于對Java的生疏和最開始沒有認真看清楚題目,導緻我程式設計的過程中十分吃力,需要是不是的回頭去看題目的要求,再根據實際去修改代碼。
這個寫了改,改了再寫的過程讓我非常煩躁。然而煩躁之後還是要繼續苦逼的碼代碼,這再次讓我感受到提前規劃的重要性,并不能因為項目小而不去提前規劃。
在完成開發并進行簡單測試符合基本要求之後,我還是能得到心理滿足的,畢竟是好不容易得來的成果。
然而這并不代表着本次作業的結束。這次作業需要用到Github,這是我沒有接觸過的新事物。仔細閱讀完相關的使用教程之後,我就自己簡單體驗了一下。
這體驗就給我打開了新世界的大門!之前的我并不知道還有這等好的分享軟體。我一直都隻是按照課程來學習,也一直苦于找不到相關的學習資源。
找到的CSDN、部落格園、菜鳥驿站等等都隻是滿足基本的入門需求,但是深入的東西并不多,然而這回就讓我找到好東西了!
收獲:再一次讓我感受到軟體工程規劃的重要性,也給我敲醒了警鐘:程式設計一定要多練多交流才可以提升自己的水準。
10.技術路線圖相關的5個倉庫
(1)倉庫1:
名稱 | linux-command |
---|---|
連結 | 倉庫連結 |
簡介 | Linux指令大全搜尋工具,内容包含Linux指令手冊、詳解、學習、搜集。 |
(2)倉庫2:
Linux-Tutorial | |
---|---|
《Java 程式員眼中的 Linux》/1.Shell常用指令(如啟動/停止Web容器,kill程序,檢視log等)2.檔案管理 3.軟體安裝 4.在Linux上搭建環境 5.JavaEE項目釋出在Linux伺服器上,一般來說,Windows環境進行開發,Linux環境進行部署 |
(3)倉庫3:
DD-LinuxDeviceDrivers | |
---|---|
核心調試工具集錦, 包括debugfs, trace, gdb, systemtap 的介紹和使用/核心設計的奇技淫巧, 介紹核心中使用的一些進階文法技巧和設計思路 |
(4)倉庫4:
linux-kernel-exploits | |
---|---|
linux-kernel-exploits Linux平台提權漏洞集合 |
(5)倉庫5:
Linux-NetSpeed | |
---|---|
将Linux現常用的網絡加速內建在一起 |