软工实践寒假作业(2/2)
这个作业属于哪个课程 | 2020春|S班 |
---|---|
这个作业要求在哪里 | 软工实践寒假作业(2/2) |
这个作业的目标 | 开发一个疫情统计程序 |
作业正文 | 软工实践寒假作业(2/2) |
其他参考文献 | java编程思想,简书,csdn |
github仓库地址
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 1260 | 1540 |
Analysis | 需求分析 (包括学习新技术) | 180 | 360 |
Design Spec | 生成设计文档 | 120 | 180 |
Design Review | 设计复审 | 60 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 240 | 150 |
Design | 具体设计 | 30 | 20 |
Coding | 具体编码 | 360 | 200 |
Code Review | 代码复审 | 30 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 540 |
Reporting | 报告 | 80 | 80 |
Test Repor | 测试报告 | 20 | 20 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1380 | 1660 |
需求分析
- 能通过外部命令进行参数传递
- 能够通过外部命令的参数进行不同情况的处理
- 文件的读取和写入
- 字符串的格式化处理
- 字符串格式的匹配
设计思路
- 用正则表达式匹配不同输入格式的字符串
- 用责任链模式对不同输入格式的字符串进行相应的处理
- 构造一个命令处理类CmdArgs对读入的外部参数进行处理
- 设计一个FileProcess文件处理类对读入的文件进行分类、读取、写入、保存
- 设计一个ProvinceData类对不同省份的数据进行分类保存
代码部分细节说明(附图
代码细节分析
- 数据结构的选择
根据题目给出的输入数据,很明显能知道数据的对应类型是键值对(一个字符串对应一个数组)故果断选择用HashMap进行存储。
class ProvinceData{
private static ProvinceData provinceData = new ProvinceData();
private HashMap<String,int[]> AllData = new HashMap<>();
/*
用于键值对的初始化
其中initStr作为常量数组存储省名
*/
private ProvinceData(){
for(int i = 0; i < Constant.initStr.length; i++){
int[] initArray = {0,0,0,0};
AllData.put(Constant.initStr[i],initArray);
}
}
...其他数据处理函数
}
- 读入数据的处理
对字符串的处理常用方法是split函数和正则表达式匹配,由于输入数据的多样性和格式不同,因此选用正则表达式进行匹配。对不同情况的分支处理,选择责任链模式进行设计。
//部分正则表达式
public final static String s1 = "^[\\u4e00-\\u9fa5]*\\s(新增)\\s(感染患者)\\s(\\d+)人?";
public final static String s2 = "^[\\u4e00-\\u9fa5]*\\s(新增)\\s(疑似患者)\\s(\\d+)人?";
abstract class AbstractDataHandle{
protected String model;
protected AbstractDataHandle nextDataHandle;
public void setNextDataHandle(AbstractDataHandle nextDataHandle){
this.nextDataHandle = nextDataHandle;
}
//数据处理函数
public void dataProcessing(String str){
Pattern pattern = Pattern.compile(model);
Matcher matcher = pattern.matcher(str);
boolean result = matcher.matches();
if(result){
processing(str);
}else if(nextDataHandle != null){
nextDataHandle.dataProcessing(str);
}
}
//责任链构造函数
public static AbstractDataHandle getChainOfDataHandle(){...}
//数据处理细节作为抽象函数由子类实现
public abstract void processing(String str);
}
- 外部参数的处理
由于外部命令的参数和参数的值存在顺序和个数的不确定性,因此不选择用正则表达式去匹配传入的参数列表,而选择用split函数进行处理,而后用将参数和参数对应的值存入hashmap进行存储,方便运用。
class CmdArgs{
//用于读入外部给入的命令和命令参数
private String[] args;
//用于存放参数和参数的值
private HashMap<String, ArrayList<String>> paramToValue = new HashMap<>();
CmdArgs(String[] args){
this.args = args;
argsProcess();
}
//传入命令的参数的初始化处理
private void argsProcess(){
ArrayList<String> tempValue = new ArrayList<>();
String tempKey = new String();
int index = 0;
for(int i = 1; i < args.length; i++){
if(args[i].contains("-") && !HasDigit(args[i]) && index == 0){
tempKey = args[i];
}else if(args[i].contains("-") && index == 1){
ArrayList<String> tempForValue = (ArrayList<String>) tempValue.clone();
paramToValue.put(tempKey,tempForValue);
tempKey = args[i];
tempValue.clear();
}else{
tempValue.add(args[i]);
index = 1;
}
}
paramToValue.put(tempKey,tempValue);
}
//获得参数的值
public ArrayList<String> argVals(String key){
return paramToValue.get(key);
}
private boolean HasDigit(String content) {
boolean flag = false;
Pattern p = Pattern.compile(".*\\d+.*");
Matcher m = p.matcher(content);
if (m.matches()) {
flag = true;
}
return flag;
}
}
代码单元测试部分
对cmdArgs中argVals方法的测试
@Test
void argVals() {
String[] argc = "list -log D:/log/ -out D:/output.txt -data 2020-01-14 -type sp cure ip".split(" ");
CmdArgs cmdArgs = new CmdArgs(argc);
System.out.println(cmdArgs.argVals("-log"));
System.out.println(cmdArgs.argVals("-out"));
System.out.println(cmdArgs.argVals("-date"));
System.out.println(cmdArgs.argVals("-type"));
System.out.println(cmdArgs.argVals("-province"));
}
结果如下
@Test
void argVals() {
// 改变输入的参数后
String[] argc = "list -log D:/log/ -out D:/output.txt -data 2020-01-14 -type sp cure ip -province 全国 浙江".split(" ");
CmdArgs cmdArgs = new CmdArgs(argc);
System.out.println(cmdArgs.argVals("-log"));
System.out.println(cmdArgs.argVals("-out"));
System.out.println(cmdArgs.argVals("-date"));
System.out.println(cmdArgs.argVals("-type"));
System.out.println(cmdArgs.argVals("-province"));
}
结果如下
@Test
void argVals() {
//删除date参数后,读取本地日期
String[] argc = "list -log D:/log/ -out D:/output.txt -type sp cure ip -province 全国 浙江".split(" ");
CmdArgs cmdArgs = new CmdArgs(argc);
System.out.println(cmdArgs.argVals("-log"));
System.out.println(cmdArgs.argVals("-out"));
System.out.println(cmdArgs.argVals("-date"));
System.out.println(cmdArgs.argVals("-type"));
System.out.println(cmdArgs.argVals("-province"));
}
结果如下
对AddInfectPeople方法的测试
@Test
void addInfectPeople() {
ProvinceData provinceData = ProvinceData.getInstance();
provinceData.AddInfectPeople("湖北",10);
ArrayList<String> province = new ArrayList<>(Arrays.asList("湖北".split(" ")));
ArrayList<String> type = new ArrayList<>(Arrays.asList("ip sp".split(" ")));
System.out.println(provinceData.logProcess(province,type));
}
结果如下
对AbstractDataHandle族中processing方法的测试
@Test
void processing() {
AbstractDataHandle abstractDataHandle = AbstractDataHandle.getChainOfDataHandle();
ProvinceData provinceData = ProvinceData.getInstance();
String temp = "湖北 新增 感染患者 15030人";
abstractDataHandle.dataProcessing(temp);
ArrayList<String> province = new ArrayList<>(Arrays.asList("全国 湖北".split(" ")));
ArrayList<String> type = new ArrayList<>(Arrays.asList("ip sp".split(" ")));
System.out.println(provinceData.logProcess(province,type));
}
结果如下
对文件读取类中的getFileData方法进行测试
@Test
void getFileData() throws IOException {
FileProcess fileProcess = FileProcess.getInstance();
fileProcess.FileInit(".//log",".//result//output.txt");
ArrayList<File> fileList = fileProcess.InputFileData();
System.out.println(fileList);
ArrayList<String> temp = fileProcess.getFileData(fileList.get(0));
System.out.println(temp);
}
结果如下
对logData方法进行测试
@Test
void logData() throws IOException {
FileProcess fileProcess = FileProcess.getInstance();
fileProcess.FileInit(".//log",".//result//output.txt");
ArrayList<File> fileList = fileProcess.InputFileData();
System.out.println(fileList);
ArrayList<String> temp = fileProcess.getFileData(fileList.get(1));
AbstractDataHandle dataHandle = AbstractDataHandle.getChainOfDataHandle();
for (int j = 0; j < temp.size(); j++)
dataHandle.dataProcessing(temp.get(j));
ArrayList<String> pro = new ArrayList<>(Arrays.asList("全国 湖北 福建 山东".split(" ")));
fileProcess.LogData(pro, new ArrayList<> (Arrays.asList("ip sp cure dead".split(" "))));
}
结果如下
对照相应日期文件知道所写入结果正确
代码覆盖率及性能优化
- 代码覆盖率
在编写代码的过程中,考虑到分支跳转可能过多的情况,在设计过程中即选择了用责任链模式处理字符串的匹配问题,故分支主要用于判断文件读取和读入参数的正确性和完整性,因此覆盖率较高。
删除一些无用的测试函数以及部分循环语句用包内函数替换后
- 性能测试
性能测试通过Jprofiler插件进行测试
内存回收前
内存回收后
可以发现,在JVM回收内存前后,其占用内存没有明显的连续增加过程,因此可以判定没有发生内存泄露。
代码规范链接
心路历程
首先,武汉加油,中国加油。
开发这个疫情统计程序,我重新复习了Java语言的文件处理部分内容,有关文件的读写,目录的判定等,以及有关字符串处理的内容。最早在分析题目时,看到复杂的字符串匹配问题,看了就头大第一反应就是很多的分支与跳转,而后思考有没有比较容易,或者比较少的分支跳转,甚至没有if-else语句出现的方法来实现这个过程,最后在以前看过的设计模式中找到了命令模式和责任链模式与这种情况较适应。较多的分支跳转语句会导致代码结构的冗余和增加扩展的难度。太吾绘卷可牛逼了因此着重学习了这两种设计模式,并且运用于实践(最终用责任链模式实现)因为最初没有掌握传入参数形式的命令模式,经测试后删除更改。由于开发过程中,是先编写文件处理和数据处理部分,最后处理外部命令一时难以完全符合要求,部分方法进行了重构,重构一时爽,一直重构一直爽重构方法的过程中,我加深了对这部分代码的理解,同时优化了部分代码的逻辑,比如,将日志写入部分进行了封装。
阅读构建之法前三章呢,则给作为软件工程专业的学生的我,对未来的道路有了一定预先的认识。软件=程序+软件工程,而之前,我对软件的认识仅仅是软件=程序组合,而没有一个软件生产过程的体系架构,想当然的认为生产软件就是写代码。而书中涉及一些理论,或多或少对我未来的生产工作会起到指导作用。
与第一次作业有关的仓库
mybatis
Spring integration for MyBatis 3
spring
spring的框架
java
一个有关java后端编程的仓库,其中包括了部分java基础知识和数据库连接的方法,以及一些常用框架。
maven
apache的maven资源在github上的clone
springboot的实例
lxy-go根据bilibili网站的教程所编写的一个demo