文章目录
作业基本信息
这个作业属于哪个课程 | 软件工程实践-2023学年-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践第二次作业–个人实战 |
这个作业的目标 | 完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序 |
其他参考文献 | 无 |
Gitcode项目地址
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 15 |
• Estimate | • 估计这个任务需要多少时间 | 20 | 15 |
Development | 开发 | 1200 | 1475 |
• Analysis | •需求分析(包括学习新技术) | 120 | 180 |
• Design Spec | • 生成设计文档 | 20 | 20 |
• Design Review | • 设计复审 | 40 | 30 |
• Coding Standard | • 代码规范(为目前的开发制定合适的规范) | 10 | 10 |
• Design | • 具体设计 | 20 | 30 |
• Coding | • 具体编码 | 900 | 1080 |
• Code Review | • 代码复审 | 30 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 75 |
Reporting | 报告 | 120 | 95 |
• Test Repor | • 测试报告 | 30 | 30 |
• Size Measurement | • 计算工作量 | 30 | 20 |
• Postmortem & Process Improvement Plan | • 事后总结,并提出过程改进计划 | 60 | 45 |
合计 | 1340 | 1585 |
解题思路描述
流程图
问题1
功能要求是输出所有选手信息,分别要输出Full Name,Gender,Country三个部分,
我们爬取这三个部分的数据并排序,接下来就按照题目要求进行文件的读取和输出即可。
问题2
功能要求是输出决赛每个运动项目结果,先按照题目的输出格式要求准备好输出函数,
然后将项目名作为参数来查找对应的数据文件,因为不同类型的项目比赛数据文件的格式会有不同,
在输出时进行判断这个文件是否有某个部分的数据,如果有则进行输出,没有就不输出。
问题3
功能要求是输出详细结果,在问题2的基础上判断是否有"detail",
因为每个选手参加的比赛数目可能不同,所以在获取该选手参加的比赛数据时,
先看json文件中最近的那场比赛(按照初赛、半决赛、决赛),然后获取选手姓名,
再根据选手姓名去每个比赛中查找他的比赛数据,然后依次输出即可。
接口设计和实现过程
按照题目要求,接口设计为两个功能,一个是输出所有选手信息,另一个是输出每个比赛项目结果,
//输出所有选手信息
interface PlayerInfoOutput {
void outputPlayersInfo(BufferedWriter writer) throws IOException;
}
//输出每个比赛项目结果
interface CompetitionResultOutput {
void outputCompetition(BufferedWriter writer, String competitionName) throws IOException;
}
最后会在输出需要结果时调用相应的接口实现函数
函数设计图
关键代码展示
指令处理函数
public CommandType orderCompare(String order) {
//默认指令号
CommandType commandType = CommandType.error;
//读取空文件的情况
if (order == null) {
return commandType;
}
try (BufferedReader reader = new BufferedReader(new FileReader(LIST_FILE_PATH))) {
String orders;
//遍历指令集并比较
while ((orders = reader.readLine()) != null) {
//读取空行
if (order.isEmpty()) {
break;
}
//一样的情况
else if (orders.equals(order)) {
if (order.startsWith("result ")) {
// 删除 "result " 部分
order = order.substring("result ".length());
// 将空格替换为下划线,这样才能与枚举类型相同
order = order.replace(" ", "_");
}
commandType = CommandType.valueOf(order);
break;
}
//如果正确输入且有detail,排除players
else if (order.equals(orders + " detail")&&order.startsWith("result ")) {
commandType = CommandType.detail;
break;
}
//不以result开头直接error
else if (!order.startsWith("result ")) {
break;
}
//前面为result+“ ”开头,同时不以result men/women开头
else if (order.startsWith("result ") && !order.startsWith("result women") && !order.startsWith("result men")) {
commandType = CommandType.na;
break;
}
//以result men/women开头,后面不符合条件
else if (order.startsWith("result men") || order.startsWith("result women")) {
//当遍历到最后一条仍然未满足,则不符合条件
if (commandType.ordinal() == CommandType.values().length) {
commandType = CommandType.na;
break;
}
commandType = getNextCommandType(commandType);
}
//不一样的指令
else {
commandType = getNextCommandType(commandType);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return commandType;
}
指令执行函数
public void outPut(CommandType commandType, BufferedWriter writer) {
try {
switch (commandType) {
case players:
outputPlayersInfo(writer);
break;
case women_1m_springboard:
case women_3m_springboard:
case women_10m_platform:
case women_3m_synchronised:
case women_10m_synchronised:
case men_1m_springboard:
case men_3m_springboard:
case men_10m_platform:
case men_3m_synchronised:
case men_10m_synchronised:
outputCompetition(writer, name);
break;
case error:
outputWrongOrder(writer);
break;
case na:
outputWrongCondition(writer);
break;
case detail:
outputCompetition(writer, name);
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
错误处理函数
//输出无法识别的指令
public static void outputWrongOrder(BufferedWriter writer) {
try {
writer.write("Error");
writer.newLine();
writer.write("-----");
writer.newLine();
} catch (Exception e) {
e.printStackTrace();
}
}
//输出条件不符合的指令
public static void outputWrongCondition(BufferedWriter writer) {
try {
writer.write("N/A");
writer.newLine();
writer.write("-----");
writer.newLine();
} catch (Exception e) {
e.printStackTrace();
}
}
接口实现
@Override
public void outputPlayersInfo(BufferedWriter writer) {
try {
// 读取JSON文件内容
String jsonContent = new String(Files.readAllBytes(Paths.get("src/data/players.json")));
// 将JSON字符串转换为JSONObject
JSONArray playersArray = new JSONArray(jsonContent);
// 遍历每个运动员的信息
for (int i = 0; i < playersArray.length(); i++) {
JSONObject player = playersArray.getJSONObject(i);
// 获取运动员的信息
String country = player.getString("country");
String fullName = player.getString("full_name");
String gender = player.getString("gender");
// 将运动员信息写入文件
writer.write("Full Name:" + fullName);
writer.newLine();
writer.write("Country:" + country);
writer.newLine();
writer.write("Gender:" + gender);
writer.newLine();
writer.write("-----");
writer.newLine();
}
} catch (Exception e) {
e.printStackTrace();
}
}
//输出m米跳板信息,只需修改json的文件名就可以实现全部比赛的信息输出
@Override
public void outputCompetition(BufferedWriter writer, String name) {
//不包含detail
if (!name.contains("detail")) {
try {
// 构建JSON文件路径
String filePath = "src/data/" + name + ".json";
// 读取JSON文件内容,使用缓存
String jsonContent = readJsonFile(filePath);
// 将JSON字符串转换为JSONObject
JSONObject jsonData = new JSONObject(jsonContent);
// 获取 "final"、 "semifinal"和"preliminary" 对应的JSONArray,并输出
JSONArray finalArray = jsonData.getJSONArray("final");
writePlayerInfo(writer, finalArray);
if (name.contains("platform")) {
JSONArray semifinalArray = jsonData.getJSONArray("semifinal");
writePlayerInfo(writer, semifinalArray);
}
if (name.contains("springboard") || name.contains("platform")) {
JSONArray preliminaryArray = jsonData.getJSONArray("preliminary");
writePlayerInfo(writer, preliminaryArray);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//包含detail
else {
try {
//先处理文件名,删除掉后面的"_detail"
name = name.replace("_detail", "");
// 构建JSON文件路径
String filePath = "src/data/" + name + ".json";
// 读取JSON文件内容,使用缓存
String jsonContent = readJsonFile(filePath);
// 将JSON字符串转换为JSONObject
JSONObject jsonData = new JSONObject(jsonContent);
writePlayerInfoDetail(writer, jsonData);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void writePlayerInfo(BufferedWriter writer, JSONArray playersArray) {
try {
for (int i = 0; i < playersArray.length(); i++) {
JSONObject player = playersArray.getJSONObject(i);
// 获取运动员的信息
int rank = player.getInt("rank");
String fullName = player.getString("player_full_name");
JSONObject divePoints = player.getJSONObject("dive_points");
// 将运动员信息写入文件
writer.write("Full Name: " + fullName);
writer.newLine();
writer.write("Rank: " + rank);
writer.newLine();
// 遍历每次跳水的得分
writer.write("Dive Points: ");
double totalScore = 0.0;
for (String diveNumber : divePoints.keySet()) {
double diveScore = divePoints.getDouble(diveNumber);
totalScore += diveScore;
writer.write(String.format("%.2f", diveScore) + " + ");
}
// 输出总分
writer.write("= " + String.format("%.2f", totalScore));
writer.newLine();
writer.write("-----");
writer.newLine();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
性能改进
使用了hashMap进行数据缓存,避免多次重复访问json数据
// JSON文件缓存
private Map<String, String> jsonCache = new HashMap<>();
// 从文件中读取JSON内容,添加缓存机制------------------------------------------------
private String readJsonFile(String filePath) throws IOException {
if (jsonCache.containsKey(filePath)) {
// 如果缓存中已存在,直接返回缓存的内容
return jsonCache.get(filePath);
} else {
// 读取文件内容
String jsonContent = new String(Files.readAllBytes(Paths.get(filePath)));
// 将内容放入缓存
jsonCache.put(filePath, jsonContent);
return jsonContent;
}
}
单元测试
按照错误类型,分成Error错误和N/A错误
测试类
public class TestMethod {
//N/A测试
@Test
public void test1() throws IOException {
String inputFilePath = "test_input.txt";
String outputFilePath = "test_output.txt";
// 准备多个N/A的测试数据
String[] testInputs = {
"result women_1m_springboard",
"result men 3m_synchronised",
"result men 3m synchronised ",
"result men 3m synchronised",
"result men 3m synchronised",
"result men 3m synchronised",
"result men 3m synchronised",
"result women 3m platform",
"result women",
"result men",
"result men 10m springboard",
"result men 1 springboard",
"result me 1 springboard",
"result me 1m springboard",
"result women 1m springboard detail ",
};
try {
// 执行多个测试,一次读一条
for (String inputContent : testInputs) {
// 将当前测试数据写入文件
writeToFile(inputFilePath, inputContent);
// 执行测试
Lib lib = new Lib();
lib.start(inputFilePath, outputFilePath);
// 验证输出是否符合预期
String expectedOutput = getExpectedOutput1(inputContent);
String actualOutput = readFromFile(outputFilePath);
assertEquals(expectedOutput, actualOutput);
}
}catch (Exception e)
{
e.printStackTrace();
}
}
//error测试
@Test
public void test2() throws IOException {
String inputFilePath = "test_input.txt";
String outputFilePath = "test_output.txt";
// 准备多个Error的测试数据
String[] testInputs = {
"a",
"playerss",
"players ",
"players detail",
"resultmen 1m springboard",
"resultwomen 1m springboard",
"aresult women 1m springboard",
" players",
"women_1m_springboard",
"men_3m_synchronised",
"unknown_command women_1m_springboard",
"unknown_directive players",
"unknown_result men_10m_platform",
"unknown_info women_3m_synchronised extra_info",
"unknown women_10m_platform",
"additional_info",
"",
};
try {
// 执行多个测试,一次读一条
for (String inputContent : testInputs) {
// 将当前测试数据写入文件
writeToFile(inputFilePath, inputContent);
// 执行测试
Lib lib = new Lib();
lib.start(inputFilePath, outputFilePath);
// 验证输出是否符合预期
String expectedOutput = getExpectedOutput2(inputContent);
String actualOutput = readFromFile(outputFilePath);
assertEquals(expectedOutput, actualOutput);
}
}catch (Exception e)
{
e.printStackTrace();
}
}
// 根据输入内容生成预期输出
private String getExpectedOutput1(String inputContent) {
return "N/A\n-----\n";
}
private String getExpectedOutput2(String inputContent) {
return "Error\n-----\n";
}
// 将字符串写入文件
private void writeToFile(String filePath, String content) {
try(BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
writer.write(content);
} catch (IOException e) {
e.printStackTrace();
}
}
// 从文件中读取字符串
private String readFromFile(String filePath) {
StringBuilder content = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return content.toString();
}
}
test1的覆盖率
test1的运行速度
test2的覆盖率
test2的运行速度
TestMethod的覆盖率
TestMethod的运行速度
异常处理
1.出现文件为空的情况时,会按照Error操作处理
2.输入文件中出现空行时,按照Error操作处理
3.除"players"以外,以"result “开头出现错误按照N/A处理
4.除以"result ”开头的,其他均按照Error处理
5.当输入文件不存在时,会提示"输入文件不存在”
心得体会
一开始看到这个题目,可能是太久没有写编程作业的原因,有点无从下手,想到哪写到哪,
写着写着就有点混乱了,后面梳理了一下流程后,按照流程开始编写就顺利很多了,这也让我意识到
设计的重要性,一开始的编程就像老师上课讲的搭狗窝式编程,想到哪写到哪,这样没有计划的编程效率确实会比较低,
有计划的编程,知道自己每一步要做什么该做什么,效率就会高效很多。
这次小练手最大的收获就是意识到设计的重要性以及使用git上传文件到远程仓库的原理有了一定的了解,操作更加熟练。