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

软工实践寒假作业(2/2)疫情统计程序


这个作业属于哪个课程 <2020春S班(福州大学)>
这个作业要求在哪里 <寒假作业(2/2)>
这个作业的目标 设计、开发一个疫情统计的程序、学习对程序的优化、学习GitHub的使用、PSP(个人软件开发流程)的学习使用、《构建之法》的学习
作业正文 <寒假作业(2/2)>
其他参考文献 ...

GitHub仓库地址

https://github.com/damagedmj/InfectStatistic-main

阅读《构建之法》及PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 35 40
Estimate 估计这个任务需要多少时间 20 15
Development 开发 425 500
Analysis 需求分析 (包括学习新技术) 300 335
Design Spec 生成设计文档 25 20
Design Review 设计复审 10 15
Coding Standard 代码规范 (为目前的开发制定合适的规范) 15 20
Design 具体设计 40 40
Coding 具体编码 500 650
Code Review 代码复审 15 10
Test 测试(自我测试,修改代码,提交修改) 60 105
Reporting 报告 35 30
Test Repor 测试报告 30 25
Size Measurement 计算工作量 20 15
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 10 10
合计 1540 1860

解题思路

思考路线

首先我以前从来没有接触到过有关多人合作的编程,所以当我第一次看到git命令的是时候我是一脸懵逼的,什么fork,commit,push这一系类的词都是我从来没有见过的,然后还有就是psp表格,以及单元测试一类的,从我着手开始了解作业要求,我大概的经过的以下的流程。

  • GitHub的学习
    我因为以前从来没有使用过GitHub来进行编程,所以在此我算一个完完全全的小白,我便开始根据附录1里面的要求按照要求,一步一步的先看懂,把整个作业流程看懂,要干些什么,要学习哪些新东西,从哪里学习,有的地方的资料也不是特别的全面,所以从我把这一切的准备工作全部就绪,差不多理解到作业的要求和目的就花费了我整整一天的时间,然后我便开始着手注册GitHub账号,下载github desktop,下载完成以后开始按照博客园教学里面的要求,开始一步一步的学习怎么使用多人编程,以及为什么要使用多人编程他的目的和好处在什么地方
  • Git命令的学习
    学习了git的各种命令要求
  • 代码规范的制定
    这个因为给出了大概有哪些要求,所以写起来还算是得心应手的,所以就按照自己以往的编程习惯来写
  • 程序编写

学习资料

  • GitHub和GitHub desktop
    我先在GitHub官网上面注册了GitHub账号同时也下载了github desktop,学习了fork,clone,push,commit等的使用方法。使用GitHub
    desktop可以直接将fork到本机的文件修改以后不用使用命令手动上传到仓库里面
  • bilibili
    同时我在哔哩哔哩上面还查找有关github的使用教程以及相关资料
  • 代码规范要求
    参考于作业附件中所给的资料
  • CSDN
    在CSDN上面查找有关代码编写的相关资料,java的文件读取,以及相关库函数的使用

解题思路

首先得到命令行参数,分析命令行参数的类型,根据命令行参数的类型做具体的统计和计算

开发实现过程

类的设置

  • InfectStatistic总类
  • cmdArgs内部类:包含命令行参数成员变量和具有命令参数类型检验功能的方法。
  • province内部类:对应统计后最终输出的每一条信息,相当于一个结构。包含有成员变量省名、感染患 者个数、疑似患者个数、治愈人数、死亡人数。按要求结构组织信息返回该条信息的方法。

功能方法

  • 判断日期、文档路径是否正确
  • 按照拼音排序
  • 日志管理、读取、截取读取:将每条信息传送到统计方法进行整合
  • 计算:整合同一省份的各个症状人数,包括全国的总和
  • 筛选:返回命令行中设置的省份或类型数组
  • 获取指定地址的记录、路径、最新日志时间
  • 输出全国情况或者筛选之后的情况

关键函数以及流程图

  • 日志读取

  • 统计

  • 流程图

代码实现分析

  • 主函数部分
	public static void main(String[] args) throws IOException {
    	if(args.length==0) {
    		System.out.println("输入命令行为空,请重新输入!");
    		return;
    	}
    	if(!args[0].equals("list")) {//命令错误
    		System.out.println("未输入命令‘list’,则不可以带参数,请重新输入!");
    		return;
    	}
    	for(int j=0;j<34;j++) {
	    	all[j]=new Province();
	    	result[j]=new Province();
	    	proresult[j]=new Province();
	    }
    	cmdArgs cmd=new cmdArgs(args);
    	int hasDate=cmd.hasParam("-date");//存命令的索引
    	int hasPro=cmd.hasParam("-province");//检查是否有province命令
    	int hasType=cmd.hasParam("-type");//检查是否有类型命令
    	int hasPath=cmd.hasParam("-out");//获取输出路径索引
    	int hasLog=cmd.hasParam("-log");//获取log路径索引
    	getTopath(args,hasPath);
    	getFrompath(args,hasLog);
    	if(!isCorformpath(frompath)) {//输入日志所在文件夹有错
    		return;
    	}
    	if(hasDate!=-1) {//有指定日期
    		readLog(args[hasDate+1],true);  
    		if(index!=-2&&isWrong==0&&hasPro!=-1) {//有指定省份
    			String[] province=selectPro(args,hasPro);    			
    			Province[] a=selectMes(province);   			 			
    			Province[] b=sortline1(a,selcount);
    			printSel(b);
    			if(hasType!=-1) {//有指定类型
        			String[] type=selectType(args,hasType);
        			printSelpart(proresult,type,selcount);
    			}
    		}
    		else if(index!=-2&&isWrong==0&&hasType!=-1) {//有指定类型未指定省份
    			String[] type=selectType(args,hasType);
    			addAll();
    			printSelpart(result,type,count+1);
			}    		
    	}
    	else {//未指定日期
    		readLog(args[hasDate+1],false);   		
    		if(hasPro!=-1) {//有指定省份   			
    			String[] province=selectPro(args,hasPro);    			
    			Province[] a=selectMes(province);  			
    			Province[] b=sortline1(a,selcount);
    			printSel(b);
    			if(hasType!=-1) {//有指定类型和省份
        			String[] type=selectType(args,hasType);
        			printSelpart(proresult,type,selcount);
    			}
    		}
    		else if(hasType!=-1) {//有指定类型未指定省份
    			String[] type=selectType(args,hasType);
    			addAll();
    			printSelpart(result,type,count+1);
			}
    	}    	
	}

主函数是程序的入口函数。

  • 刚开始进行命令行参数的正确性检验,如果为空或者不存在“list”命令则会报错并终止程序。
  • 接着判断-date、-province、-type的存在情况并通过-log和-out参数得到输入输出路径并存为全局变量。
  • 检验路径的正确性,分为格式检验、文件夹不存在、文件夹内无内容。
  • 分为有-date和无-date两种情况,两种情况又分别对应是否有省份是否有类型,有不同的方法调用方式。
  • 流程为先读取满足日期要求的日志,然后根据省份要求选取省份并排序获得输出的line结构,最后根据类型要求配对按需求输出到文档。如果没有省份或类型要求则跳过该步骤方法按默认输出。
  • provence结构
     	static class Province {

            /** 省份名称 **/
            String provinceName; 
            /** 感染患者 **/
            int ip; 
            /** 疑似患者**/
            int sp;
            /** 治愈 **/
            int cure;
            /** 死亡 **/
            int dead;

            Province(String provinceName, int ip, int sp, int cure, int dead) {
                this.provinceName = provinceName;
                this.ip = ip;
                this.sp = sp;
                this.cure = cure;
                this.dead = dead;
            }
        }

将每个省份对应的信息存入

  • 统计各类人数
static void statistics(String[] ssp,Province[] all) {   	
		String location="";    	
		location=ssp[0];
		Province Province1;
		if(!isExistlocation(location,all)) {
			Province1=new Province(location,0,0,0,0);		
			all[count]=Province1;
			count++;
		}
		else {
		//获得原有的数据条
			Province1=getLine(location,all);
		}
		if(ssp[1].equals("新增")) {
		//获得感染人数
			if(ssp[2].equals("感染患者")) {
				Province1.ip+=Integer.valueOf(ssp[3].substring(0,ssp[3].length()-1));
				
			}
			//疑似患者
			else {
				Province1.sp+=Integer.valueOf(ssp[3].substring(0,ssp[3].length()-1));
			}
		}
		else if(ssp[1].equals("死亡")) {
			Province1.dead+=Integer.valueOf(ssp[2].substring(0,ssp[2].length()-1));
			Province1.ip-=Integer.valueOf(ssp[2].substring(0,ssp[2].length()-1));
		}
		else if(ssp[1].equals("治愈")) {
			Province1.cure+=Integer.valueOf(ssp[2].substring(0,ssp[2].length()-1));
			Province1.ip-=Integer.valueOf(ssp[2].substring(0,ssp[2].length()-1));
		}
		else if(ssp[1].equals("疑似患者")) {
			if(ssp[2].equals("确诊感染")){
				int change=Integer.valueOf(ssp[3].substring(0,ssp[3].length()-1));
				Province1.ip+=change;
				Province1.sp-=change; 			
			}
			//流入情况
			else {
				String tolocation=ssp[3];
				int change=Integer.valueOf(ssp[4].substring(0,ssp[4].length()-1));
				Province Province2;
		    	if(!isExistlocation(tolocation,all)) {
		    		Province2=new Province(tolocation,0,0,0,0);
		    		all[count]=Province2;
		    		count++;
		    	}
		    	else {
		    		Province2=getLine(tolocation,all);
		    	}
		    	Province1.sp-=change;
		    	Province2.sp+=change;
			}
		}
		else if(ssp[1].equals("排除")) {
			Province1.sp-=Integer.valueOf(ssp[3].substring(0,ssp[3].length()-1));   		
		}
		//感染患者流入情况
		else {
			String tolocation=ssp[3];
			//System.out.print(ssp[0]);
			int change=Integer.valueOf(ssp[4].substring(0,ssp[4].length()-1));
			Province Province2;
	    	if(!isExistlocation(tolocation,all)) {
	    		Province2=new Province(tolocation,0,0,0,0);
	    		all[count]=Province2;
	    		count++;
	    	}
	    	else {
	    		Province2=getLine(tolocation,all);
	    	}
	    	Province1.ip-=change;
	    	Province2.ip+=change;   		
		}
		}

统计各地的情况,创建最后会输出的数组结构Province,接受原有log文件中的每行的数据,总和统计每个省份的情况。

  • 各省份排序
 static Province[] sortline1(Province[] wannasort,int num) {
    	String[] location=new String[num];
    	int i=0; 
    	Province[] aa=new Province[num];
    	for(i=0;i<num;i++) {
    		aa[i]=new Province();
    	}
    	for(i=0;i<num;i++) {
    		location[i]=wannasort[i].provinceName;   		
    	}    	
        Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
        Arrays.sort(location, cmp);
        i=0;
        int j=0;        	
    	while(i<num) {
        	if(wannasort[i].provinceName.equals(location[j])) {
        		aa[j]=wannasort[i];
        		j++;       		
        		if(j>=num) {
        			break;
        		}
        		i=-1;
        	}
        	i++;
    	}    
        return aa;
    }
  • 先将所要排序的Province数组的省份提出存入String数组。
  • 用Collator类的方法按照拼音排序省份。
  • 按照排序后省份的顺序循环对比未排序的Province数组的provinceName。若匹配到了则重新开始循环对比。
  • 设置空的结果Province数组存放排序结束后的Province结构。

单元测试截图和描述

  • 检验日志文件路径的正确性

  • 输入日期检验



    检验两个日期的早晚判断和正确性判断

  • 查找日志位置索引



    查找日志位置的索引,用于指定-date的情况,读取时控制循环变量小于索引,循环读取。

性能优化

覆盖率
所有的命令包括-date、-province和-type全部按规则输入后得到覆盖率81.0%
加上所有错误处理的情况即尽可能走遍所有分支得覆盖率95.9%


没有达到利用率百分百的方法

优化方法:

优化后的利用率:

代码规范的链接

<点击查看>

心路历程与收获

这一次的作业感觉确实比第一次的作业要难很多,不管是从花费的时间上,还是从需要拓展的知识面上,都比第一次作业要多许多,当我第一次看到这么多的作业要求的时候,心里是完全摸不着头脑,不管是学习构建之法,还是要注册github账号以及代码的编写实现多人开发确实对我来说都算是白手起家。刚开始的要求是学习github,我对github的第一感觉就是难。因为是全英文的网站,正常的阅读都较为困难,注册就耗费了蛮久的时间。

我把作业需求看了几遍,才终于理解了60%不到,心里开始构思如何写代码并且开始着手复习一下有关java的基础知识,Java对文件的读取输出,还有Java库函数的使用,以及对于命令行的输入产生不同结果。对githubdesktop里面的按键功能不熟悉,我便跟着教程一步一步的,先fork到自己的仓库然后,commit以后还要push。整完文件夹后,便开始代码的编写了。

编码是软件开发过程中最基本、最底层的技艺,然而也是最重要的技艺。任何一个领域的专家都需要花费大量的时间来进行基本技艺的锻炼,木匠需要花费大量的时间来锻炼他们对各种工具的掌握,厨师则需要练习刀工和火候。程序员也是一样的,对我们来说,语言的各种特性必须要了然于胸。而对软件的管理也需要从代码做起。

软件的开发过程就象是一部精密的机器,任何一个环节的变化,都会对其它的环节产生影响。把软件过程按照瀑布的形式进行划分是一种分解的处理思路,但同时我们还应该看到不同活动之间的相互影响。软件开发中的生命周期模型也是一个层次模型,从业务建模一直到软件实现,需要跨越数个层次,同样会出现执行不力的情况,例如,代码设计偏离需求、偏离设计的情况比比皆是。

所以我先提前分析设计要需要用到的函数类型,函数要调用哪些参数等等。
设计了程序进行的流程图,还有只要函数进行的历程图。
我先把主要的函数都写出来,最后再汇总成main函数。最关键的就是形成了province数据结构存放每条数据好调用。代码编写的过程比较普通,就是一步一步来,都是有一定思路的,没有遇到不会实现的情况,顶多就是写出来有bug。
单元测试部分挺难的,我参考了作业中的附录教程,只做了一个JUnit的简易测试。之前不知道单元测试的时候我一直是用输出函数来实时调整我的代码的,因为程序量不是很大,所以单元测试还没有发挥它最大的作用。

技术路线图相关的5个仓库

使用 Spring Boot 框架,完成的现代化的个人独立博客系统。具有完备的 Markdown 编辑器以及文章/页面系统,包含分类,附件管理,评论系统,系统设置等功能。

Spring Boot的官方仓库,有详细的指导文档和全套的Spring Boot解决方案,提供Spring Framework的相关配置策略。

一份涵盖大部分Java程序员所需要掌握的核心知识。

存放JAVA开发的设计思想、算法:《剑指Offer》、《编程珠玑》、《深入理解Java虚拟机:JVM高级特性与最佳实践》、《重构-改善既有代码的设计 中文版》、《clean_code(中文完整版)》、《Java编程思想(第4版)》、《Java核心技术 卷I (第8版)》、《Quartz_Job+Scheduling_Framework》;

互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识,后端同学必看,前端同学也可学习

posted @ 2020-02-19 21:49  DD_mj  阅读(175)  评论(1编辑  收藏  举报