软件工程实践第二次作业

222000408黄一帆 2023-02-25 18:30:07
这个作业属于哪个课程<软件工程-23年春季学期>
这个作业要求在哪里<软件工程实践第二次作业---文件读取>
这个作业的目标<完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序>
其他参考文献《构建之法》

目录

  • 0.gitcode仓库地址
  • 1.PSP表格
  • 2.解题思路
  • 2.1 从相关网站获取json
  • 2.2 json解析
  • 3.设计与实现
  • 4.性能改进
  • 5.单元测试
  • 6.异常处理
  • 7.心路历程

0.gitcode仓库地址

仓库地址

1.PSP表格

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

2.解题思路

2.1 从相关网站获取json

进入网站相关页面,按F12开启浏览器开发者模式(这里使用的是火狐浏览器),选择“网络”项,分别搜索players和results,找到json文件并下载到本地。

img

img

img

2.2 json解析

使用阿里的fastjson解析,参考CSDN:fastjson的基本使用方法

根据从网上获取的json文件结构设计获取目标数据的步骤

对于输出选手信息,从players.json里直接以相关键值获取

img

对于输出比赛结果,时间直接从actual_start_time键值获取,比分则需要解析team键值的json,其中的score键值json的game键值中保存了比分数据

img

获取胜者的步骤则更繁琐,先获得胜利队伍的id,在teams数组中找到相应队伍,

img

获取队伍json中保存的选手id,再去players数组中找到相应选手

img

3.设计与实现

在工具类Lib中实现相应功能,Lib对外提供两个静态函数write_Players()和write_Results(String date)分别实现写入选手信息和比赛结果两个功能。

首先是读取json文件:

private static String get_JsonStr(String fileName) throws IOException{
        File jsonFile = new File(fileName);
        if (!jsonFile.exists())//检查json文件是否存在
            throw new FileNotFoundException("未找到" + fileName);

        FileInputStream is = new FileInputStream(jsonFile);
        int iAvail = is.available();
        byte[] bytes = new byte[iAvail];
        is.read(bytes);
        String allMsg = new String(bytes);//从文件中获取json字符串
        is.close();
        return allMsg;
}

write_Players()代码:

public static void write_Players() throws Exception {
        JSONObject js = JSONObject.parseObject(get_JsonStr(FILE_PLAYERS));
        JSONArray players = JSONArray.parseArray(js.getString("players"));//获取运动员json数组

        StringBuilder playersMsg = new StringBuilder();//遍历运动员数组,把相关信息加入文件输出
        for (int i = 0;i < players.size();i++){
            JSONObject player = players.getJSONObject(i);
            playersMsg.append("full_name:" + player.getString("full_name") + "\n");
            playersMsg.append("gender:" +
                    (player.getString("gender").equals("M")?"male":"female") + "\n");
            playersMsg.append("nationality:" +
                    player.getJSONObject("nationality").getString("name") + "\n");
            playersMsg.append("-----\n");
        }
        write_Msg(playersMsg.toString());
    }

对于比赛结果,需要多次解析和查询json数组,为此将根据id查找队伍和查找选手独立成函数:

private static JSONObject find_Team(String teamUuid,JSONArray teams){
        for (int i = 0;i < teams.size();i++)
            if (teams.getJSONObject(i).getString("uuid").equals(teamUuid))
                return teams.getJSONObject(i);
        return null;
}
private static JSONArray find_Winners(String playerUuids,JSONArray players){
        JSONArray winners = new JSONArray();
        JSONArray uuids = JSONArray.parseArray(playerUuids);//需要查找的选手uuid
        for (int i = 0;i < uuids.size();i++){
            String uuid = uuids.getString(i);
            for (int j = 0;j < players.size();j++)
                if (players.getJSONObject(j).getString("uuid").equals(uuid))
                    winners.add(players.getJSONObject(j));
        }
        return winners;
}

在write_Results(String date)中调用这些查询函数来获得每场比赛的详细数据,包括双方队伍成员、比分,和胜利队伍。

JSONObject match = matches.getJSONObject(i);//当天的每场比赛
JSONArray teamAB = JSONArray.parseArray(match.getString("teams"));
JSONObject teamA = teamAB.getJSONObject(0);
JSONObject teamB = teamAB.getJSONObject(1);//比赛双方队伍
JSONObject winnerTeam = teamA.getString("status") == null ? teamB:teamA;//获胜队伍
//通过team_id从teams中查找详细的队伍信息json(用于获取选手id)
String winnerUuids = find_Team(winnerTeam.getString("team_id"),teams).getString("players");
JSONArray winners = find_Winners(winnerUuids,players);//获得获胜选手json数组

4.性能改进

对每条指令都执行了完整的数据获取流程,其中读取文件花费了较多时间,于是使用缓存机制,每次输出的结果先保存在程序中,在访问文件之前判断该指令是否是重复指令,为重复指令省去了读取文件的时间。

public static void write_Players() throws Exception {
        if(cache.containsKey("players")){
            write_Msg(cache.get("players"));
            return;
        }
        ...
        cache.put("players",playersMsg.toString());
        write_Msg(playersMsg.toString());
}
public static void write_Results(String date){
        if(cache.containsKey(date)){
            write_Msg(cache.get(date));
            return;
        }
        ...
        cache.put(date,resultsMsg.toString());
        write_Msg(resultsMsg.toString());
}

生成一个万级输入样例:

private static void writeTest(){
        Lib.set_OutputFile("input.txt");
        for (int i = 0;i < 10000;i++){
            Lib.write_Msg("players\nresult 0116\nresult Q1\n");
        }
}

不使用缓存:304,798ms
使用缓存:18,791ms
可以看出减少了文件读取后,运行时间大幅提升。

5.单元测试

使用了Junit5.8.1进行测试,对main函数在类中设置标志值passed表示命令行指令是否合法。

AOSearchTest.java:

class AOSearchTest {
    @Test
    void main() {
        AOSearch.main(new String[]{"input.txt","output.txt"});
        assertTrue(AOSearch.passed);
        AOSearch.main(new String[]{"in.txt","output.txt"});
        assertFalse(AOSearch.passed);
        AOSearch.main(new String[]{"input.txt"});
        assertFalse(AOSearch.passed);
        AOSearch.main(new String[]{"input.txt","output.txt","x"});
        assertFalse(AOSearch.passed);
    }
}

覆盖率:

img

对Lib类的两个基本输出功能进行测试,主要是对是否抛出异常做检查,当找不到对应文件时抛出异常。

LibTest.java

class LibTest {
    @Test
    void write_Players() {
        assertDoesNotThrow(()->Lib.set_OutputFile("output.txt"));
        assertDoesNotThrow(()->Lib.write_Players());
    }
    @Test
    void write_Results() {
        assertDoesNotThrow(()->Lib.set_OutputFile("output.txt"));
        assertThrows(FileNotFoundException.class,()->Lib.write_Results("X"));
        assertThrows(FileNotFoundException.class,()->Lib.write_Results("XX XX"));
        assertThrows(FileNotFoundException.class,()->Lib.write_Results("0115"));
        assertThrows(FileNotFoundException.class,()->Lib.write_Results("0130"));
        assertThrows(FileNotFoundException.class,()->Lib.write_Results("Q5"));
        assertThrows(FileNotFoundException.class,()->Lib.write_Results("Qq"));
        //assertThrows(FileNotFoundException.class,()->Lib.write_Results("q1"));
        assertDoesNotThrow(()->Lib.write_Results("0129"));
        assertDoesNotThrow(()->Lib.write_Results("0118"));
        assertDoesNotThrow(()->Lib.write_Results("Q3"));
        assertDoesNotThrow(()->Lib.write_Results("Q1"));
    }
}

覆盖率:

img

经过测试发现java打开txt文件时不区分文件名大小写,导致指令INPUT.txt,result q1都判为正确指令,并且能找到相应文件,对此不作处理
(开摆)

6.异常处理

发现指令错误时手动抛出异常跳转到程序底部处理,进行两层try-catch,一层接收Lib类抛出的异常,表示文件(input.txt)中的指令错误,最上层即对命令行指令错误的提示。

Lib内部异常抛出代码

JSONObject js = null;
try {
        js = JSONObject.parseObject(get_JsonStr(FILE_RESULT + date + ".json"));
}catch (Exception e){
        write_Msg("N/A\n-----\n");
        throw new FileNotFoundException("未找到" + FILE_RESULT + date + ".json");
}

main部分获取异常代码

passed = true;
        try{
            if (args.length != 2)
                throw new IOException("参数数量必须是2");
            Lib.set_OutputFile(args[1]);//设置输出文件名
            BufferedReader reader = new BufferedReader(new FileReader(args[0]));//读取输入文件
            String commands;
            while ((commands = reader.readLine()) != null){//逐行读取指令
                if (commands.isEmpty())
                    continue;
                commands = commands.trim();
                String[] command = commands.split("\\s+");//拆分指令
                try{
                    if (command[0].equals("players")){
                        if (command.length != 1)
                            throw new IOException("\"" + commands + "\"错误");
                        Lib.write_Players();//通过检查,指令为players
                    }
                    else if (command[0].equals("result")){
                        if (command.length != 2){
                            Lib.write_Msg("N/A\n-----\n");
                            throw new FileNotFoundException("\"" + commands + "\"错误");
                        }
                        Lib.write_Results(command[1]);//通过初步检查,指令为result X
                    }
                    else{
                        throw new Exception("\"" + commands + "\"错误");
                    }
                }catch (FileNotFoundException e){
                    System.out.println(args[0] + "中指令错误:" + e.getMessage());
                }
                catch (Exception e){
                    System.out.println(args[0] + "中指令错误:" + e.getMessage());
                    Lib.write_Msg("Error\n-----\n");
                }
            }
        }catch(Exception e){
            System.out.println("命令行参数错误:" + e.getMessage());
            passed = false;
        }

7.心路历程

  一开始看到作业要求的时候没觉得多复杂,实际开始项目的时候却一头雾水,对很多要求不进一步思考就无法动手实现。于是花了很多时间搜索相关内容,设计工具类,研究下载的json文件的结构,由于程序本身功能并不复杂,开始编码后很快就实现了基础的两个功能。

  但是到此离完成程序还有一段距离,之前做测试都只是在控制台手动输入输出,对单元测试,自动化测试一窍不通,卡了几天没学会具体到idea使用测试的方法,还好[第二届构建之法论坛] 预培训文档(Java版)救了我。

...全文
1869 1 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
SoftwareTeacher 2023-03-02
精选
  • 打赏
  • 举报
回复
5.00元

对单元测试,自动化测试一窍不通,卡了几天没学会具体到idea使用测试的方法,还好[第二届构建之法论坛] 预培训文档(Java版)救了我。


请你也写高质量博客, 救救其他的学生。

586

社区成员

发帖
与我相关
我的任务
社区描述
软件工程-2022-23学年(第二学期)
软件工程 高校
社区管理员
  • LinQF39
  • promisekoloer
  • 异梦1
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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