软工实践寒假作业(2/2)
这个作业属于哪个课程 | 班级链接 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 完成项目并编写博客,学习使用github |
作业正文 | 正文在这里 |
其他参考文献 | 百度, 博客园, csdn |
疫情分析
设计思路
三思而后行
将输入和输出分开处理.
在输入时, 循环读入日志文件中的每一行, 并按顺序解析每行的据,将其储存在类的私有变量中. 而输出时, 只需读取私有变量, 按要求写入文件即可
因此, 使用Map来存储每个省的信息. 解析时将省名为key, 省的四种情况构成一个新的Map作为值.
项目代码
文件解析部分
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
String textLine = null;
while ((textLine = bufferedReader.readLine())!=null) {
if (textLine.contains("//"))
continue;
......
使用输入流BufferedReader
来读取项目下的文件, 在读取行时注意是否带有//
标记. 有则直接跳过该行
String[] params = textLine.split(" ");
String province = params[0];
if (!infectMap.containsKey(province)) {
HashMap<String, Integer> map = new HashMap<>();
map.put("感染患者", 0);
map.put("疑似患者", 0);
map.put("治愈", 0);
map.put("死亡", 0);
infectMap.put(province, map);
}
......
对读到的一行, 将其分为String数组. 通过判断数组的第一个元素也就是该行所代表的省份, 可以判断是否应该直接生成一个新的map, 或是直接更新原有的map.
if (textLine.contains("新增")) {
//新增人数
count = infectMap.get(province).get(params[2]) + Integer.valueOf(params[3].replaceAll("人", ""));
infectMap.get(province).replace(params[2], count);
} else if (textLine.contains("流入")) {
//流出省份减少人数
count = infectMap.get(province).get(params[1]) - Integer.valueOf(params[4].replaceAll("人", ""));
infectMap.get(province).replace(params[1], count);
//流入省份增加人数
count = infectMap.get(params[3]).get(params[1]) + Integer.valueOf(params[4].replaceAll("人", ""));
infectMap.get(params[3]).replace(params[1], count);
} else if (textLine.contains("确诊感染")) {
//感染者人数增加
count = infectMap.get(province).get("感染患者") + Integer.valueOf(params[3].replaceAll("人", ""));
infectMap.get(province).replace("感染患者", count);
//疑似患者人数减少
count = infectMap.get(province).get("疑似患者") - Integer.valueOf(params[3].replaceAll("人", ""));
infectMap.get(province).replace("疑似患者", count);
} else if (textLine.contains("排除")) {
//疑似患者人数减少
count = infectMap.get(province).get("疑似患者") - Integer.valueOf(params[3].replaceAll("人", ""));
infectMap.get(province).replace("疑似患者", count);
} else { //治愈 死亡
//感染者人数减少
count = infectMap.get(province).get("感染患者") - Integer.valueOf(params[2].replaceAll("人", ""));
infectMap.get(province).replace("感染患者", count);
count = infectMap.get(province).get(params[1]) + Integer.valueOf(params[2].replaceAll("人", ""));
infectMap.get(province).replace(params[1], count);
}
......
判断每个参数来更新map的内容
//修正全国人数变化
int ip = 0, sp = 0, cure = 0, dead = 0;
for (Map.Entry<String, Map<String, Integer>> e : infectMap.entrySet()) {
if (!e.getKey().equals("全国")) {
for (Map.Entry<String, Integer> entry: e.getValue().entrySet()) {
switch (entry.getKey()) {
case "感染患者": ip += entry.getValue(); break;
case "疑似患者": sp += entry.getValue(); break;
case "治愈": cure += entry.getValue(); break;
case "死亡": dead += entry.getValue(); break;
}
}
}
}
infectMap.get("全国").replace("感染患者", ip);
infectMap.get("全国").replace("疑似患者", sp);
infectMap.get("全国").replace("治愈", cure);
infectMap.get("全国").replace("死亡", dead);
bufferedReader.close();
最后修正全国
的人数变化, 一个文件就算解析完成了
控制台参数部分
int optNumber = 0;
if (args[0].equals("list")) {
String input = null, output = null, date = null;
List<String> typeList = new LinkedList<>(), provinceList = new LinkedList<>();
while (optNumber < args.length) {
if (args[optNumber].equals("-log")) {
input = args[++optNumber];
} else if (args[optNumber].equals("-out")) {
output = args[++optNumber];
} else if (args[optNumber].equals("-date")) {
date = args[++optNumber];
} else if (args[optNumber].equals("-type")) {
String type = args[++optNumber];
while (!type.contains("-")) {
typeList.add(type);
if (optNumber == args.length-1)
break;
type = args[++optNumber];
}
} else if (args[optNumber].equals("-province")) {
String province = args[++optNumber];
while (!province.contains("-")) {
provinceList.add(province);
if (optNumber == args.length-1)
break;
province = args[++optNumber];
}
} else {
optNumber++;
}
}
parseDirectory(input, date);
output(output, typeList, provinceList);
}
遍历args数组, 判断并提取参数, 交给parseDirectory
(解析目录下的所有文件)和output
(输出)两个方法来处理
输出部分
File file = new File(path);
if (!file.exists()) {
file.createNewFile();
}
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
List<String> results = parseType(typeList, provinceList);
StringBuilder stringBuilder = new StringBuilder();
String country = null;
//排序
Collator cmp = Collator.getInstance(Locale.CHINA);
results.sort(cmp);
for (String s : results) {
if (s.contains("全国")) {
country = s+"\n";
continue;
}
stringBuilder.append(s+"\n");
}
String result = country + stringBuilder.toString();
bufferedWriter.write(result);
bufferedWriter.write("// 该文档并非真实数据,仅供测试使用");
bufferedWriter.close();
注意此方法每次写入相同文件会覆盖, 而不是新增
处理type和province参数部分
List<String> typeName = new LinkedList<>();
List<String> result = new LinkedList<>();
if (typeList.size() != 0) {
for (String s:typeList) {
switch (s) {
case "ip": typeName.add("感染患者"); break;
case "sp": typeName.add("疑似患者"); break;
case "cure": typeName.add("治愈"); break;
case "dead": typeName.add("死亡"); break;
}
}
} else {
typeName.add("感染患者");
typeName.add("疑似患者");
typeName.add("治愈");
typeName.add("死亡");
}
if (provinceList.size() != 0) {
provinceList.add("全国");
for (String province : provinceList) {
StringBuilder sb = new StringBuilder();
sb.append(province+ " ");
if (infectMap.containsKey(province)) {
for (String type : typeName) {
sb.append(type+infectMap.get(province).get(type)+"人 ");
}
} else {
for (String type : typeName) {
sb.append(type+"0人 ");
}
}
result.add(sb.toString());
}
} else {
for (Map.Entry<String, Map<String, Integer>> e : infectMap.entrySet()) {
StringBuilder sb = new StringBuilder();
sb.append(e.getKey()+" ");
for (String type : typeName) {
sb.append(type+e.getValue().get(type)+"人 ");
}
result.add(sb.toString());
}
}
return result;
将typeList
中的参数简写ip sp dead cure
转化为所对应的全称, 也是map中的key感染患者 疑似患者 死亡 治愈
, 之后遍历map, 将符合province
参数中的省份拼接, 最后添加到result
这个List
中返回, 由输出部分处理.
测试部分
测试均为单元测试, 输出忽略// 该文档并非真实数据,仅供测试使用
test1
list -log ./log -out ./result/out.txt
输出所有信息到result下的out.txt中, 文件内容如下
全国 感染患者149人 疑似患者324人 治愈27人 死亡21人
福建 感染患者22人 疑似患者38人 治愈3人 死亡0人
湖北 感染患者125人 疑似患者279人 治愈24人 死亡21人
江苏 感染患者2人 疑似患者0人 治愈0人 死亡0人
浙江 感染患者0人 疑似患者7人 治愈0人 死亡0人
test2
list -log ./log -out ./result/out.txt -date 2020-01-23
输出2020-01-23
以前的所有信息, 包括23日当日. 内容如下
全国 感染患者44人 疑似患者69人 治愈4人 死亡3人
福建 感染患者9人 疑似患者16人 治愈1人 死亡0人
湖北 感染患者33人 疑似患者46人 治愈3人 死亡3人
江苏 感染患者2人 疑似患者0人 治愈0人 死亡0人
浙江 感染患者0人 疑似患者7人 治愈0人 死亡0人
test3
list -log ./log -out ./result/out.txt -date 2020-01-23 -province 福建 浙江
输出23日以前福建和浙江的信息
福建 感染患者9人 疑似患者16人 治愈1人 死亡0人
浙江 感染患者0人 疑似患者7人 治愈0人 死亡0人
test4
list -log ./log -out ./result/out.txt -date 2020-01-23 -province 福建 浙江 山东
输出23日以前福建,浙江,山东的信息(其中山东省的信息在日志中没有记载)
福建 感染患者9人 疑似患者16人 治愈1人 死亡0人
山东 感染患者0人 疑似患者0人 治愈0人 死亡0人
浙江 感染患者0人 疑似患者7人 治愈0人 死亡0人
test5
list -log ./log -out ./result/out.txt -date 2020-01-23 -type ip sp dead
输出23日以前所有信息, 过滤掉治愈
全国 感染患者44人 疑似患者69人 死亡3人
福建 感染患者9人 疑似患者16人 死亡0人
湖北 感染患者33人 疑似患者46人 死亡3人
江苏 感染患者2人 疑似患者0人 死亡0人
浙江 感染患者0人 疑似患者7人 死亡0人
test6
list -log ./log -out ./result/out.txt -province 福建 浙江 -type ip dead
输出福建和浙江感染患者和死亡人数
福建 感染患者22人 死亡0人
浙江 感染患者0人 死亡0人
test7
list -log ./log -out ./result/out.txt -province 福建 浙江 -type ip sp cure dead alive
输出福建和浙江的感染患者,疑似患者,治愈,死亡和存活(并不存在,应该忽略)
福建 感染患者22人 疑似患者38人 治愈3人 死亡0人
浙江 感染患者0人 疑似患者7人 治愈0人 死亡0人
test8
list -log ./log -out ./result/out.txt -date 2020-01-01
输出2020-01-01以前的日志(空)
全国 感染患者0人 疑似患者0人 治愈0人 死亡0人
test9
list -log ./log -out ./result/out.txt -province 福建 浙江 江苏 -type ip sp cure
组合测试
福建 感染患者22人 疑似患者38人 治愈3人
江苏 感染患者2人 疑似患者0人 治愈0人
浙江 感染患者0人 疑似患者7人 治愈0人
test10
list -log ./log -out ./result/out.txt -province 福建 浙江 江苏 西藏 -type sp cure
组合测试
福建 疑似患者38人 治愈3人
江苏 疑似患者0人 治愈0人
西藏 疑似患者0人 治愈0人
浙江 疑似患者7人 治愈0人
心路历程
此次作业, 首先让我再一次确定了我自己的代码规范, 进一步提高了我自己代码的可读性. 其次让我理解了先思考的重要性. 这样减少了很多代码的弯路. 最后, 我明白了资料的可靠性是非常重要的. 有些博客写的实例代码根本就不能跑, 在借鉴的时候需要擦亮双眼仔细甄别.
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 80 |
Estimate | 估计这个任务需要多少时间 | 300 | 250 |
Development | 开发 | 200 | 180 |
Analysis | 需求分析 (包括学习新技术) | 60 | 45 |
Design Spec | 生成设计文档 | 20 | 15 |
Design Review | 设计复审 | 10 | 10 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 0 |
Design | 具体设计 | 30 | 45 |
Coding | 具体编码 | 200 | 180 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 60 | 60 |
Test Repor | 测试报告 | 10 | 20 |
Size Measurement | 计算工作量 | 20 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 20 |
合计 | 1060 | 955 |
技术路线相关的仓库
- spring-boot
SpringBoot是由Pivotal团队在2013年开始研发、2014年4月发布第一个版本的全新开源的轻量级框架。它基于Spring4.0设计,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化了Spring应用的整个搭建和开发过程。另外SpringBoot通过集成大量的框架使得依赖包的版本冲突,以及引用的不稳定性等问题得到了很好的解决。
其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域成为领导者。 - mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object)映射成数据库中的记录。
- redis
Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。
- shiro
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
- junit4/junit5
JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。Junit测试是程序员测试,即所谓白盒测试. 另外junit是在极限编程和重构(refactor)中被极力推荐使用的工具,因为在实现自动单元测试的情况下可以大大的提高开发的效率.
- swagger
Swagger 是一款RESTFUL接口的、基于YAML、JSON语言的文档在线自动生成、代码自动生成的工具。