软件实践寒假作业(2/2)
这个作业属于哪个课程 | 福大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 | 30min |
Development | 开发 | 60min | 60min |
Analysis | 需求分析 (包括学习新技术) | 180min | 240min |
Design Spec | 生成设计文档 | 60min | 60min |
Design Review | 设计复审 | 600min | 600min |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60min | 120min |
Design | 具体设计 | 30min | 600min |
Coding | 具体编码 | 1200min | 1200min |
Code Review | 代码复审 | 30min | 180min |
Test | 测试(自我测试,修改代码,提交修改) | 300min | 600min |
Reporting | 报告 | 120min | 60min |
Test Repor | 测试报告 | 120min | 60min |
Size Measurement | 计算工作量 | 30min | 30min |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 120min | 120min |
合计 | 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现常用的网络加速集成在一起 |