这个作业属于哪个课程 | 软件工程实践2022年春-F班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业 |
这个作业的目标 | 使用命令行 从网络上获取冬奥会奖牌榜和赛程的数据 并输出 |
其他参考文献 | 优快云、博客园 |
文章目录
1、GitCode项目地址
m0_46536118 / PersonalProject-Java
2、PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 60 | 80 |
• Design Spec | • 生成设计文档 | 30 | 50 |
• Design Review | • 设计复审 | 20 | 30 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 20 |
• Design | • 具体设计 | 30 | 40 |
• Coding | • 具体编码 | 300 | 400 |
• Code Review | • 代码复审 | 30 | 120 |
• Test | • 测试(自我测试,修改代码,提交修改) | 300 | 500 |
Reporting | 报告 | ||
• Test Repor | • 测试报告 | 90 | 120 |
• Size Measurement | • 计算工作量 | 30 | 30 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 30 | 60 |
合计 | 960 | 1480 |
3、解题思路描述
程序总体流程
数据获取
根据提供的冬奥会网站,审查元素找到对应的奖牌和赛程json文件
冬奥专栏
之后使用java自带的HTTP(java.net.HTTPURLConnection)构造并发送GET请求获取json字符串。
json解析
使用HTTP获取数据,处理字符串,创建对应类后使用gson解析库来解析
4、接口设计和实现过程
项目的类分为三种 工具类 实体类 业务类。
工具类有文件类用于输入输出文件、HTTP类用于获取需要的冬奥会数据。
实体类有奖牌类和赛程类,用于json解析。
业务类有getData和outPutData用于控制业务逻辑。
最终的项目结构如图
项目的流程为:获取数据、解析数据、输出数据
获取数据首先检查本地是否有数据文件,无则使用HTTP GET请求获取。
解析数据使用gson解析,解析部分抽象了totalJsonToString、scheduleJsonToString两个接口,获取json字符串的类需要继承对应的接口后实现处理json转为字符串的函数。
输出数据部分根据输入文件获取需要输出的编号,最后一次性输出。
5、关键代码展示
OlympicSearch.java
public class OlympicSearch {
public static void main(String[] args) {
if(args.length!=2) {
System.out.println("运行格式异常,正确格式需要加上输入文件和输出文件");
return;
}
ArrayList<String> requestType =new ArrayList<>();
//按行读取input文件内容
try {
FileInputStream inputStream = new FileInputStream(args[0]);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String str;
while((str = bufferedReader.readLine()) != null){
requestType.add(str);
}
//close
inputStream.close();
bufferedReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("未找到该文件");
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
OutputData out=new OutputData(requestType);
out.outPutToFile(args[1]);
}
}
getSchedule
public void getSchedule(String date)
{
int dateInt=Integer.parseInt(date);
Gson gson = new Gson();
String jsonString = file.readFile("data/schedule/"+date+".json");
try{
scheduleFile sf = gson.fromJson(jsonString, scheduleFile.class);
fileString[dateInt-201]=sf.data.getSchedule();
}catch (Exception e){
//解析文件夹中的json文件失败后需要重新爬取json文件
if(!getData.getFile(date)){
System.out.println("json文件损坏,并且爬取数据失败");
}
else {
System.out.println("json文件损坏,已经重新爬取文件");
jsonString = file.readFile("data/schedule/"+date+".json");
scheduleFile sf = gson.fromJson(jsonString, scheduleFile.class);
fileString[dateInt-201]=sf.data.getSchedule();
}
}
}
getTotal
public void getOutput()
{
Gson gson = new Gson();
String jsonString = file.readFile("data/total.json");
try{
totalFile tf = gson.fromJson(jsonString, totalFile.class);
fileString[0]=tf.data.getListByRank();
}catch (Exception e){
//解析文件夹中的json文件失败后需要重新爬取json文件
if(!getData.getFile("total")){
System.out.println("json文件损坏,并且爬取数据失败");
}
else {
System.out.println("json文件损坏,已经重新爬取文件");
jsonString = file.readFile("data/total.json");
totalFile tf = gson.fromJson(jsonString, totalFile.class);
fileString[0]=tf.data.getListByRank();
}
}
}
6、性能改进
第一个版本的流程为每读入一行 根据该行的数据解析json字符串后输出到对应文件。
第一次改进: 每读入一行解析一次json字符串花销太大 所以让程序保存需要输出的数据
下次需要输出相同的数据可以使用已保存的而不用再次去解析json。
第二次改进:每读入一行输出一次到文件中反复打开文件的开销太大 所以让程序使用一个ArrayList保存需要输出的代码标号 最后根据ArrayList拼接所有需要输出的字符串后输出。
最终两万行输入耗时平均耗时900ms
内存平均占用237mb
7、单元测试
覆盖率测试
data文件夹存在对应文件不需要HTTP获取
data文件夹不存在对应文件需要HTTP获取
代码测试
总体测试
in.txt为输入文件 out.txt为输出文件 expected.txt为预期文件
测试代码使用输入文件输出到输出文件 之后读取输出文件和预期文件的内容进行对比
@Test
public void outPutToFile(){
ArrayList<String> requestType =new ArrayList<>();
//按行读取input文件内容
try {
FileInputStream inputStream = new FileInputStream("in.txt");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String str;
while((str = bufferedReader.readLine()) != null){
requestType.add(str);
}
//close
inputStream.close();
bufferedReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println("未找到该文件");
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
OutputData out=new OutputData(requestType);
out.outPutToFile("out.txt");
String outFile=readFile("out.txt");
String expected=readFile("expected.txt");
Assert.assertEquals(expected,outFile);
}
单元测试
@Test
public void getOutput() {
ArrayList<String> requestType =new ArrayList<>();
OutputData ou=new OutputData(requestType);
ou.getOutput();
String expected=readFile("1.txt");
Assert.assertEquals(expected,ou.fileString[0]);
}
@Test
public void getSchedule() {
ArrayList<String> requestType =new ArrayList<>();
OutputData ou=new OutputData(requestType);
ou.getSchedule("0215");
String expected=readFile("1.txt");
Assert.assertEquals(expected,ou.fileString[14]);
}
8、异常处理
命令行读取时参数个数异常 会提醒输入参数错误 并结束程序
数据文件不存在时会自动使用HTTP获取数据
数据文件损坏或者被修改 会使用HTTP重新获取数据
9、心得体会
开始编码前最重要的是先弄清楚需求。
之后要先设计程序的大体结构 再设计接口 使程序的代码可迁移。
最后开始具体的函数编码。
测试是繁琐的,但是十分重要。