软件工程实践第二次作业(改)

222000314邵彧扬 学生 2023-06-12 18:05:12
这个作业属于哪个课程 2023软工W班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标完成对澳大利亚网球公开赛相关数据的收集,并实现一个能够对赛事数据进行统计的控制台程序
其他参考文献《构建之法》、软件测试开发实战

目录

  • 1. gitcode仓库地址
  • 2. PSP表格
  • 3. 解题思路描述
  • 3.1 JSON数据的获取
  • 3.2 对JSON的解析
  • 3.3 需求分析
  • 4. 接口设计和实现过程
  • 4.1 项目中的类
  • 4.2 Lib下的函数
  • 4.3. 实现过程
  • 5. 关键代码展示
  • 6. 性能改进
  • 6.1 IO优化
  • 6.2 优化缓存
  • 7. 单元测试
  • 8. 异常处理
  • 8.1 函数异常处理
  • 8.2 json 异常处理
  • 8.3 AOSearch 主函数部分异常处理
  • 9. 心得体会

1. gitcode仓库地址

我的代码

2. PSP表格

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

 

3. 解题思路描述

3.1 JSON数据的获取

    进入目标网站,打开F12开发者模式(浏览器为edge),打开网络搜索players和results,找到对应的json文件并下载到本地。
    players只有一份。而results一共分为14个比赛日和4个赛季Q1,Q2,Q3,Q4共18份。

img

img

3.2 对JSON的解析

使用的是fastjson解析,在idea中导入相关的jar包,然后再编写一个读取JSON文件的函数,将JSON中的数据转换为String。
再用JSONObject.parseObject将读取到的String转换为JSON对象。最后用JSONArry保存getString()函数读取到需要的数据,遍历输出即可。

 

3.3 需求分析

1.输出players

在对于players的输出中,逻辑比较简单,直接遍历输出即可。

/*
先用函数读取JSON文件中的信息,将其转换为JSONObject对象,
再用getString函数获取"players"相关数据,用JSONArry保存。
再对JSONArry对象进行遍历查询相关输出,用StringBuilder的append函数添加,
最后用编写的输出函数输入到相关的文件中。
*/
public static void writePlayers() throws Exception
    {
        if(map.containsKey("players")){
            writeMsg(map.get("players"));
            return;
        }
 
        JSONObject js = JSONObject.parseObject(getJsonStr(PlayersFile));
        JSONArray players = JSONArray.parseArray(js.getString("players"));//获取运动员的json数组
        StringBuilder playersMsg = new StringBuilder();
        //for循环查找运动员中指定的数据,输出到指定文件中
        for (int i = 0;i < players.size();i++)
        {
            JSONObject player = players.getJSONObject(i);
            playersMsg.append("full_name:").append(player.getString("full_name")).append("\n");
            playersMsg.append("gender:").append(player.getString("gender").equals("M") ? "Male" : "Female").append("\n");
            playersMsg.append("nationality:").append(player.getJSONObject("nationality").getString("name")).append("\n");
            playersMsg.append("-----\n");
        }
        writeMsg(playersMsg.toString());
        map.put("players",playersMsg.toString());
    }

2.输出result

一共分为三行输出,比赛的实际开始时间,获胜选手,每轮次的比分。

/*
获胜选手信息的获取,先对JSONObject对象js中获取matches相关数据,先定位到某一场比赛match,
再获取这场match中的teams数据,teams再分为teamA和teamB。
在teamA或B中有个"status"相关数据表示是否为胜者,根据此得出winnerTeam。
再根据winnerTeam中的uid去搜寻队伍中选手的uid,
最后根据选手的uid去选手数组里面遍历对比出胜者。
每轮次比分的获取,从teamA和teamB中搜寻score再循环输出即可。
*/
public static void writePlayers() throws Exception
    {
        if(map.containsKey("players")){
            writeMsg(map.get("players"));

            return;
        }
 
        JSONObject js = JSONObject.parseObject(getJsonStr(PlayersFile));

        JSONArray players = JSONArray.parseArray(js.getString("players"));

        StringBuilder playersMsg = new StringBuilder();
 
        for (int i = 0;i < players.size();i++)
        {
            JSONObject player = players.getJSONObject(i);
            playersMsg.append("full_name:").append(player.getString("full_name")).append("\n");

            playersMsg.append("gender:").append(player.getString("gender").equals("M") ? "Male" : "Female").append("\n");
            playersMsg.append("nationality:").append(player.getJSONObject("nationality").getString("name")).append("\n");

            playersMsg.append("-----\n");
        }

        writeMsg(playersMsg.toString());

        map.put("players",playersMsg.toString());
    }

 

4. 接口设计和实现过程

4.1 项目中的类

   本项目就分成两类:Lib(工具类)和 AOSearch(程序的入口)

    Lib:工具类,包含读取json文件,输出选手和比赛信息,写信息到文件,等需要用到的方法。
    AOSearch: 程序的入口,有对指令的输入是否合法进行判断。

 

4.2 Lib下的函数

//根据winnerUID查找选手
public static JSONArray findplayers(String winnerUID,JSONArray players)

//根据teamID查找队伍
public static JSONObject findTeams(String teamID,JSONArray teams)

//写入比赛信息
public static void writeResults(String date) throws Exception

//写入选手信息
public static void writePlayers() throws Exception

//读取JSON文件返回String
private static String getJsonStr(String fileName) throws IOException

//向文件中输出字符串
public static void writeMsg(String msg)

//写入胜者
public static void writeWinners( JSONObject match,StringBuilder resultsMsg,JSONObject js) 

//根据队伍写入比分
public static void writeScore( JSONObject match,StringBuilder resultsMsg)

//写入实际开始时间
public static void writeTime(JSONObject match,StringBuilder resultsMsg)

 

4.3. 实现过程

    AOSearch控制台启动后会调用Lib类中的writeResults和writePlayers进行输出。

 

5. 关键代码展示

下列代码是Lib.java的代码,包括了AOSearch程序的核心函数。

public class Lib {

    private static String outputFIle = "output.txt";//输出文件
    
    private static final String  PlayersFile= "src/data/players.json";//运动员文件路径
   
    private static final String  ResultsFile= "src/data/schedule/";//运动员文件路径

    private static HashMap<String,String> map = new HashMap<>();

    //将读取到的运动员数据输出到输出文件中
    public static void writePlayers() throws Exception
    {
        if(map.containsKey("players")){
           
            writeMsg(map.get("players"));
          
            return;
        }

        JSONObject js = JSONObject.parseObject(getJsonStr(PlayersFile));
      
        JSONArray players = JSONArray.parseArray(js.getString("players"));//获取运动员的json数组
      
        StringBuilder playersMsg = new StringBuilder();
       
        //for循环查找运动员中指定的数据,并输出到指定文件中
        for (int i = 0;i < players.size();i++)
        {
            JSONObject player = players.getJSONObject(i);
          
            playersMsg.append("full_name:").append(player.getString("full_name")).append("\n");
           
            playersMsg.append("gender:").append(player.getString("gender").equals("M") ? "Male" : "Female").append("\n");
          
            playersMsg.append("nationality:").append(player.getJSONObject("nationality").getString("name")).append("\n");
          
            playersMsg.append("-----\n");
        }
       
        writeMsg(playersMsg.toString());
       
        map.put("players",playersMsg.toString());
    }

    public static void writeResults(String date) throws Exception
    {
        if(map.containsKey(date)){
          
            writeMsg(map.get(date));
          
            return;
        }

        //把比赛信息存入到JSONObject中
        JSONObject js;
       
        js = JSONObject.parseObject(getJsonStr(ResultsFile + date + ".json"));
       
        JSONArray matches = JSONArray.parseArray(js.getString("matches"));
       
        StringBuilder resultsMsg = new StringBuilder();//写入信息
        
        //循环遍历matches
        for (int i = 0;i < matches.size();i++)
        {
            JSONObject match = matches.getJSONObject(i);//当天第i场比赛
         
            writeTime(match,resultsMsg);
           
            writeWinners(match,resultsMsg,js);
          
            writeScore(match,resultsMsg);
          
            resultsMsg.append("\n-----\n");
        }
        writeMsg(resultsMsg.toString());
       
        map.put(date,resultsMsg.toString());
    }

    public static JSONObject findTeams(String teamID,JSONArray teams)
    {
        for(int i=0;i<teams.size();i++)
        {
            if(Objects.equals(teamID, teams.getJSONObject(i).getString("uuid")))
                return teams.getJSONObject(i);
        }
        return null;
    }

    //写入实际开始时间
    public static void writeTime(JSONObject match,StringBuilder resultsMsg)
    {
        resultsMsg.append("time:").append(match.getString("actual_start_time")).append("\n");
    }

    //根据队伍写入比分
    public static void writeScore( JSONObject match,StringBuilder resultsMsg)
    {
        JSONArray teamAB = JSONArray.parseArray(match.getString("teams"));//这场比赛的双方队伍
     
        JSONObject teamA = teamAB.getJSONObject(0);
      
        JSONObject teamB = teamAB.getJSONObject(1);//比赛双方队伍
     
        JSONArray scoreA=JSONArray.parseArray(teamA.getString("score"));//A队得分
  
        JSONArray scoreB=JSONArray.parseArray(teamB.getString("score"));//B队得分

        for(int k=0;k<scoreA.size();k++)
        {
            resultsMsg.append(scoreA.getJSONObject(k).getString("game")).append(":").append(scoreB.getJSONObject(k).getString("game"));
           
            if (k + 1 < scoreA.size())
                resultsMsg.append(" | ");
        }
    }

    public static void writeWinners( JSONObject match,StringBuilder resultsMsg,JSONObject js)  {
        JSONArray teams = JSONArray.parseArray(js.getString("teams"));
       
        JSONArray players = JSONArray.parseArray(js.getString("players"));
      
        JSONArray teamAB = JSONArray.parseArray(match.getString("teams"));

        JSONObject teamA = teamAB.getJSONObject(0);
    
        JSONObject teamB = teamAB.getJSONObject(1);//比赛双方队伍
   
        JSONObject winnerTeam;
       
        if(teamA.getString("status")!=null)  winnerTeam=teamA;
      
        else winnerTeam=teamB;
     
        String winnerteamID=winnerTeam.getString("team_id");//获胜队伍的uid
        
        String winnerUID= Objects.requireNonNull(findTeams(winnerteamID, teams)).getString("players");
       
        JSONArray winners=findplayers(winnerUID,players);

        resultsMsg.append("winner:");
     
        for(int j=0;j<winners.size();j++)
        {
            resultsMsg.append(winners.getJSONObject(j).getString("short_name"));
        
            if(j+1<winners.size()) resultsMsg.append(" & ");
        }
       
        resultsMsg.append("\n");
    }

    //胜者可能是多人,所以用JSONArray
    public static JSONArray findplayers(String winnerUID,JSONArray players)
    {
        JSONArray winners=new JSONArray();
       
        JSONArray uuids=JSONArray.parseArray(winnerUID);//将uid转为jsonarray格式
       
        for(int i=0;i<uuids.size();i++)
        {
            String uuid=uuids.getString(i);//第一位胜者ID
          
            for(int j=0;j<players.size();j++)//对players数组循环查询对比
            {
                if(players.getJSONObject(j).getString("uuid").equals(uuid))
                    winners.add(players.getJSONObject(j));//添加进winners数组中
            }
        }
     
        return winners;
    }
    
    public static void setOutputFile(String filename){
        outputFIle = filename;
    }  

    private static String getJsonStr(String fileName) throws IOException
    {
        StringBuilder stringBuilder = new StringBuilder();
       
        if (!new File(fileName).exists())
            throw new FileNotFoundException("未找到" + fileName);
       
        else
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), StandardCharsets.UTF_8));
          
            String line;
          
            while ((line = reader.readLine()) != null)
            {
                stringBuilder.append(line);
            }
          
            reader.close();
         
            return stringBuilder.toString();
        }
    }

    public static void writeTest() throws Exception {
        Lib.setOutputFile("input.txt");
       
        for (int i = 0;i < 500;i++){
            Lib.writeMsg("players\nresult Q3\n");
        }
     
        Lib.setOutputFile("output.txt");
    }

    public static void writeMsg(String msg){
        try
        {
            BufferedWriter bw = new BufferedWriter(new FileWriter(outputFIle,true));
        
            bw.write(msg);
      
            bw.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    
}

 

6. 性能改进

6.1 IO优化

优化并保证程序最少IO。为了尽可能降低IO次数,在主类中使用了StringBuilder,来构造一次存入的全部字符串。当读取完全部行之后,再完成一次IO。

6.2 优化缓存

因为大量的指令其中会有重复的指令,每次执行一次指令都是需要遍历查询,这样会花费大量时间,使用HashMap之后,在每次输出之前检查一下此指令是否为重复指令,可以通过散列表存储键值对以此来实现缓存功能,就无需因多次重复读取相同指令花费大量时间。

一次性输入一千条数据
使用缓存前耗时:111678ms

img

使用缓存后耗时:2748ms

img

 

7. 单元测试

本次测试对所有验证函数和结果输出进行测试,修复后的结果如下:

 覆盖率:

img

 

代码如下

//AOSearchTest.java 单元测试
AOSearch.main(new String[]{"input.txt","output.txt"});
        assertTrue(AOSearch.flag);
        AOSearch.main(new String[]{"in.txt","output.txt"});
        assertFalse(AOSearch.flag);
        AOSearch.main(new String[]{"input.txt"});
        assertFalse(AOSearch.flag);
        AOSearch.main(new String[]{"inpxxut.txt","outxxput.txt","x"});
        assertFalse(AOSearch.flag);
        assertFalse(AOSearch.flag);
        AOSearch.main(new String[]{"in.txt","out.txt"});
        assertFalse(AOSearch.flag);
        AOSearch.main(new String[]{"input.txt","output.txt","input,txt"});
        assertFalse(AOSearch.flag);
        AOSearch.main(new String[]{});
        assertFalse(AOSearch.flag);

//LibTest.java 单元测试
   assertDoesNotThrow(()->Lib.writeResults("Q4"));
        assertDoesNotThrow(()->Lib.writeResults("Q3"));
        assertDoesNotThrow(()->Lib.writeResults("Q1"));
        assertDoesNotThrow(()->Lib.writeResults("Q2"));
        assertDoesNotThrow(()->Lib.writeResults("0128"));
        assertDoesNotThrow(()->Lib.writeResults("0127"));
        assertDoesNotThrow(()->Lib.writeResults("0126"));
        assertDoesNotThrow(()->Lib.writeResults("0125"));
        assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("SSS SADSA"));
        assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("XXX"));
        assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("0216"));
        assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("01a2"));
        assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("Q5"));
        assertThrows(FileNotFoundException.class, (Executable) ()->Lib.writeResults("Q"));

 

8. 异常处理

8.1 函数异常处理

  对可能出现异常的函数进行try,catch 

 

8.2 json 异常处理

  使用 getJsonStr 函数。

private static String getJsonStr(String fileName) throws IOException
    {
        StringBuilder stringBuilder = new StringBuilder();
        if (!new File(fileName).exists())
            throw new FileNotFoundException("未找到" + fileName);

8.3 AOSearch 主函数部分异常处理

因为作业要求的指令格式比较简单,就简单辨别处理,不对就直接抛出异常

 

9. 心得体会

   

这次的实践作业是我第一次使用java对json文件进行分析,在自主学习了使用fastJson对json文件进行解析的过程中,我遇到了许许多多的问题,也学习到了很多新的知识,从最初的学习爬取数据、解析json文件、下载各种jar包以及配置,到后来的改进性能、单元测试、修复bug等等,可以说是一路坎坷,但也收获颇丰。主要是学会了Java中单元测试的方法,使用Junit来进行单元测试,并且通过覆盖率测试查看有多少的分支代码被我们测试到。其次我也初步了解了软件工程PSP(个人开发流程)表格。
    通过这一次的实践,我颇为深刻地感受到了项目前期需求分析的重要性,若能开发前设计好规划文档,将会对后期的编码、测试等带来极大的好处。

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

688

社区成员

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

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