我与我周旋,宁做我

软工实践寒假作业(2/2)

这个作业属于哪个课程 2020春|S班 (福州大学)
这个作业要求在哪里 软工实践寒假作业(2/2)
这个作业的目标 开发一个疫情统计程序
作业正文 软工实践寒假作业(2/2)
其他参考文献

1. Github仓库地址。

InfectStatistic-main

2. # PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
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. 代码规范的链接 ### [codestyle](https://github.com/kylin773/InfectStatistic-main/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开发的小项目,也适合初学者进行练习

11. 解决无法pull requests到fork过来仓库的办法(保持commit日志记录存在)

(可能出现原因:当你fork一个项目并clone到本地,然后你复制了一些文件,并且自己创建了一个仓库,此时和原来的仓库已经无关了,并且多次commit并完成了项目,
但是后面你发现你可能要pr该项目到原来的作者那,那此时你可以使用下面这个方法。我的个人原因:因为刚开始不明白pr规则,只把代码拷贝下来并进行修改,然后后面需要保留commit
message并pr,经过了好久才解决了这个问题,也就是将两个仓库进行合并,两个仓库没有很大的关系)

  1. fork老师的仓库,并clone到电脑,然后在目录上进行git。(或者进入clone到的本地地址执行git命令)
  2. git remote add other ../repo1/ #../repo1/ 参数为你已经完成git地址,如https://github.com/xxxx/xxx
  3. git fetch other # 把你完成的项目同步到本仓库
  4. git checkout -b repo1 other/master #创建分支repo1,并切换到分支repo1,并把刚同步的项目放到该分支上
  5. git checkout master #换到该项目主分支
  6. git merge repo1 --allow-unrelated-histories #将repo1分支合并到master分支,加--allow-unrelated-histories参数是因为两个项目可能经过修改已经没有什么关系了(一个初始项目,一个做好的项目)
  7. 后面可能会出现文件冲突,如两个都有.gitignore,可以先保存副本,然后删除项目中的.gitignore,然后就合并完成了
  8. 再到github上面发现fork下来的项目,已经有了已经完成的项目的commit message,并且可以pull requests
posted @ 2020-02-20 21:59  I_Deal_With_Me  阅读(119)  评论(4编辑  收藏  举报