寒假作业2
这个作业属于哪个课程 | w班 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 回顾报考软件工程的初心,当初怎么看待软件工程和自己是如何学习的,并立下学习目标建立学习路线 |
作业正文 | .... |
其他参考文献 | ... |
一、Github仓库地址
本项目使用java语言开发
https://github.com/jokercan/InfectStatistic-main ->点击进入
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 25 |
Estimate | 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 1400 | 1550 |
Analysis | 需求分析 (包括学习新技术) | 50 | 50 |
Design Spec | 生成设计文档 | 30 | 45 |
Design Review | 设计复审 | 50 | 60 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 50 |
Design | 具体设计 | 250 | 230 |
Coding | 具体编码 | 500 | 630 |
Code Review | 代码复审 | 40 | 50 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 90 |
Reporting | 报告 | 120 | 150 |
Test Repor | 测试报告 | 40 | 60 |
Size Measurement | 计算工作量 | 30 | 35 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 40 |
合计 | 2680 | 3085 |
三、解题思路
1.什么是输入
将题目看完之后能够发现存在有两个程序有两个输入的东西,一个是输入的日志内容,一个是命令行里面的参数。所以在程序里面要处理的就是这两个东西。
- 输入的日志分析
日志的文本内容是固定的形式。即为:
<省> 新增 感染患者 n人
<省> 新增 疑似患者 n人
<省1> 感染患者 流入 <省2> n人
<省1> 疑似患者 流入 <省2> n人
<省> 死亡 n人
<省> 治愈 n人
<省> 疑似患者 确诊感染 n人
<省> 排除 疑似患者 n人
从日志的这个形式我们可以认识到,日志文件中可以通过正则表达式来抓取省份和人数信息,再通过不同的情况来做人员的计算处理。
- 输入的命令行分析
该程序是通过命令行参数进行运行的,命令的一个实例如下:
$ java InfectStatistic list -date 2020-01-22 -log D:/log/ -out D:/output.txt
在这其中java InfecStatistic表示执行主类InfectStatistic,list为命令,-date和-out是参数,其后的是参数值
题目也给出了list命令的参数支持
- -log 指定日志目录的位置,该项必会附带,请直接使用传入的路径,而不是自己设置路径
- -out 指定输出文件路径和文件名,该项必会附带,请直接使用传入的路径,而不是自己设置路径
- -date 指定日期,不设置则默认为所提供日志最新的一天。你需要确保你处理了指定日期之前的所有log文件
- -type 可选择[ip: infection patients 感染患者,sp: suspected patients 疑似患者,cure:治愈 ,dead:死亡患者],使用缩写选择,如 -type ip 表示只列出感染患者的情况,-type sp cure则会按顺序【sp, cure】列出疑似患者和治愈患者的情况,不指定该项默认会列出所有情况。
- -province 指定列出的省,如-province 福建,则只列出福建,-province 全国 浙江则只会列出全国、浙江
所以在程序的一开始我们就需要检测语句是否是正确的。可以知道-log参数我们需要它指定的路径下面一定要具有相应的日志文件。-out所指示的文件是能够创建的,-date,-province,-type的参数值是固定值或者固定形式的。根据这些参数和参数值从而筛选得到每个省份的各种人的数值。
2.什么是输出
程序输出的内容最终存放于用户所给的文件。输出也有特定的形式。例如:
全国 感染患者22人 疑似患者25人 治愈10人 死亡2人
福建 感染患者2人 疑似患者5人 治愈0人 死亡0人
浙江 感染患者3人 疑似患者5人 治愈2人 死亡1人
// 该文档并非真实数据,仅供测试使用
所以这种简单的形式可以用pojo类来完成。
四、设计实践的过程
1.正则表达式->REUtil
保存需要的正则表达式。设置函数传入相应的字符串对比输出相等与否。eg:
public boolean checkProvinces(String a){
String province = "全国|北京|天津|上海|重庆|河北|山西|辽宁|吉林|黑龙江|江苏|浙江|安徽|福建|江西|山东|河南|湖北|湖南|广东|海南|四川|贵林|云南|陕西|甘肃|青海|台湾|内蒙古|广西|西藏|宁夏|新疆|香港|澳门";
return Pattern.matches(province,a);
}
2.语句判断->CommandJudge
判断输入的命令行是否是正确的.基本规则如下
- 命令行的第一个参数是list
- list后面紧跟的是参数
- 参数之间必须存在间隔
- 参数必须是-log,-out,-date,-province,-type里面的一种
- 参数-log,-out必须存在
在这之后还有参数值的校验,如果能够通过检验才运行后面的程序。
3.pojo类->Province
保存省份的ip,sp,cure,dead的人数。供文件的输入输出使用。
4.具体执行->ListCommand
执行命令语句。
五、代码说明
1.REUtil
判断是否符合-type的参数值
public boolean checkType(String a) {return Pattern.matches("ip|sp|cure|dead",a);}
判断参数是否正确
public boolean checkParamter(String a){...}
判断日期是否符合形式
public boolean checkDate(String a){}{...}
判断输入省份是否符合要求
public boolean checkProvinces(String a){...}
判断是否为所需要的日志形式
public boolean checkLogFile(String a){...}
判断文件行里面的内容
public int txtType(String a){
String log1 = "(\\S+) 新增 感染患者 (\\d+)人";
String log2 = "(\\S+) 新增 疑似患者 (\\d+)人";
String log3 = "(\\S+) 感染患者 流入 (\\S+) (\\d+)人";
String log4 = "(\\S+) 疑似患者 流入 (\\S+) (\\d+)人";
String log5 = "(\\S+) 死亡 (\\d+)人";
String log6 = "(\\S+) 治愈 (\\d+)人";
String log7 = "(\\S+) 疑似患者 确诊感染 (\\d+)人";
String log8 = "(\\S+) 排除 疑似患者 (\\d+)人";
if (Pattern.matches(log1 , a))
return 1;
if (Pattern.matches(log2 , a))
return 2;
if (Pattern.matches(log3 , a))
return 3;
if (Pattern.matches(log4 , a))
return 4;
if (Pattern.matches(log5 , a))
return 5;
if (Pattern.matches(log6 , a))
return 6;
if (Pattern.matches(log7 , a))
return 7;
if (Pattern.matches(log8 , a))
return 8;
return 0;
}
2.CommandJudge
判断语句输入的正确性。不正确返回0
public int judge(String[] args){
java.util.List<Integer> integers = new LinkedList<Integer>();//存储参数的下标
if (!args[0].equals("list") && !args[0].equals("help") && !args[0].equals("present")){
System.out.println("Command " + args[0] + " not found");
return 0;
}else{
if(args[0].equals("list)){
...//判断语句是否能够执行
}
if(args[0].equals("help")){
...//判断新功能help命令是否能执行
}
if(args[0].equals("present"){
...//判断新功能present是否能够执行
}
}
}
寻找参数的下标值
public int foundSpecialIndex(String[] args , String special) {...}
是否存在参数
public boolean foundSpecial(String[] args , String special) {...}
获得命令行中的省份信息
public LinkedList<String> foundProvinces(String[] args) {...}
获得命令行中的类型信息
public LinkedList<String> foundType(String[] args) {...}
3.ListCommand
对日志文件的名字进行排序,以达到顺序读取的效果
public void sort(String[] filesName) {...}
将从日志文件读取到的一行字符进行匹配,并修改相应省对应的Province类
public void getProvinceInfect(String input , LinkedList<Province> provinceList) {
...
String[] provinces = input.split(" ");
String s = provinces[provinces.length - 1].replaceAll(regEx , "");
int peopleNumber = 0;
if (!s.isEmpty())
peopleNumber = Integer.parseInt(s);
int inputType = new REUtil().txtType(input);
if (inputType == 1) {
provinceList.get(hashMap.get(provinces[0])).setIp(provinceList.get(hashMap.get(provinces[0])).getIp() + peopleNumber);
}
...
...
}
执行语句并输出
public void Command(String[] args) throws IOException{
LinkedList<Province> provinceList = new LinkedList<Province>();
...
String logPath = args[new CommandJudge().foundSpecialIndex(args , "-log") + 1];
File file = new File(logPath);
File[] files = file.listFiles();
String[] filesName = new String[files.length];
for (int j = 0 ; j < files.length ; j++){
filesName[j] = files[j].getName();
}
...
if (new CommandJudge().foundSpecial(args , "-date")){
for (int j = 0 ; j < filesName.length ; j++){
if (args[new CommandJudge().foundSpecialIndex(args , "-date")+1].compareTo(filesName[j]) > 0)
realfilesName.add(filesName[j]);
}
}else {
for (int j = 0 ; j < filesName.length ; j++){
realfilesName.add(filesName[j]);
}
}
...
if (new CommandJudge().foundSpecial(args , "-province")){
infectProvinces.clear();
LinkedList<String> list = new CommandJudge().foundProvinces(args);
System.out.println(list);
for (int k = 0 ; k < list.size() ; k++){
// System.out.println(list.get(k));
int num = hashMap.get(list.get(k));
infectProvinces.add(num);
}
}else {
infectProvinces.clear();
for (int k = 0 ; k < provinceList.size() ; k++){
infectProvinces.add(k);
}
}
...
if (new CommandJudge().foundSpecial(args , "-type")){
LinkedList<String> types = new CommandJudge().foundType(args);
for (int k = 0 ; k < infectProvinces.size() ; k++){
String putIn = provinceList.get(infectProvinces.get(k)).getName();
for (int p = 0 ; p < types.size() ; p++){
if (types.get(p).equals("ip"))
putIn += (" 感染患者" + provinceList.get(infectProvinces.get(k)).getIp() + "人");
if (types.get(p).equals("sp"))
putIn += (" 疑似患者" + provinceList.get(infectProvinces.get(k)).getSp() + "人");
if (types.get(p).equals("cure"))
putIn += (" 治愈" + provinceList.get(infectProvinces.get(k)).getCure() + "人");
if (types.get(p).equals("dead"))
putIn += (" 死亡" + provinceList.get(infectProvinces.get(k)).getDead() + "人");
}
//识别系统。不同系统换行符不一样
String osName = System.getProperty("os.name");
if (osName.startsWith("Mac os")) {
bw.write(putIn + "\n");
}else if(osName.startsWith("Windows")){
bw.write(putIn + "\r\n");
}else {
bw.write(putIn + "\r");
}
}
}else{
for (int k = 0 ; k < infectProvinces.size() ; k++){
String putIn = provinceList.get(k).getName() + " 感染患者" + provinceList.get(k).getSp() + "人 疑似患者" + provinceList.get(k).getIp() + "人 治愈" +
provinceList.get(k).getCure() + "人 死亡" + provinceList.get(k).getDead() + "人";
String osName = System.getProperty("os.name");
if (osName.startsWith("Mac os")) {
bw.write(putIn + "\n");
}else if(osName.startsWith("Windows")){
bw.write(putIn + "\r\n");
}else {
bw.write(putIn + "\r");
}
}
}
...
}
六、单元测试截图和描述
该程序实现主要由三个类构成,即REUtil,CommadnJudge,ListCommand所完成。所以单元测试在这三个类中展开。
使用maven项目进行管理。测试的依赖为
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
1.REUtil
- 函数checkParameter用来检测参数是否符合要求
@Test
public void checkParameter() {
// 检测正常的参数能够使用
assertTrue(new REUtil().checkParameter("-date"));
assertTrue(new REUtil().checkParameter("-province"));
assertTrue(new REUtil().checkParameter("-type"));
// 参数错误不能通过
assertFalse(new REUtil().checkParameter("list"));
}
- 函数checkDate检测日期,日期形式必须为YYYY-mm-dd,且日期必须存在
@Test
public void checkDate() {
assertTrue(new REUtil().checkDate("2020-01-01"));
assertFalse(new REUtil().checkDate("2020-13-01"));
assertTrue(new REUtil().checkDate("2020-02-29"));
assertFalse(new REUtil().checkDate("2020-02-30"));
assertTrue(new REUtil().checkDate("1998-12-14"));
assertFalse(new REUtil().checkDate("1998-12-00"));
}
- 函数checkProvinces检测省份,省份必须是那三十一个里面的一个,外加一个全国
@Test
public void checkProvinces() {
assertTrue(new REUtil().checkProvinces("全国"));
assertTrue(new REUtil().checkProvinces("福建"));
assertFalse(new REUtil().checkProvinces("美国"));
assertTrue(new REUtil().checkProvinces("北京"));
assertFalse(new REUtil().checkProvinces("sadasd"));
}
- 函数checkLogFile检测日志文件,日志文件需要符合YYYY-mm-dd.log.txt形式,并且日期要存在
@Test
public void checkLogFile() {
assertTrue(new REUtil().checkLogFile("2020-01-02.log.txt"));
assertFalse(new REUtil().checkLogFile("2020-01-02.log"));
assertFalse(new REUtil().checkLogFile("2020-01-02.txt"));
assertFalse(new REUtil().checkLogFile("2020-13-01.log.txt"));
assertFalse(new REUtil().checkLogFile("2020-02-30.log.txt"));
}
- 函数checkType检测类型,类型一定是ip,sp,cure,dead里面的一种
@Test
public void checkType() {
assertTrue(new REUtil().checkType("ip"));
assertTrue(new REUtil().checkType("sp"));
assertTrue(new REUtil().checkType("cure"));
assertTrue(new REUtil().checkType("dead"));
assertFalse(new REUtil().checkType("福建"));
assertFalse(new REUtil().checkType("北京"));
assertFalse(new REUtil().checkType("2020-13-01"));
assertFalse(new REUtil().checkType("-date"));
}
- 函数txtType检测字符串内容,筛选日志文件里内容
@Test
public void txtType() {
// 文档要求形式才能够通过
assertTrue(new REUtil().txtType("福建 新增 感染患者 2人") != 0);
assertTrue(new REUtil().txtType("// 该文档并非真实数据,仅供测试使用") == 0);
assertTrue(new REUtil().txtType("湖北 感染患者 流入 福建 2人") != 0);
assertTrue(new REUtil().txtType("// 湖北 感染患者15-2-1-2=10人 疑似患者20-3-2=15人 治愈2人 死亡1人") == 0);
assertTrue(new REUtil().txtType("湖北 排除 疑似患者 2人") != 0);
assertTrue(new REUtil().txtType("当日情况:list") == 0);
}
测试结果:
2.CommandJudge
- 函数judge用来接收命令,判断命令是否是正确的,能够使用解析的。其余的函数是在judge中有被调用,所以不再测试。
- e1-e7是部分错误的命令形式,还有部分没有列出来s1-s6是正确的。错误理由在代码注释中有解释。
@Test public void judge() {
// 命令不完整
String e1 = "list";
// -log 不存在日志文件
String e2 = "list -log /Users/a/file -out /Users/a/Desktop/infect.txt";
// -log -out需要同时存在
String e3 = "list -out /Users/a/Desktop/infect.txt";
// -province的参数值不存在
String e4 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect1.txt -province 美国";
// -type的参数值错误
String e5 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect1.txt -province 福建 -type a";
// -date的参数形式错误
String e6 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect1.txt -province 福建 -type ip -date 2020-20-20";
// 参数重复
String e7 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect1.txt -province 福建 -type ip -type ip";
String s1 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect1.txt";
String s2 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect2.txt -type ip";
String s3 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect3.txt -province 福建 北京";
String s4 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect4.txt -date 2020-01-24";
String s5 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect5.txt -type ip";
String s6 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect6.txt -type ip sp -province 福建 北京 -date 2020-02-11";
String[] ee1 = e1.split(" ");
String[] ee2 = e2.split(" ");
String[] ee3 = e3.split(" ");
String[] ee4 = e4.split(" ");
String[] ee5 = e5.split(" ");
String[] ee6 = e6.split(" ");
String[] ee7 = e7.split(" ");
String[] ss1 = s1.split(" ");
String[] ss2 = s2.split(" ");
String[] ss3 = s3.split(" ");
String[] ss4 = s4.split(" ");
String[] ss5 = s5.split(" ");
String[] ss6 = s6.split(" ");
assertTrue(new CommandJudge().judge(ee1) == 0);
assertTrue(new CommandJudge().judge(ee2) == 0);
assertTrue(new CommandJudge().judge(ee3) == 0);
assertTrue(new CommandJudge().judge(ee4) == 0);
assertTrue(new CommandJudge().judge(ee5) == 0);
assertTrue(new CommandJudge().judge(ee6) == 0);
assertTrue(new CommandJudge().judge(ee7) == 0);
assertTrue(new CommandJudge().judge(ss1) != 0);
assertTrue(new CommandJudge().judge(ss2) != 0);
assertTrue(new CommandJudge().judge(ss3) != 0);
assertTrue(new CommandJudge().judge(ss4) != 0);
assertTrue(new CommandJudge().judge(ss5) != 0);
assertTrue(new CommandJudge().judge(ss6) != 0);
}
```
测试结果,即命令行错误的报错信息:
3.ListCommandTest
- 主要函数为command()。同上并利用上面的s1-s6来检测输出文件内容是否正确。代码如下
@Test
public void command() throws IOException {
String s1 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect1.txt";
String s2 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect2.txt -type ip";
String s3 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect3.txt -province 福建 北京";
String s4 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect4.txt -date 2020-01-24";
String s5 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect5.txt -type ip";
String s6 = "list -log /Users/a/Desktop/Infectlog -out /Users/a/Desktop/infect6.txt -type ip sp -province 福建 北京 -date 2020-02-11";
String[] ss1 = s1.split(" ");
String[] ss2 = s2.split(" ");
String[] ss3 = s3.split(" ");
String[] ss4 = s4.split(" ");
String[] ss5 = s5.split(" ");
String[] ss6 = s6.split(" ");
new ListCommand().Command(ss1);
new ListCommand().Command(ss2);
new ListCommand().Command(ss3);
new ListCommand().Command(ss4);
new ListCommand().Command(ss5);
new ListCommand().Command(ss6);
}
七、单元测试覆盖优化和性能测试
- 单元测试覆盖率
- 性能测试:使用JProfiler分析
程序存在较多的循环判断,但还未有较好的解决办法。
八、代码规范
https://github.com/jokercan/InfectStatistic-main/blob/master/221701431/codestyle.md ->点击进入
九、心路历程与收获
每次进行一项作业或者任务的时候都会发现很多自己的不足,虽然这次作业不难,但还是让自己知道了一些知识点的缺失,比如正则表达式,还有性能优化的具体办法。程序也同样暴露了一些问题,过于依赖if-else形式,循环。事先设计的时候也遗漏了一些东西,只能边写边找补。暴露了不足才能更好的学习吧。
写完程序发现都是在用if/else的连环写法🐶
实在是好丑好丑,所以上网查询了一下解决办法。
发现有三种可以进行一定的替换:枚举,工厂模式,策略模式
下次一定下次一定。
十、技术相关的五个仓库
- 1.java学习加面试指南
https://github.com/Snailclimb/JavaGuide
题目意思
- 2.webmagic
https://github.com/code4craft/webmagic
java爬虫框架它具有简单的API,可快速上手,模块化的结构,可轻松扩展,且提供提供多线程和分布式支持
- 3.LeetCodeAnimation
https://github.com/MisterBooo/LeetCodeAnimation
用动画的形式呈现解LeetCode题目的思路
- 4.spring-boot
https://github.com/spring-projects/spring-boot
Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。
优秀的Java博客系统
十一、新命令
- help:显示所有命令的用法
eg. java InfectStatistic help
- present:参数-out必须存在
eg. java InfectStatisticc present -out /Users/a/Desktop/a.txt