软工实践寒假作业(2/2)
软工实践寒假作业(2/2)
格式描述
| 这个作业属于哪个课程 | 2020春|S班 (福州大学) |
|---|---|
| 这个作业要求在哪里 | 作业要求的链接 |
| 这个作业的目标 | 开发疫情统计程序,并借此熟悉程序开发流程和github的使用 |
| 作业正文 | .... |
| 其他参考文献 | 知乎、博客园 |
github仓库地址:https://github.com/bzzd2333/InfectStatistic-main
1、PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 30 |
| Estimate | 估计这个任务需要多少时间 | 30 | 30 |
| Development | 开发 | 365 | 475 |
| Analysis | 需求分析 (包括学习新技术) | 20 | 25 |
| Design Spec | 生成设计文档 | 20 | 30 |
| Design Review | 设计复审 | 10 | 10 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 15 | 20 |
| Design | 具体设计 | 20 | 25 |
| Coding | 具体编码 | 240 | 300 |
| Code Review | 代码复审 | 10 | 40 |
| Test | 测试(自我测试,修改代码,提交修改) | 30 | 25 |
| Reporting | 报告 | 140 | 170 |
| Test Repor | 测试报告 | 20 | 20 |
| Size Measurement | 计算工作量 | 30 | 30 |
| Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 90 | 120 |
| 合计 | 535 | 675 |
2、思路描述
我看到这个题目后,决定将程序分为命令初始化、处理日志、输出日志三个部分。使用的数据结构为:HashMap<String,Province>,其中String为省份的名称,Province为一个表示省份详细情况的类。省份的名称由一个List<String>存放。
处理日志思路如下:日志每一行的结构如下图,只要判断array[1]、array[2]的值就可以知道所有情况并处理。

输出日志就是正常输出到文件没什么特别的思路。
3、设计实现过程
我的程序模块结构图:

我的关键函数流程图:

4、代码说明
-
命令行初始化:
其中date为指定的日期,logPath、outputPath分别为日志目录的位置与输出文件的路径与文件名。dealType函数将-type的参数:要输出的人员情况存入output数组。dealProvince函数将-province的参数:指定列出的省份存入List<String>provinces。public InfectStatistic(String[] args)
{
/*
类的成员变量的初始化。
*/
//对命令行参数的初始化
this.init();
}
//初始化
public void init()
{
for(int i=0;i<arg.length;i++)
{
switch(arg[i])
{
case "-date":
date = new String(arg[i+1]);
isRead = false;
break;
case "-log":
logPath = new String(arg[i+1]);
break;
case "-out":
outputPath = new String(arg[i+1]);
break;
case "-type":
isOutput = false;
dealType(i+1);
break;
case "-province":
isOutputAll = false;
dealProvince(i+1);
default:
break;
}
}
}
//处理-type参数
public void dealType(int index)
{
for(int i=0;index<arg.length && i<4;i++)
{
switch(arg[index])
{
case "ip":
output[i] = arg[index];
break;
case "sp":
output[i] = arg[index];
break;
case "cure":
output[i] = arg[index];
break;
case "dead":
output[i] = arg[index];
break;
default:
break;
}
index++;
}
}
//处理-province参数
private void dealProvince(int index)
{
while(index<arg.length)
{
switch(arg[index])
{
case "-date":
return;
case "-log":
return;
case "-out":
return;
case "-type":
return;
default:
provinces.add(arg[index]);
map.put(arg[index],new Province(arg[index]));
}
index++;
}
} -
处理日志:
deal函数处理所有日志文件,dealOneLine函数处理日志文件中的一行。public void deal() throws IOException
{
String logDate;
String[] sArray;
File file = new File(logPath);
File[] tempList = file.listFiles();/*
判断-date提供的日期是否晚于日志最晚一天的日期
*///读取日志文件
for (int i = 0; i < tempList.length; i++)
{logDate = new String(tempList[i].getName());
sArray = logDate.split("\.");
logDate = new String(sArray[0]);//读取的日志日期小于指定的日期
if (isRead || (logDate.compareTo(date)) <= 0)
{
BufferedReader br = null;
String line = null;
br = new BufferedReader(new InputStreamReader(new FileInputStream(tempList[i].toString()), "UTF-8"));while((line = br.readLine()) != null)
{
String[] array = line.split(" ");
//处理单行
dealOneLine(array);
}
br.close();
}
}
//统计全国的情况
allStatistic();
}
//处理单行
private void dealOneLine(String[] array)
{
//忽略注释行
if (array[0].equals("//"))
{
return;
}
//处理的省份未初始化
if (map.get(array[0]) == null)
{
map.put(array[0], new Province(array[0]));
}switch (array[1])
{
case "新增":
if (array[2].equals("疑似患者"))
{
map.get(array[0]).addSp(array[3]);
}
else
{
map.get(array[0]).addIp(array[3]);
}
break;
case "感染患者":
//流出省份减少感染患者
map.get(array[0]).removeIp(array[4]);
//流入省份增加感染患者
if (map.get(array[3]) == null)
map.put(array[3], new Province(array[3]));
map.get(array[3]).addIp(array[4]);
break;
case "疑似患者":
if (array[2].equals("流入"))
{
//流出省份减少疑似患者
map.get(array[0]).removeSp(array[4]);
//流入省份增加疑似患者
if (map.get(array[3]) == null)
map.put(array[3], new Province(array[3]));
map.get(array[3]).addSp(array[4]);
}
//疑似患者确诊感染
else
{
//感染患者增加
map.get(array[0]).addIp(array[3]);
//疑似患者减少
map.get(array[0]).removeSp(array[3]);
}
break;
case "死亡":
map.get(array[0]).dead(array[2]);
break;
case "治愈":
map.get(array[0]).cure(array[2]);
break;
case "排除":
map.get(array[0]).removeSp(array[3]);
break;
default:
break;
}
}
//统计全国的情况
public void allStatistic()
{
for (int i = 0; i < name.size(); i ++ )
{
if (map.get(name.get(i)) != null)
{
country.allAdd(map.get(name.get(i)));
}
}
} -
日志输出:
InfectStatistic类中的output方法用于输出所有要输出的省份到输出文件。Province类中的output方法用于输出单个省份的情况。List<String>name存放所有省份的名称,List<String>provinces存放-province参数指定的省份的名称。
在InfectStatistic类中的output方法里,若boolean变量isOutputAll为true,即默认情况,则输出所有省份,遍历List<String>name;否则输出-province参数指定的省份,遍历存放所有省份的String数组provinceName,逐个判断其是否为-province参数指定的省份,若是则输出。
在Province类中的output方法里,若boolean变量isOutput为true,即默认情况,则输出该省份四种人群的数量;否则按照output数组中指定人员类型进行输出。//InfectStatistic类中的output方法
public void output() throws IOException
{
if (isFinish)
{
return;
}
BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath)) ;
//默认情况,没有-province参数
if (isOutputAll)
{
country.output(isOutput,output,bw);
for (int i = 0; i < name.size(); i ++ )
{
if (map.get(name.get(i)) != null)
{
map.get(name.get(i)).output(isOutput, output, bw);
}
}
}
//有-province参数
else
{
if (provinces.contains("全国"))
{
country.output(isOutput,output,bw);
}
for (int i = 0; i < provinceName.length; i ++ )
{
if (provinces.contains(provinceName[i]))
{
if (map.get(name.get(i)) == null)
{
map.put(provinceName[i], new Province(provinceName[i]));
}
map.get(provinceName[i]).output(isOutput, output, bw);
}
}
}
bw.write("// 该文档并非真实数据,仅供测试使用");
bw.close();
}//Province类中的output方法
public void output(boolean isOutput,String[] output,BufferedWriter bw) throws IOException
{
//默认输出
if(isOutput)
{
bw.write(name + " 感染患者 " + infectionPatients + "人 " +
"疑似患者 " + suspectedPatients + "人 " +
"治愈 " + cure + "人 " +
"死亡 " + dead + "人");
bw.newLine();
}
//有-type参数情况下的输出
else
{
bw.write(name);
for(int i=0;i<4;i++)
{
switch(output[i])
{
case "ip":
bw.write(" 感染患者 " + infectionPatients + "人");
break;
case "sp":
bw.write(" 疑似患者 " + suspectedPatients + "人");
break;
case "cure":
bw.write(" 治愈 " + cure + "人");
break;
case "dead":
bw.write(" 死亡 " + dead + "人");
break;
default:
break;
}
}
bw.newLine();
}
}
5、单元测试截图和描述
本次单元测试读取的日志为example下的三个log.txt文件
单元测试1:测试默认情况下,即没有参数-date、-type、-province的结果。输出为所提供日志最新的一天所有省份的情况:


单元测试2:测试参数-date为2020-01-23的情况,此时程序需要处理日志:2020-01-22.log.txt、2020-01-23.log.txt,输出为1月23日所有省份的情况:


单元测试3:测试参数-date为2020-01-25的情况,因为没有日志2020-01-24.log.txt,所以此时程序只需要处理日志:2020-01-22.log.txt、2020-01-23.log.txt,输出为1月25日所有省份的情况:


单元测试4:测试参数-date为2020-01-27,-type为ip的情况,输出为1月27日所有省份的感染患者的人数:


单元测试5:测试参数-date为2020-01-27,-type为ip,sp的情况,输出为1月27日所有省份的感染患者与疑似患者的人数:


单元测试6:测试参数-date为2020-01-27,-type为ip,cure,dead,sp的情况,按顺序输出1月27日所有省份的感染患者、治愈、死亡、疑似患者的人数:


单元测试7:测试参数-date为2020-01-27,-type为ip,-province为“福建”,“全国”的情况,因为全国总是排在第一个,所以按顺序输出1月27日全国、福建的感染患者的人数:


单元测试8:测试参数-date为2020-01-27,-province为“福建”,“全国”,“北京”的情况,其中因为输入文档没有涉及到北京,所以北京的各项数据均为0,且因为全国总是排在第一个,且按拼音排序北京应该在福建的前面,所以按顺序输出1月27日全国、北京、福建的所有人数:


单元测试9:测试参数-province为“福建”,“全国”,“北京”,-date为2020-01-27的情况,因为命令行参数的顺序可以调换,所以单元测试9的结果与单元测试8一样


单元测试10:测试参数-date为2020-01-29的情况,因为最新一天的日志为2020-01-27.log.txt,所以应给与“日期超出范围”的错误提示。


6、单元测试覆盖率优化和性能测试
单元测试覆盖率如下:
因为程序中未覆盖到的代码都为一些错误命令的判断,所以没有什么要优化的地方。

性能如下:


7、代码规范链接
代码规范链接:https://github.com/bzzd2333/InfectStatistic-main/blob/new/221701236/codestyle.md
8、心路历程与收获
在本次的程序开发中,首先我明白了程序的开发并不只有编写程序,从一开始的需求分析到最后的测试,每个环节都大有门道;其次,我学习了很多新东西,如github、单元测试、JProfiler的使用,这让我明白了持续学习的重要性;第三,通过分析PSP表格,我发现我在具体编码上多花了很多时间,这也是因为我的粗心大意以及对需求分析不足所导致的,所以我以后应该更加仔细地分析需求。最后,本次的作业使我受益良多,我相信这次作业学到的东西和积累的经验必可活用于下次。
9、技术路线图相关的5个仓库
1、JavaGuide
链接:https://github.com/bzzd2333/JavaGuide
简介:该仓库总结了JAVA的学习路线及知识点和面试技巧,适合进行JAVA知识点的复习。
2、SpringAll
链接:https://github.com/bzzd2333/SpringAll
简介:该仓库包括了Spring Boot,Spring Boot&Shiro,Spring Cloud,Spring Boot&Spring Security&Spring Security OAuth2等系列教程。
3、JAVAWeb-Project
链接:https://github.com/bzzd2333/JAVAWeb-Project
简介:该仓库存放的是开始学习JAVA-WEB开发的一些练手项目,这些也适合初学者进行练习
4、3y
链接:https://github.com/bzzd2333/3y
简介:该仓库从Java基础、JavaWeb基础到常用的框架再到面试题都有完整的教程,几乎涵盖了Java后端必备的知识点。
5、JAVAEETest
链接:https://github.com/bzzd2333/JavaEETest
简介:该仓库存放了一些Spring、SpringMVC、MyBaits、Spring Boot案例。
浙公网安备 33010602011771号