688
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | 2023软工W班 |
|---|---|
| 这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
| 这个作业的目标 | 完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序 |
| 其他参考文献 | 《构建之法》、软件测试开发实战 |
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 25 |
| Estimate | 估计这个任务需要多少时间 | 20 | 30 |
| Development | 开发 | 1200 | 1450 |
| Analysis | 需求分析 (包括学习新技术) | 180 | 240 |
| Design Spec | 生成设计文档 | 40 | 30 |
| Design Review | 生成设计文档 | 30 | 50 |
| Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
| Design | 具体设计 | 60 | 50 |
| Coding | 具体编码 | 600 | 700 |
| Code Review | 代码复审 | 20 | 30 |
| Test | 测试(自我测试,修改代码,提交修改) | 60 | 120 |
| Reporting | 报告 | 100 | 60 |
| Test Repor | 测试报告 | 30 | 30 |
| Size Measurement | 计算工作量 | 15 | 15 |
| Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 20 |
| 合计 | 2425 | 2880 |
进入目标网站,打开F12开发者模式(浏览器为edge),打开网络搜索players和results,找到对应的json文件并下载到本地。
players只有一份。而results一共分为14个比赛日和4个赛季Q1,Q2,Q3,Q4共18份。


使用的是fastjson解析,在idea中导入相关的jar包,然后再编写一个读取JSON文件的函数,将JSON中的数据转换为String。
再用JSONObject.parseObject将读取到的String转换为JSON对象。最后用JSONArry保存getString()函数读取到需要的数据,遍历输出即可。
1.输出players
在对于players的输出中,逻辑比较简单,直接遍历输出即可。
/*
先用函数读取JSON文件中的信息,将其转换为JSONObject对象,
再用getString函数获取"players"相关数据,用JSONArry保存。
再对JSONArry对象进行遍历查询相关输出,用StringBuilder的append函数添加,
最后用编写的输出函数输入到相关的文件中。
*/
public static void writePlayers() throws Exception
{
if(map.containsKey("players")){
writeMsg(map.get("players"));
return;
}
JSONObject js = JSONObject.parseObject(getJsonStr(PlayersFile));
JSONArray players = JSONArray.parseArray(js.getString("players"));//获取运动员的json数组
StringBuilder playersMsg = new StringBuilder();
//for循环查找运动员中指定的数据,输出到指定文件中
for (int i = 0;i < players.size();i++)
{
JSONObject player = players.getJSONObject(i);
playersMsg.append("full_name:").append(player.getString("full_name")).append("\n");
playersMsg.append("gender:").append(player.getString("gender").equals("M") ? "Male" : "Female").append("\n");
playersMsg.append("nationality:").append(player.getJSONObject("nationality").getString("name")).append("\n");
playersMsg.append("-----\n");
}
writeMsg(playersMsg.toString());
map.put("players",playersMsg.toString());
}
2.输出result
一共分为三行输出,比赛的实际开始时间,获胜选手,每轮次的比分。
/*
获胜选手信息的获取,先对JSONObject对象js中获取matches相关数据,先定位到某一场比赛match,
再获取这场match中的teams数据,teams再分为teamA和teamB。
在teamA或B中有个"status"相关数据表示是否为胜者,根据此得出winnerTeam。
再根据winnerTeam中的uid去搜寻队伍中选手的uid,
最后根据选手的uid去选手数组里面遍历对比出胜者。
每轮次比分的获取,从teamA和teamB中搜寻score再循环输出即可。
*/
public static void writePlayers() throws Exception
{
if(map.containsKey("players")){
writeMsg(map.get("players"));
return;
}
JSONObject js = JSONObject.parseObject(getJsonStr(PlayersFile));
JSONArray players = JSONArray.parseArray(js.getString("players"));
StringBuilder playersMsg = new StringBuilder();
for (int i = 0;i < players.size();i++)
{
JSONObject player = players.getJSONObject(i);
playersMsg.append("full_name:").append(player.getString("full_name")).append("\n");
playersMsg.append("gender:").append(player.getString("gender").equals("M") ? "Male" : "Female").append("\n");
playersMsg.append("nationality:").append(player.getJSONObject("nationality").getString("name")).append("\n");
playersMsg.append("-----\n");
}
writeMsg(playersMsg.toString());
map.put("players",playersMsg.toString());
}
本项目就分成两类:Lib(工具类)和 AOSearch(程序的入口)
Lib:工具类,包含读取json文件,输出选手和比赛信息,写信息到文件,等需要用到的方法。
AOSearch: 程序的入口,有对指令的输入是否合法进行判断。
//根据winnerUID查找选手
public static JSONArray findplayers(String winnerUID,JSONArray players)
//根据teamID查找队伍
public static JSONObject findTeams(String teamID,JSONArray teams)
//写入比赛信息
public static void writeResults(String date) throws Exception
//写入选手信息
public static void writePlayers() throws Exception
//读取JSON文件返回String
private static String getJsonStr(String fileName) throws IOException
//向文件中输出字符串
public static void writeMsg(String msg)
//写入胜者
public static void writeWinners( JSONObject match,StringBuilder resultsMsg,JSONObject js)
//根据队伍写入比分
public static void writeScore( JSONObject match,StringBuilder resultsMsg)
//写入实际开始时间
public static void writeTime(JSONObject match,StringBuilder resultsMsg)
AOSearch控制台启动后会调用Lib类中的writeResults和writePlayers进行输出。
下列代码是Lib.java的代码,包括了AOSearch程序的核心函数。
public class Lib {
private static String outputFIle = "output.txt";//输出文件
private static final String PlayersFile= "src/data/players.json";//运动员文件路径
private static final String ResultsFile= "src/data/schedule/";//运动员文件路径
private static HashMap<String,String> map = new HashMap<>();
//将读取到的运动员数据输出到输出文件中
public static void writePlayers() throws Exception
{
if(map.containsKey("players")){
writeMsg(map.get("players"));
return;
}
JSONObject js = JSONObject.parseObject(getJsonStr(PlayersFile));
JSONArray players = JSONArray.parseArray(js.getString("players"));//获取运动员的json数组
StringBuilder playersMsg = new StringBuilder();
//for循环查找运动员中指定的数据,并输出到指定文件中
for (int i = 0;i < players.size();i++)
{
JSONObject player = players.getJSONObject(i);
playersMsg.append("full_name:").append(player.getString("full_name")).append("\n");
playersMsg.append("gender:").append(player.getString("gender").equals("M") ? "Male" : "Female").append("\n");
playersMsg.append("nationality:").append(player.getJSONObject("nationality").getString("name")).append("\n");
playersMsg.append("-----\n");
}
writeMsg(playersMsg.toString());
map.put("players",playersMsg.toString());
}
public static void writeResults(String date) throws Exception
{
if(map.containsKey(date)){
writeMsg(map.get(date));
return;
}
//把比赛信息存入到JSONObject中
JSONObject js;
js = JSONObject.parseObject(getJsonStr(ResultsFile + date + ".json"));
JSONArray matches = JSONArray.parseArray(js.getString("matches"));
StringBuilder resultsMsg = new StringBuilder();//写入信息
//循环遍历matches
for (int i = 0;i < matches.size();i++)
{
JSONObject match = matches.getJSONObject(i);//当天第i场比赛
writeTime(match,resultsMsg);
writeWinners(match,resultsMsg,js);
writeScore(match,resultsMsg);
resultsMsg.append("\n-----\n");
}
writeMsg(resultsMsg.toString());
map.put(date,resultsMsg.toString());
}
public static JSONObject findTeams(String teamID,JSONArray teams)
{
for(int i=0;i<teams.size();i++)
{
if(Objects.equals(teamID, teams.getJSONObject(i).getString("uuid")))
return teams.getJSONObject(i);
}
return null;
}
//写入实际开始时间
public static void writeTime(JSONObject match,StringBuilder resultsMsg)
{
resultsMsg.append("time:").append(match.getString("actual_start_time")).append("\n");
}
//根据队伍写入比分
public static void writeScore( JSONObject match,StringBuilder resultsMsg)
{
JSONArray teamAB = JSONArray.parseArray(match.getString("teams"));//这场比赛的双方队伍
JSONObject teamA = teamAB.getJSONObject(0);
JSONObject teamB = teamAB.getJSONObject(1);//比赛双方队伍
JSONArray scoreA=JSONArray.parseArray(teamA.getString("score"));//A队得分
JSONArray scoreB=JSONArray.parseArray(teamB.getString("score"));//B队得分
for(int k=0;k<scoreA.size();k++)
{
resultsMsg.append(scoreA.getJSONObject(k).getString("game")).append(":").append(scoreB.getJSONObject(k).getString("game"));
if (k + 1 < scoreA.size())
resultsMsg.append(" | ");
}
}
public static void writeWinners( JSONObject match,StringBuilder resultsMsg,JSONObject js) {
JSONArray teams = JSONArray.parseArray(js.getString("teams"));
JSONArray players = JSONArray.parseArray(js.getString("players"));
JSONArray teamAB = JSONArray.parseArray(match.getString("teams"));
JSONObject teamA = teamAB.getJSONObject(0);
JSONObject teamB = teamAB.getJSONObject(1);//比赛双方队伍
JSONObject winnerTeam;
if(teamA.getString("status")!=null) winnerTeam=teamA;
else winnerTeam=teamB;
String winnerteamID=winnerTeam.getString("team_id");//获胜队伍的uid
String winnerUID= Objects.requireNonNull(findTeams(winnerteamID, teams)).getString("players");
JSONArray winners=findplayers(winnerUID,players);
resultsMsg.append("winner:");
for(int j=0;j<winners.size();j++)
{
resultsMsg.append(winners.getJSONObject(j).getString("short_name"));
if(j+1<winners.size()) resultsMsg.append(" & ");
}
resultsMsg.append("\n");
}
//胜者可能是多人,所以用JSONArray
public static JSONArray findplayers(String winnerUID,JSONArray players)
{
JSONArray winners=new JSONArray();
JSONArray uuids=JSONArray.parseArray(winnerUID);//将uid转为jsonarray格式
for(int i=0;i<uuids.size();i++)
{
String uuid=uuids.getString(i);//第一位胜者ID
for(int j=0;j<players.size();j++)//对players数组循环查询对比
{
if(players.getJSONObject(j).getString("uuid").equals(uuid))
winners.add(players.getJSONObject(j));//添加进winners数组中
}
}
return winners;
}
public static void setOutputFile(String filename){
outputFIle = filename;
}
private static String getJsonStr(String fileName) throws IOException
{
StringBuilder stringBuilder = new StringBuilder();
if (!new File(fileName).exists())
throw new FileNotFoundException("未找到" + fileName);
else
{
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null)
{
stringBuilder.append(line);
}
reader.close();
return stringBuilder.toString();
}
}
public static void writeTest() throws Exception {
Lib.setOutputFile("input.txt");
for (int i = 0;i < 500;i++){
Lib.writeMsg("players\nresult Q3\n");
}
Lib.setOutputFile("output.txt");
}
public static void writeMsg(String msg){
try
{
BufferedWriter bw = new BufferedWriter(new FileWriter(outputFIle,true));
bw.write(msg);
bw.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
优化并保证程序最少IO。为了尽可能降低IO次数,在主类中使用了StringBuilder,来构造一次存入的全部字符串。当读取完全部行之后,再完成一次IO。
因为大量的指令其中会有重复的指令,每次执行一次指令都是需要遍历查询,这样会花费大量时间,使用HashMap之后,在每次输出之前检查一下此指令是否为重复指令,可以通过散列表存储键值对以此来实现缓存功能,就无需因多次重复读取相同指令花费大量时间。
一次性输入一千条数据
使用缓存前耗时:111678ms

使用缓存后耗时:2748ms

本次测试对所有验证函数和结果输出进行测试,修复后的结果如下:
覆盖率:

代码如下
//AOSearchTest.java 单元测试
AOSearch.main(new String[]{"input.txt","output.txt"});
assertTrue(AOSearch.flag);
AOSearch.main(new String[]{"in.txt","output.txt"});
assertFalse(AOSearch.flag);
AOSearch.main(new String[]{"input.txt"});
assertFalse(AOSearch.flag);
AOSearch.main(new String[]{"inpxxut.txt","outxxput.txt","x"});
assertFalse(AOSearch.flag);
assertFalse(AOSearch.flag);
AOSearch.main(new String[]{"in.txt","out.txt"});
assertFalse(AOSearch.flag);
AOSearch.main(new String[]{"input.txt","output.txt","input,txt"});
assertFalse(AOSearch.flag);
AOSearch.main(new String[]{});
assertFalse(AOSearch.flag);
//LibTest.java 单元测试
assertDoesNotThrow(()->Lib.writeResults("Q4"));
assertDoesNotThrow(()->Lib.writeResults("Q3"));
assertDoesNotThrow(()->Lib.writeResults("Q1"));
assertDoesNotThrow(()->Lib.writeResults("Q2"));
assertDoesNotThrow(()->Lib.writeResults("0128"));
assertDoesNotThrow(()->Lib.writeResults("0127"));
assertDoesNotThrow(()->Lib.writeResults("0126"));
assertDoesNotThrow(()->Lib.writeResults("0125"));
assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("SSS SADSA"));
assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("XXX"));
assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("0216"));
assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("01a2"));
assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("Q5"));
assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("Q"));
对可能出现异常的函数进行try,catch
使用 getJsonStr 函数。
private static String getJsonStr(String fileName) throws IOException
{
StringBuilder stringBuilder = new StringBuilder();
if (!new File(fileName).exists())
throw new FileNotFoundException("未找到" + fileName);
因为作业要求的指令格式比较简单,就简单辨别处理,不对就直接抛出异常
这次的实践作业是我第一次使用java对json文件进行分析,在自主学习了使用fastJson对json文件进行解析的过程中,我遇到了许许多多的问题,也学习到了很多新的知识,从最初的学习爬取数据、解析json文件、下载各种jar包以及配置,到后来的改进性能、单元测试、修复bug等等,可以说是一路坎坷,但也收获颇丰。主要是学会了Java中单元测试的方法,使用Junit来进行单元测试,并且通过覆盖率测试查看有多少的分支代码被我们测试到。其次我也初步了解了软件工程PSP(个人开发流程)表格。
通过这一次的实践,我颇为深刻地感受到了项目前期需求分析的重要性,若能开发前设计好规划文档,将会对后期的编码、测试等带来极大的好处。