这个作业属于哪个课程 | 软工 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 设计、开发一个疫情统计的程序、学习对程序的优化、学习GitHub的使用、PSP(个人软件开发流程)的学习使用、《构建之法》的学习 |
作业正文 | 作业 |
其他参考文献 | Eclipse单元测试 JProfiler使用 github 博客园相关文章... |
一、GitHub仓库地址
GItHub仓库地址
二、构建之法学习成果
2.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 45 |
Estimate | 估计这个任务需要多少时间 | 30 | 45 |
Development | 开发 | 450 | 500 |
Analysis | 需求分析 (包括学习新技术) | 30 | 15 |
Design Spec | 生成设计文档 | 30 | 25 |
Design Review | 设计复审 | 30 | 35 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 45 | 60 |
Design | 具体设计 | 30 | 50 |
Coding | 具体编码 | 300 | 450 |
Code Review | 代码复审 | 60 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 150 | 40 |
Reporting | 报告 | 60 | 135 |
Test Repor | 测试报告 | 60 | 90 |
Size Measurement | 计算工作量 | 15 | 15 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 40 |
合计 | 1350 | 1605 |
三、解题思路描述
1.所需知识点
本次任务我打算使用java语言,看到这个题目以后我想到了以下几个知识点需要被使用到
命令行参数的获取
在命令行中用户输入的参数,即list命令及其所带选项需要被程序读入并且正确处理
于是我找寻了相关的知识点如何获取java程序的命令行参数
并且编写了测试程序确保能够正确获取到命令行参数
文件的读入与输出
我们需要通过读取/log目录下的文件并且对其正确处理后将结果输出
这就涉及到java对文件输入输出的管理以及一些需要注意的细节(如编码问题)
java文件读写
2.选取正确的数据结构
仔细读取题目可以发现可以将每一个省定义为一个对象,即可定义Province类,该类拥有如下数据成员
/*记录一个省的各项数据*/
class Province{
String name; /*省名*/
int infect; /*感染人数*/
int seeming; /*疑似人数*/
int dead; /*死亡人数*/
int cured; /*治愈人数*/
public Province() {
infect = seeming = dead = cured = 0;
}
}
3.定义文件结构
221701126(目录名为学号)
\--src
\--InfectStatistic.java
\--Lib.java
\--README.md
描述你的项目,包括如何运行、功能简介、作业链接、博客链接等
\--codestyle.md
描述你之前定的代码风格
4.代码风格制定
编写代码前肯定需要规范自己的代码风格,这关系编程过程中的代码样式,故参考了所给的阿里巴巴的java代码风格推荐,制定了属于自己的代码风格
5.需求分析
能够统计统计文件内信息并打印出需要了解的内容到目标文件中
1.根据命令行处理参数
2.选出目标目录下合适的文件
3.对每个文件的每行字符串进行处理,并记录数据
4.输出要求的行到目标文件
6.文件中字符串类型
分为以下几种:
新增 感染患者 n人
新增 疑似患者 n人
感染患者 流入 n人
疑似患者 流入 n人
死亡 n人
治愈 n人
疑似患者 确诊感染 n人
排除 疑似患者 n人
7.处理文件
看到文件结构就可以想到应该逐行处理,那么需要将字符串以空格分割成数组,更方便。接着可以知道第一个字符串是省份,可以存起来,最后应该字符串是人数,可以取出,中间的字符串就是行为,我们可以一个一个判断,遇到可以直接判断的字符串,就直接进行处理,不然就判断下一个字符串,再综合判断其行为,这样整个逻辑的判断就十分清晰了。
8.输出文件
先将所有省份按首字母顺序存放到一个数组,接着遍历数组,对每个省份进行判断是否要输出,如若需要输出,再将其作为参数传入相应的方法,再做具体判断。接着该方法中会进行type province选项参数的判定。
四、设计实现过程
代码流程
具体开发过程(逐步实现)
五、代码说明
1.数据结构
数据结构采用Province,封装了省名以及各项指标数据,如感染人数、疑似患者等,使用其即可表示单独一个省的情况。
class Province{
String name; /*省名*/
int infect; /*感染人数*/
int seeming; /*疑似人数*/
int dead; /*死亡人数*/
int cured; /*治愈人数*/
public Province() {
infect = seeming = dead = cured = 0;
}
}
2.处理命令行参数
获取args的参数,对args数组的数据进行处理
如果是-log命令,则存储输入路径
如果是-out命令,则存储输出路径
如果是-date命令,则存储日期
如果是-type命令,则存储type选项
如果是-province命令,则存储province选项
public static void solveArgs(String[] args) {
int i = 0;
int pos = 1;
while(pos < args.length) {
String arg = args[pos];
// System.out.println(pos + "-" + arg);
if(arg.indexOf('-') == 0) {//这是命令
if(arg.equals("-log")) {//处理输入路径
inputPath = args[pos + 1] + "\\";
pos+=2;
}
else if(arg.equals("-out")) {//处理输出路径
outputPath = args[pos + 1] + "\\";
pos+=2;
}
else if(arg.equals("-date")) {//处理日期
targetDate = args[pos + 1];
pos+=2;
}
else if(arg.equals("-type")) {
for(i = pos + 1; i < args.length; i++) {
String param = args[i];
if(param.indexOf('-') != 0) {//这是参数
typeItem.add(param);
}
else {
pos = i;
break;
}
}
}
else if(arg.equals("-province")) {//处理province命令
for(i = pos + 1; i < args.length; i++) {
String param = args[i];
if(param.indexOf('-') != 0) {//这是参数
provinceItem.add(param);
}
else {
pos = i;
break;
}
}
}
if(i == args.length) {
break;
}
}
}
}
以下展示用于存储各项数据的数据成员
public static String inputPath = "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log\\";
public static String outputPath = "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt";
public static String targetDate = "";
public static ArrayList typeItem = new ArrayList();/*记录type命令所带的选项*/
public static ArrayList provinceItem = new ArrayList();/*记录province命令所带的选项*/
3.寻找需要处理的文件
用一个vector来存储需要处理的文件,对目录下每一文件进行判断,若其日期小于命令行获取的date参数,则进入vector中存储
比较算法则是采用字符串的compareTo,即将文件名转化成字符串,接着进行判断
最后再放入“全国”
public static void solveDateOrder(String targetDate) {
String maxDate = getMaxDate();
if(targetDate.compareTo(maxDate) > 0) {
System.out.println("日期超出范围");
return;
}
//获取输入路径下的所有文件
File file = new File(inputPath);
if(file.isDirectory()) {
Vector toHandleDate = new Vector();//获取符合要求待处理的日期文件
String[] fileNames = file.list(); // 获得目录下的所有文件的文件名
for(String fileName : fileNames) {
fileName = fileName.substring(0, fileName.indexOf('.'));//截断后缀名
//日期比较
if(fileName.compareTo(targetDate) <= 0) {
toHandleDate.add(fileName);
System.out.println(fileName);
}
else {
break;
}
//System.out.println(fileName);
}
if(toHandleDate.size() > 0) {
solveEveryFile(toHandleDate);
}
map.put("全国", country);
}
}
4.处理每个文件
对每个文件都逐行获取,接着使用字符串的split函数对其按空格进行分割,即可得到information数组
String[] information = str.split("\\s+");
关键算法:判断第二个字符串是什么
若是新增,则判断第三个字符串是感染患者还是疑似患者,进行数据统计
switch (information[1]) {
case "新增":
if(information[2].equals("感染患者")) {//感染患者的情况
p.infect += number;
country.infect += number;
//System.out.println(num);
}
else {//疑似患者的情况
p.seeming += number;
country.seeming += number;
}
break;
若是“感染患者”,则断定为感染者流入情况,则取出流入的省份已经人数进行数据处理
case "感染患者":
String p2 = information[3];//取出流入的省份名称
if(map.get(p2) != null) {//若该省份已经出现过
Province anotherProvince = map.get(p2);
anotherProvince.infect += number;
p.infect -= number;
}
else {
Province province2 = new Province();
province2.name = p2;
province2.infect += number;
p.infect -= number;
map.put(p2, province2);
}
break;
若是“疑似患者”,则判断是确诊还是流入,若为确诊,则减少疑似人数,增加感染人数,若为流入,如上处理
case "疑似患者":
//判断是流入还是确诊
if(information[2].equals("流入")) {
String p3 = information[3];//取出流入的省份名称
if(map.get(p3) != null) {//若该省份已经出现过
Province anotherProvince = map.get(p3);
anotherProvince.seeming += number;
p.seeming -= number;
}
else {
Province province3 = new Province();
province3.name = p3;
province3.infect += number;
p.infect -= number;
map.put(p3, province3);
}
}
else {//确诊
p.infect += number;
p.seeming -= number;
country.infect += number;
country.seeming -= number;
}
break;
若为“死亡”,则减少感染人数,增加死亡人数
case "死亡":
p.infect -= number;
p.dead += number;
country.infect -= number;
country.dead += number;
break;
若为“治愈”,则减少感染人数,增加治愈人数
case "治愈":
p.infect -= number;
p.cured += number;
country.infect -= number;
country.cured += number;
break;
若为“排除”,则减少疑似人数
case "排除":
p.seeming -= number;
country.seeming -= number;
break;
5.获取各行人数
通过substring来截取到“人”的下标之前的子串
public static int getNumber(String[] information) {
//获取人数
String numString = information[information.length - 1];
int index = numString.indexOf("人");
numString = numString.substring(0, index);
int number = Integer.parseInt(numString);
return number;
}
6.打印结果
事先按顺序存储所有的省份
public static String[] province = {"全国", "安徽", "北京", "重庆", "福建", "甘肃", "广东", "广西", "贵州", "海南", "河北",
"河南", "黑龙江", "湖北", "湖南", "吉林", "江苏", "江西", "辽宁", "内蒙古", "宁夏", "青海", "山东", "山西", "陕西",
"上海", "四川", "天津", "西藏", "新疆", "云南", "浙江"
};
通过for循环来判断每一个省,看看是否需要输出
public static void printResult() {
try {
File output = new File(outputPath);
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(output));
if(provinceItem.size() == 0) {
for(String provinceName : province) {
if(map.get(provinceName) != null) {
printTheProvince(provinceName, osw);
}
}
}
else {
for(String provinceName : province) {
if(provinceItem.contains(provinceName)) {
printTheProvince(provinceName, osw);
}
}
}
osw.flush();
osw.close();
} catch (IOException e) {
}
}
7.对每个省的判断
先判断是否有type命令,若有则配合for和switch进行指定顺序输出,否则就按原来的的顺序输出
if(map.get(provinceName) != null) {
Province province = map.get(provinceName);
if(typeItem.size() != 0) {
for(String item : typeItem) {
switch (item) {
case "ip":
System.out.print(" 感染患者" + province.infect + "人");
osw.write(" 感染患者" + province.infect + "人");
break;
case "sp":
System.out.print(" 疑似患者" + province.seeming + "人");
osw.write(" 疑似患者" + province.seeming + "人");
break;
case "cure":
System.out.print(" 治愈" + province.cured + "人");
osw.write(" 治愈" + province.cured + "人");
break;
case "dead":
System.out.print(" 死亡" + province.dead + "人");
osw.write(" 死亡" + province.dead + "人");
break;
default:
break;
}
}
}
else {
osw.write(" 感染患者" + province.infect + "人 疑似患者" + province.seeming + "人 治愈" + province.cured + "人 死亡" + province.dead + "人");
System.out.print(" 感染患者" + province.infect + "人 疑似患者" + province.seeming + "人 治愈" + province.cured + "人 死亡" + province.dead + "人");
}
}
六、单元测试截图和描述
@Test1 测试能否正确获得命令行参数
@Test
public void testSolveArgs() {
String[] order = {"list", "-date", "2020-01-23", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-type", "ip", "sp", "dead", "-province", "福建", "浙江"};
InfectStatistic.main(order);
}
@Test
public void testPrintTheProvince() throws IOException {
File output = new File("C:\\\\Users\\\\Peter\\\\Documents\\\\GitHub\\\\InfectStatistic-main\\\\221701126\\\\result\\\\out.txt");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(output));
InfectStatistic.printTheProvince("福建", osw);
osw.flush();
osw.close();
}
由于该省份还没有给与任何数据,故都是人数都是0。
@Test3 测试能否打印所有需要打印的省份
@Test
public void testPrintResult() {
InfectStatistic.outputPath = "C:\\\\Users\\\\Peter\\\\Documents\\\\GitHub\\\\InfectStatistic-main\\\\221701126\\\\result\\\\out.txt";
InfectStatistic.provinceItem.add("全国");
InfectStatistic.provinceItem.add("福建");
InfectStatistic.provinceItem.add("内蒙古");
InfectStatistic.printResult();
}
@Test4 测试能否获得每行字符串的人数
@Test
public void testGetNumber() {
String[] testStr1 = {"福建", "新增", "疑似患者", "5人"};
System.out.println(InfectStatistic.getNumber(testStr1));
String[] testStr2 = {"湖北", "感染患者", "流入", "福建", "23人"};
System.out.println(InfectStatistic.getNumber(testStr2));
String[] testStr3 = {"湖北", "排除", "疑似患者", "15人"};
System.out.println(InfectStatistic.getNumber(testStr3));
}
@Test5 测试每个文件的处理,打印出需要处理的文件名
@Test
public void testSolveEveryFile() {
Vector toHandleFile = new Vector();
toHandleFile.add("2020-01-22");
toHandleFile.add("2020-01-23");
toHandleFile.add("2020-01-27");
InfectStatistic.solveEveryFile(toHandleFile);
}
@Test6 测试获得目录下的最大日期,打印出目录下最大的日期
@Test
public void testGetMaxDate() {
System.out.println(InfectStatistic.getMaxDate());
}
@Test7 测试获取目录下符合要求的所有文件,打印出其文件名
@Test
public void testSolveDateOrder() {
System.out.println("测试1");
InfectStatistic.targetDate = "2020-01-29";
InfectStatistic.solveDateOrder(InfectStatistic.targetDate);
System.out.println("测试2");
InfectStatistic.targetDate = "2020-01-24";
InfectStatistic.solveDateOrder(InfectStatistic.targetDate);
System.out.println("测试3");
InfectStatistic.targetDate = "2020-01-27";
InfectStatistic.solveDateOrder(InfectStatistic.targetDate);
}
@Test8 测试没有date选项的命令,打印出最新日期的日志,测试命令为:list -log "C:\Users\Peter\Documents\GitHub\InfectStatistic-main\221701126\log" "-out" "C:\Users\Peter\Documents\GitHub\InfectStatistic-main\221701126\result\out.txt" "-type" "ip" "sp" "dead" "-province" "福建" "浙江"
@Test
public void testNoDate() {
String[] order = {"list", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-type"
, "ip", "sp", "dead", "-province", "福建", "浙江"};
InfectStatistic.main(order);
}
@Test9 测试-date选项,参数为2020-01-23的情况
@Test
public void testDate1() {
String[] order = {"list", "-date", "2020-01-23", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt"};
InfectStatistic.main(order);
}
@Test10 测试-date选项,参数为2020-01-25的情况,结果同2020-01-23的情况
@Test
public void testDate2() {
String[] order = {"list", "-date", "2020-01-25", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt"};
InfectStatistic.main(order);
}
@Test11 测试-date选项,参数为2020-01-27的情况
@Test
public void testDate3() {
String[] order = {"list", "-date", "2020-01-27", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt"};
InfectStatistic.main(order);
}
@Test12 测试-date选项,参数为2020-01-28的情况,打印“日期超出范围”
@Test
public void testDate4() {
String[] order = {"list", "-date", "2020-01-28", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt"};
InfectStatistic.main(order);
}
@Test
public void testType1() {
String[] order = {"list", "-date", "2020-01-22", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-type", "cure", "ip"};
InfectStatistic.main(order);
}
@Test14 测试-type选项,参数为cure dead ip sp
@Test
public void testType2() {
String[] order = {"list", "-date", "2020-01-23", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-type", "cure", "dead", "ip", "sp",
"-province", "全国", "浙江", "福建"};
InfectStatistic.main(order);
}
@Test15 测试-province选项,参数为全国 浙江 福建
@Test
public void testProvince1() {
String[] order = {"list", "-date", "2020-01-23", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-province", "全国", "浙江", "福建"};
InfectStatistic.main(order);
}
@Test16 测试-province选项,测试需要打印的省份不在文件中出现,参数为陕西 云南
@Test
public void testProvince2() {
String[] order = {"list", "-date", "2020-01-23", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-province", "陕西", "云南"};
InfectStatistic.main(order);
}
@Test17 测试-province选项,参数为福建 山东 全国 广西
@Test
public void testProvince3() {
String[] order = {"list", "-date", "2020-01-23", "-log", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\log",
"-out", "C:\\Users\\Peter\\Documents\\GitHub\\InfectStatistic-main\\221701126\\result\\out.txt", "-province", "福建", "山东", "全国", "广西"};
InfectStatistic.main(order);
}
七、单元测试覆盖率优化和性能测试
单元测试覆盖率,由于测试的比较全面,故覆盖率较高,几乎覆盖了所有代码,还有一些未覆盖的地方即为测试用例不到位或一些异常捕捉语句,提升空间不大
八、代码规范
代码规范
九、心路历程与收获
在这次作业之前,我还没有过团队合作开发的经验,因此不会使用像git这样的版本控制工具,以及像github这样子的开源仓库工具,但是在这次的开发过程中,我尝试的一步一步查询相关的文章,去使用github。
这次的编码任务是疫情统计程序,我认为在学过java的基础上开发这个程序的难度并不大,只不过需要认真的复习一下之前java的知识,但在开发过程中比较重要的就是学会记录自己的代码版本,虽然说这次还是个个人开发的小作业,但是养成存储代码版本这个好习惯是每个开发人员必备的,因此我使用了githubDestop来记录自己对这个项目的完善工程,我将这个项目分成许多模块,当完成一个模块的开发以后我就commit一下,这样就算后续代码出了什么问题,我也能根据历史记录回溯到之前正常的版本,可以说学会这项技能是十分方便和必要的。
在整个任务完成后,我还学会了单元测试。何谓单元测试?就是通过将许多测试函数封装成一个类,这样就一个一次性的跑完所需要进行的所有测试,而不需要在命令行一个一个例子去试验,提高了测试的效率,测试也是开发中的一个必不可少的流程,有了测试,我就能清晰的知道自己的代码还存在哪些不足,存在哪些bug,予以修复后,就可以进行一次commit,这样代码的稳定性就在不断的提升中,而且学会使用单元测试来测试自己的代码也是一个开发人员的责任,自己写的代码自己测,这样以后如果代码需要修改维护,就可以对症下药,而不会手忙脚乱。
测试完成以后,我通过开发工具eclipse测试了一下代码覆盖率和性能,发现了自己在编写代码过程中一些不够完美的语法处理,有一些不会被用到的方法,或者说有一些语句的顺序或者嵌套十分繁杂,都会降低覆盖率和性能,因此就着覆盖率的展示,我照着一个一个方法去改进,删除了不必要的语句,调整了语句结构,提升了一些覆盖率和性能,开发人员在开发过程中,不可只关注到任务的完成,还需要关心系统的性能问题,因为只有我们自己了解我们自己的代码,因此自己对此做出一些简单的优化是很有必要性的,也是一个好习惯,一个优秀的项目不仅应该完成用户的需求,还应该在性能方便有显著的优势,哪怕提升了一点的性能,在计算机世界中也是巨大的进步。
剩下的一些工作例如代码规范和文档的攥写也是十分重要的,不仅考察一个开发人员的编程能力,更是考验其程序的规范性和有序性,一个开发人员不应该是混乱无章的,反而应该井井有序。
通过这次的任务,我深入的理解的软件工程的开发流程,十分期待以后的多人协作,但是在多人协作之前,我们每个人应该要先规范好自己,这样整个团队才能有序团结。
十、五个相关仓库
1.vue.js
基于 vue2 + vuex 构建一个具有 45 个页面的大型单页面应用,模仿饿了么打造的一个购物demo,适合找不到demo巩固的学习人员进行练习。
2.ant design pro
是基于Ant Design这个框架搭建的中后台管理控制台的脚手架,使用其可快速搭建ant d框架,便于开发,节约时间。
3.react select
React的选择控件,最初构建用于KeystoneJS,是一种用来开发React组件的方法,组件可以开箱即用,也可以自行定制。
4.h5模板
腾讯微信优化的H5动效模板,帮助你快速构建全屏滚动型H5页面。
5.css.animation
一个跨浏览器的CSS动画库,简单易用,拥有许多可以直接使用的控件。