| 这个作业属于哪个课程 | 2020春|S班(福州大学) |
|---|---|
| 这个作业要求在哪里 | 软工实践寒假作业(2/2) |
| 作业的目标 | 学习使用Git、学习和了解效能分析及个人软件开发流程(PSP) |
| 作业正文 | |
| 其他参考文献 |
《构建之法》
阅读心得
...........
个人软件开发流程(PSP)
| PSP2.1 | Personal Software Process Stages | 预估耗时(min) | 实际耗时(min) |
|---|---|---|---|
| Planning | 计划 | ||
| Estimate | 估计这个任务需要多少时间 | ||
| Development | 开发 | ||
| Analysis | 需求分析 (包括学习新技术) | 90 | 90 |
| Design Spec | 生成设计文档 | ||
| Design Review | 设计复审 | ||
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
| Design | 具体设计 | ||
| Coding | 具体编码 | 300 | 510 |
| Code Review | 代码复审 | ||
| Test | 测试(自我测试,修改代码,提交修改) | 180 | 150 |
| Reporting | 报告 | 60 | |
| Test Repor | 测试报告 | ||
| Size Measurement | 计算工作量 | ||
| Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | ||
| 合计 |
解题过程
坎坷的学习经历
当我看到作业的时候第一反应是啥也不会,虽然好像有挺长时间的,但是因为看不懂题目要做什么觉得很慌,但是觉得老师既然这么布置肯定认为我们绝大多数人在这段时间内可以完成新技术的学习和作业的完成,所以我便开始完成作业的规划。首先我看了一下我总共有哪些技术不会,Git、Github、单元测试、性能分析、PSP表格、参数处理(一开始以为也需要什么特殊的技术)。呃(⊙o⊙)… ,当时看完了确实觉得很绝望,我首先决定先学习Git(因为我当时觉得单元测试和性能分析是代码写完以后的事情,读《构建之法》的话看起来也不是很着急),所以我上网找到了廖雪峰的Git教程学习Git,在cmd了解了Git的基本语法,大致明白了工作原理,然后当我再去使用GitHub Desktop的时候就发现GitHub Desktop挺容易用的。在学习完Git后我就去大致的阅读了一下《构建之法》前三章,这时候我了解了PSP等知识,尤其是什么是单元测试有了初步的认识,这时我意识到测试数据应该在设计的时候就一起准备好了,而不是等到代码全部都写完了才去测试,因此在粗略的阅读《构建之法》后我改变了最初的想法,决定先学习如何使用IDEA进行单元测试和性能优化等知识。但是事情并不总是那么的顺利,我学习单元测试的路途中遇到了各种各样的问题,时间耽误了很多,等我终于大致明白要如何单元测试的时候我发现自己的时间已经不多了(大致还剩4-5天),因此我这时我也没有完全按照书中说的步骤,而是直接进行了本次作业的程序编写,程序编写完成后完成了单元测试部分。
解题思路

面向对象设计
针对这三个步骤我认为需要三个类:处理参数类(ProcessParameter类)、读入日志并处理数据的统计类(InfectStatistic类)和保存单省数据的省份类(ProvanceInfo类)
ProcessParameter类:
使用ArrayList
InfectStatistic类:
该类是程序的核心,使用一个String[] provinceList按拼音顺序保存了所有的省份(这里是为了省事没有再写一个排序方法,如果以后有需要另一种排序方式,可以用一个成员方法对provinceList进行重新排序);使用HashMap<String, ProvanceInfo> provinceMap 保存所有的省份,key是省名,value保存的是省的具体感染情况;List
ProvinceInfo类
使用String name、int infectNum、suspectedNum、diedNum、cureNum、boolean doesRefered保存省份的必要信息,这里的doesRefered用于没有传入-province参数的情况,判断本省是否在日志中被提到

核心代码说明
参数处理函数
本函数的形参是字符数组,用于传入main函数的参数数组,在函数内部将其转换为list变量(方便操作),对于只有单个值的参数(如-date、-log等)只需要获得改参数在list中的位置index,将下标为index+1的成员即该参数对应的值,将其保存到对应的成员变量。对于有多个值的参数(如-province、-type等)则通过findLastIndex方法获取多个值中最后一个值的索引lastIndex,并将list中index+1到lastIndex的数据保存。
public void processParameters(String []args){
List list = Arrays.asList(args);
int dateIndex = list.indexOf("-date");//截止日期.
String dateString = "";
if (dateIndex > 0) {//如果有传入-date参数
dateString = args[dateIndex + 1];
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
date = null;
if (dateIndex >= 0) {
try {
date = simpleDateFormat.parse(dateString);
} catch (ParseException e) {
System.out.println("时间参数非法");
System.exit(-1);
}
}
int dirIndex = list.indexOf("-log");//日志文件目录
if (dirIndex < 0) {
System.out.println("错误:没有传入日志文件路径");
System.exit(-1);
}
String dirPath = args[dirIndex + 1];//日志文件夹路径
logDir = new File(dirPath);//日志文件夹
int outputIndex = list.indexOf("-out");//输出文件目录
if (outputIndex < 0) {
System.out.println("错误:没有传入输出文件路径");
return;
}
outputPath = args[outputIndex + 1];
int provinceListIndex = list.indexOf("-province");//传入参数中-province的下标索引
int provinceListLastIndex = findLastIndex(args, provinceListIndex);//查询的最后一个省份的下标索引
provinceList = new ArrayList<String>();//省份列表
if (provinceListIndex >= 0) {//有传入-province参数
for (int i = provinceListIndex + 1; i <= provinceListLastIndex; i++) {
provinceList.add(args[i]);
}
}
typeMap = new HashMap<>();//查询的类别ip、sp、cure、dead
int typeIndex = list.indexOf("-type");
int typeLastIndex = findLastIndex(args, typeIndex);
int t = typeIndex > 0 ? 0 : 1;//是否有传入-type参数 t=0:有 t=1:没有
typeMap.put("ip", t);
typeMap.put("sp", t);
typeMap.put("cure", t);
typeMap.put("dead", t);
if (typeIndex >= 0) {//有传入-type参数
for (int i = typeIndex + 1; i <= typeLastIndex; i++) {
typeMap.put(args[i], 1);
}
}
}
统计函数
统计函数传入参数dir为日志文件夹,date为查询的截止日期,首先将dir目录下所有的文件名(.log前的日期)依次加入fileList列表,在对列表进行升序排序,获取最早日期和最晚日期校验传入的截止日期是否合法。然后通过二重循环,依次读取每个文件的每一行(非注释行),通过调用函数updateProvinceInfo函数对数据进行更新保存。
public int statistic(File dir, Date date){//统计
try {
BufferedReader reader = null;
String line;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (File f : dir.listFiles()) {//将目录下所有的文件名加入fileList列表用于排序
fileList.add(f.getName().split("\\.")[0]);
}
Collections.sort(fileList);
String lastDateString = fileList.get(fileList.size() - 1);//最大日期
Date lastDate = simpleDateFormat.parse(lastDateString);
if (date != null && date.compareTo(lastDate) > 0) {
System.out.println("抱歉,日期超出范围");
return -1;
}
for (String fileDateString : fileList) {
File file = new File(dir.getPath() + "/" + fileDateString + ".log.txt");
Date fileDate = simpleDateFormat.parse(fileDateString);//日志日期
if (date != null && fileDate.compareTo(date) > 0) {
break;
}
reader = new BufferedReader(new FileReader(file));
while ((line = reader.readLine()) != null) {
if (line.charAt(0) != '/') {//该行不为注释
updateProvinceInfo(line);
}
}
}
}catch (Exception e){
}
return 0;
}
更新处理函数
该函数用于在读取数据后更新各省份的感染数据,思路是首先将该行的字符串以空格符(‘ ’)分割成数组,通过数组长度判断该行信息是对应哪种情况(3:死亡/治愈 4:新增/确诊/排除 5:从A省流入B省),再细分语句后通过省名利用provinceMap对对应的省份更新感染数据。
public void updateProvinceInfo(String line){
String message[] = line.split(" ");
ProvanceInfo province = provinceMap.get(message[0]);
String lastMessage = message[message.length-1];
int num = Integer.parseInt(lastMessage.substring(0,lastMessage.length()-1));//更新人数
switch (message.length){
case 3://死亡、治愈
province.setDoesRefered(true);
if(message[1].equals("死亡")){
province.diedNumAdd(num);
province.infectNumSub(num);
diedTotalNum += num;
infectTotalNum -= num;
}else{//治愈
province.cureNumAdd(num);
province.infectNumSub(num);
cureTotalNum += num;
infectTotalNum -= num;
}
break;
case 4://新增、确诊、排除
province.setDoesRefered(true);
if(message[1].equals("新增")){
if(message[2].equals("感染患者")){
province.infectNumAdd(num);
infectTotalNum += num;
}else{//疑似患者
province.suspectedAdd(num);
suspectedTotalNum += num;
}
}else if(message[1].equals("排除")){//排除疑似患者
province.suspectedSub(num);
suspectedTotalNum -= num;
}else{//确诊感染
province.suspectedSub(num);
province.infectNumAdd(num);
suspectedTotalNum -= num;
infectTotalNum += num;
}
break;
case 5://从A省流入B省
ProvanceInfo provinceB = provinceMap.get(message[3]);
province.setDoesRefered(true);
provinceB.setDoesRefered(true);
if(message[1].equals("感染患者")){
province.infectNumSub(num);
provinceB.infectNumAdd(num);
}else{//疑似患者
province.suspectedSub(num);
provinceB.suspectedAdd(num);
}
break;
}
}
单元测试
参数处理检验(1):只包含-date、-log、-out
该测试是调用processParameter对象的processParameters方法来处理传入的参数,在本次测试传入的参数只包含上述的三种参数,因此在processParameter对象的成员变量中date(截止日期)、outputPath(输出文件路径)、logDir(日志文件夹)的文件路径应与传入的参数相同,provinceList(查询省份列表)的个数为空,typeMap中的所有value(是否应该输出)都应为1(要输出)。


参数处理检验(2):包含-date、-log、-out、-province、-type参数(全部参数)
该测试传入的参数数组包含了所有种类的参数(-province 全国 福建 -type ip),所以在processParameter对象的成员变量中ate(截止日期)、outputPath(输出文件路径)、logDir(日志文件夹)的文件路径应与传入的参数相同,provinceList(查询省份列表)的长度为2,且包含"全国"和”福建“。typeMap中”ip“键对应的值为1(输出),其他均为0(不输出)。


参数处理检验(3):只包含-log、-out
该情况与检验(1)处理情况相似,唯一的区别是没有传入-date参数,因此在该测试下data的值应为null,且文件的输出应将所有日志文件统计后输出,省份因为没有指定应为所有日志中提到的省份


获取多值参数最后一个值的下标索引函数的检验
本测试主要是针对该参数是否是最后一种参数两种情况进行测试,如下图所示。其中第一个测试应返回数最后一个元素的下标(4),第二个测试应返回北京的下标(5)


日志文件夹处理测试
本测试主要针对InfectStatistic类的日志文件夹处理功能processLogDir方法,声明一个InfectStatistic对象,统计指定文件夹的日志文件,并检验日志文件个数,最大日期,最小日期

期限判定处理测试
本测试主要针对InfectStatistic类的日期范围判定功能processDate方法,程序是否能检验,返回值为0为没超出范围,-1为超出范围

更新省份传染信息函数测试
该测试主要是要检验当从文件读入不同的描述事件时(如新增、死亡等),函数是否能准确地更新相关省份的数据信息


来源:https://www.cnblogs.com/wshbolgs/p/12340070.html