2020春 软工实践寒假作业(2/2)——疫情统计
这个作业属于哪个课程 | 2020春福大软工实践W班 |
---|---|
这个作业要求在哪里 | 寒假作业 2/2 |
这个作业的目标 | 学习github、制定代码规范、完成疫情统计的命令行程序 |
作业正文 | 2020春 软工实践寒假作业(2/2)——疫情统计 |
其他参考文献 | ...... |
1、Github仓库地址
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、解题思路描述
简化程序步骤第一眼看到需求文档的时候,有点懵,因为原先没有接触过命令行类的程序设计,于是就对照着需求文档,利用思维导图,简化程序步骤,让自己的思路更加清晰一些
前期准备
根据任务要求从学习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开发手册》
编程准备
需要明确程序运行的大致流程,根据每个大的步骤结合面向对象的知识,依次设计出需要的类和字段,一开始就觉得应该采用map的结构去进行存取、映射数据,所以应该去熟悉相关API的使用,然后再根据数据的流动设计函数功能
编码从java命令行程序运行着手,查看如何通过Windows的dos界面运行一个java程序
测试需求
EclEmma是一款代码覆盖测试工具,通常作为Eclipse的插件使用
JProfiler是一款功能强大的Java开发分析工具,能帮助从事编程工作的朋友们分析你们的代码数据,确定内存泄漏并了解线程问题
4、设计实现过程
-
模块结构图+整体流程图
-
主要类函数设计
-
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() 得到的结果字符串输入到参数指定路径的文件中
-
-
-
关键的数据结构
//记录参数和参数值的对应关系 private Map<String,ParameterValue> parameterMap=new HashMap<>();
//记录一天中省份和感染情况的对应关系 private Map<String,DailyInfectItem> infectMap=new HashMap<String, DailyInfectItem>();
5、代码说明
-
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; } } }
-
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; } } } }
-
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; }
-
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); } } }
-
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); } }
-
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中进行单元测试
-
测试基本参数+类型参数-type
-
测试基本参数+类型参数-type
-
测试基本参数+省份参数-province
-
测试基本参数+类型参数-type+省份参数-province
-
测试基本参数+日期参数-date+类型参数-type+省份参数-province
-
测试基本参数+日期参数-date,日期为日志的第一天
-
测试基本参数+日期参数-date,日期为日志的第二天
-
测试基本参数+日期参数-date,日期不为日志对应日期,并且在日志日期范围之内
-
测试基本参数+省份参数-province+类型参数-type+日期参数-date,日期为日志最后一天
-
交换各个参数的位置,省份参数-province+-log+类型参数-type+-out+-date
7、单元测试覆盖率优化和性能测试
什么是代码测试覆盖率?
基于代码的测试覆盖评测测试过程中已经执行的代码的多少,与之相对的是要执行的剩余代码的多少。代码覆盖可以建立在控制流(语句、分支或路径)或数据流的基础上。控制流覆盖的目的是测试代码行、分支条件、代码中的路径或软件控制流的其他元素。数据流覆盖的目的是通过软件操作测试数据状态是否有效,例如,数据元素在使用之前是否已作定义。
覆盖率等于覆盖面积/总面积EclEmma是一款代码覆盖测试工具,通常作为Eclipse的插件使用。
-
Coverage As—>JUnit Test,前十个单元测试均通过检测
-
查看Coverage窗口得到代码的覆盖率为84.2%
-
查看源代码,得到类似的标记后的代码,结果分析
- 红色代表未执行
- 黄色代表条件没有全部执行
- 绿色代表执行过了
-
修改源代码,删除部分由于编写代码过程中,用于测试或其他而构造出来的字段代码,优化后代码覆盖率提升至89.4%代码整理后覆盖率没有完全优化达到百分百,原因是测试用例大多都是正确的,没有被覆盖到的地方是进行了异常的抛出
JProfiler是一款功能强大的Java开发分析工具,能帮助从事编程工作的朋友们分析你们的代码数据,确定内存泄漏并了解线程问题。
8、代码规范的链接
9、心路历程与收获
这次的疫情统计程序设计任务对于我来说是一个蛮大的挑战,和其他那些一拿到题目就有思路的同学比起来,我拿到就是觉得有点无从下手的感觉。所以完成这项任务花费了较长的时间,并且由于自己前期进行任务的时间安排的不够合理紧凑,导致后期不断地赶进度,deadline就是生产力(不是,利用Personal Software Process (PSP,个人开发流程,或称个体软件过程)的表格去对照了整个过程中花费的时长,能够很好的计划和回顾整个业务流程,
对于Git的用法学习其实是花了蛮多的时间弄清楚它的用法,感受就是GitHub确实有种宝藏平台的感觉,可以在这个地方找到很优质的学习资源,也可以让自己很好的管理代码,同时体会到它将在团队合作项目中起到非常重要的作用。
然后是代码规范制定,原先学习C++程序设计的时候也有制定过类似的规范,到这次Java代码规范的制定还是有花费一定的工作量的。培养一个良好的代码风格,可以一定程度上减小工作量,节约时间,提高效率,所以制定一个合理的代码规范是十分必要的。
由于原来接触的程序设计,功能需求都相对的简单明了,类字段的设计都比较直观,但这一次个人觉得逻辑就没有那么简单了,最后实现的和最开始的设想是有一些出入的,原先的设想比较复杂,原因是自己在阅读需求的过程中把实现方法想的过于复杂,没有很科学的组织代码,所以耗费了很长的时间才确定下思路。从中吸取的经验是程序设计不要贪大求全,遵循演化优于一步到位的原则,要不断地在实际应用过程中改进迭代。
程序测试部分,原先很少接触程序的测试,这次在Eclipse中使用JUnit4进行单元测试,感受到利用现成框架进行单元测试可以准确、快速地保证程序基本模块的正确性,比较遗憾的是这次任务中未能很好的进行代码的优化,在今后的项目开发中,应该进一步的学习相关知识。
经过本次任务的洗礼,收获挺大的,在这个过程中跌跌撞撞,常常自己和代码一起崩溃,最后的成果不够完美,很多地方没有很好地优化,设计也不是很合理,同时也吃了需求没有阅读清晰的亏,但是对于学习来说的话,实践过程和知识积累本身最重要。虽然这次的任务结束了,但是从中得到的有价值的经验、教训、逻辑、设计等却可以在新程序任务中延续,继续加油,嗯对
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 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识,后端同学必看,前端同学也可学习