软件工程实践第二次作业---文件读取

本文详细描述了一次软件工程作业,涉及数据抓取、世界游泳锦标赛跳水项目数据处理、控制台程序编写,包括GitHub项目地址、代码规范、数据获取方法、单元测试和异常处理,强调了Git在项目中的重要性及代码组织和优化的过程。
这个作业属于哪个课程<福州大学-202302软件工程实践>
这个作业要求在哪里软件工程第二次作业–文件读取
这个作业的目标完成对世界游泳锦标赛跳水项目相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献《构建之法》《源代码管理》

1. GItHub项目地址

项目地址:https://gitcode.net/AXF_COCO/project-java/
旧项目地址:https://gitcode.net/AXF_COCO/se_deprecated/(含有commit记录)

2. PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划3010
• Estimate• 估计这个任务需要多少时间105
Development开发700420
• Analysis• 需求分析 (包括学习新技术)300720
• Design Spec• 生成设计文档6035
• Design Review• 设计复审6020
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)30300
• Design• 具体设计18060
• Coding• 具体编码300300
• Code Review• 代码复审120120
• Test• 测试(自我测试,修改代码,提交修改)240240
Reporting报告300120
• Test Repor• 测试报告12020
• Size Measurement• 计算工作量6030
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划6040
合计28302380

3. 解题思路描述

3.1 阅读题目并梳理思路

我习惯于先做好所有准备再动手做,先明确了自己完成这个项目需要分几个阶段。接着了解各个阶段需要做的事,例如是否仍然在eclipse上开发,还是重新配置一个IDEA;代码规范要参照哪家公司的等等问题。我先把任务拟定为五步:

  1. 撰写代码规范
  2. 配置环境
  3. 爬取数据
  4. 数据处理
  5. 处理输入
  6. 处理输出

3.2 撰写代码规范

与我在c课程中使用的代码规范一致,我去搜寻了Google Java的代码规范。于是便开始了漫长的转译为markdown的过程,期间还要对翻译中不合理不通顺的地方进行润色,最后便完成了代码规范的确定。
使用Google的代码规范的好处是IDEA中有相关的代码规范插件可以使用。
谷歌代码规范插件的GitHub网址:https://github.com/google/google-java-format
具体安装方法可以到GitHub上的README.md中查看,这里就不过多的赘述。

3.3 获取网站数据

  1. 打开网站后,按F12进入开发者工具,选择菜单栏上的网络,以监听文件的传送。
    网站和开发者工具图片
  2. 刷新界面,即可从监听窗口中找到althletes的数据
    监听到athletes的图片
  3. 同样的方法,我们可以得到events的数据和比赛结果的数据
    监听到events的图片
    监听到比赛结果的图片
  4. 右键文件,选择复制->复制URL,我们就得到了这个数据的API,然后就可以在网页中打开了,之后便可以下载文件,亦或是直接复制内容。

3.4 数据处理

  1. 首先获取fastjson2的依赖。我是用的是maven的项目,所以直接到maven库中搜索相关依赖。
    maven库中的fastjosn2展示
  2. 将依赖加入文件pom.xml,并刷新项目即可开始使用JSON相关工具。
    pom.xml文件内容
  3. 查看json文件的数据结构,对照作业内容的要求,确定代码撰写时的思路。下图为我构思时候撰写的txt文件,确定思路并记录,这样撰写代码时就不会因为遗忘,对一个问题重复思考。
    对数据结构的解析

3.5 代码撰写

接着就是按照思路进行一点一点的推进,我习惯在写每一个关键的代码时,先去求证当前方法是否是最高效的。例如,去搜索读取txt文件最高效的方法,所以我就直接使用了BufferedReader,可以为后期优化减少压力。

4. 设计实现过程

4.1 数据处理

  1. 对读取文件内容单独写一个函数Object getContent(String fileName)。因为不能确定是JSONObject还是JSONArray,所以统一返回Object,得到结果之后再去强制转换。
  2. 对于运动员,单独设计一个函数JSONArray filterPlayers(JSONArray content)去按照具体的JSON文件格式进行所需数据的过滤,最后返回一个仅包含需要输出数据的JOSNArray。将二者结合,封装为一个getFilteredPlayers的函数,用以获取所有所需运动员数据。
  3. 对于比赛结果,虽然分为两种要求,但是需要的数据基本是一致的。首先,设计函数JSONObject getEvent(String eventName)在events文件中找到具体项目的Id,然后,使用函数JSONArray getResults(JSONObject event, String phaseName)通过Id找到比赛结果对应的文件,并提取出结果这一JSONArray。与运动员类似,使用filter函数过滤出输出所需要的数据。最后使用函数JSONArray getFilteredResults(String eventName, String phaseName)对操作进行封装。

4.2 处理输入输出

  1. 输入,需要判断参数个数是否为2个,然后读取数据,并在handleIO中根据每一行内容,调用所需输出格式内容即可。
  2. 输出,分为三类,运动员、比赛结果和比赛结果详情,因为在获取的时候已经对数据进行了过滤,所以只需要按照格式输出即可。

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就这样被解开了面纱。另外,以前天真的以为学习语言只是学一下语法就行了,这次作业涉及到了项目不同组织结构的选择,以及第三方库的使用,让我发现,原来学习语言不只是语法的学习,更重要的是掌握各种库的使用,在遇到相应需求时可以想到对应工具,这样才能有的放矢,否则,如果从零开始造轮子的话,效率会大大下降。对于代码的编写,我发现我会耗费很多时间在资料的搜寻与对比上,比如去对比各种组织结构的优劣,具体数据结构的选择等等,这往往要耗费我一两天的时间。但是,在上手编写代码时思路会十分的流畅,因为有了提前的思考,也能确保面向对象思想的正确应用,让代码的可维护性和可扩展性提高。希望在未来的作业中,我可以探索到更多未知的世界。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值