软工实践寒假作业(2/2)
这个作业属于哪个课程 | 2020春s班 |
---|---|
这个作业要求在哪里 | 寒假作业(2/2) |
这个作业的目标 | 开发疫情统计程序,并借此熟悉程序开发流程和github的使用 |
作业正文 | 第二次寒假作业 |
其他参考文献 | 无 |
Github仓库链接地址:https://github.com/wjchenkk/InfectStatistic-main
1.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
Estimate | 估计这个任务需要多少时间 | 45 | 45 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 90 | 300 |
Design Spec | 生成设计文档 | 30 | 30 |
Design Review | 设计复审 | 20 | 20 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 60 | 60 |
Coding | 具体编码 | 1200 | 1320 |
Code Review | 代码复审 | 300 | 300 |
Test | 测试(自我测试,修改代码,提交修改) | 240 | 300 |
Reporting | 报告 | ||
Test Repor | 测试报告 | 30 | 30 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 45 | 45 |
total | 合计 | 2120 | 2510 |
2.解题思路描述
-
思考
刚开始拿到题目时脑袋懵懵的,因为听过GitHub却没有用过,看着题目描述有点莫名,所以转而直接看代码编程部分。题目的意思简单可以分解为命令行输入运行程序、读日志文件处理信息、写结果文件三个部分。然后根据输入输出文件的内容描述,对于数据存储的第一想法是简单的将数据用二维数组模拟,一维模拟省份二维模拟类型,所以并非面向对象思想构建省份类。命令行参数保存于字符串数组中,提取参数需要进行较多的合法检测,比如必须唯一、必须存在、必须合法。处理日志信息由于日志文件格式固定,内容整齐,所以没有分开处理每一个类型的信息行,而是利用每行的长度划分数据,将其写在一个日志文件处理方法中。基于文件日期的合法检测,另外编写一个处理文件夹的方法和获取合法日志文件的方法。最后写结果文件便是常规的根据命令行的参数以及日志文件处理完的二维数组信息写入目标文件中。
-
查找
通过题目描述和博客和论坛网站查询,了解数据的意义便于处理日志文件数据。通过题目提供的教学网站和博客学习GitHub应用。java代码基于记忆,所以可能代码比较低级,少数查询使用。
3.设计实现过程
1.设计疫情统计类
StatisticsNumber[][]存储统计数据、changeProvince[]记录变化省份,provinceName[]、typeName[]、enTypeName[]保存按序名称,logPath、outPath、date、parameterType[]、parameterProvince[]保存参数
2.程序运行流程
主要流程
主要函数流程
4.代码说明
1.主要数据
//省份数
final int provinceNumber=34;
//类型数
final int typeNumber=4;
//各省各类型统计数
int StatisticsNumber[][];
//省份参数
int parameterProvince[];
//类型参数
int parameterType[];
//日志日期
Date date;
//输入输出路径
String logPath,outPath;
//省份名字
String provinceName[]= {
"安徽","澳门","北京","重庆","福建","甘肃","广东","广西","贵州","海南","河北",
"河南","黑龙江","湖北","湖南","吉林","江苏","江西","辽宁","内蒙古","宁夏","青海",
"山东","山西","陕西","上海","四川","天津","西藏","香港","新疆","台湾","云南","浙江"
};
//类型中文名
String typeName[]= {"感染患者","疑似患者","治愈","死亡"};
//类型英文名
String enTypeName[]= {"ip","sp","cure","dead"};
//改变的省份
int ChangeProvince[]=new int[34];
//参数存在标记
boolean isExistParameterType=true;
boolean isExistParameterProvince=true;
//命令行
String args[];
//默认构造函数
public infectStatistic() {
// TODO Auto-generated constructor stub
}
//构造函数
infectStatistic(String args[]){
this.args=args;
StatisticsNumber=new int[provinceNumber][typeNumber];
for(int i=0;i<provinceNumber;i++) {
ChangeProvince[i]=-1;
Arrays.fill(StatisticsNumber[i], 0);
}
parameterProvince=new int[35];
parameterType=new int[4];
Arrays.fill(parameterProvince, -1);
Arrays.fill(parameterType, -1);
}
数据含义基于大致注释
2.日志文件处理
//处理日志文件
public void dealFile(File file) {
try {
if(!file.exists()&&file.isDirectory()) {
throw new FileNotFoundException();
}
InputStreamReader inputReader=new InputStreamReader(new FileInputStream(file),"UTF-8");
BufferedReader br=new BufferedReader(inputReader);
String temp;
while((temp=br.readLine())!=null) {
//跳过//
if(temp.indexOf("//")!=0) {
String temps[]=temp.split(" ");
String number=temps[temps.length-1].substring(0, temps[temps.length-1].length()-1);
int changeNumber=Integer.parseInt(number);
int firstProvinceIndex=getProvinceIndex(temps[0]);
if(temps.length==4) {
if(temps[1].equals("新增")) {
if(temps[2].equals(typeName[0])) {
StatisticsNumber[firstProvinceIndex][0]+=changeNumber;
}
else {
StatisticsNumber[firstProvinceIndex][1]+=changeNumber;
}
}
else {
StatisticsNumber[firstProvinceIndex][1]-=changeNumber;
if(temps[1].equals("疑似患者")) {
StatisticsNumber[firstProvinceIndex][0]+=changeNumber;
}
}
}
else if(temps.length==3) {
if(temps[1].equals("死亡")) {
StatisticsNumber[firstProvinceIndex][3]+=changeNumber;
}
else {
StatisticsNumber[firstProvinceIndex][2]+=changeNumber;
}
StatisticsNumber[firstProvinceIndex][0]-=changeNumber;
}
else {
int secondProvinceIndex=getProvinceIndex(temps[3]);
if(temps[1].equals(typeName[0])) {
StatisticsNumber[firstProvinceIndex][0]-=changeNumber;
StatisticsNumber[secondProvinceIndex][0]+=changeNumber;
}
else {
StatisticsNumber[firstProvinceIndex][1]-=changeNumber;
StatisticsNumber[secondProvinceIndex][1]+=changeNumber;
}
}
ChangeProvince[getProvinceIndex(temps[0])]=1;
}
}
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
处理日志文件,因为最开始没读透题目,所以没有建立省份类进行保存信息,所以也就没有分开编写处理方法了,而是简单的集合,利用文件格式可分为3、4、5三种字段情况,根据第一个字段为省份最后一个字段为人数,提取首尾字段信息,然后区分中间字段的区分点,比如“新增”、“死亡”、“流入”等,可以提取type数据直接没有语义的加减保存于二维数组中。
3.生成输出文件
//生成输出文件
public void outResult() throws IOException {
File file=new File(outPath);
if(!file.exists()) {
file.createNewFile();
}
OutputStreamWriter outputWriter=new OutputStreamWriter(new FileOutputStream(file),"UTF-8");
int[] outTypeIndex=parameterType;
int[] outProvinceIndex;
if(isExistProvince) {
outProvinceIndex=parameterProvince;
}
else {
outProvinceIndex=ChangeProvince;
}
BufferedWriter bw=new BufferedWriter(outputWriter);
String allString="";
//输出第一行全国
if(parameterProvince[34]==1) {
allString="全国 ";
for(int i=0;i<typeNumber;i++) {
if(outTypeIndex[i]!=-1) {
allString+=typeName[outTypeIndex[i]];
int count=0;
for(int j=0;j<provinceNumber;j++) {
count+=StatisticsNumber[j][outTypeIndex[i]];
}
if(i==typeNumber-1||outTypeIndex[i+1]==-1) {
allString+="人";
}
else {
allString+="人 ";
}
}
}
allString+="\n";
}
//判断参数省份
for(int i=0;i<provinceNumber;i++) {
if(outProvinceIndex[i]==-1) {
continue;
}
else if(outProvinceIndex[i]==1) {
allString+=(provinceName[i]+" ");
for(int j=0;j<typeNumber;j++) {
if(outTypeIndex[j]!=-1) {
allString+=typeName[outTypeIndex[j]];
allString+=StatisticsNumber[i][outTypeIndex[j]];
if(j==typeNumber-1||outTypeIndex[j+1]==-1) {
allString+="人";
}
else {
allString+="人 ";
}
}
}
allString+="\n";
}
}
allString+="// 该文档并非真实数据,仅供测试使用\n//命令:";
for(int i=0;i<args.length;i++) {
allString+=args[i];
if(i!=args.length-1) {
allString+=" ";
}
}
bw.write(allString);
bw.close();
}
生成输出文件,输出文件关键在于输出的格式,通过检查时获取两个格式数组,而后根据两个格式数组,循环写入文件。
4.处理命令行参数并检测其合法性唯一性必要性
// 处理命令行参数并检测其合法性唯一性必要性
public boolean dealCommand() throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
int parameterExist[] = { 0, 0, 0, 0, 0 };
for (int i = 1; i < args.length; i++) {
if (args[i].equals("-date")) {
if (i != args.length - 1 && isValidDate(args[++i])) {
date = dateFormat.parse(args[i]);
parameterExist[0]++;
}
else {
return false;
}
}
else if (args[i].equals("-log")) {
++i;
if (i != args.length && args[i].matches("^[A-z]:\\\\(.+?\\\\)*$")
|| (args[i] + "\\").matches("^[A-z]:\\\\(.+?\\\\)*$")) {
logPath = args[i];
parameterExist[1]++;
}
else {
return false;
}
}
else if (args[i].equals("-out")) {
++i;
String temp = args[i].substring(0, args[i].lastIndexOf("\\") + 1);
if (i != args.length && temp.matches("^[A-z]:\\\\(.+?\\\\)*$")) {
outPath = args[i];
parameterExist[2]++;
}
else {
return false;
}
}
else if (args[i].equals("-province")) {
while (i + 1 < args.length) {
if (getProvinceIndex(args[++i]) != -1) {
if (parameterProvince[getProvinceIndex(args[i])] == -1) {
parameterProvince[getProvinceIndex(args[i])] = 1;
}
else {
return false;
}
}
else {
--i;
break;
}
}
parameterExist[3]++;
} else if (args[i].equals("-type")) {
int j = 0;
int type[] = { 0, 0, 0, 0 };
while (i + 1 < args.length) {
// 检测参数值正确与否,错误i--回退检测是否其他参数
if (getTypeIndex(args[++i]) != -1) {
// 判断参数值是否重复
if (type[getTypeIndex(args[i])] == 0) {
parameterType[j] = getTypeIndex(args[i]);
type[getTypeIndex(args[i])] = 1;
j++;
}
else {
return false;
}
}
else {
--i;
break;
}
}
parameterExist[4]++;
}
else {
return false;
}
}
// 检测参数必要唯一
if (parameterExist[1] == 1 && parameterExist[2] == 1 && parameterExist[0] == 1 && parameterExist[3] < 2
&& parameterExist[4] < 2) {
// 无type、province按默认输出
if (parameterExist[3] == 0) {
Arrays.fill(parameterProvince, 1);
isExistParameterProvince = false;
}
if(parameterExist[4]==0) {
isExistParameterType = false;
for (int i=0;i<typeNumber;i++) {
parameterType[i]=i;
}
}
return true;
}
return false;
}
检测并设置参数,检测“参数值”唯一必要性,空或重复皆为非法命令,参数存在次数记录于parameterExist中,用于检测“参数”唯一必要性,date、log、out为必要参数,所有参数为唯一参数不得重复,检测参数值的合法性,例如日期(闰年和31日)、type(ip,sp,cure,dead)、province(34省级)以及文件路径。
5.获取合法日志文件
//获取文件名对应时间
public Date getFileNameDate(File file) throws ParseException {
String date=file.getName();
int index=date.indexOf(".");
date=date.substring(0, index);
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
return dateFormat.parse(date);
}
//检测基于日期参数日志文件是否合法
public boolean isExistValidDateFile(File folder,Date parameterDate) throws ParseException {
File files[]=folder.listFiles();
for(File file : files) {
int result=getFileNameDate(file).compareTo(parameterDate);
//判断存在大于日期参数的文件
if(result>0||result==0) {
return true;
}
}
return false;
}
//通过日期参数获取合法日志文件
public File[] getFilesByDate(Date parameterDate,File folder) throws ParseException {
ArrayList<File> fileList=new ArrayList<File>();
if(isExistValidDateFile(folder, parameterDate)) {
File files[]=folder.listFiles();
for(File file : files) {
int result=getFileNameDate(file).compareTo(parameterDate);
//判断日期,处理小于等于命令行日期的日志文件
if(result<0||result==0) {
fileList.add(file);
}
}
}
else{
System.out.println("基于日期参数,日志文件不合法");
}
return (File[]) fileList.toArray(new File[fileList.size()]);
}
通过获取文件名对应时间比较是否存在比参数大的日志文件,从而判断得到合法的可以处理的日志文件。
5.单元测试描述
1.单元测试结果
2.测试获取文件名中的时间
)
3.获取日志文件
4.检测基于日期参数的日志文件合法
5.获取type/province按默认顺序的下标
6.检测日期合法性
7.检测命令的合法性
通过十几组命令测试命令行检测函数,主要测试命令行参数的合法、必要、唯一等准确性质。
8.测试main函数(命令行检测、处理、输出)
测试成功的四条命令的输出结果展示。
测试主函数包括测试了十几组不同情况的命令,得到了命令行、文件处理、输出的不同结果。
6.单元测试覆盖率优化和性能测试,性能优化描述
提取变量到作用域外,减少迭代次数造成内存申请和释放。
for(){
getProvinceIndex[temps[0]];
}
//换成
int index= getProvinceIndex[temps[0]];
for(){
index...;
}
减少表达式的迭代使用,减少函数的迭代使用,在循环外使用表达式或函数。
由于不太懂优化性能和覆盖率,就只能这样子试试看。
7.代码规范
https://github.com/wjchenkk/InfectStatistic-main/blob/master/041702303/codestyle.md
8.心路历程与收获
心路历程:刚开始进行这题目编写时,思考较少,简单除暴的运用了已经习惯的面向过程的思想,没有考虑到编程和实际的应用需求的适合度,直接进行一个疫情统计类的编写令我头疼不堪。同时由于时间选择错误,编写程序已经完成大部分时才真正理解需求,所以修改程序变得十分困难,外加平时编写程序的做法十分随便不严谨,所以检测合法性总是未考虑全面,所以完成代码十分困难,不断循环问题-修改-测试这个过程中甚至想要随便完成,在看到别人写的代码,简短整齐规范易懂更加心情压抑。
收获:在看了构建之法后及进行实践编程后,收获还是蛮大的,又重新的编写Java代码,为这学期课程提前一步复习(预习)。。。同时又学习了git的基本使用,为以后的项目开发打下薄弱的基础。。。更学习了简单的单元测试,懂得了一些基础的白盒测试(编写测试代码也好累)。。。最大的收获是,懂得开发一个项目(哪怕简单)都要进行分析和设计,不能不经过思考下意识的编写代码,要注重需求和选择的编程思想的契合度,懂得开发项目要一次就真正理解需求在进行,而非逐步理解开发,这样会造成代码修改度很大。。。
9.技术路线图相关的5个仓库
1.Linux命令行大全
https://github.com/wjchenkk/linux-command
550 多个 Linux 命令,内容包含 Linux 命令手册、详解、学习,值得收藏的 Linux 命令速查手册,此外还增加一些作者的补充数据。对于初学者可以快速查询Linux命令,解决各方面的Linux基础使用,同时可以帮助学习简单的shell学习。
2.Linux-shell脚本学习
https://github.com/wjchenkk/shell-book
Shell是一种脚本语言,初学者如果想学习shell编程,这个仓库可以帮助我们进行入门到深入的基本学习,根据上面的shell-book目录,只要一步一步尝试book上面的代码样例,我们也许真的可以轻松又快乐的使用shell。
3.Linux-内核揭秘
https://github.com/wjchenkk/linux-insides-zh
一系列关于 Linux 内核和其内在机理的帖子。分享对 Linux 内核内在机理的一点知识,帮助对 Linux 内核内在机理感兴趣的人,和其他低级话题。学习Linux系统内核是必不可少的,了解掌握内核机制有助于理解Linux系统。该仓库更新来自国外丰富的内核翻译内容。
4.Linux-服务器基础
https://github.com/wjchenkk/linux_course
只有了解linux的启动流程才能更好的去优化和配置Linux,了解基础的服务器设置,体验简单控制服务器,简单懂得为什么要保证软件运行,懂得怎么启动必须运行的软件,都是学习服务器基础的必要过程,在这里你可以尝试做这一切。
5.Linux-运维工程师
https://github.com/wjchenkk/LinuxSRE
流程化、标准化的工作越来越依赖于信息系统,是各企业发展的必然趋势,信息系统开发和运维也会创造越来越多的工作岗位,机器化、自动化、智能化是人类科技发展趋势,也越来越要求人们对信息系统有更深入的管控能力,Linux系统已经几乎无处不在。了解运维工程师对学习Linux,选择学习方向有很大的帮助。