586
社区成员




这个作业属于哪个课程 | <软件工程-23年春季学期> |
---|---|
这个作业要求在哪里 | <软件工程实践第二次作业---文件读取> |
这个作业的目标 | <完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序> |
其他参考文献 | 《构建之法》 |
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 1190 | 1020 |
• Analysis | • 需求分析(包括学习新技术) | 360 | 300 |
• Design Spec | • 生成设计文档 | 60 | 90 |
• Design Review | • 设计复审 | 30 | 40 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
• Design | • 具体设计 | 120 | 60 |
• Coding | • 具体编码 | 360 | 300 |
• Code Review | • 代码复审 | 120 | 90 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 210 | 135 |
• Test Repor | • 测试报告 | 90 | 60 |
• Size Measurement | • 计算工作量 | 60 | 45 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 60 | 30 |
合计 | 1420 | 1170 |
进入网站相关页面,按F12开启浏览器开发者模式(这里使用的是火狐浏览器),选择“网络”项,分别搜索players和results,找到json文件并下载到本地。
使用阿里的fastjson解析,参考CSDN:fastjson的基本使用方法
根据从网上获取的json文件结构设计获取目标数据的步骤
对于输出选手信息,从players.json里直接以相关键值获取
对于输出比赛结果,时间直接从actual_start_time键值获取,比分则需要解析team键值的json,其中的score键值json的game键值中保存了比分数据
获取胜者的步骤则更繁琐,先获得胜利队伍的id,在teams数组中找到相应队伍,
获取队伍json中保存的选手id,再去players数组中找到相应选手
在工具类Lib中实现相应功能,Lib对外提供两个静态函数write_Players()和write_Results(String date)分别实现写入选手信息和比赛结果两个功能。
首先是读取json文件:
private static String get_JsonStr(String fileName) throws IOException{
File jsonFile = new File(fileName);
if (!jsonFile.exists())//检查json文件是否存在
throw new FileNotFoundException("未找到" + fileName);
FileInputStream is = new FileInputStream(jsonFile);
int iAvail = is.available();
byte[] bytes = new byte[iAvail];
is.read(bytes);
String allMsg = new String(bytes);//从文件中获取json字符串
is.close();
return allMsg;
}
write_Players()代码:
public static void write_Players() throws Exception {
JSONObject js = JSONObject.parseObject(get_JsonStr(FILE_PLAYERS));
JSONArray players = JSONArray.parseArray(js.getString("players"));//获取运动员json数组
StringBuilder playersMsg = new StringBuilder();//遍历运动员数组,把相关信息加入文件输出
for (int i = 0;i < players.size();i++){
JSONObject player = players.getJSONObject(i);
playersMsg.append("full_name:" + player.getString("full_name") + "\n");
playersMsg.append("gender:" +
(player.getString("gender").equals("M")?"male":"female") + "\n");
playersMsg.append("nationality:" +
player.getJSONObject("nationality").getString("name") + "\n");
playersMsg.append("-----\n");
}
write_Msg(playersMsg.toString());
}
对于比赛结果,需要多次解析和查询json数组,为此将根据id查找队伍和查找选手独立成函数:
private static JSONObject find_Team(String teamUuid,JSONArray teams){
for (int i = 0;i < teams.size();i++)
if (teams.getJSONObject(i).getString("uuid").equals(teamUuid))
return teams.getJSONObject(i);
return null;
}
private static JSONArray find_Winners(String playerUuids,JSONArray players){
JSONArray winners = new JSONArray();
JSONArray uuids = JSONArray.parseArray(playerUuids);//需要查找的选手uuid
for (int i = 0;i < uuids.size();i++){
String uuid = uuids.getString(i);
for (int j = 0;j < players.size();j++)
if (players.getJSONObject(j).getString("uuid").equals(uuid))
winners.add(players.getJSONObject(j));
}
return winners;
}
在write_Results(String date)中调用这些查询函数来获得每场比赛的详细数据,包括双方队伍成员、比分,和胜利队伍。
JSONObject match = matches.getJSONObject(i);//当天的每场比赛
JSONArray teamAB = JSONArray.parseArray(match.getString("teams"));
JSONObject teamA = teamAB.getJSONObject(0);
JSONObject teamB = teamAB.getJSONObject(1);//比赛双方队伍
JSONObject winnerTeam = teamA.getString("status") == null ? teamB:teamA;//获胜队伍
//通过team_id从teams中查找详细的队伍信息json(用于获取选手id)
String winnerUuids = find_Team(winnerTeam.getString("team_id"),teams).getString("players");
JSONArray winners = find_Winners(winnerUuids,players);//获得获胜选手json数组
对每条指令都执行了完整的数据获取流程,其中读取文件花费了较多时间,于是使用缓存机制,每次输出的结果先保存在程序中,在访问文件之前判断该指令是否是重复指令,为重复指令省去了读取文件的时间。
public static void write_Players() throws Exception {
if(cache.containsKey("players")){
write_Msg(cache.get("players"));
return;
}
...
cache.put("players",playersMsg.toString());
write_Msg(playersMsg.toString());
}
public static void write_Results(String date){
if(cache.containsKey(date)){
write_Msg(cache.get(date));
return;
}
...
cache.put(date,resultsMsg.toString());
write_Msg(resultsMsg.toString());
}
生成一个万级输入样例:
private static void writeTest(){
Lib.set_OutputFile("input.txt");
for (int i = 0;i < 10000;i++){
Lib.write_Msg("players\nresult 0116\nresult Q1\n");
}
}
不使用缓存:304,798ms
使用缓存:18,791ms
可以看出减少了文件读取后,运行时间大幅提升。
使用了Junit5.8.1进行测试,对main函数在类中设置标志值passed表示命令行指令是否合法。
AOSearchTest.java:
class AOSearchTest {
@Test
void main() {
AOSearch.main(new String[]{"input.txt","output.txt"});
assertTrue(AOSearch.passed);
AOSearch.main(new String[]{"in.txt","output.txt"});
assertFalse(AOSearch.passed);
AOSearch.main(new String[]{"input.txt"});
assertFalse(AOSearch.passed);
AOSearch.main(new String[]{"input.txt","output.txt","x"});
assertFalse(AOSearch.passed);
}
}
覆盖率:
对Lib类的两个基本输出功能进行测试,主要是对是否抛出异常做检查,当找不到对应文件时抛出异常。
LibTest.java
class LibTest {
@Test
void write_Players() {
assertDoesNotThrow(()->Lib.set_OutputFile("output.txt"));
assertDoesNotThrow(()->Lib.write_Players());
}
@Test
void write_Results() {
assertDoesNotThrow(()->Lib.set_OutputFile("output.txt"));
assertThrows(FileNotFoundException.class,()->Lib.write_Results("X"));
assertThrows(FileNotFoundException.class,()->Lib.write_Results("XX XX"));
assertThrows(FileNotFoundException.class,()->Lib.write_Results("0115"));
assertThrows(FileNotFoundException.class,()->Lib.write_Results("0130"));
assertThrows(FileNotFoundException.class,()->Lib.write_Results("Q5"));
assertThrows(FileNotFoundException.class,()->Lib.write_Results("Qq"));
//assertThrows(FileNotFoundException.class,()->Lib.write_Results("q1"));
assertDoesNotThrow(()->Lib.write_Results("0129"));
assertDoesNotThrow(()->Lib.write_Results("0118"));
assertDoesNotThrow(()->Lib.write_Results("Q3"));
assertDoesNotThrow(()->Lib.write_Results("Q1"));
}
}
覆盖率:
经过测试发现java打开txt文件时不区分文件名大小写,导致指令INPUT.txt,result q1都判为正确指令,并且能找到相应文件,对此不作处理(开摆)
发现指令错误时手动抛出异常跳转到程序底部处理,进行两层try-catch,一层接收Lib类抛出的异常,表示文件(input.txt)中的指令错误,最上层即对命令行指令错误的提示。
Lib内部异常抛出代码
JSONObject js = null;
try {
js = JSONObject.parseObject(get_JsonStr(FILE_RESULT + date + ".json"));
}catch (Exception e){
write_Msg("N/A\n-----\n");
throw new FileNotFoundException("未找到" + FILE_RESULT + date + ".json");
}
main部分获取异常代码
passed = true;
try{
if (args.length != 2)
throw new IOException("参数数量必须是2");
Lib.set_OutputFile(args[1]);//设置输出文件名
BufferedReader reader = new BufferedReader(new FileReader(args[0]));//读取输入文件
String commands;
while ((commands = reader.readLine()) != null){//逐行读取指令
if (commands.isEmpty())
continue;
commands = commands.trim();
String[] command = commands.split("\\s+");//拆分指令
try{
if (command[0].equals("players")){
if (command.length != 1)
throw new IOException("\"" + commands + "\"错误");
Lib.write_Players();//通过检查,指令为players
}
else if (command[0].equals("result")){
if (command.length != 2){
Lib.write_Msg("N/A\n-----\n");
throw new FileNotFoundException("\"" + commands + "\"错误");
}
Lib.write_Results(command[1]);//通过初步检查,指令为result X
}
else{
throw new Exception("\"" + commands + "\"错误");
}
}catch (FileNotFoundException e){
System.out.println(args[0] + "中指令错误:" + e.getMessage());
}
catch (Exception e){
System.out.println(args[0] + "中指令错误:" + e.getMessage());
Lib.write_Msg("Error\n-----\n");
}
}
}catch(Exception e){
System.out.println("命令行参数错误:" + e.getMessage());
passed = false;
}
一开始看到作业要求的时候没觉得多复杂,实际开始项目的时候却一头雾水,对很多要求不进一步思考就无法动手实现。于是花了很多时间搜索相关内容,设计工具类,研究下载的json文件的结构,由于程序本身功能并不复杂,开始编码后很快就实现了基础的两个功能。
但是到此离完成程序还有一段距离,之前做测试都只是在控制台手动输入输出,对单元测试,自动化测试一窍不通,卡了几天没学会具体到idea使用测试的方法,还好[第二届构建之法论坛] 预培训文档(Java版)救了我。
对单元测试,自动化测试一窍不通,卡了几天没学会具体到idea使用测试的方法,还好[第二届构建之法论坛] 预培训文档(Java版)救了我。
请你也写高质量博客, 救救其他的学生。