欢迎来到SeinoNana的博客

雨下整夜,我的爱溢出就像雨水。院子落叶,跟我的思念厚厚一叠。几句是非,也无法将我的热情冷却,你出现在我诗的每一页
扩大
缩小
返回顶部

软工实践寒假作业(2/2)

这个作业属于哪个课程 软工
这个作业要求在哪里 作业要求
这个作业的目标 设计、开发一个疫情统计的程序、学习对程序的优化、学习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人
<省1> 感染患者 流入 <省2> n人
<省1> 疑似患者 流入 <省2> 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<String> typeItem = new ArrayList<String>();/*记录type命令所带的选项*/
public static ArrayList<String> provinceItem = new ArrayList<String>();/*记录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<String> toHandleDate = new Vector<String>();//获取符合要求待处理的日期文件
    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);
}


@Test2 测试打印一个省的信息

@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();
}


可以打印出所有省份,由于没有数据,故人数都为0

@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<String> toHandleFile = new Vector<String>();
	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);
} 


@Test13 测试-type选项,参数为cure ip

@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动画库,简单易用,拥有许多可以直接使用的控件。

posted @ 2020-02-20 10:50  SeinoNana  阅读(287)  评论(3编辑  收藏  举报