软工实践寒假作业(2/2)
这个作业属于哪个课程 | 班级的链接 |
---|---|
这个作业要求在哪里 | 软工实践寒假作业(2/2) |
这个作业的目标 | 学习Github的使用,设计一个疫情统计系统,学习PSP,学习《构建之法》,学习对程序进行优化 |
作业正文 | https://edu.cnblogs.com/campus/fzu/2020SPRINGS/homework/10287 |
其他参考文献 | 知乎github使用教程,知乎单元测试,《构建之法》,... |
1.Github仓库地址
2.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 45 |
Estimate | 估计这个任务需要多少时间 | 30 | 45 |
Development | 开发 | 910 | 960 |
Analysis | 需求分析 (包括学习新技术) | 90 | 150 |
Design Spec | 生成设计文档 | 20 | 25 |
Design Review | 设计复审 | 20 | 15 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 25 |
Design | 具体设计 | 30 | 30 |
Coding | 具体编码 | 600 | 540 |
Code Review | 代码复审 | 30 | 25 |
Test | 测试(自我测试,修改代码,提交修改) | 90 | 150 |
Reporting | 报告 | 60 | 75 |
Test Repor | 测试报告 | 30 | 40 |
Size Measurement | 计算工作量 | 15 | 25 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 15 | 10 |
合计 | 1000 | 1080 |
3.解题思路表述
首先我们来看需求,并进行分析。
需求:
1.通过命令行输入命令和参数。
2.根据输入参数的不同来选择性操作。
3.读取指定目录下的文件。
4.处理文件中的数据,并以指定格式、与文件名有序输出到文件。
4.设计实现过程
程序流程图
主要流程如下图所示,部分功能在图中已简化,在代码说明中将详细描述。
模块结构
输入的参数的存放
public static String inputPath = "";
public static String outputPath = "";
public static String [] provinceList = new String[]{"","","","","","","","","","","","","",""
,"","","","","","","","","","","","","","","","","","","","",""};
public static String [] typeList = new String[]{"","","",""};
public static String dateString = "";
public static Date inputDate = new Date();
public static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
public static String initMaxDateString = "1900-01-01";
public static String now = "";
public static int nowIndex = 0;
Province类
public static class Province
{
int ip = 0;
int sp = 0;
int cure = 0;
int dead = 0;
String name = "";
Province(String name)
{
this.ip = 0;
this.sp = 0;
this.cure = 0;
this.dead = 0;
this.name = name;
}
}
Province类数据修改方法
public static void ipIncrease(Province p, int num){...}
public static void spIncrease(Province p, int num){...}
public static void cureIncrease(Province p, int num){...}
public static void deadIncrease(Province p, int num){...}
public static void spToIp(Province p, int num){...}
public static void spDecrease(Province p, int num){...}
public static void ipMoveTo(Province p1, Province p2, int num){...}
public static void spMoveTo(Province p1, Province p2, int num){...}
main函数
public static void main(String[] args)
{
// TODO Auto-generated method stub
int number = args.length;
Province nation = new Province("全国");
HashMap<String,Province> map = new HashMap<>();
if(args[0].equals("list"))
{
for(int i=1;i<number;i++)
{
getParameters(args[i]);
}
}
else
{
System.out.print("不存在该命令!");
return ;
}
if(!Arrays.asList(args).contains("-log")||!Arrays.asList(args).contains("-out"))
{
System.out.print("缺少参数-log或-out");
return ;
}
if(dateString.equals(""))
{
try
{
inputDate = format.parse(format.format(getMaxDate(inputPath)));
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(!checkDate(inputPath,inputDate))
{
System.out.print("输入的日期超出范围!");
return ;
}
else
{
map = readInfo(inputPath, inputDate, map);
nation = calTotal(map);
}
Set set = map.keySet();
Object[] arr = set.toArray();
Comparator<Object> com = Collator.getInstance(java.util.Locale.CHINA);
Arrays.sort(arr);
Arrays.sort(provinceList,com);
outputInfo(typeList, provinceList, nation, outputPath, map, set);
}
5.代码说明
读取命令行参数方法
思路:now为当前将要写入的参数名,nowIndex为当前将要写入的参数数组的下标,当读到非参数名的参数是,可通过now来判断其值对应的参数,这样可以实现参数顺序不定情况下的正确读取。
public static void getParameters(String s)
{
if(s.equals("-log"))
{
now = "log";
nowIndex = 0;
}
else if(s.equals("-out"))
{
now = "out";
nowIndex = 0;
}
else if(s.equals("-date"))
{
now = "date";
nowIndex = 0;
}
else if(s.equals("-type"))
{
now = "type";
nowIndex = 0;
}
else if(s.equals("-province"))
{
now = "province";
nowIndex = 0;
}
else
{
if(now.equals("type"))
{
typeList[nowIndex] = s;
nowIndex++;
}
else if(now.equals("province"))
{
provinceList[nowIndex] = s;
nowIndex++;
}
else if(now.equals("log"))
{
inputPath = s;
}
else if(now.equals("out"))
{
outputPath = s;
}
else if(now.equals("date"))
{
try
{
dateString = s;
inputDate = format.parse(s);
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
读取文件方法
思路:
public static HashMap<String,Province> readInfo(String path, Date date, HashMap<String,Province> map)
{
String [] list = new File(path).list();
int compareTo = 0;
for(int i=0;i<list.length;i++)
{
try
{
Date current = format.parse(list[i].substring(0, 10));
compareTo = date.compareTo(current);
if (compareTo >= 0)
{
String filePath = path + list[i];
File file = new File(filePath);
FileInputStream fis = new FileInputStream(filePath);
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
try (BufferedReader br = new BufferedReader(isr))
{
String line = null;
String [] info = null;
String provinceName1 = null;
String provinceName2 = null;
int operateNum = 0;
while ((line = br.readLine()) != null)
{
line = line.trim();
info = line.split(" ");
Province p1 = null;
Province p2 = null;
if(info[0].substring(0, 2).equals("//"))
{
break;
}
else if(info.length==3)
{
provinceName1 = info[0];
operateNum = Integer.parseInt(info[2].substring(0, info[2].length() - 1));
if(map.containsKey(provinceName1))
{
p1 = map.get(provinceName1);
}
else
{
p1 = new Province(provinceName1);
}
if(info[1].equals("死亡"))
{
deadIncrease(p1, operateNum);
map.put(provinceName1,p1);
}
else if(info[1].equals("治愈"))
{
cureIncrease(p1, operateNum);
map.put(provinceName1,p1);
}
}
else if(info.length==4)
{
provinceName1 = info[0];
operateNum = Integer.parseInt(info[3].substring(0, info[3].length() - 1));
if(map.containsKey(provinceName1))
{
p1 = map.get(provinceName1);
}
else
{
p1 = new Province(provinceName1);
}
if(info[1].equals("新增"))
{
if(info[2].equals("感染患者"))
{
ipIncrease(p1, operateNum);
map.put(provinceName1,p1);
}
else if(info[2].equals("疑似患者"))
{
spIncrease(p1, operateNum);
map.put(provinceName1,p1);
}
}
else if(info[1].equals("排除"))
{
spDecrease(p1, operateNum);
map.put(provinceName1,p1);
}
else if(info[1].equals("疑似患者"))
{
spToIp(p1, operateNum);
map.put(provinceName1,p1);
}
}
else if(info.length==5)
{
provinceName1 = info[0];
provinceName2 = info[3];
operateNum = Integer.parseInt(info[4].substring(0, info[4].length() - 1));
if(map.containsKey(provinceName1))
{
p1 = map.get(provinceName1);
}
else
{
p1 = new Province(provinceName1);
}
if(map.containsKey(provinceName2))
{
p2 = map.get(provinceName2);
}
else
{
p2 = new Province(provinceName2);
}
if(info[1].equals("感染患者"))
{
ipMoveTo(p1, p2, operateNum);
map.put(provinceName1,p1);
map.put(provinceName2,p2);
}
else if(info[1].equals("疑似患者"))
{
spMoveTo(p1, p2, operateNum);
map.put(provinceName1,p1);
map.put(provinceName2,p2);
}
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
catch (ParseException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
return map;
}
输出文件方法
思路:
public static void outputInfo (String[] typeList, String[] provinceList, Province nation, String path, HashMap<String,Province> map, Set set)
{
FileWriter fileWriter = null;
String dirPath = "";
if(path.indexOf("/")>1)
{
dirPath = path.substring(0,path.lastIndexOf("/"));
}
else
{
dirPath = path.substring(0,path.lastIndexOf("/") + 1);
}
File dir = new File(dirPath);
if (!dir.exists())
{
dir.mkdirs();
}
File file = new File(path);
fileWriter = null;
try
{
if(!file.exists())
{
file.createNewFile();
}
else
{
file.delete();
file.createNewFile();
}
fileWriter = new FileWriter(file, true);
if(provinceList[34].equals("")) //输出全部省份信息
{
if(typeList[0].equals(""))
{
fileWriter.write(nation.name + " 感染患者"+nation.ip + "人 疑似患者"
+nation.sp + "人 治愈" + nation.cure + "人 死亡"+nation.dead + "人\n");
}
else
{
fileWriter.write(nation.name);
for(int i=0;i<typeList.length&&!typeList[i].equals("");i++)
{
if(typeList[i].equals("ip"))
{
fileWriter.write(" 感染患者" + nation.ip + "人");
}
else if(typeList[i].equals("sp"))
{
fileWriter.write(" 疑似患者" + nation.sp + "人");
}
else if(typeList[i].equals("cure"))
{
fileWriter.write(" 治愈" + nation.cure + "人");
}
else if(typeList[i].equals("dead"))
{
fileWriter.write(" 死亡" + nation.dead + "人");
}
}
fileWriter.write("\n");
}
Province p = null;
for (Object str : set)
{
String name = (String)str;
p = map.get(name);
if(typeList[0].equals(""))
{
fileWriter.write(p.name + " 感染患者" + p.ip + "人 疑似患者"
+ p.sp + "人 治愈" + p.cure + "人 死亡" + p.dead+"人\n");
}
else
{
fileWriter.write(p.name);
for(int i=0;i<typeList.length&&!typeList[0].equals("");i++)
{
if(typeList[i].equals("ip"))
{
fileWriter.write(" 感染患者" + p.ip + "人");
}
else if(typeList[i].equals("sp"))
{
fileWriter.write(" 疑似患者" + p.sp + "人");
}
else if(typeList[i].equals("cure"))
{
fileWriter.write(" 治愈" + p.cure + "人");
}
else if(typeList[i].equals("dead"))
{
fileWriter.write(" 死亡" + p.dead + "人");
}
}
fileWriter.write("\n");
}
}
}
else //输出部分省份信息
{
if(Arrays.asList(provinceList).contains("全国"))
{
if(typeList[0].equals(""))
{
fileWriter.write(nation.name + " 感染患者" + nation.ip + "人 疑似患者"
+ nation.sp+"人 治愈" + nation.cure + "人 死亡" + nation.dead + "人\n");
}
else
{
fileWriter.write(nation.name);
for(int j=0;j<typeList.length&&!typeList[j].equals("");j++)
{
if(typeList[j].equals("ip"))
{
fileWriter.write(" 感染患者" + nation.ip + "人");
}
else if(typeList[j].equals("sp"))
{
fileWriter.write(" 疑似患者" + nation.sp + "人");
}
else if(typeList[j].equals("cure"))
{
fileWriter.write(" 治愈" + nation.cure + "人");
}
else if(typeList[j].equals("dead"))
{
fileWriter.write(" 死亡" + nation.dead + "人");
}
}
fileWriter.write("\n");
}
}
for(int i=0;i<provinceList.length&&!provinceList[34].equals("");i++)
{
if(provinceList[i].equals("全国"))
{
continue;
}
else if(!map.containsKey(provinceList[i])&&!provinceList[i].equals(""))
{
if(typeList[0].equals(""))
{
fileWriter.write(provinceList[i] + " 感染患者0人 疑似患者0人 治愈0人 死亡0人\n");
}
else
{
fileWriter.write(provinceList[i]);
for(int k=0;k<typeList.length&&!typeList[0].equals("");k++)
{
if(typeList[k].equals("ip"))
{
fileWriter.write(" 感染患者0人");
}
else if(typeList[k].equals("sp"))
{
fileWriter.write(" 疑似患者0人");
}
else if(typeList[k].equals("cure"))
{
fileWriter.write(" 治愈0人");
}
else if(typeList[k].equals("dead"))
{
fileWriter.write(" 死亡0人");
}
}
fileWriter.write("\n");
}
}
else
{
if(provinceList[i].equals(""))
{
continue;
}
Province p = null;
String name = provinceList[i];
p = map.get(name);
if(typeList[0].equals(""))
{
fileWriter.write(p.name + " 感染患者" + p.ip+"人 疑似患者"
+ p.sp + "人 治愈" + p.cure + "人 死亡" + p.dead + "人\n");
}
else
{
fileWriter.write(p.name);
for(int k=0;k<typeList.length&&!typeList[0].equals("");k++)
{
if(typeList[k].equals("ip"))
{
fileWriter.write(" 感染患者" + p.ip + "人");
}
else if(typeList[k].equals("sp"))
{
fileWriter.write(" 疑似患者" + p.sp + "人");
}
else if(typeList[k].equals("cure"))
{
fileWriter.write(" 治愈" + p.cure + "人");
}
else if(typeList[k].equals("dead"))
{
fileWriter.write(" 死亡" + p.dead + "人");
}
}
fileWriter.write("\n");
}
}
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
try
{
fileWriter.write("// 该文档并非真实数据,仅供测试使用\n");
fileWriter.flush();
fileWriter.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
6.单元测试截图和描述
测试1
测试2
测试3
测试4
测试5
测试6
测试7
测试8
测试9
测试10
7.单元测试覆盖率优化和性能测试
以下为单元测试覆盖率性能测试
文件输出方法的性能非常低,我是不意外的,因为前期构思的问题,导致输出文件仅由一个方法来实现,而本程序本身就是对数据整理,并选择性输出,单靠一个方法实现这个功能,性能低时情理之中,这也是我在本次作业中应该反思的地方,提高性能需要把这个方法拆分开来。
另外,在《构建之法》中提到一点,让我印象深刻:“100%的代码覆盖率并不等同于100%的正确性”
确实如此,100%的代码覆盖率代表着每一次运行,每一行代码都被完美运行,这表示着不存在没有达到的分支。但是在程序运行时,参数存在各种各样的可能,不可能每次运行所有的方法代码覆盖率都达到100%,如果如此,这反而说明考虑到的情况不够多,存在隐含的bug。因此,不能一昧追求极高的代码覆盖率,在保证能对多种情况进行操作的前提下,合理提升代码覆盖率才是提高程序效率的王道。
8.代码规范
9.心路历程和收获
这次的作业真是让我的心情感到非常复杂。由于自己编程基础不够扎实,对自己的编程技术也不是很有信心,看到作业就手忙脚乱,头脑一片乱麻,总是很难静下心来慢慢整理思路,甚至有点惧怕编程。但是作业还是要做的,经过煎熬的挣扎还是开始了学习、分析需求、组织思路、设计、编程......其实自己还是可以做出来的嘛,希望今后对自己更有信心。
经过这次作业,巩固了java的知识,也学习到Github的使用方法,以及PSP表格的绘制......同时在本次作业的反思中吸取到了深刻的教训!回顾本次作业的具体代码,发现许多方法长度不够精简,导致编程时时思路混乱、复审时思路不清,应该在设计时对方法进行总结概括,将具有类似或重复步骤的代码段单独封装成新的方法,时程序更加精简,可读性更强。
说要图文并茂,但是我觉得,用文字记录自己的心路历程就已足够,收获应该记在脑子里,硬要加一张图片满足需求意义不大。
10.技术路线图相关的5个仓库
1.Java知识学习及面试指南
https://github.com/Snailclimb/JavaGuide
2.轻量级简洁MVC框架
https://github.com/lets-blade/blade
3.包括了Spring Boot,Spring Boot&Shiro,Spring Cloud,Spring Boot&Spring Security&Spring Security OAuth2等系列教程。
https://github.com/wuyouzhuguli/SpringAll
4.Apache Flink 声明式的数据分析开源系统,结合了分布式 MapReduce 类平台的高效,灵活的编程和扩展性。同时在并行数据库发现查询优化方案
https://github.com/apache/flink
5.数据结构与算法
https://github.com/phishman3579/java-algorithms-implementation