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

222000311潘昕 学生 2023-03-02 14:50:57
这个作业属于哪个课程2023年福大-软件工程实践-W班
这个作业要求在哪里软件工程实践第二次作业
这个作业的目标完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献工程师的能力评估和发展单元测试 & 回归测试阿里巴巴JAVA开发手册JAVA中文件写入的6种方法

目录

  • 1. Gitcode项目地址
  • 2. PSP表格
  • 3. 解题思路描述
  • 4. 设计和实现过程
  • 5. 关键代码展示
  • 6. 性能改进
  • 7. 单元测试
  • 8. 异常处理
  • 9. 心得体会

1. Gitcode项目地址

oorty / Project-Java · GitCode

2. PSP表格

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

3. 解题思路描述

  • 爬取数据

    打开需要的网页,F12调出开发者工具,点击某日程,选择名为“ result ”的请求,因为并没有要求程序内爬取,请求的 Response 即为所需内容

img

  • 指令解析

    和读取指令方法放在一个类中,供主类调用,返回值表示不同的解析类型

  • JSON解析

    一开始的想法是转化为bean处理,没有想到 json 结构这么复杂,对象数组各种嵌套,于是采用(暴力的)直接解析,使用 fastjson

    这里还涉及处理后字符串的拼接,需要选择合适的类

  • 文件的读写

    这部分参考了网上许多的文章,在不断优化的过程中,自己对于这些读写类也有了更深的理解

4. 设计和实现过程

img

  • AOSearch

    仅含main函数,负责程序总的流程控制,调用其它方法

  • CommandReader

    包含两个静态方法 readcheck,分别负责指令的读取与检验

  • PlayerReader & TournamentReader

    包含各自返回相应字符串的静态方法 getResult ,同时引入了缓存机制

    赛程中获取winner是个比较麻烦的点,需要team_id -- player_id -- short_name这么一个过程。在遍历matches我使用ArrayList<String>保存获胜的team_id,在遍历teams时替换为对应的player_id,值得注意的是player_id可能不止一个,但该情况并不多见,于是放弃将数组改为ArrayList<String[]>(太浪费了),改用一个HashMap存储,键为player_id,值为对应的数组下标。(略显丑陋的做法

  • Tournament

    赛程类对象,主要目的在于方便输出结果(toString,以及一些特殊输出)。为什么没有player对象?因为player解析没这么麻烦,不需要

5. 关键代码展示

  • 核心控制流程

    try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputPath,true))){
                CommandReader.read(inputPath);
                for(String s:CommandReader.command){
                    switch (CommandReader.check(s)){
                        case 0:
                            bufferedWriter.append("ERROR\n-----\n");
                            break;
                        case 1:
                            bufferedWriter.append(PlayerReader.getResult());
                            break;
                        case 2:
                            String date = s.substring(7);
                            bufferedWriter.append(TournamentReader.getResult(date));
                            break;
                        case 3:
                            bufferedWriter.append("N/A\n-----\n");
                            break;
                    }
                }
            }
            catch (FileNotFoundException e){
                System.out.println();
            }
    
  • 指令检验

     /**
         * 检验指令的合法性,返回结果
         * @return 检测结果
         * 0-ERROR 1-players 2-result 3-N/A
         */
        public static int check(String command) {
            //按空格拆分字符串
            String[] s = command.split("\\s+");
    
            if(s[0].equals("players")&& s.length==1){
                return 1;
            }
            else if(s[0].equals("result")){
                if(s.length != 2){
                    return 3;
                }
                //检验日期是否为 Q1 ~ Q4
                if(s[1].charAt(0) == 'Q'){
                    if(s[1].length() == 2 && s[1].charAt(1) > '0' && s[1].charAt(1) < '5'){
                        return 2;
                    }
                    return 3;
                }
                //检验日期是否为 0116 ~ 0129
                else if(s[1].charAt(0) != '0' || s[1].length()!=4){
                    return 3;
                }
                int date = 0;
                try {
                    date = Integer.parseInt(s[1]);
                } catch (NumberFormatException e) {
                    return 3;
                }
                if(date<116 || date>129){
                    return 3;
                }
                return 2;
            }
            return 0;
        }
    
  • 赛程处理

    public static String getResult(String date) throws Exception {
            // 读取缓存
            String res = memory.get(date);
            if(res != null){
                System.out.println("return "+date+" memory");
                return res;
            }
    
            //构造文件路径
            String path = "src/main/java/data/"+date+".json";
    
            File file = new File(path);
            if (!file.exists()) {
                throw new FileNotFoundException("赛程文件 " + date + ".json 不存在");
            }
            //转为字符流
            InputStreamReader isr = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
            StringBuilder sb = new StringBuilder();
            int ch;
            while ((ch = isr.read()) != -1) {
                sb.append((char) ch);
            }
            isr.close();
    
            //记录获胜者信息(team_id ~ player_id ~ name
            ArrayList<String> winner = new ArrayList<>();
            Map<String,Integer> anotherWinner = new HashMap<>();
            List<Tournament> tournaments = new ArrayList<>();
    
            JSONObject root = JSONObject.parseObject(sb.toString());
            JSONArray matches = root.getJSONArray("matches");
            for (int i = 0; i < matches.size(); i++) {
                //双方比分
                ArrayList<String> scores = new ArrayList<>();
                //回合数
                int n = 0;
                //赛程对象
                Tournament temp = new Tournament();
                //记录比赛时间
                String time = matches.getJSONObject(i).getString("actual_start_time");
                //平局
                if(time == null){
                    temp.setWO(true);
                    tournaments.add(temp);
                    continue;
                }
                temp.setTime(time);
    
                JSONArray teams = matches.getJSONObject(i).getJSONArray("teams");
                for (int l = 0; l < 2; l++) {
                    JSONArray sco = teams.getJSONObject(l).getJSONArray("score");
                    n = sco.size();
                    for (int p = 0; p < n; p++) {
                        scores.add(sco.getJSONObject(p).getString("game"));
                    }
                }
                //记录获胜者team_id
                if (teams.getJSONObject(0).getString("status") != null) {
                    winner.add(teams.getJSONObject(0).getString("team_id"));
                } else {
                    winner.add(teams.getJSONObject(1).getString("team_id"));
                }
    
                //记录比分
                StringBuilder scoBuilder = new StringBuilder();
                for (int q = 0; q < n; q++) {
                    scoBuilder.append(scores.get(q) + ":" + scores.get(q + 2));
                    //添加间隔
                    if (q != n - 1) {
                        scoBuilder.append(" | ");
                    }
                }
                temp.setScore(scoBuilder.toString());
                tournaments.add(temp);
            }
    
            //记录获胜者player_id
            JSONArray teams = root.getJSONArray("teams");
            for (int j = 0; j < teams.size(); j++) {
                int q = winner.indexOf(teams.getJSONObject(j).getString("uuid"));
                if(q == -1){
                    continue;
                }
                JSONArray players = teams.getJSONObject(j).getJSONArray("players");
                winner.set(q,players.getString(0));
                //若获胜者不止一个
                if (players.size() > 1) {
                    anotherWinner.put(players.getString(1),q);
                }
            }
    
            //记录获胜者姓名
            JSONArray players = root.getJSONArray("players");
            for(int k=0;k<players.size();k++){
                String uuid = players.getJSONObject(k).getString("uuid");
                int q = winner.indexOf(uuid);
    
                //数组没有,map找找
                if(q == -1){
                    if(anotherWinner.containsKey(uuid)==true){
                        int t = anotherWinner.get(uuid);
                        anotherWinner.remove(uuid);
                        tournaments.get(t).setWinner(players.getJSONObject(k).getString("short_name"));
                    }
                    continue;
                }
    
                //数组有
                tournaments.get(q).setWinner(players.getJSONObject(k).getString("short_name"));
            }
    
            StringBuilder sBuilder = new StringBuilder();
            for(Tournament t:tournaments){
                sBuilder.append(t.toString());
            }
    
            res = sBuilder.toString();
            memory.put(date,res);
            return res;
        }
    

6. 性能改进

  • 将字符串拼接操作由StringBuffer替换为 StringBuilder

    对于字符串的修改,有StringStringBufferStringBuilder三种方式

    • 采用String修改,每次都会创建一个新的string对象,然后指向新串
    • StringBuffer是线程安全的,公开方法中都有synchronized 修饰
    • StringBuilder不是线程安全的,因此效率比StringBuffer

    因为该程序为单线程,所以StringBuffer替换为 StringBuilder

    替换前

img

替换后

img

7. 单元测试

  • 覆盖率测试

    未到达100%的原因在于异常处理部分,单元测试无法全部覆盖

img

  • 测试用例

基本包含了所有情况

img

8. 异常处理

  • 读取文件失败,输入文件无法打开的异常

    //in CommandReader
    File file = new File(path);
    if (!file.exists()) {
        throw new FileNotFoundException("输入文件不存在");
    }
    
    //TournamentReader
    File file = new File(path);
    if (!file.exists()) {
         throw new FileNotFoundException("赛程文件 " + date + ".json 不存在");
    }
    
    //PlayerReader
    File file = new File(path);
    if (!file.exists()) {
          throw new FileNotFoundException("选手信息文件不存在");
    }
    

    以上异常在main中捕获

img

  • 写入文件失败,输出文件无法打开的异常

    try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputPath,true))){....}
    

    使用此方式打开输出文件,若文件不存在将自动新建

    此外,在 JDK 7 以上的版本,使用 try-with-resource 的方式可以实现资源的释放,上面的BufferedWriterFileWriter都将在执行语句块后自行释放

9. 心得体会

  • 对于文件划分的合理性,代码的规范性还有许多要学习的地方
  • 代码不要想着一下子写得很好,面面俱到,先抓大体,能跑再说。初期阶段过于注重细节会导致开发效率低下,心态还容易崩
  • 虽说遇到问题要先自己解决,但如果解决进展不顺,不妨与其它同学交流交流,或许能有新的启发
...全文
191 1 打赏 收藏 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
  • 打赏
  • 举报
回复

代码不要想着一下子写得很好,面面俱到,先抓大体,能跑再说。初期阶段过于注重细节会导致开发效率低下,心态还容易崩

可以之后再重构~
优化是否可以再进一步?

688

社区成员

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

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