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

222000434杨蕊蘭 学生 2023-03-02 16:52:21
这个作业属于哪个课程2023年福大-软件工程实践-W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标GitCode项目上传、代码编写并测试、博客撰写
其他参考文献相关链接置于文末

目录

  • 1. Gitcode项目地址
  • 2. PSP表格
  • 3. 解题思路描述
  • 3.1 获取数据
  • 3.2 解析数据
  • 3.3 读取并分析指令
  • 3.4 数据输出
  • 4. 接口设计和实现过程
  • 5. 关键代码展示
  • 5.1 解析json获取实体类对象
  • 5.2 主要流程
  • 5.3 指令判断
  • 5.4 输出winner
  • 6. 性能改进
  • 6.1 优化前
  • 6.2 第一次优化
  • 6.3 第二次优化
  • 6.4 CMD测试效果
  • 7. 单元测试
  • 7.1 部分测试代码
  • 7.2 覆盖率
  • 8. 异常处理
  • 8.1 文件个数异常
  • 8.2 输入文件不存在
  • 8.3 读写异常
  • 9. 心得体会
  • 相关链接

1. Gitcode项目地址

仓库地址:project-java

2. PSP表格

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

3. 解题思路描述

    经过多次阅读题目要求,分析之后得出本次作业可以分为四个部分。首先是从官方网站获取作业所需的数据;二是解析数据,将数据转换为所需要的格式;三是从input文件中读取并分析指令;四是将指令对应的数据写入output文件。

3.1 获取数据

①打开澳大利亚网球公开赛官网,进入开发者模式;

img


②刷新页面请求数据,找到result双击打开,url即为数据地址;

img


③将页面数据保存至本地,修改地址中的数字即可跳转至相应日期的赛程数据。

img

3.2 解析数据

    因为上学期的移动应用开发课程有介绍一些json的相关知识,所以选择用gson解析数据。一开始观察数据格式,发现数据量实在是太大了,看的很头疼,不知道应该如何设计实体类,所以选择通过在线转换工具,转换出Javabean,再选择需要的属性,嵌套生成对应的players和schedule类,在项目中导入gson的jar包即可使用。
    部分转换效果如图:

img

3.3 读取并分析指令

    使用BufferedReader对input文件进行读取,每读取一行就对指令进行相应的判断,流程图如下:

img

3.4 数据输出

    对于players可以使用解析数据得到的实体类的get方法得到相应的full_name、gender、nationality,使用BufferedWriter的write方法向output文件写入对应格式的数据。
    对于赛程数据的输出,首先需要判断该场比赛是否正常进行,还是存在弃赛情况,若比赛正常进行,由于JSON数据的结构并不是直接对应我们需要的数据,需要通过获胜队伍的id在teams列表中找到对应的队伍,获取其包含的队员的id,再从players列表中查找对应的player,得到short_name;弃赛则输出W/O。

img


img


img

4. 接口设计和实现过程

    首先是JSON数据解析所需要的两个实体类,其内部结构必须与数据结构相同,正确嵌套,对应的名称也要一样。

//省略对应的get、set函数
public class Json2players {
    private List<Players> players;
    public static class Players {
        private String uuid;
        private String full_name;
        private String gender;
        private Nationality nationality;

        public static class Nationality {

            private String name;
        }
    }
}
//省略对应的get、set函数
public class Json2schedule {
    private List<Matches> matches;
    private List<Players> players;
    private List<Teams> teams;
    public static class Matches {
        private String actual_start_time;
        private List<Teams> teams;
        private Match_status match_status;
        public static class Teams {
            private String team_id;
            private List<Score> score;
            private String status;
            public static class Score {
                private String game;
            }
        }

        public static class Match_status {
            private String abbr;
        }
    }

    public static class Players {
        private String uuid;
        private String short_name;
    }

    public static class Teams {
        private String uuid;
        private List<String> players;
    }
}

    AOSearch类作为程序的入口,进行相应的文件异常的处理,以及程序执行流程的条件判断,调用Lib类中相应的功能函数。
    Lib类中包括以下功能函数

//team_id对应player缩写list
public static HashMap<String, List<String>> hashMap=new HashMap<>();
//对player.json进行解析,返回Json2players类对象
public static Json2players analyzeplayers() {...}

//date为指令中的对应日期字符串
//对date.json进行解析,返回Json2schedule类对象
public static Json2schedule analyzeschedule(String date) {...}

//比赛情况输出流程
public static void output(StringBuilder stringBuilder, Json2schedule jsonschedule) throws IOException {...}

//匹配对应获胜队伍的队员缩写
//输出winner
public static  void output_winner(StringBuilder stringBuilder, Json2schedule.Matches matches, Json2schedule jsonschedule) {...}

//输出score
public  static void output_score(StringBuilder stringBuilder, Json2schedule.Matches matches) {...}

//按格式输出player
public static void output_players(StringBuilder stringBuilder, Json2players jsonplayers) throws IOException {...}

//判断指令
public static String analyzeinstruct(String instruct) {...}

5. 关键代码展示

5.1 解析json获取实体类对象

//对player.json进行解析,返回Json2players类对象
public static Json2players analyzeplayers() {
    String path="./src/data/players.json";
    BufferedReader br=null;
    try {
        br=new BufferedReader(new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    Gson gson=new Gson();
    Type type=new TypeToken<Json2players>() {}.getType();
    Json2players json_players=gson.fromJson(br,type);
    try {
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return json_players;
}

5.2 主要流程

String instruct;
while ((instruct=reader.readLine())!=null) {
    instruct.trim();
    StringBuilder sb=new StringBuilder();
    //判断指令类型
    String str=Lib.analyzeinstruct(instruct);
    if (!"null".equals(str)) {//跳过空行
        if (!str.equals("N/A")&&!str.equals("Error")) {
            if (hashMap.containsKey(str)) {
                stringBuilder.append(hashMap.get(str));
            }
            else {
                if (str.equals("players")) Lib.output_players(sb,Lib.analyzeplayers());
                else Lib.output(sb,Lib.analyzeschedule(str));
                hashMap.put(str,sb);
                stringBuilder.append(sb);
            }
        }
        else stringBuilder.append(str).append("\n-----\n");
    }
    count++;
    if (count==10000) {
        writer.write(String.valueOf(stringBuilder));
        stringBuilder.setLength(0);
        count=0;
    }
}
writer.write(String.valueOf(stringBuilder));

5.3 指令判断

//判断指令
public static String analyzeinstruct(String instruct) {
    String str = "";
    if (instruct.equals("")) str="null";//空行
    else {
        String[] list=instruct.split("\\s+");
        if (list.length==0) str="null";
        else if ("result".equals(list[0])) {
            if (list.length==2&&(list[1].equals("0116")||list[1].equals("0117")||list[1].equals("0118")||list[1].equals("0119")||list[1].equals("0120")
                    ||list[1].equals("0121")||list[1].equals("0122")||list[1].equals("0123")||list[1].equals("0124")||list[1].equals("0125")||list[1].equals("0126")
                    ||list[1].equals("0127")||list[1].equals("0128")||list[1].equals("0129")||list[1].equals("Q1")||list[1].equals("Q2")||list[1].equals("Q3")||list[1].equals("Q4"))) str=list[1];
            else str="N/A";
        }
        else if ("players".equals(list[0])) {
            if (list.length==1) str="players";
            else str="Error";
        }
        else if (list[0].equals("")) {
            if ("result".equals(list[1])) {
                if (list.length==3&&(list[2].equals("0116")||list[2].equals("0117")||list[2].equals("0118")||list[2].equals("0119")||list[2].equals("0120")
                        ||list[2].equals("0121")||list[2].equals("0122")||list[2].equals("0123")||list[2].equals("0124")||list[2].equals("0125")||list[2].equals("0126")
                        ||list[2].equals("0127")||list[2].equals("0128")||list[2].equals("0129")||list[2].equals("Q1")||list[2].equals("Q2")||list[2].equals("Q3")||list[2].equals("Q4"))) str=list[2];
                else str="N/A";
            }
            else if ("players".equals(list[1])) {
                if (list.length==2) str="players";
                else str="Error";
            }
        }
        else str="Error";
    }
    return str;
}

5.4 输出winner

//获取获胜队伍id
if ("Winner".equals(matches.getTeams().get(0).getStatus()))
    id=matches.getTeams().get(0).getTeam_id();
else id=matches.getTeams().get(1).getTeam_id();
//根据队伍id匹配队伍
//访问过的队伍数据直接从hashmap中获取
//未访问过的通过json数据得到,并放入hashmap
List<String> list=new ArrayList<>();
if (hashMap.containsKey(id)) {
    list = hashMap.get(id);
    for (int i = 0; i < list.size(); i++) {
        str.append(list.get(i));
        if (i != list.size() - 1) str.append(" & ");
    }
}
else {
    for (int j=0;j<jsonschedule.getTeams().size();j++) {
        if (id.equals(jsonschedule.getTeams().get(j).getUuid())) {
            for (int k=0;k<jsonschedule.getTeams().get(j).getPlayers().size();k++) {
                String playerid=jsonschedule.getTeams().get(j).getPlayers().get(k);
                //根据playerid匹配short_name
                for (int l=0;l<jsonschedule.getPlayers().size();l++) {
                    if (playerid.equals(jsonschedule.getPlayers().get(l).getUuid())) {
                        str.append(jsonschedule.getPlayers().get(l).getShort_name());
                        list.add(jsonschedule.getPlayers().get(l).getShort_name());
                        break;
                    }
                }
                if (k!=jsonschedule.getTeams().get(j).getPlayers().size()-1) str.append(" & ");
            }
            break;
        }
    }
    hashMap.put(id,list);
}

6. 性能改进

6.1 优化前

    一开始没有使用StringBuilder记录需要往output文件内写入的内容,最后一次性写入,而是一边判断,执行相应的获取数据功能,一边向文件写入,当写入的数据量很大时,有多少行数据,就要写入多少次,十分耗时;并且还存在每读取一条有效指令,就要打开、关闭一次json数据文件,并且使用Gson进行解析,也是十分耗时的操作。优化前执行32万行有效指令耗时如图:

img

6.2 第一次优化

    使用StringBuilder记录需要写入的内容,每次只需要使用append()方法加入,最后只需要向文件写入一次。使用HashMap记录日期与对应的结果,每次读取到有效指令时先判断是否在HashMap中,存在可直接取出对应键值加入StringBuilder中,不存在则执行相应的获取数据的功能函数,并加入HashMAp。第一次优化执行3万行有效指令耗时如图:

img


当执行30万行有效指令时,程序报错:Java heap space,内存溢出,我认为是因为StringBuilder过大。

6.3 第二次优化

    再次使用HashMap,用于从team_id匹配到player的short_name,每次查找时,先判断是否在HashMap中,存在则取出缩写的list,不存在则进行解析查找,最后加入HashMap。因为StringBuilder过大,所以增加一个计数的count变量,每读取解析10000条指令,就向output文件输入一次,使用将StringBuilder的length置零的方法清除数据。第二次优化执行3万行有效指令耗时如图:

img


第二次优化执行32万行有效指令耗时如图,文件大小2.04G:

img


    虽然第二次优化执行3万行有效指令的速度和第一次优化差不多,但是解决了内存溢出问题,并且从优化前到第二次优化后,速度快了95倍!

6.4 CMD测试效果

    在CMD中测试32万条有效指令,运行时间会比直接运行源程序慢3秒左右。

img

7. 单元测试

7.1 部分测试代码

  • 对分析指令类型进行测试
    @Test
    public void analyzeinstruct() {
      assertEquals("Error",Lib.analyzeinstruct("12345"));
      assertEquals("Error",Lib.analyzeinstruct("resu"));
      assertEquals("Error",Lib.analyzeinstruct("resultplayer"));
      assertEquals("Error",Lib.analyzeinstruct("pla yers"));
      assertEquals("Error",Lib.analyzeinstruct("re sult0120"));
      assertEquals("Error",Lib.analyzeinstruct("result0116"));
      assertEquals("Error",Lib.analyzeinstruct("player"));
      assertEquals("Error",Lib.analyzeinstruct("players  2"));
      assertEquals("Error",Lib.analyzeinstruct("  players  11"));
      assertEquals("N/A",Lib.analyzeinstruct("   result   0116  23"));
      assertEquals("N/A",Lib.analyzeinstruct("result Q 01"));
      assertEquals("N/A",Lib.analyzeinstruct("result 01166"));
      assertEquals("N/A",Lib.analyzeinstruct("result 0155"));
      assertEquals("N/A",Lib.analyzeinstruct("result q"));
      assertEquals("N/A",Lib.analyzeinstruct("result"));
      assertEquals("null",Lib.analyzeinstruct(""));
      assertEquals("null",Lib.analyzeinstruct("       "));
      assertEquals("Q4",Lib.analyzeinstruct("result Q4"));
      assertEquals("0116",Lib.analyzeinstruct("result 0116"));
      assertEquals("0116",Lib.analyzeinstruct("result   0116"));
      assertEquals("0129",Lib.analyzeinstruct("   result   0129"));
      assertEquals("players",Lib.analyzeinstruct("players"));
      assertEquals("players",Lib.analyzeinstruct("players  "));
      assertEquals("players",Lib.analyzeinstruct("  players  "));
    }
    
  • 对解析数据进行测试
    @Test
    public void analyzeschedule() {
      assertNotNull(Lib.analyzeschedule("0116"));
      assertNotNull(Lib.analyzeschedule("0117"));
      assertNotNull(Lib.analyzeschedule("0118"));
      assertNotNull(Lib.analyzeschedule("0119"));
      assertNotNull(Lib.analyzeschedule("0120"));
      assertNotNull(Lib.analyzeschedule("0121"));
      assertNotNull(Lib.analyzeschedule("0122"));
      assertNotNull(Lib.analyzeschedule("0123"));
      assertNotNull(Lib.analyzeschedule("Q1"));
      assertNotNull(Lib.analyzeschedule("Q2"));
      assertNotNull(Lib.analyzeschedule("Q3"));
    }
    

7.2 覆盖率

只对Lib类中的功能函数进行测试

img

8. 异常处理

8.1 文件个数异常

if (args.length!=2) {
    System.out.println("输入的文件个数有误!");
    System.exit(0);
}

8.2 输入文件不存在

if (!input.exists()) {
    System.out.println("未找到该输入文件!请检查您的文件路径:" + args[0]);
    System.exit(0);
}

8.3 读写异常

try {
    BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(args[0]), StandardCharsets.UTF_8));
    BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(args[1]), StandardCharsets.UTF_8));
    stringBuilder= new StringBuilder();
    hashMap= new HashMap<>();
    try {
        String instruct;
        while ((instruct=reader.readLine())!=null) {...}
        writer.write(String.valueOf(stringBuilder));
        reader.close();
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
}

9. 心得体会

    首先是合理安排时间真的非常重要!因为疫情原因这学期开学还需要进行上学期一些课程的缓考,而且还有本学期课程要上,导致时间非常紧,考试和作业都很重要,只能疯狂挤时间😭😭😭。
    然后是要努力提高专注力和效率,这样才能深入地去思考和设计如何实现这些功能,避免磕磕绊绊地编程,保证程序的质量同时节约时间。
    再是心态很重要!面对这么多事情的压力,我很担心自己没有办法处理好它们,一开始看到这次实战的项目要求,我的内心是抗拒的,因为涉及到的知识点、方法还有整个设计过程都是我不曾接触、学习的,特别是当我看到庞大的json数据并且担心gson没有办法成功解析的时候,我整个人都非常的消极。所以我觉得保持良好的心态是十分重要的,遇到困难害怕是正常的,但是要有迎难而上的心,而不是知难而退。
    最后是多学一点很重要,学无止境,会的越多,在遇到困难的时候能解决的办法就越多,就比如说在解析数据的时候,如果我会使用其他方法解析json数据,那么也就不会担心gson是否能成功,在面对项目要求的时候,如果我学习过性能改进、单元测试这些知识的话,心态是否就大不相同了呢?
    在本次作业中,对JSON数据的处理,文件的使用,性能分析以及单元测试等,对我来说都是比较困难的点,特别是性能分析和单元测试,希望在之后的课程能进一步地学习如何设计测试数据,使测试更加有效。

相关链接

【Java】Map集合概述
Java使用GSON对JSON进行解析
保姆级教程——IDEA中使用Junit进行测试
[JAVA] java: 错误: 无效的源发行版:17

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

完成度很高,赞!

虽然第二次优化执行3万行有效指令的速度和第一次优化差不多,但是解决了内存溢出问题,并且从优化前到第二次优化后,速度快了95倍!

整个优化过程有思路有数据支持,很好地体现优化过程,赞!
勇敢走出舒适圈,不断挑战自己的上限,就会发现事情并没有那么困难。加油!!!

222000434杨蕊蘭 学生 2023-03-22
  • 举报
回复
@2023年福大-软件工程实践-W班 谢谢老师!我会继续努力的

686

社区成员

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

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