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

222000324郑淏 学生 2023-03-03 17:27:26
这个作业属于哪个课程<2023年福大-软件工程实践-W班>
这个作业要求在哪里<作业要求在这里>
这个作业的目标以Asutralian Open为背景建立一个赛事数据查询器,锻炼读取文件的能力,并将项目上传至GitCode,撰写相关文档,熟悉软件开发基本步骤
其他参考文献参考较多,见文末

说明:本次实践使用Java语言编写

目录

  • 1.GitCode项目地址
  • 2.PSP表格
  • 3.解题思路描述
  • Q1:如何获取额外的赛事数据?
  • Q2:如何将json字段处理成想要的格式?
  • Q3:如何识别用户输入的数据?
  • Q4: 如何删去文件最后一行的换行符?
  • 4.接口设计和实现过程
  • 4.1 类设计
  • 4.2 接口设计及重要变量一览
  • 5 关键代码展示
  • 6.性能优化
  • 6.1 优化前性能
  • 6.2 对可优化内容的思考
  • 6.3 优化后性能
  • 7.单元测试
  • 7.1 测试函数
  • 7.2 测试前覆盖率
  • 7.3 测试后覆盖率
  • 8.异常处理
  • 9.心得体会


1.GitCode项目地址

项目链接点这里


2.PSP表格

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

3.解题思路描述

Q1:如何获取额外的赛事数据?

  A:首先进入Australian Open官网,将鼠标放在TOURNAMENT上,点击Results,按下F12进入开发者模式并刷新(向服务器发出请求):

  然后点击XHR,并按大小排序,会发现有一个名为results的文件,这个就是我们需要的JSON文件:

  双击查看,由文件路径可知,该文件为1月29日的JSON文件(14+16-1),如果我们需要其他日期的文件,可以通过修改路径上的数字来获得,如果我们需要赛季数据,可以通过把MD改成Q,day后的数字改成对应数字获得(1、2、3、4):

  接着我们右键点击查看源代码,可以看到格式并不美观:

  我们把源代码全部复制,使用JSON在线解析网站,可以很方便地把格式不规范的JSON文件转换为格式美观的JSON文件:

  点击保存本地,修改文件名为对应日期即可。

Q2:如何将json字段处理成想要的格式?

  A:通过使用阿里巴巴的fastjson第三方包,可以从JSON文件中提取想要的数据:

JSON对象字符串转为JSON对象

JSONObject jsonObj = JSON.parseObject(jsonStr);

JSON数组字符串转为JSON数组

JSONArray jsonArr = JSON.parseArray(jsonStr);
jsonArray.size();// 获取数组元素个数
List<TrackNodeDto> nodes = jsonArray.toJavaList(TrackNodeDto.class);//转化为特定的List

在程序中,利用fastjson提取JSON数据的代码如下:

//从Json字符串中获取节点数据并存入ArrayList
    public  void jsonToString(String json){
        jsonToPlayerMap(json);
        jsonToTeamMap(json);
        if (json!=null) {
            JSONObject jsonObject = JSON.parseObject(json);
            JSONArray matches = jsonObject.getJSONArray("matches");
            for (var tempActivity:matches){
                JSONObject activity = (JSONObject)tempActivity;
                String startTime = activity.getString("actual_start_time");
                JSONArray teams = activity.getJSONArray("teams");
                if (activity.getJSONObject("match_status") != null&&
                        activity.getJSONObject("match_status").getString("code").equals("W")){
                    dailySchedule.timeList.add("null");
                    dailySchedule.ScoreList.add("null");
                    dailySchedule.WinnerList.add("null");
                    continue;
                }
                if (teams == null)
                    continue;
                JSONObject teamA = teams.getJSONObject(0);
                JSONObject teamB = teams.getJSONObject(1);
                String winnerTeamID = teamA.getString("status")==null?teamB.getString("team_id"):
                        teamA.getString("team_id");
                JSONArray scoresA = teamA.getJSONArray("score");
                JSONArray scoresB = teamB.getJSONArray("score");
                dailySchedule.timeList.add(startTime);
                String winnerName = null;
                ArrayList<String> winnerID = teamMap.get(winnerTeamID);
                for (String id:winnerID){
                    if (winnerName == null)
                        winnerName = playerMap.get(id);
                    else
                        winnerName = winnerName + " & " + playerMap.get(id);
                }
                String score = null;
                for (var tempScoreA:scoresA){
                    JSONObject scoreA = (JSONObject)tempScoreA;
                    int set = scoreA.getInteger("set");
                    JSONObject scoreB = scoresB.getJSONObject(set-1);
                    String gradeA = scoreA.getString("game");
                    String gradeB = scoreB.getString("game");
                    if (score==null)
                        score = gradeA + ':' + gradeB;
                    else
                        score = score + " | " + gradeA + ':' + gradeB;
                }
                dailySchedule.ScoreList.add(score);
                dailySchedule.WinnerList.add(winnerName);
            }
        }
    }

Q3:如何识别用户输入的数据?

  A:识别用户输入数据的重点在于如何处理读入的字符串(比如对空白字符的处理)以及如  何判断输入的合法性。由于正确的指令只有两种:players 和 result xxxx ,且严格区分大  小写,所以可以先判断是否为players,直接使用字符串的equals函数即可。再判断是否为  result 0116形式的指令,可以使用正则分割的形式,判断分割后的字符串长度是否为2,  首项是否为result。上述判断都不满足时就是错误指令.
  相关思路的流程图如下:

在这里插入图片描述

Q4: 如何删去文件最后一行的换行符?

  A:这个问题涉及到java文件操作,一种可行且简单的方法是使用RandomAccessFile。由于我们只需要删掉文件的最后一行的换行符,所以可以使用setLength直接将文件长度缩短1个字节即可。

RandomAccessFile raf = new RandomAccessFile(fileOutput,"rw");
                if(raf.length()!=0){
                    raf.setLength(raf.length()-1);//remove \n in the end
                    raf.close();
                }

4.接口设计和实现过程

4.1 类设计

//主类,主要用来读input.txt、分析指令,并调用其他类的相应获取、写入函数得到内容,并写入output.txt
public class AOSearch{...}
//core模块,用于获取选手信息并写入文件
public class PlayerList{...}
//core模块,用于分析指令正误、获取指定日期的赛程并写入文件
public class ResultList{...}
//于性能优化后加入的新类,主要目的是存储选手信息
public class PlayerStruct{...}
//于性能优化后加入的新类,主要目的是存储某一天的赛程信息
public class ResultStruct{...}

4.2 接口设计及重要变量一览

AOSeach类

//存放曾经读到的赛程信息
public static HashMap<String,ResultStruct> ResultData = new HashMap<>();
//存放曾经读到的排行榜信息
public static PlayerStruct PlayerData = new PlayerStruct();
//程序入口,读文件并调用其他函数来写文件
public static void main(String[] args) throws IOException{...}
//分析input.txt的指令,根据分析结果调用相应的处理函数
public static void writeToFile(){...}
//处理无法识别的指令,即写入“Error”到文件
public static void writeInvalidCommandInfo(){...}

PlayerStruct(工具类,存放所有选手信息)

public ArrayList<String> fullNameList = null; //记录选手全名
public ArrayList <String>  genderList = null; //记录选手性别
public ArrayList <String>  nationalityList = null; //记录选手国籍

ResultStruct(工具类,一个对象存放单日赛事情况)

public ArrayList <String> timeList = null; //记录比赛时间
public ArrayList <String>  WinnerList = null; //记录胜出者
public ArrayList <String>  ScoreList =null; //记录比分列表

PlayerList类

public void getPlayerList() throws IOException{...}
//PlayerList为空时调用,初始化rankingData
public void initializeArray(){...}
//PlayerList为空时调用,向PlayerData填充信息
public static void fillArray() throws IOException{...}
//写入选手信息到文件
public void writeCertainResult() throws IOException{...}

ResultList类

//0116对应的int值,作为日期起始值判断日期是否在范围内
public static final int BEGIN_OF_WINTEROLYMPIC = 116;
//0116对应的int值,作为日期结束值判断日期是否在范围内
public static final int END_OF_WINTEROLYMPIC = 129;
//编号到选手姓名的映射表
public HashMap<String, String> playerMap = new HashMap<>();
//队伍id到成员id的映射表
public HashMap<String, ArrayList<String>> teamMap = new HashMap<String, ArrayList<String>>();
//获取赛程日期,根据具体日期或赛季执行不同的动作
public void getResultList(String date) throws IOException{...}
//判断日期是否在赛会期间
public static boolean isValidDate(String testString){...}
//根据提供的日期键值dateKey,填充ResultData
public void fillArray(String dateKey) throws IOException, ParseException{...}
//初始化ResultData中的一个成员,以存放赛程信息
public void initializeArray(){...}
//将已存在ResultData的赛事信息写入文件
public void writeCertainResultByMap(String dateKey) throws IOException{...}
//从Json字符串中获取节点数据并存入ArrayList
public  void jsonToString(String json){...}
//处理不在赛会日期范围内的情况,输出N/A
public static void writeInvalidDateInfo() throws IOException{...}
//建立队伍id到成员id的映射表
private  void jsonToTeamMap(String json)
//建立选手编号到选手姓名的映射表
private  void jsonToPlayerMap(String json)
//处理不在赛事时间内的情况,输出N/A
public static void writeInvalidDateInfo(){...}
//处理无效日期格式(如11602xx)的情况,输出N/A
public static void writeInvalidDateFormatInfo() throws IOException{...}

5 关键代码展示

- AOSearch.writeToFile():

    //分析input.txt指令,根据分析结果调用相应处理函数
    public static void  writeToFile(){
        String tempString ;
        try{
            while((tempString=br.readLine())!=null){
                if(tempString.trim().equals("")){
                }
                else if(tempString.trim().equals("players")){
                    (new PlayerList()).getPlayerList();
                }
                else{
                    tempString = tempString.trim();
                    String[] stringSlice = tempString.split("\\s+");
                    try{
                        if(!stringSlice[0].equals("result")){
                            writeInvalidCommandInfo();
                        }
                        else if(stringSlice.length!=2){
                            writeInvalidDateContentInfo();
                        }
                        else{
                            (new ResultList()).getResultList(tempString);
                        }
                    }
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

-ResultList.getResultList()

//获取赛程,根据具体日期或赛季执行不同的动作
    public void getResultList(String date) throws IOException {
        
        String dateStr = date.replace("result", "").replaceAll(" ", "").trim(); //get the date we need
        //日期格式应为四位数字xxxx,或赛程格式Qx
        try {
            if (!dateStr.equals("Q1" ) && !dateStr.equals("Q2" ) && !dateStr.equals("Q3" ) && !dateStr.equals("Q4" ) && dateStr.length() != 4) {
                writeInvalidDateFormatInfo();
            } else {
                
                boolean isValidDateFlag = isValidDate(dateStr);
                if (isValidDateFlag) {
                    if (AOSearch.ResultData != null && AOSearch.ResultData.containsKey(dateStr)) {
                        writeCertainResultByMap(dateStr);
                    } else {
                        String sourcefile = "src"+File.separator+"data"+File.separator
                                + "schedule" + File.separator + dateStr + ".json";
                        File file = new File(sourcefile);
                        
                        try {
                            br = new BufferedReader(new FileReader(file));
                            initializeArrayList();
                            fillArrayList(dateStr);
                            writeCertainResultByMap(dateStr);
                            br.close();
                        } catch (ParseException e) {
                            e.printStackTrace();
                        } finally {
                            if (br != null) {
                                try {
                                    br.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                } else {
                    writeInvalidDateInfo();
                }
            }
            
        } catch (NumberFormatException e) {
            writeInvalidDateFormatInfo();
        }
    }

-ResultList.writeCertainResultByMap()

//将已存在ResultData的赛事信息写入文件
    public void writeCertainResultByMap(String dateKey) throws IOException {
        ResultStruct result = AOSearch.ResultData.get(dateKey);
        for (int i = 0; i < result.timeList.size(); i++) {
            if(result.timeList.get(i)=="null" && result.WinnerList.get(i)=="null"
                    && result.ScoreList.get(i)=="null"){
                AOSearch.bw.write("W/O\n-----\n");
            }
            else{
                AOSearch.bw.write("time:"+result.timeList.get(i) + '\n');
                AOSearch.bw.write("winner:"+result.WinnerList.get(i) + '\n');
                AOSearch.bw.write("score:"+result.ScoreList.get(i)+ '\n');
                AOSearch.bw.write("-----\n");
            }
        }
    }

-ResultList.jsonToPlayerMap

//建立选手编号到选手姓名的映射表
    private  void jsonToPlayerMap(String json){
        if(json != null){
            JSONObject jsonObj = JSON.parseObject(json);
            JSONArray players = jsonObj.getJSONArray("players");
            for (var tempPlayer:players){
                JSONObject player = (JSONObject)tempPlayer;
                playerMap.put(player.getString("uuid"),player.getString("short_name"));
            }
        }
        else{
            System.out.println("未能成功读取json");
        }
    }

-PlayerList.getPlayerInfoFromJson

//从Json获取Player数据
    public void getPlayerInfoFromJson(String json){
        if(json!=null){
            JSONObject jsonObj = JSON.parseObject(json);
            JSONArray players = jsonObj.getJSONArray("players");
            for(var tempPlayer:players){
                JSONObject player = (JSONObject)tempPlayer;
                AOSearch.PlayerData.fullNameList.add(player.getString("full_name"));
                AOSearch.PlayerData.genderList.add(player.getString("gender"));
                AOSearch.PlayerData.nationalityList.add(player.getJSONObject("nationality").getString("name"));
            }
        }
    }

6.性能优化

6.1 优化前性能

  优化前性能如下:
输出所有Json文件的数据(result 0116-0129、players、result Q1-Q4)

在这里插入图片描述

真的是久到离谱

在这里插入图片描述

6.2 对可优化内容的思考

程序存在的一些问题

  • 频繁打开关闭output文件,每处理一行指令,就要对输出文件进行一组io操作。
  • 如果指令是对的,每次都要打开对应的数据文件来处理文件。(后来发现这是优化的关键点

优化思路

  • 将对output文件操作的有关变量全部设置为static,这样可以不再读一行就关一次文件,而且可以在其他类中对output文件执行读写操作。
  • 在主类AOSearch中设置两个static变量用于存储已经读到的数据信息,这样要处理数据时,先看一下相应数据是否已经存储,若确实已经存储,则直接输出即可,没有的时候再打开数据文件读数据。

6.3 优化后性能

在这里插入图片描述

大概快了两倍

在这里插入图片描述


7.单元测试

  主要思路:采用junit4,以程序写入的方式,在AOSearch中进行测试,获得测试结果,并观察代码覆盖率。

7.1 测试函数

@Test
    public void testMain() {
        AOSearch.main(new String[]{"input.txt","output.txt"});
        try{
            AOSearch.main(new String[]{"input.txt"});
        }catch (ArrayIndexOutOfBoundsException e)
        {
            System.out.println("WARNING:You have not given 2 arguments!");
            e.printStackTrace();
        }
        AOSearch.main(new String[]{"123.txt","output.txt"});
        AOSearch.writeInvalidCommandInfo();
        AOSearch.writeInvalidDateContentInfo();
    }
@Test
    public void testWriteToFile() {
    }
 @Test
    public void testWriteInvalidCommandInfo() {
    }
@Test
    public void testWriteInvalidDateContentInfo() {
    }

7.2 测试前覆盖率

在这里插入图片描述

7.3 测试后覆盖率

在这里插入图片描述

可以发现覆盖率还是很高的

在这里插入图片描述


8.异常处理

  由于之前极少有对异常处理的经历,所以对这部分不是很了解。大部分异常是idea自动补全的,遇到异常时只是简单的输出一下异常信息。下面给几个例子:

1、IOException
  本次作业最可能出现的异常,发生于文件读写不成功等一系列与文件有关的操作时。

在这里插入图片描述


2、ParseException
  日期转换错误,有可能是因为字符串非纯数字,这时可以认定为不是有效日期。

在这里插入图片描述


3、ArrayIndexOutOfBoundsException
  出现在分割指令时。如果指令为空,那么分割结果也为空,抛出此异常,可由此判定为无效指令。

在这里插入图片描述


4、FileNotFoundException
  发生于未查找到input.txt文件时。

在这里插入图片描述

9.心得体会

  • 不要做ddl战士!否则可能会因为忙乱发生各种各样的错误,早完成还有时间进行完善。
  • 要学会独立查资料解决问题。有关一些以前未接触的技术,如爬取网页数据、使用fastjson解析JSON文件,现在这些虽然可以问同学,但最好还是要学会自己独立解决,因为以后工作之后大家都很忙,同事之间的关系可能也不会像现在同学之间这么好,可能会失去很多问别人的机会。
  • 本次编码中发现对于单元测试、异常处理不是很熟练,希望可以给一些示例学习一下。
  • 博客可以边做项目边写,等到deadline再写就感觉很多以前想到的东西一下子忘了,一些关于项目的思考很难提及。

附:参考文献
博客园-如何将java项目打成jar包
CSDN-Java JUnit单元测试教程
fastjson的基本使用方法
博客园-获取Java程序运行时间

...全文
180 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
助教-吴雨薇 助教 2023-03-12
  • 打赏
  • 举报
回复

完成的很不错,解题思路分析清楚,流程图绘制的让人一下子能理解你的思路。

688

社区成员

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

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