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

222000228黄雨洁 学生 2023-03-02 17:03:19
这个作业属于哪个课程2023春季软件工程&实践w班
这个作业要求在哪里软件工程实践第二次作业——个人实战
这个作业的目标实现程序的基本功能和附加功能
单元测试和性能分析
错误处理
其他参考文献《构建之法》

目录

  • Gitcode项目地址
  • PSP表格
  • 解题思路描述
  • 问题1 如何思考题目?
  • 问题2 如何查找资料?
  • 接口设计和实现过程
  • 一、接口设计
  • 二、实现过程
  • 关键代码展示
  • 性能改进
  • 单元测试
  • 异常处理
  • 心得体会

Gitcode项目地址

222000228

PSP表格

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

解题思路描述

问题1 如何思考题目?

拿到题目后,我首先对作业的要求进行了一个粗略的概览,最后我将本次作业的要点分为:
1.准备数据->如何获取未提供的JSON文件?(利用开发者工具在网页直接获取)

在这里插入图片描述

2.读写文件->程序将从input.txt中读取指令,并输出到output.txt中
3.解析JSON->程序将借助GSON库对JSON文件进行解析,从中提取所需要的信息
eg:
players(JsonArray)->full_name(JsonObject)
                                      gender(JsonObject)
                                      nationality(JsonObject)->name(JsonObject)
4.条件判断->程序能够对不同指令做出对应的处理,包括正确的指令、错误的指令以及多行指令

问题2 如何查找资料?

我按照我所分类的作业要点,结合作业要求,依次达到本作业不同方面的要求,过程中我所查找的资料都是根据我不熟练的点来搜索。
例如,在程序需求中,我进行了有关Java读写文件、GSON解析JSON文件、idea如何打包jar等方面的资料查找;
而在单元测试时,我进行了Java如何编写单元测试的查找。

接口设计和实现过程

一、接口设计

首先,我对该程序所用到的资源文件,即囊括信息的JSON文件进行了拆解,结合程序所产生的内容,决定只用一个主类(即AOSearch.java)来完成整个程序。这样做的理由是,如果通过创建类的方式来获取JsonObject,会产生许多用不到的字段的浪费,例如,我一开始想尝试只定义程序需要的内容相关的变量,但是这样无法解析类来获得对象,我又搜索了其他的相关资料,发现都是将整个JSON文件的变量写全的(也可能因为我经验不足没找到只获取相关字段的写法),我认为这样没有必要,所以直接采用逐层解析JSON文件的方法。
因此,在再次分析程序的需求之后,我将该程序所需要的函数划分为:

①读取input.txt文件的内容
②将字符串写入output.txt
③读取所有选手信息
④读取正式赛和资格赛信息
⑤读取jar包资源文件内容

接着,我根据这五个需求创建了五个函数,分别是

readFile、writeFile、readPlayers、readOfficialCompetition、readJsonFileInJar

这些函数都将在主类AOSearch.java中实现和调用。而这些函数之间的关系如下图:

请添加图片描述

二、实现过程

  • 读取文件和写入文件都是很简单的步骤,网上也能直接搜索到高效简洁的代码,因此这两个函数跳过不提。
  • 读取所有选手信息是我在本次作业中第一次结合JSON文件写的函数,可以说是整个程序的正式开始,因为我采用的是逐层剖析JSON文件的方式,所以我的代码会比较繁琐,但是结合JSON文件观看更加容易理解。在此我用了GSON库的getAsJsonArray()JsonPaser.parse(String)get(String).getAsJsonObject()get(String).getAsJsonObject()等关键函数获取我所需要的信息。
  • 由于比赛信息比选手信息的结构更加复杂,因此我在读取比赛信息时加了一些条件判断和在某些情况下并不会被赋值的变量(例如比赛赢家的单人赛情况中,不会有第二位选手名字的出现)。而在获取比赛的比分时,也是特地建了一个数组来按顺序存储单次的所有比分,再按赛次输出。不过这些特殊情况除了需要新引入的JsonPrimitive类之外,并没有在技术上有其他改变。
  • 最后是读取jar包资源文件内容,因为我在完成读取选手信息的功能前,一直是在IDE上测试代码,所以一开始我并没有考虑到这个功能,后来我根据网上的信息得知jar包的文件路径与在IDE上不同,因此必须用流的形式来读取内容。

关键代码展示

    String nameOfFirstPlayer = "";
    String nameOfSecondPlayer = "";
    int i2 = 0;
    for (Object o6 : playersInTeams) {
        i2++;
        JsonPrimitive playerInTeams = (JsonPrimitive) o6;
        ...
        for (Object o7 : firstPlayers) {
        ...
        if (playerUuidInTeams.equals(puuid)) {
                  String short_name = firstPlayer.get("short_name").getAsString();
                  if (i2 == 1) {
                        nameOfFirstPlayer = short_name;
                   }
                  if (i2 == 2) {
                        nameOfSecondPlayer = short_name;
                   }
                 }
               }
            }
         if (!nameOfSecondPlayer.equals("")) {
              data += "winner:" + nameOfFirstPlayer + " & " + nameOfSecondPlayer + "\n";
             } else {
              data += "winner:" + nameOfFirstPlayer + "\n";
             }

该段代码是为了应对团队赛和单人赛的不同情况写的,与JSON文件中有时不存在的actual_start_timestatus是相同的原理,因此此处只放出这段比较代表性的代码。但由于该信息是同一个playersJSONArray中不同size的情况,因此还用到了JsonPrimitive抽象类的转换。如果不对这些情况进行提前判断,就会抛出空指针异常(Null Pointer Exception)。

String[] RightDay = {"Q1", "Q2", "Q3", "Q4", "0116", "0117", "0118", "0119", "0120", "0121", "0122",
                    "0123", "0124", "0125", "0126", "0127", "0128", "0129"};
            String[] datas;
            datas = data.split("\\r?\\n");
            int i = 0;
            while (i != datas.length) {
                String dataWithoutBlank = datas[i].trim() ;
                if (dataWithoutBlank.equals("players")) {
                   ...
                } else if (dataWithoutBlank.startsWith("result ")) {
                        String str = dataWithoutBlank.replaceAll(" ","");
                        String resultStr = str.substring(6);
                    for (String rd : RightDay) {
                        if (rd.equals(resultStr)) {
                            isRightDate = true;
                            outPutData += readOfficialCompetition(rd);
                            break;
                        }
                    }
                    if (!isRightDate) {
                        outPutData += "N/A\n" + "-----\n";
                    }
                    isRightDate = false;
                } 

这是我为读取文件写的一段代码,其中datas = data.split("\\r?\\n");是按换行符分割读取的字符串,从而逐一判断输入的多行指令。而String[] RightDay则是负责判断所需资格赛和正式赛的日期以及方便调用输出信息的函数。

BufferedReader in = new BufferedReader(new InputStreamReader(AOSearch.class.getClassLoader().getResourceAsStream(filename)));
        if (in == null) {
            System.out.println("null");
            return "null";
        } else {
            StringBuffer buffer = new StringBuffer();
            String line = "";
            while (true) {
                try {
                    if (!((line = in.readLine()) != null)) break;
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                buffer.append(line);
            }
            String input = String.valueOf(buffer);
            return input;
        }

这段代码是用于以流的形式获取jar包资源文件的内容,因此在整个程序中,该函数的调用返回值是整段JSON字符串。由于jar包文件路径是临时存在的,因此不能用普通的读取相对路径的方式对文件进行读取。

性能改进

  • 读取文件
    由于程序需求要读取多行指令,所以除了要将input.txt中读取到的字符串按行分割外,还要对每行的指令进行判断。我认为获取的指令一共有五种情况:

    ①players
    ②"result "+正确格式
    ③"result "+错误格式 --->"N/A"
    ④空行 --->跳过
    ⑤其他 --->"Error"
    (后经补充还有单独的“result”也输出“N/A”的情况)

其中②③是比较繁琐的判断情况,因为资格赛和正式赛的日期是有十几种情况的,所以肯定不能用枚举的方法来进行if判断,这样会产生许多相似代码片段,也降低了可读性。因此我将readOfficialCompetition函数新增传入了参数,这样在调用时可以直接通过result 后的日期来获取对应信息。但是判断还是有很多种情况,所以我新建了一个String数组来存放所有正确日期,并用for循环与获取的指令进行对比。

  • 缓存输出字符串
    我发现如果用户输入大量相同字符串,输出时间会大大增加,因此我使用类似于数据缓存的方法来保存已读过的正确指令及其输出的字符串,这样当程序判断出该指令是已经读过的字符串时就会直接输出而不用再去读取JSON文件,使得程序的性能有极大的提高。在这里我用的是HashMap存储方法。

    请添加图片描述

单元测试

  • 由于本人先前没有单元测试的习惯,因此在这次作业中也是第一次对程序进行单元测试。我在对网上的资料进行观看后决定直接用简单的断言判断输出方法。由于我事先没有经验,因此我复用了原本写的代码,写了一个新的函数专门用于测试,只调用了输出函数,导致覆盖率非常之低,由此我认为提高覆盖率的方法,其中很重要的一点就是尽量分散函数的功能,这样在测试时才能调用到程序大部分的函数。

  • 这是我编写的其中一个测试函数的部分代码,是用来判断错误情况的↓

    请添加图片描述


  • 我编测试的思路是:通过指定的输入指令来判断输出是否正确。但同时因为测试简单,覆盖率也不高,接下来的几个测试示例与第一个大同小异,代码就不展示了,直接放覆盖率。

    请添加图片描述


    关于我设计的这些测试示例,如果是单个输出肯定是可以满足程序的需求的,但是因为本人实力不够无法进行多个输入的测试,所以多行指令的正确率并不能保证。

异常处理

cmd输入的指令为Java -jar AOSearch.jar input.txt output.txt
因此我专门根据该条指令分析了可能的异常及对应措施。
已知Java -jar AOSearch.jar是运行jar包的基本格式指令,因此在输入不正确时,cmd会自动报错。
而读取网球公开赛的选手信息和比赛结果的指令输入在用户自己创建的input.txt中,因此如果用户没有创建input.txt,cmd都会输出创建文件的要求来提醒用户。因为output.txt由程序自己创建,所以不用用户另行设置。

心得体会

  • 我在进行此次程序的编写时,原本的计划是使用fastJSON来作为本次程序的外部技术支持,但是因为本人能力有限,导致导包后无法调用函数,最后只得换gson库来编写代码。通过此次经历我明白了虽然现在的软件工程发展迅速,但要运用他人提供的便利还是得多练手不同的项目。
  • 在进行准备工作时,我注意到在GitCode中提交的Markdown文件包括自己的代码风格,但是当时我并没有对此提起重视,而是决定在写完所有代码之后再进行codestyle的编写,结果在我对照codestyle的规定时发现自己先前定义的许多变量都没有按照规定命名,还有注释也没有同期进行编写,导致我在后期收尾时改了许多没必要改动的代码。因此我在这一次作业中认识到了规范的重要性。
  • 在此次作业前,我并没有经手过把Java项目打包成jar包的作业,因此在后面运行jar时出了错误,但同时我也意识到打包成jar的好处,那就是提高了程序的可用性,打包成jar后,其他人也可以在自己的计算机上方便地运行程序,我想我此后应该会经常使用这种方法来调试程序。
...全文
199 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
郭渊伟 助教 2023-03-13
  • 打赏
  • 举报
回复

条理清晰,结构清楚,赞!

688

社区成员

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

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