这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 在这 |
这个作业的目标 | 学习github、制定代码规范、撰写程序读取日志,列出全国和各省在某日的感染情况,单元测试、自我评测、寻找五个仓库 |
作业正文 | 我的寒假作业 |
其他参考文献 | 无 |
一、前置要求
1. github初始用
我的github仓库地址:https://github.com/heng1999/InfectStatistic-main
2. PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 40 |
Estimate | 估计这个任务需要多少时间 | 1440 | 1680 |
Development | 开发 | 1090 | 1120 |
Analysis | 需求分析 (包括学习新技术) | 150 | 180 |
Design Spec | 生成设计文档 | ||
Design Review | 设计复审 | 30 | 10 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | ||
Design | 具体设计 | ||
Coding | 具体编码 | 600 | 540 |
Code Review | 代码复审 | ||
Test | 测试(自我测试,修改代码,提交修改) | 100 | 210 |
Reporting | 报告 | 250 | |
Test Repor | 测试报告 | ||
Size Measurement | 计算工作量 | ||
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 120 | |
合计 | 1360 | 1410 |
二、疫情统计程序
1.解题思路
-
思考
这个程序的要求看起来很复杂繁琐,着实有点头大。所以我认真看了好几遍(不过对于一小部分要求的理解还是模模糊糊的),大概分析出了几个完成作业的关键点:
1.GitHub学习
我去GitHub官网注册了一个账号,耗费了很久的时间,原因就是网站跳转的实在是太慢了,加上我 家的网不是很好,断断续续的,慢到没脾气了。所以我就去搜索了一个让GitHub变快的方法,修改了我主机的hosts文件,这才跳转的速度稍微提高了一点(也有可能是我的心理作用)。
按照要求将老师提供的InfectStatistic-main仓库fork到我自己的账号下,然后添加同结构的学号文件 夹,因为不熟悉操作就反复commit了好几次。
2.Git命令学习
我之前是下载过git的,但是从来没用过。这次刚好让它派上用场了。3.代码规范制定
这个作业要求是最简单并且浅显易懂的,就连制定规范的几个内容都有给出,也提供了参考资料。在我的 印象里在大一就做过类似的代码规范制定,所以写起来就很得心应手了,就按照自己的一直以来编码习惯制定。4.在本地创建log文件夹
将所给log文件夹复制到本机方便操作。5.通过检验命令行参数决定功能
程序的编写从args入手
- 查找资料
1.GitHub&Git
参考多来自本题作业中给定的附件。先是注册的方法,再是fork,clone,push,commit等的使用方法。按照要求我在本机上下载了GitHub桌面版,但是除了登录了我的GitHub账号以外就没有再使用过了,因为用git命令就可以很方便地push新改动到我的仓库了,还不太知道桌面版的便捷之处(也因为我不太会用)。
Git的各样命令的学习是从百度得到的。
2.代码规范
代码规范多是我自己的日常习惯来制定的,部分有参考于作业附件中所给的《腾讯C++代码规范》。3.程序编写
有部分java自带的方法的查找来自于百度,例如:省份按照拼音排序的Collator类,用正则表达式检验文件路径的正确性等。 - 解题思路
2020寒假作业(2/2)
2. 设计实现过程
- 类的设置
- InfectStatistic总类
- cmdArgs内部类:包含命令行参数成员变量和具有命令参数类型检验功能的方法。
- line内部类:对应统计后最终输出的每一条信息,相当于一个结构。包含有成员变量地理位置信息、感染患者个数、疑似患者个数、治愈人数、死亡人数。按要求结构组织信息返回该条信息的方法。
- 全局变量的设置
- 各类结果的个数(总的数据、筛选后的数据)
- 各类结果line数组(总的数据、筛选后的数据)
- 输入出文档路径
- 控制标志(日期正确性检验)
- 功能方法
- 判断正确性(日期、文档路径)
- 按照拼音排序(挑选省份后的line排序、统计后总数据line排序)
- 日志管理(读取、截取读取):将每条信息传送到统计方法进行整合
- 计算(统计、全国):整合同一省份的各个症状人数,包括全国的总和
- 筛选(省份、类型):返回命令行中设置的省份或类型数组
- 获取(指定地址的记录、路径、最新日志时间)
- 输出(总体、筛选之后)
- 关键函数
2020寒假作业(2/2) 2020寒假作业(2/2) 2020寒假作业(2/2) - 流程图
2020寒假作业(2/2)
3.代码说明
(1)主函数片段
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;
}
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);
line[] a=selectMes(province);
line[] 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);
}
}
else {//未指定日期
readLog(args[hasDate+1],false);
if(hasPro!=-1) {//有指定省份
String[] province=selectPro(args,hasPro);
line[] a=selectMes(province);
line[] 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);
}
}
}
主函数是程序的入口函数。
- 刚开始进行命令行参数的正确性检验,如果为空或者不存在“list”命令则会报错并终止程序。
- 接着判断-date、-province、-type的存在情况并通过-log和-out参数得到输入输出路径并存为全局变量。
- 检验路径的正确性,分为格式检验、文件夹不存在、文件夹内无内容。
- 分为有-date和无-date两种情况,两种情况又分别对应是否有省份是否有类型,有不同的方法调用方式。
- 流程为先读取满足日期要求的日志,然后根据省份要求选取省份并排序获得输出的line结构,最后根据类型要求配对按需求输出到文档。如果没有省份或类型要求则跳过该步骤方法按默认输出。
(2)统计statistics
static void statistics(String[] sp,line[] all) {
String location="";
location=sp[0];
line line1;
if(!isExistlocation(location,all)) {//不存在对应该省的记录
line1=new line(location,0,0,0,0);//新建数据条
all[count]=line1;
count++;
}
else {
line1=getLine(location,all);//获得原有的数据条
}
if(sp[1].equals("新增")) {
if(sp[2].equals("感染患者")) {//获得感染人数
line1.grhz+=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));
}
else {//疑似患者
line1.yshz+=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));
}
}
else if(sp[1].equals("死亡")) {
line1.dead+=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
line1.grhz-=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
}
else if(sp[1].equals("治愈")) {
line1.recover+=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
line1.grhz-=Integer.valueOf(sp[2].substring(0,sp[2].length()-1));
}
else if(sp[1].equals("疑似患者")) {
if(sp[2].equals("确诊感染")){
int change=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));//改变人数
line1.grhz+=change;
line1.yshz-=change;
}
else {//流入情况
String tolocation=sp[3];//流入省
int change=Integer.valueOf(sp[4].substring(0,sp[4].length()-1));//改变人数
line line2;
if(!isExistlocation(tolocation,all)) {//不存在对应该省的记录
line2=new line(tolocation,0,0,0,0);//新建数据条
all[count]=line2;
count++;
}
else {
line2=getLine(tolocation,all);//获得原有的数据条
}
line1.yshz-=change;
line2.yshz+=change;
}
}
else if(sp[1].equals("排除")) {
line1.yshz-=Integer.valueOf(sp[3].substring(0,sp[3].length()-1));
}
else {//感染患者流入情况
String tolocation=sp[3];//流入省
//System.out.print(sp[0]);
int change=Integer.valueOf(sp[4].substring(0,sp[4].length()-1));//改变人数
line line2;
if(!isExistlocation(tolocation,all)) {//不存在对应该省的记录
line2=new line(tolocation,0,0,0,0);//新建数据条
all[count]=line2;
count++;
}
else {
line2=getLine(tolocation,all);//获得原有的数据条
}
line1.grhz-=change;
line2.grhz+=change;
}
}
统计各地的情况,创建最后会输出的数组结构line,接受原有log文件中的每行的数据,总和统计每个省份的情况。具体解释有上部分流程图显明。
(3)line结构
static class line{//统计之后的病例每条的结构
String location;//地理位置
int grhz;//感染患者人数
int yshz;//疑似患者人数
int recover;//治愈人数
int dead;//死亡人数
line(String plocation,int pgrhz,int pyshz,int precover,int pdead){
location=plocation;
grhz=pgrhz;
yshz=pyshz;
recover=precover;
dead=pdead;
}
line(){
}
/*
* 功能:打印一条统计疫情信息
* 输入参数:无
*返回值:信息字符串
*/
String printline() {
return(location+" 感染患者"+grhz+"人 疑似患者"+yshz+"人 治愈"+recover+"人 死亡"+dead+"人");
}
}
最核心的部分,将每个省份对应的一条信息存入,方便增删改查。
(4)筛选省份后的排序
static line[] sortline1(line[] wannasort,int num) {
String[] location=new String[num];
int i=0;
line[] aa=new line[num];
for(i=0;i<num;i++) {
aa[i]=new line();
}
for(i=0;i<num;i++) {
location[i]=wannasort[i].location;
}
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
Arrays.sort(location, cmp);
i=0;
int j=0;//控制省份拼音顺序索引
while(i<num) {
if(wannasort[i].location.equals(location[j])) {
aa[j]=wannasort[i];
j++;
if(j>=num) {
break;
}
i=-1;//重新开始循环
}
i++;
}
return aa;
}
- 先将所要排序的line数组的省份提出存入String数组。
- 用Collator类的方法按照拼音排序省份。
- 按照排序后省份的顺序循环对比未排序的line数组的location。若匹配到了则重新开始循环对比。
- 设置空的结果line数组存放排序结束后的line结构。
(5)日志读取片段
while(i<=index) {
FileInputStream fs=new FileInputStream(frompath+filename[i]);
InputStreamReader is=new InputStreamReader(fs,"UTF-8");
BufferedReader br=new BufferedReader(is);
String s="";
while((s=br.readLine())!=null){//一行一行读
if(s.charAt(0)=='/'&&s.charAt(1)=='/') {//排除注释掉的内容
continue;
}
else {
String[] sp =s.split(" ");//分隔开的字符串
statistics(sp,all);
}
}
br.close();
i++;
}
按行读取,跳过注释的部分,将每行发送给统计函数。
4.单元测试截图和描述
单元测试我是分方法来测试的,因为学的皮毛,只是测试了几个函数,大多数为检验函数。我代码里核心的统计函数和日志读取方法是没有返回值的,并且参数的设置也具有较大整篇代码关联性,所以并没有测试。很多功能函数的参数和返回值都与line数组相关,相应赋值也过于繁琐,所以并没有测试。
1.检验日志文件路径正确性
根据isCorformpath方法,日志文件路径的正确性取决于格式、存在、为空三种情况。
格式用正则表达式[1]:\\\\(.+?\\\\)*$检验。
2.检验最后一个日志日期
获取最后一个日期来决定读取的范围。
3.输入日期检验
日期检验分为两个方法。一个是检验两个日期的早晚判断,一个是正确性判断。正确性判断基于早晚判断。
4.查找日志位置索引
查找日志位置的索引,用于指定-date的情况,读取时控制循环变量小于索引,循环读取。若输入的日期正确且不存在于log文件夹中,则返回比该日期小的最近的一个日志的索引。
5. 性能优化
1.覆盖率
所有的命令包括-date、-province和-type全部按规则输入后得到覆盖率81.0%(左)
加上所有错误处理的情况即尽可能走遍所有分支得覆盖率95.9%(右)
点开详情发现没有达到利用率百分百的方法如图
我选择了isBefore函数优化,因为我发现这个方法的返回值太过于杂乱,很浪费空间(左)
后续选择更好用的字符串自带函数isCompareto进行优化(右)
得到满意的覆盖率
2.监控
第一次使用jdk自带的jvisualvm工具,但是还不怎么会具体分析。我是在大约22:16左右运行了我的程序。以下四个实时监控图记录了相应参数的变化过程。
1.CPU
2.类
3.线程
4.堆
三、代码规范
我的代码规范:codestyle.md
四、心路历程与收获
1. 心路历程
这个作业实话说比第一次作业难多了,还是我比较菜的原因,第一次看这个“长篇大论”心情很崩溃。刚开始的要求是学习github,之前我略有耳闻,对github的印象就是难。因为是全英文的网站,正常的阅读都较为困难,加上加载页面的时间特别特别长,注册都耗费了蛮久的时间,所以我就等到第二天再开始其他操作,以防止我心态爆炸。
第二天我又把作业需求看了几遍,终于理解了80%,并且大致有了打代码的思路,就是对于命令行的输入产生不同结果。因为对于github中繁琐功能按钮的不熟悉,连建立一个同结构的文件夹都commit了好多次。等到终于整完文件夹后,我舒服多了,终于可以开始我比较熟悉的代码编写了。
我先把主要的函数都写出来,最后再汇总成main函数,经常是函数套函数,所以在后来的单元测试上比较麻烦,因为内部耦合性比较高。最关键的就是形成了line数据结构存放每条数据,这省去了很多麻烦,也比较好调用。代码编写的过程比较平常,就是按部就班,都是有一定思路的,没有遇到不会实现的情况,顶多就是写出来有bug。令我印象比较深刻的就是写完拣选省份的函数并排序后,程序总是会自己无限循环,必须要我自己手动结束调试。这个逻辑错误还不好发现,导致我写代码一时爽,Debug火葬场。这是我所有模块里面找错时间最长的一次,重新编码了一遍也没什么结果,好嘛,世上无难事只要肯放弃,我明天再做。最后是成功了,在找到配对的省份后我又把索引设为0重新循环就可以了。
每天最怕的就是看群,一看大家的问题,再对比我的程序,哦,我少了个路径检验,哦,文件输入路径和输出路径要是命令行自己输入的,然后我就要加加加改改改,害。还好这些工作量不是很大。
单元测试部分蛮难的,我参考了作业中的附录教程,就只做了一个JUnit的简易测试。之前不知道单元测试的时候我一直是用输出函数来实时调整我的代码的,我还觉得这样也挺方便的。因为程序量不是很大,所以单元测试还没有发挥它最大的作用。
刚开始覆盖率仅为80%左右,是因为我写了很多分支并没有用到,所以又花了很长的时间去遍历尽可能多的分支,就输入了很多次可能的命令,最后总和终于达到了96%左右,检查覆盖率低的函数,并修改,效果还不错。
2. 收获
- GitHub账号
- fork别人的仓库
- 建立我自己的分支
- ...
- Git命令并与GitHub绑定
- git add .
- git commit -m ''
- git push
- 单元测试的方法
- JUnit4
- 覆盖率的使用
- jvisualvm监控
五、有关仓库
1.Flutter:https://github.com/flutter/flutter
flutter库有多达534位贡献者。flutter可通过单一代码库为移动,Web和桌面提供精美,快速的用户体验。Flutter可与现有代码一起使用,并为世界各地的开发人员和组织所使用。里面有基本函数可以在开发App时直接使用。
2.Dart初学者编程指南:https://github.com/smartherd/DartTutorial
从零开始学习Dart编程。从安装开始,包括数据类型、控制循环语句、功能方法、异常处理、面向对象编程、函数式编程、飞镖收藏和可调用类。
3. Flutter-go: https://github.com/alibaba/flutter-go
flutter 开发者帮助 APP,包含 flutter 常用 140+ 组件的demo 演示与中文文档。带有用户中心,专属个人的widget案例。可在该App上收藏组件。
4. flutter-examples:https://github.com/nisrulz/flutter-examples
其中包含所有示例应用程序,示例应用程序展示了Flutter应用程序开发中的功能/集成。
5. awesome-flutter:https://github.com/Solido/awesome-flutter/
很棒的清单,精选了最好的Flutter库,工具,教程,文章等。有很多部件内容,例如:影片、导航、模板、手势系统、图片、验证码等。
- A-z ↩︎