zoukankan      html  css  js  c++  java
  • 软工实践寒假作业(2/2)

    这个作业属于哪个课程 2020春s班
    这个作业要求在哪里 软工实践寒假作业(2/2)
    这个作业的目标 github初使用,代码规范制定,疫情统计程序的需求文档
    作业正文 正文
    其他参考文献 百度,博客园



    一、Github仓库地址

    点击进入Github仓库



    二、《构建之法》与PSP

    2.1 阅读《构建之法》心得


    2.2 PSP表格

    PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    Planning 计划 1*60 1*60
    Estimate 估计这个任务需要多少时间 1*60 1*60
    Development 开发 22.5*60 32.5*60
    Analysis 需求分析(包括学习新技术) 3*30 3*60
    Design Spec 生成设计文档 1*60 2*60
    Design Review 设计复审 1*60 1.5*60
    Coding Standard 代码规范(为目前的开发制定合适的规范) 1.5*60 2.5*60
    Design 具体设计 2*60 2*60
    Coding 具体编码 10*60 17*60
    Code Review 代码复审 1*60 1.5*60
    Test 测试(自我测试,修改代码,提交修改) 3*60 3*60
    Reporting 报告 7.5*60 10*60
    Test Report 测试报告 2*60 2*60
    Size Measurement 计算工作量 0.5*60 1*60
    Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 5*60 7*60
    Total 合计 32*60 44.5*60



    三、解题思路

    Git与Github Desktop

    • 下载git与Github Desktop,fork主仓库到自己的仓库
    • 学习git命令和Github Desktop基本操作,了解commit,.gitignore,push,pr等


    需求整理

    • 题目要求:通过cmd运行,读取传入的log目录下的所有日志,列出全国和各省在某日的感染情况
    • 日志分析:日志文件记录国内各省前一天的感染情况,由省份与患者类型数据组成

    日志文件中所有情况有8种且格式固定,于是想到正则表达式将这8种情况一一匹配。通过分析提供的.log.txt文件可得出,患者cure、dead时,ip人数应减少;各省之间人口流动时,-type人数应增减等计算规则。当各省份人数变换时,全国-type人数也应同时变化。

    • 命令行:list命令由-log、-out(必须)、-type、-date、-province(可选)组成

    命令行以前只运行过不需要传参的程序,于是查找了关于main函数参数的知识。了解到输入的命令其实就存在main(String[] arg)的arg[]数组里。程序所需要查询的日期、省份、文件路径和类型命令值都和命令类型都从arg[]提取出来。

    因此想要进行人数统计,首先要进行命令的解析和匹配。所以将整个class CmdAndFile分成两大部分,一部分用于命令的解析,一部分用于文件的读写。


    CommondAnalysis 部分

    • 用于命令的拆解、分析和验证有效性

    涉及计算较少,主要涉及-date参数日期格式、范围的合法性验证;-province参数是否在32个选项里;-type中几个参数的输出顺序排序。


    FileOpration

    • 用于日志文件的读取、计算和输出

    读取文件时按行读取,获取每行中涉及到的省份和人数;计算部分只需要根据和日志文件8种格式进行匹配,从而确定计算方式;输出文件时遍历[32][4]的数组,实现省份的按拼音排序,然后根据class CommondAnalysis中已经确定-type的几个参数的顺序输出,拼装输出字符串。(后来数组改成[32][5],多了一位来判断该省份是否需要输出)。


    思路流程图

    思路流程图



    四、设计实现过程

    代码结构

    代码结构


    CommondAnalysis

    • public boolean validCmd() 函数实现
      此函数解析、拆取输入命令,读取存储命令数组strCmd[],与-log,-out,-list,-type,-date这几个命令匹配,并且验证参数的合法性。确定从哪里读取日志文件,结果输出到哪里等等必要条件,为统计疫情结果做准备。
      validCmd()函数实现流程
      validCmd()函数实现流程


    FileOpration

    • public void getLogList () 函数实现
      此函数从-log指定文件夹中,与-date参数比较,判断当前在循环中的日志文件是否需要进行读取与计算处理。
      getLogList()函数流程

    • public void readLogFile (String logFileName) 函数实现
      此函数读取需要计算的文件,将除了//开头以外的其他内容按行读取,读取的字符串传入countLogFile (String logFileContent)进行计算。

    • public void countLogFile (String logFileContent) 函数实现
      此函数按照题目给出的8种情况进行字符串匹配,按照匹配情况计算统计结果。

    • public void writeOutFile () 函数实现
      此函数用于输出。通过循环statistics[32][5]二维数组,拼接输出字符串。



    五、代码说明

    CommondAnalysis

    • public int getValidDate(int itemDate)
      传入参数itemDate为表示"-date"参数的strCmd[]的下标。if (isDate(dateStr))判断输入日期是否符合日期格式。查询成功时将class InfectStatistic中logFileName文件名设置为"日期+.log.txt".
            public int getValidDate(int itemDate)
            {
                String dateStr = strCmd[itemDate];
                if (isDate(dateStr))
                {
                    if (dateStr.compareTo(date) <= 0)  //查询日期 <= 当前日期,查询有效
                    {
                        logFileName = dateStr+".log.txt";
                        return itemDate;
                    }
                    else
                        return -1;
                }
                else
                    return -1;
            }
    
    • public boolean validCmd() 验证输入命令是否有效
      和日期查询的套路一样,每当匹配到一个命令,调用getValidXXX(int itemXXX)函数来判断输入的参数是否有效。成功时返回参数下标;失败时则返回-1。没有匹配时:
      -date参数默设置认为当前日期
      checkType == 0,设置控制类型输出顺序的数组int typeOrder[] = {0,1,2,3};索引值表示输出顺序,数组值代表输出的类型(0:sp; 1:ip; 2:cure; 3:dead)
      checkProvince == 0,设置存放统计结果的数组statistics[0][4] = 1(statistics[0][]:全国; statistics[][4]:设置省份是否输出)
            public boolean validCmd()  //validCmd:输入命令有效
            {
                int i;
                String dateCmd = date;
                if (!strCmd[0].equals("list"))
                {
                    System.out.println("命令应以'list'开始!请重新输入!");
                    return false;
                }
                for (i = 1; i < strCmd.length; i++)
                {
                    if (strCmd[i].equals("-log"))
                    {
                        i = getValidLog(++i);
                        if (i == -1)
                        {
                            System.out.println("日志文件夹路径无效!请重新输入!");
                            return false;
                        }
                    }
                    else if (strCmd[i].equals("-out"))
                    {
                        i = getValidOut(++i);
                        if (i < 0)
                        {
                            System.out.println("输出文件路径无效!请重新输入!");
                            return false;
                        }
                    }
                    else if (strCmd[i].equals("-date"))
                    {
                        dateCmd = strCmd[i+1]+".log.txt";
                        i = getValidDate(++i);
                        if (i < 0)
                        {
                            System.out.println("查询日期无效!请重新输入!");
                            return false;
                        }
                    }
                    else if (strCmd[i].equals("-type"))
                    {
                        checkType = 1;
                        i = getValidType(++i);
                        if (i < 0)
                        {
                            System.out.println("查询类型无效!请重新输入!");
                            return false;
                        }
                    }
                    else if (strCmd[i].equals("-province"))
                    {
                        checkProvince = 1;
                        i = getValidProvince(++i);
                        if (i < 0)
                        {
                            System.out.println("查询省份无效!请重新输入!");
                            return false;
                        }
                    }
                    else
                    {
                        System.out.println("命令错误!请重新输入!");
                        return false;
                    }
                }
                if (dateCmd.compareTo(getMinLogName()) < 0)
                {
                    System.out.println("该日暂无记录!请重新输入!");
                    return false;
                }
                if (checkType == 0)
                {
                    for (int j=0; j < 4 ;j++)
                        typeOrder[j] = j;
                }
                if (checkProvince == 0)
                {
                    statistics[0][4] = 1;
                }
                return true;
            }
        }
    


    FileOpration

    • public void statistics()根据不同的匹配情况选择计算的方式。
      0:sp; 1:ip; 2:cure; 3:dead; 4:checkPrintProvince

    public void statistics(int itemProvince, int itemType, int count)传入省份索引值,类型索引值和人数。用于处理只涉及到1个省份,1种类型的情况,即"新增 感染患者"、"新增 疑似患者","排除 疑似患者"。如"xxx省新增感染患者xxx人"时调用statistics(itemProvince, 0, count)。"xxx省排除疑似患者xxx人"时调用statistics(itemProvince, 1, -count)(由于排除疑似患者时数量减少,所以传入的count要改为负数)。

    public void statistics(int itemProvince1, int itemProvince2, int itemType, int count)传入两个省份索引值、类型索引值和人数。用于处理涉及到2个省份,1种类型的情况,即"感染患者流入"、"疑似患者流入"。如"xxx省感染患者流入xxx省xxx人"时调用statistics(itemProvince1,itemProvince2,0,count)

    public void statistics(int itemProvince1, int itemType1, int itemType2, String count)省份索引值、类型索引值和人数。当count类型为int时,传入参数都为int类型和上一种情况冲突,程序不知调用哪个函数,因此将人数从int转为String。用于处理涉及到1个省份,2种类型的情况,即"死亡"、"治愈"、"疑似患者 确诊感染"。如"xxx省死亡xxx人"时调用statistics(itemProvince1,0,3,count)

            public void statistics(int itemProvince, int itemType, int count)
            {
                statistics[0][itemType] += count;
                statistics[itemProvince][itemType] += count;
            }
            public void statistics(int itemProvince1, int itemProvince2, int itemType, int count)
            {
                statistics[itemProvince1][itemType] -= count;
                statistics[itemProvince2][itemType] +=  count;
            }
            public void statistics(int itemProvince1, int itemType1, int itemType2, String count)
            {
                int intCount = Integer.valueOf(count);
                statistics[0][itemType1] -= intCount;
                statistics[0][itemType2] += intCount;
                statistics[itemProvince1][itemType1] -= intCount;
                statistics[itemProvince1][itemType2] += intCount;
            }
    
    • public void countLogFile (String logFileContent) 读取文件内容进行计算
      String logFileContent为传入的每行文档内容。文档内容8种类型分别对应ipIncrease,spIncrease,ipInflow,spInflow,ipDead,ipCure,spChecked,spRemove匹配格式,根据不同的匹配格式选择含参不同的函数public void statistics()进行计算。通过调用getItemProvince(String provinceName)获得省份索引值itemProvince;调用logFileContentArray[].replace("人", ""));将人替换为""获取人数count。
      每当匹配到一个省份且checkProvince==0时(表示没有检测到-province这一命令),就将statistics[itemProvince1][4] 置为1,用于确认该省份是否需要输出。
           public void countLogFile (String logFileContent)
            {
                int itemProvince1;
                int itemProvince2;
                int count;
                Matcher ipIncrease = Pattern.compile("\W+ 新增 感染患者 \d+人").matcher(logFileContent);
                Matcher spIncrease = Pattern.compile("\W+ 新增 疑似患者 \d+人").matcher(logFileContent);
                Matcher ipInflow = Pattern.compile("\W+ 感染患者 流入 \W+ \d+人").matcher(logFileContent);
                Matcher spInflow = Pattern.compile("\W+ 疑似患者 流入 \W+ \d+人").matcher(logFileContent);
                Matcher ipDead = Pattern.compile("\W+ 死亡 \d+人").matcher(logFileContent);
                Matcher ipCure = Pattern.compile("\W+ 治愈 \d+人").matcher(logFileContent);
                Matcher spChecked = Pattern.compile("\W+ 疑似患者 确诊感染 \d+人").matcher(logFileContent);
                Matcher spRemove = Pattern.compile("\W+ 排除 疑似患者 \d+人").matcher(logFileContent);
                String[]  logFileContentArray= logFileContent.split(" ");
                if (ipIncrease.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                    }
                    count = Integer.valueOf(logFileContentArray[3].replace("人", ""));
                    statistics(itemProvince1,0,count);
                }
                else if (spIncrease.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    if (checkProvince == 0 )
                    {
                        statistics[itemProvince1][4] = 1;
                    }
                    count = Integer.valueOf(logFileContentArray[3].replace("人", ""));
                    statistics(itemProvince1,1,count);
                }
                else if (ipInflow.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    itemProvince2 = getItemProvince(logFileContentArray[3]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                        statistics[itemProvince2][4] = 1;
                    }
                    count = Integer.valueOf(logFileContentArray[4].replace("人", ""));
                    statistics(itemProvince1, itemProvince2, 0, count);
                }
                else if (spInflow.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    itemProvince2 = getItemProvince(logFileContentArray[3]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                        statistics[itemProvince2][4] = 1;
                    }
                    count = Integer.valueOf(logFileContentArray[4].replace("人", ""));
                    statistics(itemProvince1, itemProvince2, 1, count);
                }
                else if (ipDead.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                    }
                    String scount = logFileContentArray[2].replace("人", "");
                    statistics(itemProvince1, 0, 3, scount);
                }
                else if (ipCure.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                    }
                    String scount = logFileContentArray[2].replace("人", "");
                    statistics(itemProvince1, 0, 2, scount);
                }
                else if (spChecked.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                    }
                    String scount = logFileContentArray[3].replace("人", "");
                    statistics(itemProvince1, 1, 0, scount);
                }
                else if (spRemove.find())
                {
                    itemProvince1 = getItemProvince(logFileContentArray[0]);
                    if (checkProvince == 0)
                    {
                        statistics[itemProvince1][4] = 1;
                    }
                    count = Integer.valueOf(logFileContentArray[3].replace("人", ""));
                    count = -count;
                    statistics(itemProvince1,1,count);      
                }
            }
    



    六、单元测试与测试用例

    单元测试

    • getValidXXX()函数 获取对应下标值的参数值并验证有效性
      getValidXXX()

    • getMinLogName()函数 获取-log文件夹路径找到最小日期文件,用于判断日期有效性、进行计算的最早文件
      getMinLogName()

    • countLogFile(String content)函数 对日志文本进行计算
      getMinLogName()

    • writeOutFile()函数 输出最终结果到指定文件位置
      writeOutFile()

    • 还有其余的单元测试就不展示了
      由图可知所有单元测试均正确通过
      结果


    测试用例

    • 1 只有 -log 与 -out
      list1

    • 2.1 只有-date且大于当前日期
      list2.1

    • 2.2 只有-date且小于日志文件最小日期
      list2.2

    • 3 只有-province 且参数多个,并出现日志文件中没有的省份
      list3

    • 4 只有-type 且参数多个,并且出现顺序打乱
      list4

    • 5.1 组合查询 -date -type
      list5.1
    • 5.2 组合查询 -date -province
      list5.2
    • 5.3 组合查询 -province -type
      list5.3
    • 5.4 组合查询 -date -province -type
      list5.4

    list5.5

    list5.6



    七、盖率优化和性能测试

    单元测试覆盖率

    单元测试覆盖率

    行数覆盖率只有86%,在查看源码后,发现没有覆盖到的行数均为验证。
    单元测试覆盖率

    性能测试

    性能
    性能



    八、代码规范

    代码规范



    九、心路历程与收获

    在开始这个作业的时候,其实特别懵。作业要求一下子多了起来,往下一拉一片都是作业要求。gitbash、githubdesktop什么用的,是用于github吧?看《构建之法》学习了解?输入的命令java InfectStatistic list XXXXXXX的命令怎么搞进去的?统计数据计算怎么得到的?都是认识的字合起来一句也没看懂233333。一头雾水的时候,了解完githubdesktop我决定先开始从代码规范要求和《构建之法》做起。看到PSP表格后,开始着手了解作业需求。那个要求我真的看了很多遍很多遍当然每次都没看懂。拿了小本本看懂一个删一个.......查找完main函数参数的时候是晚上11点多,那个时候突然get了整个代码大概要做什么,于是隔天开始动手。


    一开始想法很简单,也没有再对类进行划分,所有东西一股脑全被我塞进了一个类里......打到一半我就后悔了,但是整个思路已经有了,于是就没有再改。
    懵逼是懵逼,但是收获也还是有的。在完成作业的过程中,问题也不少,暴躁也不少(bushi),遇到了问题就一个个解决,遇到瓶颈就去吃吃东西(?????)到最后收获还是挺多的,触及知识盲区之后总会有收获的东西。



    "软件=程序+软件工程"。


    这是我读完前三章后感触最深的一句话。
    作为一名软件工程专业的学生,我在专业课上学习了很多专业课程,能够编写程序,实现某些具体的功能,但这些都偏向于“软件”,而非“工程”。在阅读这本书之前我也只知道分析这个程序应该实现哪些功能、可以拆分成哪些模块、每个人可以分配哪些任务。而在阅读了《构建之法》后,我才发现真正的团队开发,真正的软件工程是有很多流程规范的。


    就比如说最开始的选题和需求分析阶段,我们以往的理解就是想想要实现什么功能,决定好了就可以开始写代码了。《构建之法》却告诉我们,需求分析没有这么简单,开发者要能发现目标用户的需求,要区分需求的优先级,要编写出明确的规格说明来指导开发,编写代码前还要对软件结构进行分析设计。在软件开发的过程中,分析、设计、管理这些理论工作的重要性,不比写代码的重要性低。
    完整、良好的需求分析能够为后续开发、测试、维护、扩充等等打下坚实的基础,能够为实现我们的目标即软件工程的目标——创造“足够好”的软件。


    而如何实现“足够好”这一需求呢?我认为实现代码完成任务只是开始。在完成代码后,我们需要做的便是测试。单元测试能帮助程序员记录这个模块的历史和设计变更的理由。“100%的代码覆盖率并不等同于100%的正确性”。单元测试应该集成到自动测试的框架中。另一个重要的措施是要把单元测试自动化,这样每个人都能随时、随地运行单元测试。
    总之,朝着自己想要的方向努力吧~



    十、相关仓库

    Java-WebSocket

    链接:Java-WebSocket
    简介:Java编写的基本WebSocket服务器和客户机实现。底层类是java.nio实现的,允许非阻塞事件驱动模型(类似于web浏览器的WebSocket API)。
    web socket server抽象类实现了web socket协议的服务器端。WebSocket服务器本身除了通过HTTP建立套接字连接之外什么也不做。在那之后,由子类来添加目的。


    spring boot demo

    链接:spring boot demo
    简介:spring boot demo 是一个用来深度学习并实战 spring boot 的项目。该项目已成功集成 actuator(监控)、admin(可视化监控)、logback(日志)、aopLog(通过AOP记录web请求日志)、统一异常处理(json级别和页面级别)、freemarker(模板引擎)、thymeleaf(模板引擎)等。


    JavaGuide

    链接:JavaGuide
    简介:一些Java基础知识合集,如线程、容器、框架等。


    spring-boot

    链接:spring-boot
    简介:可以使用SpringBoot创建独立的Java应用程序,这些应用程序可以使用Java-jar或更传统的WAR部署启动,还提供了一个运行spring脚本的命令行工具。

    java-design-patterns

    链接:java-design-patterns
    简介:展示了Java设计模式,这些模式可以通过它们的高级描述或查看它们的源代码来浏览。

  • 相关阅读:
    zzuli 1908
    继承 封装 多态 java的三大特性
    FZU 2232
    zzuli 1079
    zzuli 1023
    二分图的匹配 hdu 1083
    CodeIgniter学习笔记(五)——CI超级对象中的uri
    CodeIgniter学习笔记(四)——CI超级对象中的load装载器
    CodeIgniter学习笔记(三)——CI中的视图
    CodeIgniter学习笔记(二)——CI中的控制器
  • 原文地址:https://www.cnblogs.com/77yublog/p/12301199.html
Copyright © 2011-2022 走看看