软工实践寒假作业(2/2)
格式描述
这个作业属于哪个课程 | 2020春|S班 (福州大学) |
---|---|
这个作业要求在哪里 | 作业要求的链接 |
这个作业的目标 | 开发疫情统计程序,并借此熟悉程序开发流程和github的使用 |
作业正文 | .... |
其他参考文献 | 知乎、博客园 |
github仓库地址:https://github.com/bzzd2333/InfectStatistic-main
1、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 365 | 475 |
Analysis | 需求分析 (包括学习新技术) | 20 | 25 |
Design Spec | 生成设计文档 | 20 | 30 |
Design Review | 设计复审 | 10 | 10 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 15 | 20 |
Design | 具体设计 | 20 | 25 |
Coding | 具体编码 | 240 | 300 |
Code Review | 代码复审 | 10 | 40 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 25 |
Reporting | 报告 | 140 | 170 |
Test Repor | 测试报告 | 20 | 20 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 90 | 120 |
合计 | 535 | 675 |
2、思路描述
我看到这个题目后,决定将程序分为命令初始化、处理日志、输出日志三个部分。使用的数据结构为:HashMap<String,Province>,其中String为省份的名称,Province为一个表示省份详细情况的类。省份的名称由一个List<String>存放。
处理日志思路如下:日志每一行的结构如下图,只要判断array[1]、array[2]的值就可以知道所有情况并处理。
输出日志就是正常输出到文件没什么特别的思路。
3、设计实现过程
我的程序模块结构图:
我的关键函数流程图:
4、代码说明
-
命令行初始化:
其中date为指定的日期,logPath、outputPath分别为日志目录的位置与输出文件的路径与文件名。dealType函数将-type的参数:要输出的人员情况存入output数组。dealProvince函数将-province的参数:指定列出的省份存入List<String>provinces。public InfectStatistic(String[] args) { /* 类的成员变量的初始化。 */ //对命令行参数的初始化 this.init(); } //初始化 public void init() { for(int i=0;i<arg.length;i++) { switch(arg[i]) { case "-date": date = new String(arg[i+1]); isRead = false; break; case "-log": logPath = new String(arg[i+1]); break; case "-out": outputPath = new String(arg[i+1]); break; case "-type": isOutput = false; dealType(i+1); break; case "-province": isOutputAll = false; dealProvince(i+1); default: break; } } } //处理-type参数 public void dealType(int index) { for(int i=0;index<arg.length && i<4;i++) { switch(arg[index]) { case "ip": output[i] = arg[index]; break; case "sp": output[i] = arg[index]; break; case "cure": output[i] = arg[index]; break; case "dead": output[i] = arg[index]; break; default: break; } index++; } } //处理-province参数 private void dealProvince(int index) { while(index<arg.length) { switch(arg[index]) { case "-date": return; case "-log": return; case "-out": return; case "-type": return; default: provinces.add(arg[index]); map.put(arg[index],new Province(arg[index])); } index++; } }
-
处理日志:
deal函数处理所有日志文件,dealOneLine函数处理日志文件中的一行。public void deal() throws IOException { String logDate; String[] sArray; File file = new File(logPath); File[] tempList = file.listFiles(); /* 判断-date提供的日期是否晚于日志最晚一天的日期 */ //读取日志文件 for (int i = 0; i < tempList.length; i++) { logDate = new String(tempList[i].getName()); sArray = logDate.split("\."); logDate = new String(sArray[0]); //读取的日志日期小于指定的日期 if (isRead || (logDate.compareTo(date)) <= 0) { BufferedReader br = null; String line = null; br = new BufferedReader(new InputStreamReader(new FileInputStream(tempList[i].toString()), "UTF-8")); while((line = br.readLine()) != null) { String[] array = line.split(" "); //处理单行 dealOneLine(array); } br.close(); } } //统计全国的情况 allStatistic(); } //处理单行 private void dealOneLine(String[] array) { //忽略注释行 if (array[0].equals("//")) { return; } //处理的省份未初始化 if (map.get(array[0]) == null) { map.put(array[0], new Province(array[0])); } switch (array[1]) { case "新增": if (array[2].equals("疑似患者")) { map.get(array[0]).addSp(array[3]); } else { map.get(array[0]).addIp(array[3]); } break; case "感染患者": //流出省份减少感染患者 map.get(array[0]).removeIp(array[4]); //流入省份增加感染患者 if (map.get(array[3]) == null) map.put(array[3], new Province(array[3])); map.get(array[3]).addIp(array[4]); break; case "疑似患者": if (array[2].equals("流入")) { //流出省份减少疑似患者 map.get(array[0]).removeSp(array[4]); //流入省份增加疑似患者 if (map.get(array[3]) == null) map.put(array[3], new Province(array[3])); map.get(array[3]).addSp(array[4]); } //疑似患者确诊感染 else { //感染患者增加 map.get(array[0]).addIp(array[3]); //疑似患者减少 map.get(array[0]).removeSp(array[3]); } break; case "死亡": map.get(array[0]).dead(array[2]); break; case "治愈": map.get(array[0]).cure(array[2]); break; case "排除": map.get(array[0]).removeSp(array[3]); break; default: break; } } //统计全国的情况 public void allStatistic() { for (int i = 0; i < name.size(); i ++ ) { if (map.get(name.get(i)) != null) { country.allAdd(map.get(name.get(i))); } } }
-
日志输出:
InfectStatistic类中的output方法用于输出所有要输出的省份到输出文件。Province类中的output方法用于输出单个省份的情况。List<String>name存放所有省份的名称,List<String>provinces存放-province参数指定的省份的名称。
在InfectStatistic类中的output方法里,若boolean变量isOutputAll为true,即默认情况,则输出所有省份,遍历List<String>name;否则输出-province参数指定的省份,遍历存放所有省份的String数组provinceName,逐个判断其是否为-province参数指定的省份,若是则输出。
在Province类中的output方法里,若boolean变量isOutput为true,即默认情况,则输出该省份四种人群的数量;否则按照output数组中指定人员类型进行输出。//InfectStatistic类中的output方法 public void output() throws IOException { if (isFinish) { return; } BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath)) ; //默认情况,没有-province参数 if (isOutputAll) { country.output(isOutput,output,bw); for (int i = 0; i < name.size(); i ++ ) { if (map.get(name.get(i)) != null) { map.get(name.get(i)).output(isOutput, output, bw); } } } //有-province参数 else { if (provinces.contains("全国")) { country.output(isOutput,output,bw); } for (int i = 0; i < provinceName.length; i ++ ) { if (provinces.contains(provinceName[i])) { if (map.get(name.get(i)) == null) { map.put(provinceName[i], new Province(provinceName[i])); } map.get(provinceName[i]).output(isOutput, output, bw); } } } bw.write("// 该文档并非真实数据,仅供测试使用"); bw.close(); } //Province类中的output方法 public void output(boolean isOutput,String[] output,BufferedWriter bw) throws IOException { //默认输出 if(isOutput) { bw.write(name + " 感染患者 " + infectionPatients + "人 " + "疑似患者 " + suspectedPatients + "人 " + "治愈 " + cure + "人 " + "死亡 " + dead + "人"); bw.newLine(); } //有-type参数情况下的输出 else { bw.write(name); for(int i=0;i<4;i++) { switch(output[i]) { case "ip": bw.write(" 感染患者 " + infectionPatients + "人"); break; case "sp": bw.write(" 疑似患者 " + suspectedPatients + "人"); break; case "cure": bw.write(" 治愈 " + cure + "人"); break; case "dead": bw.write(" 死亡 " + dead + "人"); break; default: break; } } bw.newLine(); } }
5、单元测试截图和描述
本次单元测试读取的日志为example下的三个log.txt文件
单元测试1:测试默认情况下,即没有参数-date、-type、-province的结果。输出为所提供日志最新的一天所有省份的情况:
单元测试2:测试参数-date为2020-01-23的情况,此时程序需要处理日志:2020-01-22.log.txt、2020-01-23.log.txt,输出为1月23日所有省份的情况:
单元测试3:测试参数-date为2020-01-25的情况,因为没有日志2020-01-24.log.txt,所以此时程序只需要处理日志:2020-01-22.log.txt、2020-01-23.log.txt,输出为1月25日所有省份的情况:
单元测试4:测试参数-date为2020-01-27,-type为ip的情况,输出为1月27日所有省份的感染患者的人数:
单元测试5:测试参数-date为2020-01-27,-type为ip,sp的情况,输出为1月27日所有省份的感染患者与疑似患者的人数:
单元测试6:测试参数-date为2020-01-27,-type为ip,cure,dead,sp的情况,按顺序输出1月27日所有省份的感染患者、治愈、死亡、疑似患者的人数:
单元测试7:测试参数-date为2020-01-27,-type为ip,-province为“福建”,“全国”的情况,因为全国总是排在第一个,所以按顺序输出1月27日全国、福建的感染患者的人数:
单元测试8:测试参数-date为2020-01-27,-province为“福建”,“全国”,“北京”的情况,其中因为输入文档没有涉及到北京,所以北京的各项数据均为0,且因为全国总是排在第一个,且按拼音排序北京应该在福建的前面,所以按顺序输出1月27日全国、北京、福建的所有人数:
单元测试9:测试参数-province为“福建”,“全国”,“北京”,-date为2020-01-27的情况,因为命令行参数的顺序可以调换,所以单元测试9的结果与单元测试8一样
单元测试10:测试参数-date为2020-01-29的情况,因为最新一天的日志为2020-01-27.log.txt,所以应给与“日期超出范围”的错误提示。
6、单元测试覆盖率优化和性能测试
单元测试覆盖率如下:
因为程序中未覆盖到的代码都为一些错误命令的判断,所以没有什么要优化的地方。
性能如下:
7、代码规范链接
代码规范链接:https://github.com/bzzd2333/InfectStatistic-main/blob/new/221701236/codestyle.md
8、心路历程与收获
在本次的程序开发中,首先我明白了程序的开发并不只有编写程序,从一开始的需求分析到最后的测试,每个环节都大有门道;其次,我学习了很多新东西,如github、单元测试、JProfiler的使用,这让我明白了持续学习的重要性;第三,通过分析PSP表格,我发现我在具体编码上多花了很多时间,这也是因为我的粗心大意以及对需求分析不足所导致的,所以我以后应该更加仔细地分析需求。最后,本次的作业使我受益良多,我相信这次作业学到的东西和积累的经验必可活用于下次。
9、技术路线图相关的5个仓库
1、JavaGuide
链接:https://github.com/bzzd2333/JavaGuide
简介:该仓库总结了JAVA的学习路线及知识点和面试技巧,适合进行JAVA知识点的复习。
2、SpringAll
链接:https://github.com/bzzd2333/SpringAll
简介:该仓库包括了Spring Boot,Spring Boot&Shiro,Spring Cloud,Spring Boot&Spring Security&Spring Security OAuth2等系列教程。
3、JAVAWeb-Project
链接:https://github.com/bzzd2333/JAVAWeb-Project
简介:该仓库存放的是开始学习JAVA-WEB开发的一些练手项目,这些也适合初学者进行练习
4、3y
链接:https://github.com/bzzd2333/3y
简介:该仓库从Java基础、JavaWeb基础到常用的框架再到面试题都有完整的教程,几乎涵盖了Java后端必备的知识点。
5、JAVAEETest
链接:https://github.com/bzzd2333/JavaEETest
简介:该仓库存放了一些Spring、SpringMVC、MyBaits、Spring Boot案例。