2020春 软工实践寒假作业(2/2)——疫情统计

2020春 软工实践寒假作业(2/2)——疫情统计


这个作业属于哪个课程 2020春福大软工实践W班
这个作业要求在哪里 寒假作业 2/2
这个作业的目标 学习github、制定代码规范、完成疫情统计的命令行程序
作业正文 2020春 软工实践寒假作业(2/2)——疫情统计
其他参考文献 ......

1、Github仓库地址

InfectStatistic-main

2、PSP表格

PSP是卡耐基梅隆大学(CMU)的专家们针对软件工程师所提出的一套模型:Personal Software Process (PSP,个人开发流程,或称个体软件过程)。

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 45 60
Estimate 估计这个任务需要多少时间 1730 1920
Development 开发 1430 1650
Analysis 需求分析 (包括学习新技术) 300 270
Design Spec 生成设计文档 200 180
Design Review 设计复审 60 30
Coding Standard 代码规范 (为目前的开发制定合适的规范) 45 60
Design 具体设计 45 40
Coding 具体编码 60*12=720 60*15=900
Code Review 代码复审 120 90
Test 测试(自我测试,修改代码,提交修改) 240 270
Reporting 报告 150 180
Test Repor 测试报告 60 45
Size Measurement 计算工作量 30 20
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 45 40
合计 1800+ 2000+

3、解题思路描述

  1. 简化程序步骤第一眼看到需求文档的时候,有点懵,因为原先没有接触过命令行类的程序设计,于是就对照着需求文档,利用思维导图,简化程序步骤,让自己的思路更加清晰一些

    404
  2. 前期准备

    • 根据任务要求从学习Git、GitHub以及GitHub Desktop的用法

      wikipedia的描述“GitHub是一个利用Git进行版本控制、专门用于存放软件代码与内容的共享虚拟主机服务。它由GitHub公司(曾称Logical Awesome)的开发者Chris Wanstrath、PJ Hyett和Tom Preston-Werner使用Ruby on Rails编写而成。”

      对照网站相关文章、视频教程,进行GitHub注册、star、fork、push、new repository等基本操作学习,还有命令行bash操作

    • 编码的进行需要按照给定要求指示,制定变量名、缩进、注释等代码规范的制定

      根据自身平时编码的风格,并且参考《码出高效_阿里巴巴Java开发手册》

  3. 编程准备

    • 需要明确程序运行的大致流程,根据每个大的步骤结合面向对象的知识,依次设计出需要的类和字段,一开始就觉得应该采用map的结构去进行存取、映射数据,所以应该去熟悉相关API的使用,然后再根据数据的流动设计函数功能

    • 编码从java命令行程序运行着手,查看如何通过Windows的dos界面运行一个java程序

  4. 测试需求

    EclEmma是一款代码覆盖测试工具,通常作为Eclipse的插件使用

    JProfiler是一款功能强大的Java开发分析工具,能帮助从事编程工作的朋友们分析你们的代码数据,确定内存泄漏并了解线程问题

4、设计实现过程

  1. 模块结构图+整体流程图

    404
  2. 主要类函数设计

    • InfectStatistic疫情统计类,主类

      • public static void main(String[] args)
    • class CommandLine命令行构造类

      • private String commandName;命令行的名字
      • private String[] parameters;命令行的参数列表
    • interface Parameters命令行参数列表类接口

      • public void formatParameters();参数格式化

      • public void judgeParameters(); 判断参数的正确性:是否包含必要的参数,参数值

      • class Parameter参数类

        String name;参数名称

        Object value;参数值

    • class ParameterValue 命令的参数的值的类

      • boolean multiValue;是否可多值的标志
      • Object value; 参数值本身
    • class ListParameters implements ParametersList命令行参数列表类实现了命令行参数列表类接口Parameters

      • private Map<String,ParameterValue> listParameterMap参数名和参数值对应关系的map
      • @Override
        public void formatParameters()格式化List命令的参数,将字符串数组的参数列表转化为map中的值
      • @Override
        public void judgeParameters() 判断格式化后的命令参数列表是否符合需求
    • class CommandLineParser 命令行解析器

      • public void parseListCommand(String[] parameters)解析list命令
      • public void excuteListCommand()执行list命令
    • class DailyInfectItem某日某省的感染状况项的类

      • private int ip,sp,cure,dead;四种感染人数
      • private boolean flag;标记该省份是否出现在感染日志中
      • 相应字段的getter和setter
    • class LogFileReader日志文件读取器

      • public static String getFileContent(File file)将文件中的有效信息行读出到一个字符串中存储

      • public static LocalDate logName2LocalDate(String logName)将日志文件名转化为日期

      • public String readContent()将所有符合条件的日志的内容读入进字符串中存储

        logDir.listFiles()获得指定目录下的所有文件->logName2LocalDate()->若日期在指定日期之后则跳过->若日期在指定日期之前—>content+=getFileContent(logList[i])

    • class LogContentParaser日志内容解析器

      • private Map<String,DailyInfectItem> infectMap记录一天中省份和感染情况的对应关系的map
      • final String IP_ADD_PATTERN = "(.) 新增 感染患者 (\d)人";用于匹配类型的字符串
      • public Map<String,DailyInfectItem> paraseLogContent(String content) 将字符串形式的感染状况转化为每日感染状况项
      • public void extractType(String line)分别匹配八种Pattern,从文本行中提取四种类型的数目,并存储到map中
      • public void matchIpAdd(Pattern p,String line)匹配新增感染患者的Pattern,新增感染患者
    • class ResultOutputter命令结果输出器

      • Map<String,DailyInfectItem> resultMap

      • Map<String,ParameterValue> listParameterMap

      • public String getFinalResult() 获得输出的最终结果

        getTotal()—>getProvinceResult() ->排序-> handleCountry()-> handleOrdinary()

      • public void getTotal()获得全国的情况并且将它加入到resultMap中

      • public void getProvinceResult() 根据-province参数获得相应的结果

      • public void handleCountry(StringBuilder sb) 特殊处理全国的情况

      • public void handleOrdinary(StringBuilder sb,List<Map.Entry<String, DailyInfectItem>> list) 处理一般省份

      • public void output()将getFinalResult() 得到的结果字符串输入到参数指定路径的文件中

  3. 关键的数据结构

    //记录参数和参数值的对应关系
    private Map<String,ParameterValue> parameterMap=new HashMap<>();
    
    //记录一天中省份和感染情况的对应关系
    private Map<String,DailyInfectItem> infectMap=new HashMap<String, DailyInfectItem>();
    

5、代码说明

  1. public void formatParameters()格式化List命令的参数,将命令行中的的字符串数组的参数列表转化为Map<String,ParameterValue> parameterMap中的值

    	public void formatParameters() {
            for(int i=0;i<parameters.length;) {
    		String parameter=parameters[i];
    		if(listParameterMap.containsKey(parameter)) {//该项为参数
    			if(listParameterMap.get(parameter).multiValue) {//参数可多值
    				List<String> value=new LinkedList<String>();
    				while(++i<parameters.length) {
    					if(!listParameterMap.containsKey(parameters[i])) {
    						value.add(parameters[i]);
    					}else {
    						break;
    					}
    				}
    				listParameterMap.get(parameter).value=value;
    				
    			}else {//该项单值
    				if(++i<parameters.length&&(!listParameterMap.containsKey(parameters[i]))){
    					listParameterMap.get(parameter).value=parameters[i];
    					i++;		
    				}else {
    					//	TODO:参数项未提供参数值,抛出异常
    				}
    			}
    		}else {//该项不为参数
    			continue;
    		}
    	}
    }
    
  2. public void getDateArrange(File[] logs)维护需要读取的日志文件的最大日期和最小日期,主要利用LocalDate的isAfter()和isBefore()完成

    public void getDateArrange(File[] logs) {	
    	Date date = null;
    	LocalDate lDate=null;
    	for(int i=0;i<logs.length;i++) {
    		String logName=logs[i].getName();
    		logName=logName.substring(0, logName.indexOf('.'));
    		lDate=logName2LocalDate(logName);
    		if(i==0) {
    			endDate=lDate;
    			startDate=lDate;
    		}else {
    			if(lDate.isAfter(endDate)) {
    				endDate=lDate;
    			}
    			if(lDate.isBefore(startDate)) {
    				startDate=lDate;
    			}
    		}
    	}
    	
    }
    
  3. public String readContent()将日志的内容读入进字符串中存储,将符合要求的日志的内容拼接成字符串

    	public String readContent() {
    		File logDir=new File(logDirectory);
    		String content="";
    		if(logDir.exists()&&logDir.isDirectory()) {
    			File[] logList=logDir.listFiles();
    			LocalDate lDate;
    			for(int i=0;i<logList.length;i++) {
    				
    				//将日志文件的名称解析为日期
    				lDate=logName2LocalDate(logList[i].getName());
    				if(parameterDate!=null) {
    					if(lDate.isAfter(parameterDate)) {//若日期在指定日期之后则跳过
    						continue;
    					}else {//若日期在指定日期之前则将其加入到结果字符串中
    						content+=getFileContent(logList[i]);
    					}
    				}else {
    					content+=getFileContent(logList[i]);
    				}
    			}
    		}else {
    			//	TODO:处理日志所在文件目录异常
    		}
    		//System.out.println(content);
    		return content;
    	}
    
  4. public void extractType(String line)将日志文本行依次匹配八种Pattern,从中提取四种类型的数目,并存储到private Map<String,DailyInfectItem> infectMap中

     public void extractType(String line) throws Exception {
        	
         	//依次匹配8个Pattern
        	Pattern p1 = Pattern.compile(IP_ADD_PATTERN);
            Pattern p2 = Pattern.compile(SP_ADD_PATTERN);
            Pattern p3 = Pattern.compile(IP_FLOW_PATTERN);
            Pattern p4 = Pattern.compile(SP_FLOW_PATTERN);
            Pattern p5 = Pattern.compile(DEAD_PATTERN);
            Pattern p6 = Pattern.compile(CURE_PATTERN);
            Pattern p7 = Pattern.compile(SP_CONFIRM_PATTERN);
            Pattern p8 = Pattern.compile(SP_EXCLUDE_PATTERN);
            
            
            matchIpAdd(p1,line);
            matchSpAdd(p2,line);
            matchIpFlow(p3,line);
            matchSpFlow(p4, line);
            matchDead(p5, line);
            matchCure(p6, line);
            matchSpConfirm(p7, line);
            matchSpExclude(p8, line);
            
            for(int i=0;i<provinces.length;i++) {
            	if(infectMap.get(provinces[i]).getIp()<0) {
            		infectMap.get(provinces[i]).setAll(0, 0, 0, 0);
            	}
    		}
            
        }
    
  5. public void matchIpAdd(Pattern p,String line)

    public void matchIpFlow(Pattern p,String line)

    利用Pattern匹配字符串,从而提取相应数据,其余匹配方法略

    public void matchIpAdd(Pattern p,String line) {
       	Matcher m=p.matcher(line);
       	while(m.find()) {
       		int addNum=Integer.valueOf(m.group(2));
       		if(infectMap.get(m.group(1)).getIp()<0) {//省份不存在,则创建一个感染项
       			infectMap.get(m.group(1)).setAll(addNum, 0, 0, 0);
       		}else {//省份存在,修改感染项
       			int ip=infectMap.get(m.group(1)).getIp();
       			infectMap.get(m.group(1)).setIp(ip+addNum);
       		}
       	}
       }
    
    public void matchIpFlow(Pattern p,String line) throws Exception {
       	Matcher m=p.matcher(line);
       	while(m.find()) {
       		if(infectMap.get(m.group(1)).getIp()<0) {//流出省不存在
       			// TODO:报错
       			throw new Exception();
       		}
       		if(infectMap.get(m.group(2)).getIp()<0) {//流入省不存在
       			infectMap.get(m.group(2)).setAll(0, 0, 0, 0);
       		}
       		
       		int flowNum=Integer.valueOf(m.group(3));
    		int orignalNum1=infectMap.get(m.group(1)).getIp();
    		int orignalNum2=infectMap.get(m.group(2)).getIp();
    		if(orignalNum1-flowNum<0) {
    			// TODO:当流出数目大于省份原有数目则报错
    			throw new Exception();
    		}
    		infectMap.get(m.group(1)).setIp(orignalNum1-flowNum);
    		infectMap.get(m.group(2)).setIp(orignalNum2+flowNum);
       	 }
       	
        }
    
  6. public void excuteListCommand()执行list命令,命令执行流程

    public void excuteListCommand() throws Exception {
        
    		//根据日志地址生成日志文件读取器
    		String logAddress=(String) parameterMap.get("-log").value;
    		LogFileReader logFileReader=new LogFileReader(logAddress,(String)parameterMap.get("-date").value);
    		
        	//生成日志解析器,解析日志文件内容
    		String content=logFileReader.readContent();
    		LogContentParaser contentParaser=new LogContentParaser();
    		Map<String,DailyInfectItem> resultMap=contentParaser.paraseLogContent(content);
    		
        	//根据结果输出地址生成输出器,将结果输出到指定路径中去
    		String outputAddress=(String)parameterMap.get("-out").value;
    		ResultOutputter outputter=new ResultOutputter(outputAddress,resultMap,parameterMap);
    		outputter.output();
    	}
    

6、单元测试截图和描述

JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。 JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。

JUnit是由 Erich Gamma 和Kent Beck 编写的一个回归测试框架(regression testing framework)。Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。

本次使用JUnit4在Eclipse中进行单元测试

  1. 测试基本参数+类型参数-type

    404
  2. 测试基本参数+类型参数-type

    404
  3. 测试基本参数+省份参数-province

    404
  4. 测试基本参数+类型参数-type+省份参数-province

    404
  5. 测试基本参数+日期参数-date+类型参数-type+省份参数-province

    404
  6. 测试基本参数+日期参数-date,日期为日志的第一天

    404
  7. 测试基本参数+日期参数-date,日期为日志的第二天

    404
  8. 测试基本参数+日期参数-date,日期不为日志对应日期,并且在日志日期范围之内

    404
  9. 测试基本参数+省份参数-province+类型参数-type+日期参数-date,日期为日志最后一天

    404
  10. 交换各个参数的位置,省份参数-province+-log+类型参数-type+-out+-date

    404

7、单元测试覆盖率优化和性能测试

什么是代码测试覆盖率?

基于代码的测试覆盖评测测试过程中已经执行的代码的多少,与之相对的是要执行的剩余代码的多少。代码覆盖可以建立在控制流(语句、分支或路径)或数据流的基础上。控制流覆盖的目的是测试代码行、分支条件、代码中的路径或软件控制流的其他元素。数据流覆盖的目的是通过软件操作测试数据状态是否有效,例如,数据元素在使用之前是否已作定义。
覆盖率等于覆盖面积/总面积

EclEmma是一款代码覆盖测试工具,通常作为Eclipse的插件使用。

  • Coverage As—>JUnit Test,前十个单元测试均通过检测404

  • 查看Coverage窗口得到代码的覆盖率为84.2%

    404
  • 查看源代码,得到类似的标记后的代码,结果分析

    • 红色代表未执行
    • 黄色代表条件没有全部执行
    • 绿色代表执行过了
    404
  • 修改源代码,删除部分由于编写代码过程中,用于测试或其他而构造出来的字段代码,优化后代码覆盖率提升至89.4%代码整理后覆盖率没有完全优化达到百分百,原因是测试用例大多都是正确的,没有被覆盖到的地方是进行了异常的抛出

    404

JProfiler是一款功能强大的Java开发分析工具,能帮助从事编程工作的朋友们分析你们的代码数据,确定内存泄漏并了解线程问题。

8、代码规范的链接

codestyle.md

9、心路历程与收获

  • 这次的疫情统计程序设计任务对于我来说是一个蛮大的挑战,和其他那些一拿到题目就有思路的同学比起来,我拿到就是觉得有点无从下手的感觉。所以完成这项任务花费了较长的时间,并且由于自己前期进行任务的时间安排的不够合理紧凑,导致后期不断地赶进度,deadline就是生产力(不是,利用Personal Software Process (PSP,个人开发流程,或称个体软件过程)的表格去对照了整个过程中花费的时长,能够很好的计划和回顾整个业务流程,

  • 对于Git的用法学习其实是花了蛮多的时间弄清楚它的用法,感受就是GitHub确实有种宝藏平台的感觉,可以在这个地方找到很优质的学习资源,也可以让自己很好的管理代码,同时体会到它将在团队合作项目中起到非常重要的作用。

  • 然后是代码规范制定,原先学习C++程序设计的时候也有制定过类似的规范,到这次Java代码规范的制定还是有花费一定的工作量的。培养一个良好的代码风格,可以一定程度上减小工作量,节约时间,提高效率,所以制定一个合理的代码规范是十分必要的。

  • 由于原来接触的程序设计,功能需求都相对的简单明了,类字段的设计都比较直观,但这一次个人觉得逻辑就没有那么简单了,最后实现的和最开始的设想是有一些出入的,原先的设想比较复杂,原因是自己在阅读需求的过程中把实现方法想的过于复杂,没有很科学的组织代码,所以耗费了很长的时间才确定下思路。从中吸取的经验是程序设计不要贪大求全,遵循演化优于一步到位的原则,要不断地在实际应用过程中改进迭代。

  • 程序测试部分,原先很少接触程序的测试,这次在Eclipse中使用JUnit4进行单元测试,感受到利用现成框架进行单元测试可以准确、快速地保证程序基本模块的正确性,比较遗憾的是这次任务中未能很好的进行代码的优化,在今后的项目开发中,应该进一步的学习相关知识。

  • 经过本次任务的洗礼,收获挺大的,在这个过程中跌跌撞撞,常常自己和代码一起崩溃,最后的成果不够完美,很多地方没有很好地优化,设计也不是很合理,同时也吃了需求没有阅读清晰的亏,但是对于学习来说的话,实践过程和知识积累本身最重要。虽然这次的任务结束了,但是从中得到的有价值的经验、教训、逻辑、设计等却可以在新程序任务中延续,继续加油,嗯对

    404

10、第一次作业中技术路线图相关的5个仓库

在github上寻找你在第一次作业中技术路线图相关的5个仓库,star并fork,在博客中提供名称、链接、简介

博客链接 简介
CS-Notes 包含技术面试必备基础知识、Leetcode、计算机操作系统、计算机网络、系统设计、Java、Python、C++等知识
JavaGuide 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识的仓库
LeetCodeAnimation Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)
java-bible 记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主
advanced-java 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识,后端同学必看,前端同学也可学习

404404

posted @ 2020-02-18 22:45  Niiiiiiiiicole  阅读(349)  评论(2编辑  收藏  举报