| 这个作业属于哪个课程 | <福州大学-202302软件工程实践> |
|---|---|
| 这个作业要求在哪里 | 软件工程第二次作业–文件读取 |
| 这个作业的目标 | 完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序 |
| 其他参考文献 | 《构建之法》《源代码管理》 |
文章目录
1. GItHub项目地址
项目地址:https://gitcode.net/AXF_COCO/project-java/
旧项目地址:https://gitcode.net/AXF_COCO/se_deprecated/(含有commit记录)
2. PSP表格
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 10 |
| • Estimate | • 估计这个任务需要多少时间 | 10 | 5 |
| Development | 开发 | 700 | 420 |
| • Analysis | • 需求分析 (包括学习新技术) | 300 | 720 |
| • Design Spec | • 生成设计文档 | 60 | 35 |
| • Design Review | • 设计复审 | 60 | 20 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 300 |
| • Design | • 具体设计 | 180 | 60 |
| • Coding | • 具体编码 | 300 | 300 |
| • Code Review | • 代码复审 | 120 | 120 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 240 | 240 |
| Reporting | 报告 | 300 | 120 |
| • Test Repor | • 测试报告 | 120 | 20 |
| • Size Measurement | • 计算工作量 | 60 | 30 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 60 | 40 |
| 合计 | 2830 | 2380 |
3. 解题思路描述
3.1 阅读题目并梳理思路
我习惯于先做好所有准备再动手做,先明确了自己完成这个项目需要分几个阶段。接着了解各个阶段需要做的事,例如是否仍然在eclipse上开发,还是重新配置一个IDEA;代码规范要参照哪家公司的等等问题。我先把任务拟定为五步:
- 撰写代码规范
- 配置环境
- 爬取数据
- 数据处理
- 处理输入
- 处理输出
3.2 撰写代码规范
与我在c课程中使用的代码规范一致,我去搜寻了Google Java的代码规范。于是便开始了漫长的转译为markdown的过程,期间还要对翻译中不合理不通顺的地方进行润色,最后便完成了代码规范的确定。
使用Google的代码规范的好处是IDEA中有相关的代码规范插件可以使用。
谷歌代码规范插件的GitHub网址:https://github.com/google/google-java-format
具体安装方法可以到GitHub上的README.md中查看,这里就不过多的赘述。
3.3 获取网站数据
- 打开网站后,按F12进入开发者工具,选择菜单栏上的网络,以监听文件的传送。

- 刷新界面,即可从监听窗口中找到althletes的数据

- 同样的方法,我们可以得到events的数据和比赛结果的数据


- 右键文件,选择复制->复制URL,我们就得到了这个数据的API,然后就可以在网页中打开了,之后便可以下载文件,亦或是直接复制内容。
3.4 数据处理
- 首先获取fastjson2的依赖。我是用的是maven的项目,所以直接到maven库中搜索相关依赖。

- 将依赖加入文件pom.xml,并刷新项目即可开始使用JSON相关工具。

- 查看json文件的数据结构,对照作业内容的要求,确定代码撰写时的思路。下图为我构思时候撰写的txt文件,确定思路并记录,这样撰写代码时就不会因为遗忘,对一个问题重复思考。

3.5 代码撰写
接着就是按照思路进行一点一点的推进,我习惯在写每一个关键的代码时,先去求证当前方法是否是最高效的。例如,去搜索读取txt文件最高效的方法,所以我就直接使用了BufferedReader,可以为后期优化减少压力。
4. 设计实现过程
4.1 数据处理
- 对读取文件内容单独写一个函数
Object getContent(String fileName)。因为不能确定是JSONObject还是JSONArray,所以统一返回Object,得到结果之后再去强制转换。 - 对于运动员,单独设计一个函数
JSONArray filterPlayers(JSONArray content)去按照具体的JSON文件格式进行所需数据的过滤,最后返回一个仅包含需要输出数据的JOSNArray。将二者结合,封装为一个getFilteredPlayers的函数,用以获取所有所需运动员数据。 - 对于比赛结果,虽然分为两种要求,但是需要的数据基本是一致的。首先,设计函数
JSONObject getEvent(String eventName)在events文件中找到具体项目的Id,然后,使用函数JSONArray getResults(JSONObject event, String phaseName)通过Id找到比赛结果对应的文件,并提取出结果这一JSONArray。与运动员类似,使用filter函数过滤出输出所需要的数据。最后使用函数JSONArray getFilteredResults(String eventName, String phaseName)对操作进行封装。
4.2 处理输入输出
- 输入,需要判断参数个数是否为2个,然后读取数据,并在
handleIO中根据每一行内容,调用所需输出格式内容即可。 - 输出,分为三类,运动员、比赛结果和比赛结果详情,因为在获取的时候已经对数据进行了过滤,所以只需要按照格式输出即可。
5. 关键代码展示
5.1 对比赛结果的过滤
如果results为空数组的话,filteredResults也会是空数组。对所有结果的每一项进行提取,get到需要的信息,并使用put添加到fiteredResult中。
private static @NotNull JSONArray filterResults(@NotNull JSONArray results) {
JSONArray filteredResults = new JSONArray();
if (!results.isEmpty()) {
filteredResults = new JSONArray();
for (int i = 0; i < results.size(); i++) {
JSONObject result = results.getJSONObject(i);
JSONObject filteredResult = new JSONObject();
JSONArray competitors = result.getJSONArray("Competitors");
// add FullName
if (competitors != null) { // Synchronized
String name1 = competitors.getJSONObject(0).getString("FullName");
String name2 = competitors.getJSONObject(1).getString("FullName");
filteredResult.put(
"FullName",
name1.compareTo(name2) > 0 ? name2 + " & " + name1 : name1 + " & " + name2);
} else { // individual
filteredResult.put("FullName", result.getString("FullName"));
}
// add Rank
filteredResult.put("Rank", result.getIntValue("Rank"));
// add Score
StringBuilder scoreBuilder = new StringBuilder();
JSONArray dives = result.getJSONArray("Dives");
for (int j = 0; j < dives.size(); j++) {
JSONObject dive = dives.getJSONObject(j);
scoreBuilder.append(dive.getString("DivePoints")).append(" + ");
}
scoreBuilder.delete(scoreBuilder.length() - 3, scoreBuilder.length());
scoreBuilder.append(" = ").append(result.getString("TotalPoints"));
filteredResult.put("Score", scoreBuilder.toString());
filteredResults.add(filteredResult);
}
}
return filteredResults;
}
5.2 写入过滤后的结果详情
为了避免逻辑过于复杂,直接创建三个JSONArray存储三种可能的赛事,因为如果找不到的话会直接返回空数组,所以并没有牺牲时间和空间复杂度。因为以第一次比赛为顺序输出,所以ifelse判断并用results存储相关数据。最后就是根据所需要的格式进行写入。
public static void writeFilteredResultsDetail(String eventName, Writer writer) throws IOException {
JSONArray preliminaryResults = Lib.getFilteredResults(eventName, Lib.PHASE_NAMES[0]);
JSONArray semifinalResults = Lib.getFilteredResults(eventName, Lib.PHASE_NAMES[1]);
JSONArray finalResults = Lib.getFilteredResults(eventName, Lib.PHASE_NAMES[2]);
JSONArray results = null;
if (!preliminaryResults.isEmpty()) {
results = preliminaryResults;
} else if (!semifinalResults.isEmpty()) { //not ganna happen
results = semifinalResults;
} else {
results = finalResults;
}
for (int i = 0; i < results.size(); i++) {
JSONObject result = results.getJSONObject(i);
String fullName = result.getString("FullName");
writer.write("Full Name:" + fullName + '\n');
String rankBuilder =
(!preliminaryResults.isEmpty() ? getRank(preliminaryResults, fullName) : '*')
+ " | "
+ (!semifinalResults.isEmpty() ? getRank(semifinalResults, fullName) : '*')
+ " | "
+ getRank(finalResults, fullName);
writer.write("Rank:" + rankBuilder + '\n');
writer.write("Preliminary Score:" + getScore(preliminaryResults, fullName) + '\n');
writer.write("Semifinal Score:" + getScore(semifinalResults, fullName) + '\n');
writer.write("Final Score:" + getScore(finalResults, fullName) + '\n');
writer.write("-----" + '\n');
}
}
6. 单元测试
import junit.framework.TestCase;
public class DWASearchTest extends TestCase {
public void testMain0() { //测试正常输入情况下结果是否正确
DWASearch.main(new String[] {"input.txt", "output.txt"});
}
public void testMain1() { //少参数
DWASearch.main(new String[] {"input.txt"});
}
public void testMain2() { //输入文件不存在
DWASearch.main(new String[] {"aaa", "output.txt"});
}
public void testMain3() { //输出文件无后缀
DWASearch.main(new String[] {"input.txt", "aaa"});
}
public void testMain4() { //多参数
DWASearch.main(new String[] {"input.txt", "output.txt", "extra.txt"});
}
public void testMain5() { //输出文件后缀异常
DWASearch.main(new String[] {"input.txt", "output.aaa"});
}
public void testMain6() { //输入输出均无后缀
DWASearch.main(new String[] {"input", "output"});
}
public void testMain7() { //参数间多空格
DWASearch.main(new String[] {"input.txt ", "output.txt "});
}
public void testMain8() { //参数内多空格
DWASearch.main(new String[] {"input.txt", "o", "utput.txt"});
}
public void testMain9() { //无参
DWASearch.main(new String[] {});
}
}
因为input.txt中内容足够丰富,所以覆盖率可以达到100%。同时可以看到,如果有异常,程序会输出提示性文字。

7. 异常处理
7.1 函数异常
函数发生异常时不做处理,交友调用者统一处理。
public static @NotNull JSONArray getFilteredPlayers() throws IOException {
Object object = getContent(PLAYERS_FILE_NAME);
return filterPlayers((JSONArray) object);
}
7.2 调用者处理
收到调用函数抛出的异常后,利用try catch进行捕获,并输出相关错误内容,结构如下。
try {
//...
} catch (Exception e) {
System.err.println(e.getMessage());
}
8. 心得体会
这次作业最大的收获是学会了如何利用git,这对我未来参与小组作业亦或是为社区贡献代码打下了夯实的基础,曾经感觉遥不可及、神秘莫测的git就这样被解开了面纱。另外,以前天真的以为学习语言只是学一下语法就行了,这次作业涉及到了项目不同组织结构的选择,以及第三方库的使用,让我发现,原来学习语言不只是语法的学习,更重要的是掌握各种库的使用,在遇到相应需求时可以想到对应工具,这样才能有的放矢,否则,如果从零开始造轮子的话,效率会大大下降。对于代码的编写,我发现我会耗费很多时间在资料的搜寻与对比上,比如去对比各种组织结构的优劣,具体数据结构的选择等等,这往往要耗费我一两天的时间。但是,在上手编写代码时思路会十分的流畅,因为有了提前的思考,也能确保面向对象思想的正确应用,让代码的可维护性和可扩展性提高。希望在未来的作业中,我可以探索到更多未知的世界。
本文详细描述了一次软件工程作业,涉及数据抓取、世界游泳锦标赛跳水项目数据处理、控制台程序编写,包括GitHub项目地址、代码规范、数据获取方法、单元测试和异常处理,强调了Git在项目中的重要性及代码组织和优化的过程。

被折叠的 条评论
为什么被折叠?



