这个作业属于哪个课程 | 2020春|W班 |
---|---|
这个作业要求在哪里 | 寒假作业(2/2)——疫情统计 |
这个作业的目标 | 学习Github的使用,设计编写疫情统计程序 |
作业正文 | 寒假作业二 |
其他参考文献 | 寒假第二次作业作业引导和提示 |
一、Github仓库地址
https://github.com/QMBX/InfectStatistic-main
二、阅读《构建之法》& 填写本次作业的PSP表格
1. √ 阅读《构建之法》第一章到第三章的内容
2. √ 填写PSP表格
PSP2.1 | Personal Software Process Stages | 预计耗时(分钟) | 实际耗时 |
---|---|---|---|
Planning | 计划 | 30 | 20 |
Estimate | 估计这个任务需要多少时间 | 14*60 | 16*60 |
Development | 开发 | 7*60 | 11*60 |
Analysis | 需求分析(包括学习新技术) | 60 | 40 |
Design Spec | 生成设计文档 | 20 | 30 |
Design Review | 设计复审 | 15 | 10 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60 | 50 |
Design | 具体设计 | 2*60 | 60 |
Coding | 具体编码 | 5*60 | 9*60 |
Code Review | 代码复审 | 30 | 20 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 60 |
Reporting | 报告 | 3*60 | 3*60 |
Test Report | 测试报告 | 30 | 60 |
Size Measurement | 计算工作量 | ||
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 15 | 60 |
合计 | 2150 | 2750 |
三、解题思路
在刚看到题目时,我的第一个想法就是直接判断命令行参数,然后根据参数的值来调整读取日志、统计结果的流程。随后刚好助教的关于第二次寒假作业的引导与提示出来,在仔细看文章时,看到了在需求分析的部分,提到了设计模式中的命令模式、状态模式、责任链模式几种模式。看到设计模式后,我就想在这次的作业中加入这个模式试试。
在网上搜索了有关这三个设计模式的一些资料后,我发现这些模式都是比较灵活的,而不是有个模板就可以直接套用。而我也对设计结构比较苦手,更别说在正常的设计中加入刚看几眼的设计模式。于是我的思路便转向我平时的思路上。采用通用设计,将我认为有必要的部分都进行解耦,这样一来,可扩展性就提高了。
通过我的分析以及参考助教的文章,我将按照数据流将本次程序的代码分成两大部分:
命令行参数处理部分
在这里我假设程序每次运行只运行一个命令且命令必须在第一个位置。该部分命令行传入程序的参数进行处理,根据第一个位置的参数生成对应的命令类,并配置命令类的状态。命令执行部分
该部分根据生成的命令类,执行对应的行为。在本次作业中,命令只有一个list,而这个命令又涉及到读写文件,又可以将读写文件的部分单独领出来成为效一部分。
四、设计的实现过程
我本次采用的思路是以数据流来分析然后采用对象封装数据流
五、代码说明
- 命令行参数类:将源命令参数与常用的一些操纵封装成对象
class CommandArgs
{
//存储输入的各个参数与对应的值
private Map parameters = new HashMap<>();
//存储命令的值
private String commandName;
//当命令有自己的参数值时,采用的参数
private static final String DEFAULT_PARAMETER = "default";
...
/**
* 将命令行参数转化成参数到值的映射,默认每次运行只有一个命令
* @param args 程序读取到的命令行参数
*/
CommandArgs(String[] args) throws MyExcepiton
{
//判断没有命令参数的情况
if (args == null || args.length <= 0)
throw new MyExcepiton("程序缺少参数");
else
{
int length = args.length;
String parameter = DEFAULT_PARAMETER;
//第一个参数认为命令
commandName = args[0];
//对其余参数进行转换,转换到parameters的映射中,多
for (int i = 1; i < length; i++)
{
//配置参数默认以'-'开头
if (args[i].startsWith("-"))
{
if (parameters.get(parameter) == null && !DEFAULT_PARAMETER.equals(parameter))
throw new MyExcepiton("参数不正确");
parameter = args[i].substring(1);
}
else
{
if (parameters.containsKey(parameter))
parameters.put(parameter, parameters.get(parameter) + " " + args[i]);
else
parameters.put(parameter, args[i]);
}
}
//结尾判断,如果最后一个参数是配置参数
if (parameters.get(parameter) == null)
throw new MyExcepiton("参数不正确");
}
}
...
}
命令工厂类
class CommandFactory { //为了避免魔值出现,故将list注册为常量 private static final String LIST_COMMAND = "list"; /** * 通过命令行对象,生成并初始化对应的命令类 * @param commandArgs 使用到的命令行对象 * @return 初始化后的命令类 */ AbstractCommand getCommand(CommandArgs commandArgs) throws MyExcepiton { AbstractCommand abstractCommand = null; String commandName = commandArgs.getCommandName(); //根据从命令行参数类得到String进行判断,并生成对应的命令类 if (LIST_COMMAND.equals(commandName)) { abstractCommand = new ListCommand(); } else throw new MyExcepiton("程序没有相应的命令"); init(abstractCommand, commandArgs); return abstractCommand; } ... }
list命令类
ListCommand { ... /** * 向命令对象传入对应参数 * @param parameter 传入的参数 * @param val 参数的值 */ public void config(String parameter, String val) throws MyExcepiton { //log参数的配置 if (PARAMETER_LOG_PATH.equals(parameter)) { File dir = new File(val); if (!dir.exists()) { throw new MyExcepiton("日志文件夹不存在"); } inputPath = val; } //out参数的配置 else if (PARAMETER_OUTPUT_FILE.equals(parameter)) { outputPath = val; } //date参数的配置 else if (PARAMETER_DEADLINE.equals(parameter)) { try { deadLine = LocalDate.parse(val); } catch(DateTimeParseException ex) { throw new MyExcepiton("日期参数不正确,应满足YYYY-MM-DD的格式"); } } //type参数的配置 else if (PARAMETER_TYPE.equals(parameter)) { String[] types = val.split(" "); showTypes.clear(); for (int i = 0 ; i < types.length; i++) { if (INFECTION_PATIENTS.equals(types[i])) showTypes.add(PATIENT_TYPE.INFECTION); else if (SUSPECTED_PATIENTS.equals(types[i])) showTypes.add(PATIENT_TYPE.SUSPECTED); else if (CURE_PATIENTS.equals(types[i])) showTypes.add(PATIENT_TYPE.CURE); else if (DEAD_PATIENTS.equals(types[i])) showTypes.add(PATIENT_TYPE.DEAD); else throw new MyExcepiton("输入参数有误"); } } //province参数的配置 else if (PARAMETER_PROVINCES.equals(parameter)) { String[] provinces = val.split(" "); boolean isMatch; //采用遍历可选区域,判断参数是否正确 for (int i = 0; i < provinces.length; i++) { isMatch = false; for (int j = 0; j < PROVINCES.length; j++) { if (provinces[i].equals(PROVINCES[j])) { showProvinces.add(PROVINCES[j]); isMatch = true; break; } } if (!isMatch) throw new MyExcepiton("省份参数错误"); } } } ... }
六、单元测试截图和描述
- 检测 没有date type province 时正常
结果
- 检测 date 正常
结果
- 检测 三个参数(date, type, province)正常使用
结果
- 检测 空参数
- 检测 type 后面没有跟类型
- 检测 date 值输入错误
- 检测 缺少log参数
- 检测 log值对应的目录下没有目录
七、单元测试覆盖率优化和性能测试,性能优化截图和描述
- 单元测试
- 性能优化
可以看一看出性能中LocalDate的构造花费较多,此外还有String.Format也花费较多。但String.Format我猜测来源于正则表达式的运用上,所以没有改动。
八、给出代码规范链接
https://github.com/QMBX/InfectStatistic-main/blob/master/221701317/codestyle.md
九、心路历程和收获
本次作业让我尝试了一次往能复杂去写就写多复杂的写法,虽然我写的不少代码可以通过优化减少空间复杂度,但是优化后还是会为了保证代码的高内聚、低耦合还是会导致原本的代码复杂度更上一层楼。此外我还学会了JUnit和JProfiler的基本使用,相比以前只用调试来走一遍程序判断有没有问题以及用time计时来判断在哪里消耗时间长,JUnit和JProfler这两个工具实在方便。
十、寻找五个相关github仓库,start并fork
名称 | 链接 | 简介 |
---|---|---|
Deep-photo-styletransfer | https://github.com/luanfujun/deep-photo-styletransfer?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more | 通过深度学习的图片处理方法,直接提取了参考图片的风格,并转换为相对应的滤镜。 |
Face Recognition | https://github.com/ageitgey/face_recognition?utm_source=mybridge&utm_medium=blog&utm_campaign=read_more | 全世界最简单的面部识别API,基于Python与命令行 |
AI learning | https://github.com/apachecn/Ailearning | 包含许多常见算法的实现,并且提供了许多学习资源 |
莫烦Python | https://github.com/MorvanZhou/tutorials | 包含了许多常用的机器学习框架 |
机器学习算法Python实现 | https://github.com/lawlite19/MachineLearning_Python | 主要内容在于实现算法,对数学基础有要求 |