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

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程序运行时间

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

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

大气污染是影响公众健康与生态环境的重要问题,精准的空气质量时空预测与污染源贡献度量化是精准治污的关键支撑。针对现有研究多源融合不充分、时空关联刻画不足、预测与源解析割裂三方面缺陷,本文设计实现了城市空气质量时空预测与污染源贡献度分析系统,融合监测、气象、工业排放与交通四类数据,构建基于时空注意力的LSTM(STAM-LSTM)预测模型与基于正定矩阵因子分解(PMF)的源解析模型,形成数据融合-特征工程-预测-源解析-可视化闭环。 系统实现四类数据时空对齐与融合,构建时序与空间邻域特征,以普通克里金插值生成1km网格浓度场;STAM-LSTM引入时空注意力自适应学习站点间污染传输时变权重,以72小时输入预测未来24小时逐小时PM2.5浓度;PMF识别交通、工业、燃煤、扬尘与二次生成五个源因子,量化各源全年贡献度并分析时空演变。 实验表明:STAM-LSTM预测RMSE 24.6、MAE 17.8、R² 0.88,相对LSTM基线(30.2)提升18.5%;普通克里金插值误差8.9,优于反距离加权(11.4);源解析显示交通源28.4%、工业源23.1%、燃煤源19.6%为主要贡献源,冬季燃煤源升至27.3%、早高峰交通源达34.8%,下风向工业源贡献高出上风向8~12个百分点;减排情景显示交通源减排20%可使年均PM2.5下降5.7%,与源贡献度排序一致。 系统按五模块14组件实现,功能测试16项用例全部通过,为大气污染预警、源管控与减排政策制定提供了决策依据。 【课程报告内容】 摘要 第1章 绪论 第2章 相关技术与理论 第3章 系统需求分析 第4章 系统总体设计 第5章 系统详细设计与实现 第6章 系统测试与分析 第7章 总结与展望 参考文献 附件-实现指南
基于iTransformer-BiGRU-KAN多模型融合的滚动轴承剩余寿命预测研究(Python代码实现)内容概要:本文提出了一种基于iTransformer-BiGRU-KAN多模型融合的滚动轴承剩余寿命预测方法,旨在通过结合多种先进深度学习模型的优势,提升在复杂工况下的预测精度与鲁棒性。该方法利用iTransformer捕捉长期时间序列中的全局依赖关系,通过BiGRU模型提取双向时序特征,最后引入KAN(Kernel Attention Network)增强非线性映射与关键特征的自适应加权能力,实现对轴承退化过程的精准建模。文中详细介绍了模型架构设计、训练流程及在公开数据集上的实验验证,结果表明该融合模型相比单一模型在预测精度和稳定性方面均有显著提升。; 适合人群:具备一定机器学习与深度学习基础,从事设备故障诊断、工业大数据分析或智能运维相关领域的研究人员及工程技术人员,尤其适合研究生及以上学历或有相关项目经验的专业人员。; 使用场景及目标:①应用于工业设备状态监测与预测性维护系统中,实现对滚动轴承等关键部件剩余寿命的精准预测;②为复杂时间序列回归任务提供多模型融合的设计思路与技术参考;③推动深度学习在智能制造与工业物联网领域的落地应用。; 阅读建议:建议读者结合Python代码实现部分,深入理解各子模型的接口设计与融合逻辑,重点关注特征融合机制与注意力权重的可视化分析,以便在实际项目中灵活调整与优化模型结构。
几何旋转和天线校准模式对GNSS相位缠绕的组合效应(Matlab代码实现)内容概要:本文研究了几何旋转和天线校准模式对全球导航卫星系统(GNSS)相位缠绕的组合效应,并提供了基于Matlab的代码实现方案。相位缠绕是GNSS高精度定位中的重要误差源,受卫星与接收机相对几何关系及天线相位中心变化的共同影响。文章通过建模分析几何旋转与天线校准参数对相位缠绕的影响机制,探讨二者耦合作用下的修正方法,旨在提升GNSS数据处理的精度与可靠性。研究涵盖了理论建模、算法实现与仿真实验,结合Matlab工具进行数值模拟与结果可视化,验证了所提方法的有效性。; 适合人群:具备一定GNSS基础知识和Matlab编程能力的科研人员、研究生及从事高精度定位相关工作的技术人员。; 使用场景及目标:①用于GNSS高精度数据处理中相位缠绕误差的精确建模与修正;②支持地壳形变监测、精密授时、卫星定轨等对定位精度要求较高的应用场景;③为相关算法开发与教学研究提供可复现的代码实例。; 阅读建议:建议读者结合GNSS误差处理的相关理论,边运行代码边理解算法细节,重点关注几何旋转模型与天线校准参数的集成方式,并可通过修改参数进行敏感性分析以加深理解。

688

社区成员

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

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