软件工程实践第二次作业——个人实战

222000230林雯雯 学生 2023-03-02 20:39:06
这个作业属于哪个课程2023年福大-软件工程实践-W班
这个作业要求在哪里作业详情
这个作业的目标文件读写、json数据解析、爬取数据
其他参考文献CSDN

目录

  • 1. Gitcode项目地址
  • 2. PSP表格
  • 3. 解题思路描述
  • 3.1 思考解决方案
  • 3.2 最终方案
  • 4. 接口设计和实现过程
  • 4.1 爬取赛事数据
  • 4.2 代码的组织
  • 4.2.1 代码结构
  • 4.2.2 方法之间的关系
  • 4.2.3 关键算法
  • 5. 关键代码展示
  • 6. 性能改进
  • 7. 单元测试
  • 7.1 测试思路
  • 7.2 测试代码
  • 7.3 测试覆盖率
  • 8. 异常处理
  • 9. 心得体会

目录

1. Gitcode项目地址

项目地址:由此进

请添加图片描述

2. PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2020
• Estimate• 估计这个任务需要多少时间2020
Development开发8401015
• Analysis• 需求分析 (包括学习新技术)180150
• Design Spec• 生成设计文档6055
• Design Review• 设计复审1010
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)90110
• Design• 具体设计6045
• Coding• 具体编码360480
• Code Review• 代码复审2015
• Test• 测试(自我测试,修改代码,提交修改)60150
Reporting报告220225
• Test Repor• 测试报告180200
• Size Measurement• 计算工作量2010
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划2015
合计10801260

3. 解题思路描述

3.1 思考解决方案

问题解决方案具体过程
需要大量的文件读写操作,使用原生的java代码过于繁琐采用第三方库commons-ioMaven仓库中下载commons-io-2.4.jar,导入项目依赖
需要解析json数据采用第三方库fastjsonMaven仓库中下载fastjson-1.2.62.jar,导入项目依赖
需要在网页上爬取数据参考自怎样使用Chrome浏览器获取网页页面的Json数据在火狐浏览器中,利用开发者工具获取响应数据
需要进行单元测试采用单元测试框架junitMaven仓库中下载junit-4.10.jar,导入项目依赖

3.2 最终方案

使用第三方库commons-io,读取input.txt内的指令,用换行符来切割多条指令。利用InputStream和第三方库commons-io,根据指令读取AOSearch.jar内的json文件,接着利用第三方库fastjson解析数据,得到想要的输出结果。最后使用commons-io库,把结果写入输出文件。

编码完成后,创建AOSearchTest.java,利用Junit框架来进行单元测试。

4. 接口设计和实现过程

4.1 爬取赛事数据

 在火狐浏览器中,F12打开开发者工具,在“网络”中,过滤器里输入“schedule”进行筛选,查看“响应”的“原始数据”,即可获取到json字符串。将它复制粘贴到新建的文件中,利用VSCode对代码进行格式化,即可拿到比赛数据。

请添加图片描述

4.2 代码的组织

4.2.1 代码结构
    • AOSearch
      • main(String[] args)
      • readBlank(File file)
      • handleError(String input, File file)
      • findErrorType(String input)
      • readMatch(String date, File file)
      • readMatchInfo(JSONArray courts, JSONObject jsonObject)
      • getScore(JSONArray teams, JSONObject jsonObject)
      • getWinner(JSONArray teams, JSONObject jsonObject)
      • getNamesByteamId(String teamId, JSONObject jsonObject)
      • readPlayers(File file)
      • readPlayersInfo(JSONArray players)
      • getGender(String gender)
      • formatInput(String code)
    • AOSearchTest
  • 第三方库
    • comonos-io-2.4.jar
    • fastjson-1.2.62.jar
    • junit-4.10.jar

本次项目共有一个类AOSearch,一个测试类AOSearchTest,三个第三方库。AOSearch类中共有13个方法。

4.2.2 方法之间的关系
  1. 获取球员数据的方法调用

    在main方法中调用readPlayers方法,在readPlayers方法中调用readPlayersInfo方法来形成所需的数据格式。其中,在readPlayersInfo方法中调用getGender方法来获取性别male/female。

  2. 获取比赛数据的方法调用

    在main方法中调用readMatch方法。在readMatch方法中调用readMatchInfo获取所需格式的比赛信息。在readMatchInfo中调用getWinner获取胜者名字,调用getScore获取比分。在getWinner方法中调用getNamesByteamId获取胜者名字。

  3. 处理空行指令

    在main方法中调用readBlank方法。

  4. 处理错误指令

    调用handleError方法,在handleError方法中调用findErrorType方法,来确定错误是“Error”还是“N/A”。

  5. 方法关系图一览

    请添加图片描述

4.2.3 关键算法
  1. 文件读写

    本次作业需要进行文件的读写操作,不仅要读写本地的input.txt和output.txt文件,还要读取jar包内的文件。若要读本地的输入文件input.txt,可调用commons-io库的FileUtils类的readFileToString(File file, String encoding)方法;若要创建输出文件output.txt,则调用commons-io库的FileUtils类的touch(File file)方法;若要写入输出文件output.txt,可调用commons-io库的FileUtils类的writeStringToFile(File file, String str , String encoding)方法。

    数据文件均被打包在AOSearch.jar包内,所以需要读取jar内的文件。利用this.getClass().getResourceAsStream方法可直接获取文件的输入流,进而获取文件内容。

  2. json数据解析

    解析json分为:解析json对象、解析json数组。当解析json对象时,使用fastjson库的getJSONObject(int index)方法;当解析json数组时,调用fastjson库的getJSONArray(String key)方法。

5. 关键代码展示

  1. 读取输入文件,以换行符切割出指令,创建输出文件
// 读取命令行的参数
        String inputFileName = args[0];  // 输入文件名
        String outputFileName = args[1];  // 输出文件名

        String inputs = null;
        File outputFile = null;
        try {
            // 读取input文件内的内容
            inputs = FileUtils.readFileToString(new File(inputFileName), "utf8");
            // 创建output.txt文件Java -jar AOSearch.jar input.txt output.txt
            try {
                outputFile = new File(outputFileName);
                FileUtils.touch(outputFile);
            } catch (IOException e) {
                // 无法创建输出方法,结束
                System.out.println("无法创建输出文件" + outputFileName);
                return;
            }
        } catch (Exception e) {
            // 找不到输入文件
            System.out.println("您的输入文件" + inputFileName + "不存在");
            return;
        }

        AOSearch search = new AOSearch();
        // 将指令按行分割
        String[] inputCode = inputs.split("\\r?\\n");
  1. 将指令格式化
    public String formatInput(String code) {
        code = code.trim();  // 去掉首尾空格
        if (code.startsWith("result ")) {
            // 切分字符串,切出日期
            String date = code.substring(7);
            date = date.trim();
            code = "result " + date;
        }
        return code;
    }
  1. 读取jar内的json文件
InputStream is = this.getClass().getResourceAsStream("data/schedule/" + date + ".json");
String content = IOUtils.toString(is, "utf8");
  1. 获取players数据
            // 读取players.json文件的内容
            InputStream is = this.getClass().getResourceAsStream("data/players.json");
            String content = IOUtils.toString(is, "utf8");
            // 将json字符串转为JSON对象
            JSONObject jsonObject = JSONObject.parseObject(content);
            // 获取players数组
            JSONArray players = jsonObject.getJSONArray("players");
            // 整理获得输出数据
            resultStr += readPlayersInfo(players);
    public String readPlayersInfo(JSONArray players) {
        // 遍历players数组,收集有用信息
        String playerInfo = "";
        JSONObject player = null;
        for (int i = 0, size = players.size(); i < size; i++) {
            player = players.getJSONObject(i);
            playerInfo += "full_name:" + player.getString("full_name") + "\n";
            playerInfo += "gender:" + getGender(player.getString("gender")) + "\n";
            playerInfo += "nationality:" + player.getJSONObject("nationality").getString("name") + "\n" +
                    "-----\n";
        }
        return playerInfo;
    }
  1. 获取比赛数据
            // 读取相应比赛日期文件的内容
            InputStream is = this.getClass().getResourceAsStream("data/schedule/" + date + ".json");
            String content = IOUtils.toString(is, "utf8");
            // 将json字符串转为JSON对象
            JSONObject jsonObject = JSONObject.parseObject(content);
            // 获取courts数组
            JSONArray courts = jsonObject.getJSONObject("schedule").getJSONArray("courts");

            // 获取结果,把结果写入文件
            resultStr += readMatchInfo(courts, jsonObject);
            FileUtils.writeStringToFile(file, resultStr, "utf8");
    public String readMatchInfo(JSONArray courts, JSONObject jsonObject) {
        String courtStr = "";
        JSONObject court = null;
        JSONArray sessions = null;
        for (int i = 0, size = courts.size(); i < size; i++) {
            court = courts.getJSONObject(i);
            sessions = court.getJSONArray("sessions");

            JSONObject session =null;
            JSONArray activities = null;
            for (int j = 0, sessionSize = sessions.size(); j < sessionSize; j++) {
                session = sessions.getJSONObject(j);
                activities = session.getJSONArray("activities");

                JSONObject activity = null;
                JSONArray teams = null;
                JSONObject matchStatus = null;
                String matchAbbr = null;
                for (int k = 0, actSize = activities.size(); k < actSize; k++) {
                    activity = activities.getJSONObject(k);
                    if (activity.getString("actual_start_time") != null) {
                        courtStr += "time:" + activity.getString("actual_start_time") + "\n";  // 获取比赛时间
                        teams = activity.getJSONArray("teams");
                        courtStr += "winner:" + getWinner(teams, jsonObject) + "\n";
                        courtStr += "score:" + getScore(teams, jsonObject) + "\n";
                        courtStr += "-----\n";
                    } else {
                        matchStatus = activity.getJSONObject("match_status");
                        if (matchStatus != null) {
                            matchAbbr = matchStatus.getString("abbr");
                            if ("W/O".equals(matchAbbr)) {
                                // 弃赛
                                courtStr += "W/O\n-----\n";
                            }
                        }
                    }

                }
            }
        }
        return courtStr;
    }
  1. 获取获胜者名字
    public String getWinner(JSONArray teams, JSONObject jsonObject) {
        String names = "";
        JSONObject team = null;
        String isWin = null;
        String teamId = null;
        for (int i = 0, size = teams.size(); i < size; i++) {
            team = teams.getJSONObject(i);
            isWin = team.getString("status");
            if ("Winner".equals(isWin)) {
                // 获取胜者名字
                teamId = team.getString("team_id");
                names += getNamesByteamId(teamId, jsonObject);
                return names;
            }
        }
        return names;
    }
    public String getNamesByteamId(String teamId, JSONObject jsonObject) {
        JSONArray teams = jsonObject.getJSONArray("teams");
        ArrayList<String> playersCode = new ArrayList<>();  // 装着players的编号
        ArrayList<String> playersName = new ArrayList<>();  // 装着players的名字
        // 根据teamID查找队伍
        JSONObject team = null;
        for (int i = 0, size = teams.size(); i < size; i++) {
            team = teams.getJSONObject(i);
            if (team.getString("uuid").equals(teamId)) {
                // 找到了队伍
                JSONArray players = team.getJSONArray("players");
                for (int j = 0, pSize = players.size(); j < pSize; j++) {
                    playersCode.add(players.getString(j));
                }
                break;
            }
        }
        // 根据球员编号获取球员名字
        JSONArray players = jsonObject.getJSONArray("players");
        JSONObject player = null;
        String uuid = null;
        for (int i = 0, size = playersCode.size(); i < size; i++) {
            for (int j = 0; j < players.size(); j++) {
                player = players.getJSONObject(j);
                uuid = player.getString("uuid");
                if (uuid.equals(playersCode.get(i))) {
                    // 找到了
                    playersName.add(player.getString("short_name"));
                    break;
                }
            }
        }

        String names = "" + playersName.get(0);
        for (int i = 1, size = playersName.size(); i < size; i++) {
            names += " & " + playersName.get(i);
        }
        return names;
    }
  1. 获取比赛的比分
    public String getScore(JSONArray teams, JSONObject jsonObject) {
        String score = "";
        JSONArray scoreA = teams.getJSONObject(0).getJSONArray("score"); // A队的分数数组
        JSONArray scoreB = teams.getJSONObject(1).getJSONArray("score"); // B队的分数数组
        score += scoreA.getJSONObject(0).getString("game") + ":"
                + scoreB.getJSONObject(0).getString("game");
        for (int i = 1, size = scoreA.size(); i < size; i++) {
            score += " | " + scoreA.getJSONObject(i).getString("game") + ":"
                    + scoreB.getJSONObject(i).getString("game");
        }
        return score;
    }
  1. 错误指令的处理
    public String findErrorType(String input) {
        if (input != null && (input.startsWith("result ") || "result".equals(input))) {
            return "N/A\n-----\n";
        } else return "Error\n-----\n";
    }
  1. 将结果写入输出文件
FileUtils.writeStringToFile(file, resultStr, "utf8");

6. 性能改进

  1. 循环内不要创建对象

例如,优化前的代码:

public String readPlayersInfo(JSONArray players) {
        // 遍历players数组,收集有用信息
        String playerInfo = "";
        for (int i = 0; i < players.size(); i++) {
            JSONObject player = players.getJSONObject(i);
            playerInfo += "full_name:" + player.getString("full_name") + "\n";
            playerInfo += "gender:" + getGender(player.getString("gender")) + "\n";
            playerInfo += "nationality:" + player.getJSONObject("nationality").getString("name") + "\n" +
                    "-----\n";
        }
        return playerInfo;
    }

这种做法会导致内存中有players.size()份的JSONObject对象存在,并且players.size()很大,此时就会消耗大量内存了。

将代码优化为:

    public String readPlayersInfo(JSONArray players) {
        // 遍历players数组,收集有用信息
        String playerInfo = "";
        JSONObject player = null;
        for (int i = 0; i < players.size(); i++) {
            player = players.getJSONObject(i);
            playerInfo += "full_name:" + player.getString("full_name") + "\n";
            playerInfo += "gender:" + getGender(player.getString("gender")) + "\n";
            playerInfo += "nationality:" + player.getJSONObject("nationality").getString("name") + "\n" +
                    "-----\n";
        }
        return playerInfo;
    }

这样的话,内存中只有一份JSONObject对象,每次循环的时候,JSONObject对象就可指向不同的JSONObject对象,但是内存中只有一份。对于本次作业来说,多重循环是不可避免的,优化了以后,就能大大节省内存空间了。

  1. 循环中减少对数组长度的计算

对方法的调用,即使方法中只有一条语句,也是有时间和空间上的消耗。

例如,优化前的代码:

    public String readPlayersInfo(JSONArray players) {
        // 遍历players数组,收集有用信息
        String playerInfo = "";
        JSONObject player = null;
        for (int i = 0; i < players.size(); i++) {
            player = players.getJSONObject(i);
            playerInfo += "full_name:" + player.getString("full_name") + "\n";
            playerInfo += "gender:" + getGender(player.getString("gender")) + "\n";
            playerInfo += "nationality:" + player.getJSONObject("nationality").getString("name") + "\n" +
                    "-----\n";
        }
        return playerInfo;
    }

若是这样写代码,则players数组的长度有多大,size方法就会被调用多少次。
优化后的代码:

    public String readPlayersInfo(JSONArray players) {
        // 遍历players数组,收集有用信息
        String playerInfo = "";
        JSONObject player = null;
        for (int i = 0, size = players.size(); i < size; i++) {
            player = players.getJSONObject(i);
            playerInfo += "full_name:" + player.getString("full_name") + "\n";
            playerInfo += "gender:" + getGender(player.getString("gender")) + "\n";
            playerInfo += "nationality:" + player.getJSONObject("nationality").getString("name") + "\n" +
                    "-----\n";
        }
        return playerInfo;
    }

优化后,只会调用一次size方法,当数组长度很大时,就减少了大量消耗。

7. 单元测试

7.1 测试思路

  本次单元测试使用了junit-4.10框架。项目分为两大功能,一是输出所有球员的数据,二是输出每日或每赛季的比赛数据,此外还要处理一些错误的指令。

  对球员的测试,设计了正确的测试用例"players",同时也设置了错误的指令"player"和"Players"。

  对比赛数据的测试,分为每日和每赛季。对于每日比赛的单人赛,设计了正确的测试用例"result 0116"和"result 0117",错误的测试用例"reslut0116"、"result 0130"、"result sss"、"result 116"。对于双人比赛,设计了正确的测试用例"0129"。

7.2 测试代码

  1. 测试球员信息的输出
    @Test
    public void testReadPlayer() {
        try {
            String content = FileUtils.readFileToString(new File("src/data/players.json"));
            JSONObject jsonObject = JSONObject.parseObject(content);
            // 获取players数组
            JSONArray players = jsonObject.getJSONArray("players");
            String playersStr = search.readPlayersInfo(players);
            // 期望输出的前两个player的信息
            String real =
                    "full_name:Radu Albot\n" +
                    "gender:male\n" +
                    "nationality:Moldova\n" +
                    "-----\n" +
                    "full_name:Marcelo Arevalo\n" +
                    "gender:male\n" +
                    "nationality:El Salvador\n" +
                    "-----\n";
            // 实际解析得来的数据是否以期望值开头(即验证前两个player信息)
            Assert.assertTrue(playersStr.startsWith(real));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

测试了AOSearch类中获取球员信息readPlayersInfo方法,其中readPlayersInfo方法调用了getGender方法。调用完方法可获得全体球员的数据,由于数据过于庞大,因此只比较了前两个球员的数据。

  1. 测试获取比赛数据
    // 测试0116的第一场比赛信息 "result 0116“
    @Test
    public void testReadMatch0116() {
        try {
            String content = FileUtils.readFileToString(new File("src/data/schedule/0116.json"));
            JSONObject jsonObject = JSONObject.parseObject(content);
            // 获取courts数组
            JSONArray courts = jsonObject.getJSONObject("schedule").getJSONArray("courts");

            // 获取输出结果
            String match = search.readMatchInfo(courts, jsonObject);
            // 期望的结果(仅取第一场比赛)
            String real = "time:00:14\n" +
                    "winner:C. Gauff\n" +
                    "score:1:6 | 4:6\n" +
                    "-----\n" ;
            Assert.assertTrue(match.startsWith(real));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

测试了AOSearch类的获取比赛信息readMatchInfo方法。在readMatchInfo方法中,调用了getWinner方法来获取胜者名字,调用getScore方法获取比分。其中,在getWinner中调用了getNamesByteamId方法获取名字。

  1. 测试错误指令的处理
    // 测试错误指令“player” 期望值"Error----\n"
    @Test
    public void testError1() {
        String result = search.findErrorType("player");
        String real =
                "Error\n" +
                "-----\n";
        Assert.assertEquals(real, result);
    }

测试了AOSearch类中处理错误指令的findErrorType方法,错误的类型分为"Error"和"N/A"。

7.3 测试覆盖率

请添加图片描述

请添加图片描述

8. 异常处理

  本次作业是一个命令行的java程序,当在命令行窗口输入"
Java -jar AOSearch.jar input2.txt output.txt"时,若在AOSearch.jar包的同级文件目录下,不存在输入文件"input2.txt",则会进入异常处理,在命令行窗口中会输出"您的输入文件input2.txt不存在",如图所示:

请添加图片描述

9. 心得体会

通过这次作业,我接触了一个用于文件操作的第三方库commons-io,用于解析json的第三方库fastjson。本次作业锻炼了我的自学能力,过程中也遇上了不少困难,例如如何爬取数据,把程序打为jar包后无法读取到json文件等,最终通过询问同学、自行查找资料解决了问题。本次作业第一次接触到了单元测试框架junit,对单元测试有了初步的认识和了解。回顾一下感慨颇多,一开始看到作业只觉得比登天还难,但是多通读几遍要求,多找一些视频资料学习,最终还是度过难关,印证了“learning by doing”。

...全文
70 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

688

社区成员

发帖
与我相关
我的任务
社区描述
2023年福州大学软件工程实践课程W班的教学社区
软件工程团队开发软件构建 高校 福建省·福州市
社区管理员
  • FZU_SE_teacherW
  • 张书旖
  • 郭渊伟
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧