310
社区成员




这个作业属于哪个课程 | 软件工程实践-2023学年-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业——个人实战 |
这个作业的目标 | ①完成对世界游泳锦标赛跳水项目相关数据的收集 ②在收集相关数据后实现一个能够对赛事数据进行统计的控制台程序 |
其他参考文献 | 《构建之法》 |
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 30 |
• Estimate | • 估计这个任务需要多少时间 | 40 | 30 |
Development | 开发 | 1200 | 1300 |
• Analysis | • 需求分析 (包括学习新技术) | 180 | 200 |
• Design Spec | • 生成设计文档 | 20 | 30 |
• Design Review | • 设计复审 | 20 | 25 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 35 |
• Design | • 具体设计 | 70 | 80 |
• Coding | • 具体编码 | 700 | 750 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | 40 | 55 |
• Test Repor | • 测试报告 | 20 | 15 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 1280 | 1385 |
- 通过作业要求的链接从网页上获取到相关数据,利用网页的开发者模式,获取到运动员和每场比赛结果数据的json文件。
- 先编写将json文件转换成字符串的函数,接着利用org.json库中的JSONArray和JSONObject来通过对应键值获取到的json字符串进行解析,对照json文件中的格式进行数据的不断获取并存在对应类中方便使用。如果是大括号对应的数据使用JSONObject解析,如果是[]对应的数据使用JSONArray解析。
- 通过命令行输入input.txt文件,读入要求的字符串,接着通过分析字符串判断出对应的情况,编写输出不同信息的函数,包括对不符合条件,编写输出对应的错误信息的函数,无法识别的指令,则输出Error,result后的条件不符合以下要求,则输出N/A,输出均输出到output.txt的文件中。
DWASearch类:
含有main函数,是程序的入口,其中包括判断输入对应的情况和调用相应功能函数。Player类:
含有输出运动员需要的信息和对应的方法PlayerDetail类:
含有输出比赛详细需要的运动员信息和对应的方法PlayerMessage类:
含有输出比赛决赛需要的运动员信息和对应的方法Lib类:
包含了所有的静态函数,实现输出比赛决赛信息,比赛详细信息和运动员信息
/*功 能:输出比赛决赛信息*/ public static void outputFinalGameResult(String gameName, String data, String outputPath){} /*功 能:输出比赛详细信息*/ public static void outputDetailGameResult(String gameName, String data, String outputPath){} /*功 能:将运动员信息输出到output.txt文件中*/ public static void outputPlayerMessage(String playersPath, String outputPath){}
- LibTest类:
测试和输出比赛决赛信息、比赛详细信息和运动员信息有关的函数,还包括对输出比赛详细信息函数前后优化对比的函数,具体函数如下:
>/*功 能:测试输出比赛决赛信息函数*/
>public void outputFinalGameResult(){}
>/*功 能:测试输出比赛详细信息函数*/
>public void outputDetailGameResult(){}
>/*功 能:测试输出比赛详细信息函数(优化前)*/
>public void outputDetailGameResultBeforeOptimization(){}
>/*功 能:测试输出运动员信息函数*/
>public void outputPlayerMessage(){}
- 功能划分
输出选手信息并加上文件输入输出
输出决赛每个运动项目结果
输出比赛详细信息
input.txt出现多行情况
处理当输入无法处理的指令中result后的条件不符合要求时输出N/A
处理当输入无法处理的指令中无法识别的指令时输出Error
优化代码结构
添加单元测试代码
性能改进
增加博客代码
public class Lib {
static String[] correctGameName = {"women 1m springboard", "women 3m springboard", "women 10m platform", "women 3m synchronised"
, "women 10m synchronised", "men 1m springboard", "men 3m springboard", "men 10m platform", "men 3m synchronised", "men 10m synchronised"};
/*功 能:输出比赛决赛信息*/
/*入口参数:比赛名字,对应比赛的json文件路径,output文件路径*/
/*返 回:无*/
public static void outputFinalGameResult(String gameName, String data, String outputPath) {
if (Arrays.asList(correctGameName).contains(gameName)) {
ArrayList<PlayerMessage> playerMessageArrayList = ParsePlayerMessage(ReadFile(data));
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) { //向output.txt输出比赛决赛信息
if (!hasContent(outputPath)) {
writer.write("\n");
}
for (int i = 0; i < playerMessageArrayList.size(); i++) {
if (i != 0) {
writer.write("\n" + playerMessageArrayList.get(i) + "\n-----");
} else {
writer.write(playerMessageArrayList.get(i) + "\n-----");
}
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
outputError(outputPath);
}
}
/*功 能:输出比赛详细信息*/
/*入口参数:比赛名字,对应比赛的json文件路径,output文件路径*/
/*返 回:无*/
public static void outputDetailGameResult(String gameName, String data, String outputPath) {
if (Arrays.asList(correctGameName).contains(gameName)) {
ArrayList<PlayerDetail> playerDetails = ParsePlayerDetail(ReadFile(data));
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) { //向output.txt输出比赛详细信息
if (!hasContent(outputPath)) {
writer.write("\n");
}
for (int i = 0; i < playerDetails.size(); i++) {
if (i != 0) {
writer.write("\n" + playerDetails.get(i) + "\n-----");
} else {
writer.write(playerDetails.get(i) + "\n-----");
}
}
} catch (IOException e) {
e.printStackTrace();
}
} else {
outputError(outputPath);
}
}
/*功 能:将Error错误信息输出到output.txt文件中*/
/*入口参数:output文件路径*/
/*返 回:无*/
public static void outputError(String outputPath) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) { //向output.txt输出N/A
if (!hasContent(outputPath)) {
writer.write("\n");
}
writer.write("N/A" + "\n-----");
} catch (IOException e) {
e.printStackTrace();
}
}
/*功 能:将运动员信息输出到output.txt文件中*/
/*入口参数:运动员json文件路径,output文件路径*/
/*返 回:无*/
public static void outputPlayerMessage(String playersPath, String outputPath) {
List<Player> players = ParsePlayer(ReadFile(playersPath));
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) { //向output.txt输出运动员信息
if (!hasContent(outputPath)) {
writer.write("\n");
}
for (int i = 0; i < players.size(); i++) {
if (i != 0) {
writer.write("\n" + players.get(i) + "\n-----");
} else {
writer.write(players.get(i) + "\n-----");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/*功 能:读取文件*/
/*入口参数:文件路径*/
/*返 回:文件的字符串*/
public static String ReadFile(String path) {
String str = "";
File file = new File(path);
try {
FileReader fileReader = new FileReader(file);
Reader reader = new InputStreamReader(new FileInputStream(file), "utf-8");
int ch = 0;
StringBuffer stringBuffer = new StringBuffer();
while ((ch = reader.read()) != -1) {
stringBuffer.append((char) ch); //读出文件信息为字符串
}
fileReader.close();
reader.close();
str = stringBuffer.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
return str;
}
/*功 能:解析运动员信息的json*/
/*入口参数:json字符串*/
/*返 回:运动员列表*/
public static ArrayList<Player> ParsePlayer(String str) {
JSONObject jsonObject = new JSONObject(str);
JSONArray objPlayer = jsonObject.getJSONArray("player");
ArrayList<Player> players = new ArrayList<>();
for (int i = 0; i < objPlayer.length(); i++) {
JSONObject playerOne = objPlayer.getJSONObject(i); //取出同一个国家运动员信息
String country = playerOne.getString("CountryName");
JSONArray sameCountryPlayer = playerOne.getJSONArray("Participations"); //将运动员数组取出
for (int j = 0; j < sameCountryPlayer.length(); j++) {
JSONObject obj = sameCountryPlayer.getJSONObject(j); //从运动员数组中取出一个运动员信息
Player player = new Player();
String fullName = "";
player.setCountry(country);
fullName += obj.getString("PreferredLastName") + " " + obj.getString("PreferredFirstName"); //读出运动员名字
player.setFullName(fullName);
if (obj.getInt("Gender") == 0) //读出运动员性别
player.setGender("Male");
else
player.setGender("Female");
players.add(player);
}
}
return players;
}
/*功 能:解析决赛比赛信息的json*/
/*入口参数:json字符串*/
/*返 回:比赛中运动员信息列表*/
public static ArrayList<PlayerMessage> ParsePlayerMessage(String str) {
JSONObject jsonObject = new JSONObject(str);
JSONArray messageArray = jsonObject.getJSONArray("Heats");
ArrayList<PlayerMessage> playerMessageArrayList = new ArrayList<>();
JSONObject gameObject = messageArray.getJSONObject(0); //取出决赛比赛信息
JSONArray gameArray = gameObject.getJSONArray("Results");
for (int j = 0; j < gameArray.length(); j++) {
JSONObject playerMessageObject = gameArray.getJSONObject(j);
JSONArray div = playerMessageObject.getJSONArray("Dives"); //取出每次跳水得分数组
String fullName = playerMessageObject.getString("FullName"); //取出运动员名字
String[] name = fullName.split("/");
ArrayList<Double> scores = new ArrayList<>();
PlayerMessage playerMessage = new PlayerMessage();
for (int n = 0; n < div.length(); n++) {
scores.add(Double.parseDouble(div.getJSONObject(n).getString("DivePoints"))); //取出每次跳水得分
}
playerMessage.setTotalScore(Double.parseDouble(playerMessageObject.getString("TotalPoints"))); //存入跳水总得分
playerMessage.setRank(playerMessageObject.getInt("Rank")); //存入跳水排名
playerMessage.setScore(scores); //存入每次跳水得分
playerMessage.setGameType(gameObject.getString("Name")); //存入final比赛
if (name.length == 1) { //判断是否为单人项目
playerMessage.setPlayerName(playerMessageObject.getString("FullName"));
} else { //是双人项目
String player1LastName = name[0].trim();
String player2LastName = name[1].trim();
if (player1LastName.compareTo(player2LastName) <= 0) { //名字排序
playerMessage.setPlayerName(player1LastName, player2LastName);
} else {
playerMessage.setPlayerName(player2LastName, player1LastName);
}
}
playerMessageArrayList.add(playerMessage);
}
return playerMessageArrayList;
}
/*功 能:解析比赛信息的json*/
/*入口参数:json字符串*/
/*返 回:比赛中运动员信息列表*/
public static ArrayList<PlayerDetail> ParsePlayerDetail(String str) {
JSONObject jsonObject = new JSONObject(str);
JSONArray messageArray = jsonObject.getJSONArray("Heats");
ArrayList<PlayerDetail> playerDetails = new ArrayList<>();
for (int i = messageArray.length() - 1; i >= 0; i--) {
JSONObject gameObject = messageArray.getJSONObject(i);
JSONArray gameArray = gameObject.getJSONArray("Results"); //每个运动员的得分
String gameType = gameObject.getString("Name"); //取出比赛是final、Semifinal还是Preliminary
int nowPlayerIndex = 0;
for (int j = 0; j < gameArray.length(); j++) { //从第一次比赛开始读
JSONObject playerMessageObject = gameArray.getJSONObject(j);
JSONArray div = playerMessageObject.getJSONArray("Dives"); //取出每次跳水得分数组
String fullName = playerMessageObject.getString("FullName"); //取出运动员名字
String[] name = fullName.split("/");
ArrayList<Double> scores = new ArrayList<>();
PlayerDetail playerDetail = new PlayerDetail();
for (int n = 0; n < div.length(); n++) {
scores.add(Double.parseDouble(div.getJSONObject(n).getString("DivePoints"))); //取出每次跳水得分
}
if (i == messageArray.length() - 1) { //判断是否为第一次比赛,是的话存入名字
if (name.length == 1) { //判断是否为单人项目
playerDetail.setFullName(playerMessageObject.getString("FullName"));
} else { //是双人项目
String player1LastName = name[0].trim();
String player2LastName = name[1].trim();
if (player1LastName.compareTo(player2LastName) <= 0) //名字排序
playerDetail.setFullName(player1LastName, player2LastName);
else
playerDetail.setFullName(player2LastName, player1LastName);
}
playerDetails.add(playerDetail);
}
for (PlayerDetail playerDetail1 : playerDetails) { //找出当前取出的运动员在队列中的下标
if (name.length == 1) {
if (playerDetail1.getFullName().equals(playerMessageObject.getString("FullName")))
nowPlayerIndex = playerDetails.indexOf(playerDetail1);
} else {
String player1LastName = name[0].trim();
String player2LastName = name[1].trim();
String playerFullName = "";
if (player1LastName.compareTo(player2LastName) <= 0)
playerFullName = player1LastName + " & " + player2LastName;
else
playerFullName = player2LastName + " & " + player1LastName;
if (playerDetail1.getFullName().equals(playerFullName))
nowPlayerIndex = playerDetails.indexOf(playerDetail1);
}
}
playerDetail = playerDetails.get(nowPlayerIndex);
if (gameType.equals("Preliminary")) { //判断是Preliminary比赛类型,存入相关信息
playerDetail.setPreliminaryTotalScore(Double.parseDouble(playerMessageObject.getString("TotalPoints")));
playerDetail.setPreliminaryRank(Integer.toString(playerMessageObject.getInt("Rank")));
playerDetail.setPreliminaryScores(scores);
} else if (gameType.equals("Semifinal")) { //判断是Semifinal比赛类型,存入相关信息
playerDetail.setSemifinalTotalScore(Double.parseDouble(playerMessageObject.getString("TotalPoints")));
playerDetail.setSemifinalRank(Integer.toString(playerMessageObject.getInt("Rank")));
playerDetail.setSemifinalScores(scores);
} else { //判断是final比赛类型,存入相关信息
playerDetail.setFinalTotalScore(Double.parseDouble(playerMessageObject.getString("TotalPoints")));
playerDetail.setFinalRank(Integer.toString(playerMessageObject.getInt("Rank")));
playerDetail.setFinalScores(scores);
}
playerDetails.set(nowPlayerIndex, playerDetail);
}
}
return playerDetails;
}
}
- 在实现对比赛详细信息展示的功能中,我刚开始使用了Map数据结构,以运动员名字为键值,运动员详细信息为值来存入信息,然后在返回时再变成ArrayList的数据结构。之后改用直接以ArrayList存入信息,然后遍历整个队列来获取当前正在存入的运动员对应索引,从而进行修改运动员比赛信息,发现直接用ArrayList的数据结构这个函数运行时间变短。
如图,outputDetailGameResultBeforeOptimization函数是使用了Map数据结构,而outputDetail
GameResult是直接使用ArrayList数据结构,很明显的看出直接使用ArrayList数据结构会缩短代码运行时间。分析原因可能是因为分析的数据量较少,直接遍历的时间花费比使用按键存取再转换来的少。- 还有想到一个将String的字符串拼接改成先用StringBuffer来进行改进,发现两个所用时间没有很明显的区别,可能是因为拼接的次数很少,看不出区别,因此就没有改用StringBuffer来拼接。
- 在单元测试中,我测试了输出比赛决赛信息的函数outputFinalGameResult,输出比赛详细信息的函数outputDetailGameResult和输出运动员信息的函数outputPlayerMessage,其中用到的测试用例有:
result (result后加空格)
result(result后没加空格)
result women 3m springboard
result detail
result women 3m springboard detail
players
@Test
/*功 能:测试输出比赛决赛信息函数*/
/*入口参数:无*/
/*返 回:无*/
public void outputFinalGameResult() {
String outputPath=new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "outputFinalGameResultTest.txt";
Lib.createClearFile(outputPath);
//在主函数中会判断出如果输入有result而没有detail的话是利用Lib中outputFinalGameResult函数来判断输出结果的,因此在这个函数中进行单元测试
//输入是result 时,后面有空格,传入函数的gameName应该是空格,输出结果应该是N/A
Lib.outputFinalGameResult(" ", new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "data" + " .json",outputPath );
//输入是result时,后面没有其他字符,传入函数的gameName应该是空,输出结果应该是N/A
Lib.outputFinalGameResult("", new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "data" + ".json", outputPath);
//输入是result women 3m springboard,传入函数的gameName应该是women 3m springboard,输出结果应该是比赛结果
Lib.outputFinalGameResult("women 3m springboard", new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "data" + File.separator + "women 3m springboard.json", outputPath);
//测试
String result=Lib.ReadFile(outputPath);
String expectation="N/A\n" +
"-----\n" +
"N/A\n" +
"-----\n" +
"Full Name:BENT-ASHMEIL Desharne\n" +
"Rank:1\n" +
"Score:61.50 + 63.55 + 66.00 + 55.50 + 45.00 = 291.55\n" +
"-----\n" +
"Full Name:MULLER Jette\n" +
"Rank:2\n" +
"Score:63.00 + 63.00 + 61.50 + 40.30 + 60.00 = 287.80\n" +
"-----";
Assert.assertTrue(result.startsWith(expectation));
}
@Test
/*功 能:测试输出比赛详细信息函数*/
/*入口参数:无*/
/*返 回:无*/
public void outputDetailGameResult() {
String outputPath=new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "outputDetailGameResultTest.txt";
Lib.createClearFile(outputPath);
//在主函数中会判断出如果输入有result且有detail的话是利用Lib中outputDetailGameResult函数来判断输出结果的,因此在这个函数中进行单元测试
//输入是result detail时,result后有空格,传入函数的gameName应该是空格,输出结果应该是N/A
Lib.outputDetailGameResult(" ", new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "data" + " .json", outputPath);
//输入是result women 3m springboard detail,传入函数的gameName应该是women 3m springboard,输出结果应该是比赛结果
Lib.outputDetailGameResult("women 3m springboard", new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "data" + File.separator + "women 3m springboard.json", outputPath);
//测试
String result=Lib.ReadFile(outputPath);
String expectation="N/A\n" +
"-----\n" +
"Full Name:HENTSCHEL Lena\n" +
"Rank:1 | 4 | 3\n" +
"Preliminary Score:54.00 + 63.55 + 48.00 + 60.00 + 51.00 = 276.55\n" +
"Semifinal Score:52.50 + 57.35 + 55.50 + 49.50 + 57.00 = 271.85\n" +
"Final Score:58.50 + 57.35 + 60.00 + 45.00 + 63.00 = 283.85\n" +
"-----\n" +
"Full Name:MULLER Jette\n" +
"Rank:2 | 8 | 2\n" +
"Preliminary Score:57.00 + 51.00 + 55.50 + 51.15 + 52.50 = 267.15\n" +
"Semifinal Score:57.00 + 51.00 + 55.50 + 31.00 + 54.00 = 248.50\n" +
"Final Score:63.00 + 63.00 + 61.50 + 40.30 + 60.00 = 287.80\n" +
"-----\n" +
"Full Name:WILSON Aimee\n" +
"Rank:3 | 2 | 4\n" +
"Preliminary Score:54.00 + 52.70 + 54.00 + 49.50 + 55.50 = 265.70\n" +
"Semifinal Score:52.50 + 55.80 + 57.00 + 52.50 + 58.50 = 276.30\n" +
"Final Score:48.00 + 62.00 + 52.50 + 49.50 + 64.50 = 276.50\n" +
"-----";
Assert.assertTrue(result.startsWith(expectation));
}
@Test
/*功 能:测试输出运动员信息函数*/
/*入口参数:无*/
/*返 回:无*/
public void outputPlayerMessage() {
String outputPath=new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "outputPlayerMessageTest.txt";
Lib.createClearFile(outputPath);
//输出运动员信息
Lib.outputPlayerMessage(new File(System.getProperty("user.dir")) + File.separator + "src" + File.separator + "data\\player.json",outputPath);
//测试
String result=Lib.ReadFile(outputPath);
String expectation="Full Name:HART Alexander\n" +
"Gender:Male\n" +
"Country:Austria\n" +
"-----\n" +
"Full Name:LOTFI Dariush\n" +
"Gender:Male\n" +
"Country:Austria\n" +
"-----\n" +
"Full Name:SCHALLER Nikolaj\n" +
"Gender:Male\n" +
"Country:Austria\n" +
"-----\n" +
"Full Name:ABRAMOWICZ Tazman\n" +
"Gender:Male\n" +
"Country:Canada\n" +
"-----";
Assert.assertTrue(result.startsWith(expectation));
}
- 总的测试用例有
result (result后加空格)
result(result后没加空格)
result detail
result women 3m springboard
result women 3m springboard detail
result Women 3m springboard
resultwomen 3m springboard
player
Players
players
result men 3m synchronised
result men 3m synchronised detail
input.txt文件中为空
- 代码覆盖率
覆盖率优化方法:
1.尽可能设计出边界的测试用例,完善测试用例。
2.删除一些没有用的代码。
3.减少判断分支。
- 对于输出文件可能不存在或者是里面有内容需要清空的情况
> /*功 能:文件不存在创建文件,文件存在将文件内容清空*/
/*入口参数:文件路径*/
/*返 回:无*/
public static void createClearFile(String path) {
File outputFile = new File(path);
try {
if (!outputFile.exists()) {
outputFile.createNewFile();
} else {
FileWriter fileWriter = new FileWriter(path);
fileWriter.write("");
fileWriter.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
- 读文件时处理异常
if (inputIsEmpty) { //输入字符串为空的情况
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) {
if (!Lib.hasContent(outputPath)) {
writer.write("\n");
}
writer.write("Error" + "\n-----");
} catch (IOException e) {
e.printStackTrace();
}
}
- 对于输入的指令result后面不符合规则输出N/A
/*功 能:将Error错误信息输出到output.txt文件中*/
/*入口参数:output文件路径*/
/*返 回:无*/
public static void outputError(String outputPath) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) { //向output.txt输出N/A
if (!hasContent(outputPath)) {
writer.write("\n");
}
writer.write("N/A" + "\n-----");
} catch (IOException e) {
e.printStackTrace();
}
}
- 对于输入的指令无法识别的情况输出Error
try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath, true))) {
//输入无法识别的指令
if (!Lib.hasContent(outputPath)) {
writer.write("\n");
}
writer.write("Error" + "\n-----");
} catch (IOException e) {
e.printStackTrace();
}
- 在编写过程中,首先对于应该获取怎么样的数据让我不知所措,在查阅相关资料和同学的帮助下,发现可以使用网页上的json数据并利用相关方法对数据进行解析。
- 在编写代码前应该做好大概的设计框架,想到哪写到哪容易出现大规模修改代码的情况。
- 在面对自己不是很熟悉的知识时,不要出现排斥心理,要主动投入的进行学习。
做的不错~继续努力!