这个作业要求在哪里 |
软工实践寒假作业(2/2) |
这个作业的目标 |
开发一个疫情统计程序 |
作业正文 |
软工实践寒假作业(2/2) |
其他参考文献 |
|
1. Github仓库地址。
https://github.com/kylin773/InfectStatistic
2. # PSP表格
Planning |
计划 |
60 |
40 |
Estimate |
估计这个任务需要多少时间 |
30 |
40 |
Development |
开发 |
960 |
600 |
Analysis |
需求分析 (包括学习新技术) |
70 |
85 |
Design Spec |
生成设计文档 |
20 |
10 |
Design Review |
设计复审 |
10 |
20 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
60 |
90 |
Design |
具体设计 |
200 |
180 |
Coding |
具体编码 |
360 |
400 |
Code Review |
代码复审 |
60 |
50 |
Test |
测试(自我测试,修改代码,提交修改) |
50 |
60 |
Reporting |
报告 |
120 |
200 |
Test Repor |
测试报告 |
40 |
50 |
Size Measurement |
计算工作量 |
10 |
15 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
20 |
30 |
合计 |
|
1140 |
840 |
3. 解题思路描述。
- 首先了解如何使用命令行选项和参数,学会了命令行传递参数并在程序中使用参数
- 选择用于存放疫情的数据的数据结构,用于统计和最后输出统计结果,选择了Map存放。
- 确定思路,根据日期来确定需要统计的日志文件,确定如恶化统计单个日志文件,确定如何处理单个日志文件的每一行记录。
- 从全局细分到日志文件的一行记录,处理好每一行记录,并处理好每个日志文件,并统计好全部的记录存放在数据结构里面,最后根据需要统计的省份和类型输出对应数据到输出文件即可。
4.实现过程。
大概的实现流程如图:
5. 代码说明。展示出项目关键代码,并解释思路。
- 首先是处理日志文件的代码,首先确定是否是需要的日志文件,然后将日志文件的文件名进行日期格式转换,转换成我们规定的日期格式,然后通过日期类已经分装好的方法进行判断日期合法性,判断日期大小,当该文件日期在我们所需要的日期之前,那么我们就将该文件进行处理统计。
public void dealLogs() {
if (log == null) {
System.out.println("请输入正确的日志地址!");
System.exit(-1);
}
File file = new File(log);
if (file.isDirectory() && file.list() != null) {
File[] files = file.listFiles();
if (files != null)
for (File aFile : files) {
String[] splitName = aFile.getName().split("\\.");
if (aFile.isFile() && splitName.length > 2 && splitName[1].equals("log")) {
if (date != null) {
try {
Date fileDate = sdf.parse(splitName[0]);
if (fileDate.before(date) || fileDate.equals(date)) {
caculateLogs(aFile);
}
}
catch (ParseException pE) {
}
}
else {
caculateLogs(aFile);
}
}
}
}
else {
System.out.println("请输入正确的日志地址!");
System.exit(-1);
}
}
- 单个文件的处理,我们读取每一行,就可以进行一行行地统计,统计一行,首先是根据日志的特征,以空格分离提取出数据存放在字符串数组中,然后判断字符串数组的长度,依据日志分析,把每行分为长度为3、4、5的长度分别进行处理,避免需要处理很多种情况带来的代码冗余。那么使用Map的好处,我们可以直接根据省份的名字,得到对应的存放数据的整数数组,然后拿出来进行与获得的人数进行操作数操作即可。当遍历完所有的需要处理的日志文件,那么数据就处理完并保存在数据结构总了。
private void caculateLogs(File file) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String dataLine;
while ((dataLine = br.readLine()) != null) {
String[] words = dataLine.split("\\s+");
if (status.containsKey(words[0])) {
int[] tempStatus = status.get(words[0]);
if (words.length == 3) {
int num = Integer.parseInt(words[2].substring(0, words[2].indexOf("人")));
if (words[1].equals("死亡") && num > 0) {
tempStatus[ip] -= num;
tempStatus[dead] += num;
internal[ip] -= num;
internal[dead] += num;
status.put(words[0], tempStatus);
}
else if (words[1].equals("治愈") && num > 0) {
tempStatus[ip] -= num;
tempStatus[cure] += num;
internal[ip] -= num;
internal[cure] += num;
status.put(words[0], tempStatus);
}
}
......//其他情况类似
- 根据处理好的数据结构,我们最后输出到文件就可以了。值得注意的是,我们根据-province和-type命令行选项的对应参数的有无,可以分为四种情况,即有省份参数有类型参数,有省份参数无类型参数,有省份参数有类型参数,无省份参数无类型参数,而且每一种情况都得分别处理,并且无法放在一起处理,因为每一种需要获取的数据都是不一样的。这里就需要考虑一件事情,这四种情况的判断要放在处理每个文件的每一行数据就处理吗?当文件数少,日志文件小的时候,这样处理是没有什么影响的,但命令行输入的时候就确定的四种情况之一,在分析每一行日志的时候都需要判断,四种情况又要分别处理对应的数据,那么会造成大量的相似代码冗余。因此我这里是将这样的情况放在输出文件的时候输出,也就是刚开始就将每个省份的数据都保存起来,因为在读取每一行日志文件的时候,如果不存起来,就需要进行各种情况判断,判断是不是需要的省份,判断是不是流入的省份,判断是不是需要的类型等问题,加大解题的问题量。因此我把所有的数据都保存起来,最后要输出的时候根据已存在数据结构的数据提取需要的数据即可。并且这样做还有个好处,该程序可以优化成一个用于查询的程序,当数据库数据很多的时候,我们可以根据需要的筛选类型,快速地找出我们需要的数据。
......
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
String dataLine;
if (province == null && type == null) {
for (String provin : provinces) {
int[] tempStatus = status.get(provin);
dataLine = String.format("%s 感染患者%d人 疑似患者%d人 治愈%d人 死亡%d人\n", provin,
tempStatus[ip], tempStatus[sp], tempStatus[cure], tempStatus[dead]);
bw.write(dataLine);
}
bw.close();
}
else if (province != null && type == null) {
int i = 0;
while (province[i] != null) {
if (status.containsKey(province[i])) {
int[] tempStatus = status.get(province[i]);
dataLine = String.format("%s 感染患者%d人 疑似患者%d人 治愈%d人 死亡%d人\n", province[i],
tempStatus[ip], tempStatus[sp], tempStatus[cure], tempStatus[dead]);
bw.write(dataLine);
}
i++;
}
bw.close();
}
......
- 以下有个细节,就是巧妙地将我们数据结构里面的数据,从int[]数组变成我们需要输出到文件的Sting[],那么根据我们输入的-type对应的可选参数有4个,根据组合排序,可能出现的输入参数类型情况就有432*1=24种,我们要根据输入的参数次序依次输出到统计文件中,那么我们需要每种情况都处理吗? 这里我巧妙地根据输入的参数的长度分为四种情况,即对应输入-type的参数为1-4个,例如当为1时,我们只需要输出对应的字符串即可,不需要知道输入的一个参数是ip|sp|cure|dead,多个参数的时候也不需要考虑参数组合的问题,只需要依次输出。
for (String provin : provinces) {
int[] tempStatus = status.get(provin);
String[] intToSring = new String[]{"感染患者" + tempStatus[ip] + "人", "疑似患者" + tempStatus[sp]
+ "人", "治愈" + tempStatus[cure] + "人", "死亡" + tempStatus[dead] + "人"};
if (needTypes == 1) {
dataLine = String.format("%s %s\n", provin, intToSring[type[0]]);
}
else if (needTypes == 2) {
dataLine = String.format("%s %s %s\n", provin, intToSring[type[0]], intToSring[type[1]]);
}
else if (needTypes == 3) {
dataLine = String.format("%s %s %s %s\n", provin, intToSring[type[0]], intToSring[type[1]],
intToSring[type[2]]);
}
else if (needTypes == 4) {
dataLine = String.format("%s %s %s %s %s\n", provin, intToSring[type[0]], intToSring[type[1]],
intToSring[type[2]], intToSring[type[3]]);
}
else {
dataLine = "";
}
bw.write(dataLine);
}
6. 单元测试截图和描述。
本次一共进行了15个单元测试,其中包括输出测试和容错处理测试
测试选项 -log 和 -out
准确性测试
测试正确输入位置进行的疫情统计
测试更换输入参数位置进行的疫情统计
容错性测试
日志路径输入错误测试:
未输入路径测试
测试选项 -date、-province、-type的组合测试
准确性测试
测试正确组合三个选项参数,进行多种组合都没有问题,并且各个选项和参数的输入位置不影响程序正确执行,并能够根据合理的输入顺序输出文件。
容错性测试
说明:当进行省份参数输入的省份错误时,输出文件掠过输错省份,当输入的类型参数输入不和要求,该参数将不会出现在输出文件中。
7. 单元测试覆盖率优化和性能测试,性能优化截图和描述。
单元测试的覆盖率:
单元测试的性能测试(整体偏大,但是基于很大的数据量的情况下相对会有一定优化):
8. 代码规范的链接
https://github.com/kylin773/InfectStatistic/blob/master/081700430/codestyle.md
9. 解决项目的心路历程与收获
- 此次解决项目过程中遇到好多问题,在编码中就出现了很多问题,首先考虑数据结构,要使用二维数组呢还是使用别的数据结构?参数会出现的情况很多,该如何优化代码,避免冗余代码行数在做重复的东西,首先提取一个共共的函数是不太可靠的,因为不一样或者参数组合情况处理数据的方法就不一样,都需要进行判断,难以提取公共函数,那怎么办?遇到需要存储到数据结构内容可以根据参数来定制,但是需要大量做判断的情况下,和需要存储所有省份的数据,但是可以避免在文件中做大量复杂情况的分类的判断,这时我该怎么权衡?因此在了解这些问题的存在的时候,我有在考虑代码该如何优化,如何考虑使用怎样的方法去解决问题,而不是想到什么代码就敲什么代码,只为了完成编码,实现功能而去敲代码,对代码有更多的考虑。
- 对于代码规范,在我大二暑期实习的时候就感受到过代码规范的重要性,并且当时的实习导师还让我根据公司的编码规范,找出公司已经投入使用的代码的规范问题,并解决代码不规范的问题,然后这次看了别的公司的代码规范,发现好的代码规范,其实很多都很类似的,几乎有一套很好的代码规范体系,并且在平时的编码中都遵循或者清楚明确的话,到以后代码敲多了,自然就养成了良好的习惯。良好的代码规范让人看着舒服,我也写下了我遵循的大概的代码规范。
- 当然,在编码解决问题的同时,还会遇到在编码的过程中带来的一系列相关技术的使用问题,比如Jprofiler软件的使用,结合IDEA,我仍然不是很熟悉该专业代码分析软件是如何运行,以及该如何合理的利用该软件来分析代码?利用Juit的时候,怎么样进行单元测试才是合理的?在github的使用上,我将老师仓库的代码clone下来并且在自己的github发布了自己创建的仓库,并且在代码完成后才发现,我想要pr代码到老师的代码的仓库下,但是这时候却没有办法,我解决了好久仍然没有解决,甚至在编写该博客的时候也还没完成,我的思路是fork了老师代码,然后再老师代码上新建了我的代码分支,然后将两份代码合并,但是合并后代码却无法使用github destop进行push到我的remote上面。这也算经历了崩溃,也尝试了网上各种方法,仍然需要解决。
10. 相关技术学习
- mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object)映射成数据库中的记录。
- Maven
Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.
- spring
spring的框架
- JaveEETest
总结了一些Spring、SpringMVC、MyBatis、Spring Boot案例
- JAVAWeb-Project
初学JAVA-WEB开发的小项目,也适合初学者进行练习