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

格式描述

这个作业属于哪个课程 2020春|S班
这个作业要求在哪里 软工实践寒假作业(2/2)
这个作业的目标 接收一个任务时学会需求分析,代码规划,在代码完成后学会单元测试,性能优化
作业正文 软工实践寒假作业(2/2)
其他参考文献 CSDN,简书相关问题,作业要求附录链接

一、Github仓库地址

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

二、PSP表格

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

三、解题思路描述

1、需求分析

针对这次的全国疫情,需要程序能够列出全国和各省在某日的感染情况
命令行(win+r cmd)cd到项目src下,之后输入命令:
$ java InfectStatistic list -date 2020-01-22 -log D:\log\ -out D:\output.txt
会读取D:/log/下的所有日志,然后处理日志和命令,在D盘下生成ouput.txt文件按顺序列出2020-01-22全国和所有省的情况

日志文件内容可能如下:
福建 新增 感染患者 2人
福建 新增 疑似患者 5人
湖北 新增 感染患者 15人
湖北 新增 疑似患者 20人
湖北 感染患者 流入 福建 2人
湖北 疑似患者 流入 福建 3人
湖北 死亡 1人
湖北 治愈 2人
福建 疑似患者 确诊感染 1人
湖北 排除 疑似患者 2人
// 该文档并非真实数据,仅供测试使用

output.txt文件内容可能如下:
全国 感染患者22人 疑似患者25人 治愈10人 死亡2人
福建 感染患者2人 疑似患者5人 治愈0人 死亡0人
浙江 感染患者3人 疑似患者5人 治愈2人 死亡1人
// 该文档并非真实数据,仅供测试使用

list命令 支持以下命令行参数:

  • -log 指定日志目录的位置,该项必会附带
  • -out 指定输出文件路径和文件名,该项必会附带
  • -date 指定日期,你需要确保你处理了指定日期以及之前的所有log文件,不设置则默认为所提供日志最新的一天。
  • -type 可选择[ip: infection patients 感染患者,sp: suspected patients 疑似患者,cure:治愈 ,dead:死亡患者],使用缩写选择,如 -type ip 表示只列出感染患者的情况,-type sp cure则会按顺序【sp, cure】列出疑似患者和治愈患者的情况,不指定该项默认会列出所有情况。
  • -province 指定列出的省,如-province 福建,则只列出福建,-province 全国 浙江则只会列出全国、浙江,不指定则列出人数有变化的省份。

需要注意的事项:

  • 日志中各种情况的出现顺序不定,省出现的顺序不定,出现哪些省不定,省出现几次不定。
  • 该日志文件的命名遵守对应的规范:年-月-日.log.txt,如2020-01-22.log.txt
  • 文件日期并不一定连续,若某天的日志缺失,默认为该天的情况没有变化。认为日志最早一天的前一天不存在任何感染情况(从最早的日志开始从0开始统计)。如只提供2020-01-23,2020-01-25则可认为2020-01-24增长变化都为0。-date不会提供在日志最晚一天后的日期,若提供应给与日期超出范围错误提示。

2、实现思路

剖析需求究其本质,就是三个步骤:
a.读取命令行: 命令行中list被认为是命令,-date等则是参数,参数后面跟的就是参数值。
b.处理命令,读取日志文件: 通过main函数的参数传递命令行,然后对其中的每一个参数进行分析处理,并且在命令行有误的时候可以报错,比如-date的参数值是否合法、是否超出范围,以防止程序运行崩溃或者产生一些莫名的文件。命令处理后就是对日志文件的每一行进行加工存储。
c.输出文件: 在指定的目录下创建文件,输出日志加工存储后的数据。

四、设计实现过程

主要分为两大模块进行设计

1、文件的输入、输出(-log,-out)

这一部分是命令的基本需求部分,于是我先设计readLog函数读取日志,logHandle函数处理日志中的每一行,writeOut函数将处理后的内容输出。

2、复杂参数的处理(-date,-type,-province)

这一部分是在达到基本的命令后,对剩余的参数进行合法判断以及数据处理。其中,用标志位provinceFlag保存省份是否需要列出,用标志位typeFlag保存患者类型是否需要列出,而在日期参数值的处理上,判断它是否合法,是否超出给定的日志范围,并处理在它之前的所有日志文件。

最后,我还完善了必带项路径是否合法的检验。

五、代码说明

1、主类(InInfectStatistic类)

a.属性:
//患者类型列表,用于指定标志位下标
public String[] allTypes = {"sp","ip","cure","dead"};
public String[] allType = {"感染患者","疑似患者","治愈","死亡患者"};

//省份顺序列表,用于指定标志位下标
public String[] allProvinces = {"全国","安徽","北京","重庆","福建","甘肃","广东","广西","贵州","海南","河北","河南","黑龙江","湖北","湖南","吉林","江苏","江西","辽宁","内蒙古","宁夏","青海","山东","山西","陕西","上海","四川","天津","西藏","新疆","云南","浙江"};

//三十一个省份(加全国),四种类型
public int[][] people = new int[32][4];
//存储所有的日志文件
ArrayList allLog = new ArrayList();
//标志位
public int[] provinceFlag = new int[32];
public int[] typeFlag = new int[4];


//存储参数值
String date = "";
String outOrder = "";
String dateOrder = "";
String provinceOrder = "";
String typeOrder = "";

b.方法:
//main函数调用该方法传入命令行,首先检查命令是否是“list”,然后遍历所有传入的参数,并进行相应的处理和保存,遍历结束后,判断-date的参数值是否合法,并将指定日期前的日志文件传入下一个函数处理。

    /*
	 * 命令行处理
	 * 输入参数:控制台命令行参数
	 * 返回值:无
	 */
	public void orderHandle(String[] inputOrder) throws IOException
	{
		for(int j = 0;j < allType.length;j++)
		{
			typeFlag[j] = 0;
		}
		for(int j = 0;j < allProvinces.length;j++)
		{
			provinceFlag[j] = 0;
		}
		if(!(inputOrder[0].equals("list")))
		{
			System.out.println("命令行仅支持list");
			return ;
		}
		for(int i = 1;i < inputOrder.length;i++)
		{
			if(inputOrder[i].equals("-log"))//指定日志目录位置
			{
				if(isValidLogAddress(inputOrder[i + 1]))
				{
					allLog = queryFileNames(inputOrder[i + 1]);
				}
				else 
				{
					System.out.print("日志目录位置有误,请重新输入。");
					return ;
				}
			}
			else if(inputOrder[i].equals("-out"))//指定输出文件路径和文件名
			{
				if(isValidOutAddress(inputOrder[i + 1]))
				{
					outOrder = inputOrder[i + 1];
				}
				else 
				{
					System.out.print("输出文件路径有误,请重新输入。");
					return ;
				}
				
			}
			else if(inputOrder[i].equals("-date"))//指定日志日期
			{
				dateOrder = inputOrder[i + 1];
				date = inputOrder[i + 1] + ".log.txt";
			}
			else if(inputOrder[i].equals("-type"))//指定列出患者类型
			{
				typeOrder = "get -type";
				int k = i + 1;
				while(k < inputOrder.length)
				{
					for(int j = 0;j < allType.length;j++)
					{
						if(inputOrder[k].equals(allTypes[j]))
						{
							typeFlag[j] = 1;
						}
					}
					k++;
				}
			}
			else if(inputOrder[i].equals("-province"))//指定列出的省
			{
				provinceOrder = "get -province";
				int k = i + 1;
				while(k < inputOrder.length)
				{
					for(int j = 0;j < allProvinces.length;j++)
					{
						if(inputOrder[k].equals(allProvinces[j]))
						{
							provinceFlag[j] = 1;
						}
					}
					k++;
				}
			}
		}
		if(!(isValidDate(dateOrder)) && !(dateOrder.equals("")))
		{
			System.out.print("日期不合法,请重新输入。");
			return ;
		}
		else if(!(isDateOutOfRange(date)) && !(date.equals("")))
		{
			System.out.print("日期超出范围,请重新输入。");
			return ;
		}
		for(int k = 0;k < allLog.size();k++)
		{
			String[] log = allLog.get(k).split("\\\\");
			if((log[log.length - 1].compareTo(date)) <= 0 || date.equals(""))
			{
				readLog(allLog.get(k));
			}
		}
		writeOut(outOrder);
	}

//一些简单的检验参数值的方法

    /*
	 * 判断日志文件目录路径名是否合法
	 * 输入参数:日志文件路径
	 * 返回值:合法返回true,否则返回false
	 */
	public boolean isValidLogAddress(String address)
	{
		if(address.matches("^[A-z]:\\\\(.+?\\\\)*$"))
		{
			return true;
		}
		return false;
	}
	/*
	 * 判断输出文件路径是否合法
	 * 输入参数:输出文件路径
	 * 返回值:合法返回true,否则返回false
	 */
	public boolean isValidOutAddress(String address)
	{
		if(address.matches("^[A-z]:\\\\(.+?\\\\)*(.+?.txt)$"))//判断输出文件是否是文本文件
		{
			return true;
		}
		return false;
	}
	/*
	 * 判断日期是否合法
	 * 输入参数:日期参数值
	 * 返回值:不合法返回false,否则返回true
	 */
	public boolean isValidDate(String inputDate)
	{
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        try 
        {
            format.setLenient(false);//此处指定日期/时间解析是否不严格,在true是不严格,false时为严格
            format.parse(inputDate);//从给定字符串的开始解析文本,以生成一个日期
            
            String[] sArray = inputDate.split("-");
            for (String s : sArray) 
            {
                boolean isNum = s.matches("[0-9]+");
                if (!isNum)
                    return false;
            }
        } 
        catch (Exception e) 
        {
            return false;
        }
        return true;
	}
	/*
	 * 判断日期是否超出范围
	 * 输入参数:日志文件名
	 * 返回值:超出范围返回false,否则返回true
	 */
	public boolean isDateOutOfRange(String inputDate)
	{
		String[] endLog = allLog.get(allLog.size() - 1).split("\\\\");
		String[] beginLog = allLog.get(0).split("\\\\");
		if((inputDate.compareTo(endLog[endLog.length - 1])) > 0)
		{
			return false;
		}
		else if((inputDate.compareTo(beginLog[beginLog.length - 1])) < 0)
		{
			return false;
		}
		return true;
	}

//readLog方法读取日志文件中的内容,按行读取,并忽略注释行。

	/*
	 * 读取日志文件的内容
	 * 输入参数:日志路径
	 * 返回值:无
	 */
	public void readLog(String filePath) throws IOException
	{
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
        		new FileInputStream(new File(filePath)),"UTF-8"));
		String line = null;
		while((line = bufferedReader.readLine()) != null)
		{
			if(!line.startsWith("//"))
			{
				logHandle(line);
			}
		}
		bufferedReader.close();
	}

//接下来是关键的处理日志行的方法logHandle,依题意一共有8种不同类型的日志行,每处理一行就从头开始遍历patterns,看它属于哪一种类型,然后依据它的类型去调用相应的方法。

	/*
	 * 处理日志中的每一行
	 * 输入参数:未注释的日志行
	 * 返回值:无
	 */
	public void logHandle(String inputLine)
	{
		int patternNum=10;
		String[] patterns =
		{
			"(\\W+) 新增 感染患者 (\\d+)人",
			"(\\W+) 新增 疑似患者 (\\d+)人",
			"(\\W+) 感染患者 流入 (\\W+) (\\d+)人",
			"(\\W+) 疑似患者 流入 (\\W+) (\\d+)人",
			"(\\W+) 死亡 (\\d+)人",
			"(\\W+) 治愈 (\\d+)人",
			"(\\W+) 疑似患者 确诊感染 (\\d+)人",
			"(\\W+) 排除 疑似患者 (\\d+)人"
		};
		boolean[] matchPattern = new boolean[8];		
		for(int i = 0;i < 8;i++)
		{
			matchPattern[i] = false;
		}
		for(int i = 0;i < 8;i++)
		{
			matchPattern[i] = Pattern.matches(patterns[i], inputLine);
		}
		for(int i = 0;i < 8;i++)
		{
			if(matchPattern[i])
			{
				patternNum=i;
				break;
			}
		}
		//System.out.print(patternNum);
		switch(patternNum)
		{
			case 0 :
				addIp(inputLine);//新增感染患者
				break;
			case 1 :
				addSp(inputLine);//新增疑似患者
				break;
			case 2 :
				inflowIp(inputLine);//确诊患者流入
				break;
			case 3 :
				inflowSp(inputLine);//疑似患者流入
				break;
			case 4 :
				deadIp(inputLine);//患者死亡
				break;
			case 5 :
				cureIp(inputLine);//患者治愈
				break;
			case 6 :
				diagnoseSp(inputLine);//疑似患者确诊
				break;
			case 7 :
				removeSp(inputLine);//排除疑似患者
				break;
			default :
				System.out.println("日志语句出错");
		}
	}

//writeOut方法中包含对标志位的一些处理,若没有标志位参数,则将所有类型置1,所有有变化的省份置1,然后判断标志位是否是1,是则输出。

	/*
	 * 输出文件
	 * 输入参数:文件路径
	 * 返回值:无
	 */
	public void writeOut(String filePath) throws IOException
	{
		if(typeOrder.equals(""))
		{
			for(int i = 0;i < allType.length;i++)
			{
				typeFlag[i] = 1;
			}
		}
		if(provinceOrder.equals(""))
		{
			for(int i = 0;i < allProvinces.length;i++)
			{
				if(people[i][0] != 0 || people[i][1] != 0 || people[i][2] != 0 || people[i][3] != 0)
				{
					provinceFlag[i] = 1;
				}
			}
		}
	    FileWriter writer = null;
	    try 
	    {
	        writer = new FileWriter(filePath);
	        for(int i = 0;i < allProvinces.length;i++)
	        {
	        	if(provinceFlag[i] == 1)
	        	{
	        		writer.write(allProvinces[i] + " ");
		        	for(int j = 0;j < allType.length;j++)
		        	{
		        		if(typeFlag[j] == 1)
		        		{
			        		writer.write(allType[j] + people[i][j] + "人 ");
		        		}
		        	}
		        	writer.write("\n");
	        	}
	        }
	        writer.write("// 该文档并非真实数据,仅供测试使用");
	    } 
	    catch (Exception e) 
	    {
	        e.printStackTrace();
	    } 
	    finally 
	    {
            try 
            {
                writer.flush();
                writer.close();
            } 
            catch(IOException ex) 
            {
                ex.printStackTrace();
            }
        }
	}

六、单元测试截图和描述

所用日志文件为example目录下的log日志文件(PS:这个测试的try,catch块是用eclipse自动生成的,语法不太符合我自己的规范)
1、缺少-out参数,会报错并要求重新输入命令行。
2、-date参数值采用中文的形式,会报错并要求重新输入命令行。
3、日志文件的日期最晚是2020-01-27,因此该日期超出范围。
4、仅包含-date参数,处理2020-01-22的日志文件。
5、仅包含-date参数,处理2020-01-25之前的所有日志文件。
6、包含-date,-province参数,处理2020-01-25之前的日志文件,并只输出全国和浙江的情况,浙江没有任何情况发生,所以都是0。
7、包含-date,-province参数,处理2020-01-25之前的所有日志文件,输出福建和湖北的情况,福建在前湖北在后。
8、包含-date,-province,-type参数,处理2020-01-25之前的所有日志文件,输出福建和湖北的情况,福建在前湖北在后,并且只输出感染患者和疑似患者的人数。
9、仅包含-type参数,处理所有的日志文件,输出有变化的省份的疑似患者和死亡患者。
10、仅包含-province参数,输出指定的福建和湖北的情况。

七、单元测试覆盖率和优化

1、分析上述10个单元测试用例的覆盖率,很明显isValidLogAddress方法和isValidOutAddress方法的覆盖率低,这是因为测试用例中并没有测试日志路径和输出文件路径不合法的情况,于是增加了两个测试用例。
2、如图是两个增加的单元测试,其中test11日志文件路径的最后没有斜杠,test12输出文件不是txt格式,这些都是不对的。
3、优化后两个方法都达到100%,其他都比较高就不需要优化了。
关于JProfiler,看了很多教程还是无法很好地集成到eclipse使用,还在研究。

八、代码规范链接

https://github.com/SIPUNK/InfectStatistic-main/blob/master/221701214/codestyle.md

九、心路历程和收获

1、学习了构建之法的部分内容,其实对软件工程这一门学科有了更深刻的认识和理解,软件工程它不仅仅涵盖编程这一方面。对于一个完整的项目来说,开发前的需求分析和模块规划,开发时的单元测试和性能优化,开发结束后的项目总结和撰写报告,都是十分重要的,正是有了这些步骤,才有了完整的软件项目。除此之外,如果能够为编程能力,工程能力制定一个公共的量化标准,可能软件工程师或者是我们普普通通的大学生能够更有前进的动力吧!
2、关于这个作业,刚拿到作业时确实是无从下手,后来将作业内容完完整整地读了几遍,并认真学习了附录的教程后,开始进行这一次的任务,当然其中很多东西是从未接触过的,但是正所谓越让你害怕的事物,越能促使你进步。最后,在作业截止的前夕,我清晰地感受到自己动手能力的增长,可以说是受益匪浅。

十、相关的五个仓库

仓库地址 简要描述
https://github.com/Mryangkaitong/python-Machine-learning 机器学习核心算法项目,包括决策树,svm支持向量机,PCA算法等
https://github.com/TTyb/python3CAPTCHA python实现验证码机器学习
https://github.com/TingNie/Coursera-ML-using-matlab-python coursera吴恩达机器学习课程作业,包括逻辑回归,线性回归,神经网络模型。
https://github.com/ahangchen/GoogleML Google机器学习新手教程学习心得与代码
https://github.com/ScrappyZhang/python_web_Crawler_DA_ML_DL python从最基础的语法历经网络基础、前端基础、后端基础和爬虫与数据基础走向机器学习
posted @ 2020-02-19 19:28  陈朝帏  阅读(232)  评论(0编辑  收藏  举报