2024 BUAA OO 第三单元

架构设计

第一次作业

JML

接口实现

根据 Network 与 Person 接口中内的JML语言实现 MyNetwork 与 MyPerson 两个类
其中, MyPerson类中需要维护的两个数组 acquaintancevalue 均采用 Hashmap 的形式实现, 以 id 为键值对, 存放 Person 与 Integer。 这样在调用 isLinkedqueryValue 等方法时较快
同理, MyNetwork需要维护的数组 persons 同样采用 Hashmap 的形式实现, 以 id 为键值对, 存放 Person

异常类

四个类均按要求实现异常类的继承, 同时声明两个静态变量

private static int count = 0;
private static final HashMap<Integer, Integer> idMap = new HashMap<>();

其中, count 用于记录该异常发生的总次数, idMap 用于记录对应id发生异常的次数。
同时需注意: 若发生异常的二id一致、 则只记录一次该id发生异常, print()时将id从小至大输出

优化

动态维护

需要注意的是, 仅根据给出的JML语言实现 queryBlockSumqueryTripleSum 两个方法会使性能极差, 不难预见强测会出现大量的TLE。 其原因在于每次query时均要遍历已有的 persons 容器, 而在容器规模较大、 出现大量query命令时会占用极多时间
因此, 弃用JML要求的方法, 而实现 blockSum 与 tripleSum 的动态维护。 即在 addRelationmodifyRelation 涉及到边的新增与删除时, 以当下的关系图为依据, 对 blockSum 与 tripleSum 进行相应的操作。 这样, 在每次query时便无需重复耗费时间, 而是直接返回 blockSum 与 tripleSum, 实现了优化

并查集

isCircle 查询二节点是否可达时, 采取原bfs遍历关系网的方法会导致开销较大, 在参考往届博客及讨论区后改用并查集。 即在 MyPerson 中维护 ancestor 这一指针, 将所有相连通节点的祖先设置为某一Person, 这样在isCircle查询时根据 ancestor 的异同便可得到答案。
由此, 需在 addRelation, modifyRelation 时维护 ancestor , 使添加关系、删除关系后祖先能被正确更新, 确保isCircle的正确性

第二次作业

JML

大致与第一次作业相同, 在实现接口的同时, 继承四个新增的异常类即可

优化

MyTag

MyTag 中的 getAgeMean, getAgeVar, getValueSum 进行动态维护。 前二者较为简单, 在维护第二个方法时将原表达式拆开, 维护年龄的平方即可。 第三个方法则需在每次 addPersonToTag, delPersonFromTag, modifyRelation 对Tag内Person进行增删时, 遍历Tag内所有Person, 找到与待增删对象有关系的人, 对valueSum进行维护; 同时, 在 addRelation, modifyRelation 对关系的值进行改变时, 需遍历所有Tag, 检测其中是否包含更改关系的两个Person, 若包含, 则对valueSum进行维护

MyNetwork

MyNetwork 中的 queryBestAcquaintance 进行动态维护, 在 MyPerson 类中新增相关属性。 在每次 addRelation, modifyRelation 时, 对关系改变后的两Person进行操作, 维护二者相应的属性即可

第三次作业

这次作业可优化的空间较少, 只有对 emojiHeatList, messages 改用为id作为键值对, 存放相应数据的 Hashmap 容器即可

测试过程

相关理解

白箱测试 根据程序内部逻辑测试程序,检查程序中的每条通路是否按照预定要求正确工作(即穷举路径),要求测试者了解程序结构和处理过程。在程序写好后,根据程序内部逻辑手搓一些数据确保覆盖所有语句、分支、条件、路径,以及注意测试一些边界值和特殊情况。

黑箱测试 根据功能需求测试程序是否按照预期工作,基于系统的需求规格和功能规范来设计测试用例,并通过输入不同的数据和条件,观察系统的输出是否符合预期(即穷举输入),而不涉及程序的内部结构和内容特性。在写数据生成器时,按照功能需求构造数据,以发现系统是否满足所有功能要求以及是否存在错误或异常。

单元测试 针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。它的目的是在开发过程中尽早发现代码中的缺陷,并确保每个功能模块都能够独立地正常运行。例如本单元中的 OK 测试方法,使用测试框架进行高度自动化的测试。

功能测试 测试软件功能是否符合需求,通常采用黑箱测试方法。

集成测试 软件测试的一种方法,用于验证不同组件、模块或子系统之间的集成是否正确、协同合作,并能够产生预期的结果。它旨在检测和解决在组件集成过程中可能出现的问题。在集成测试中,被测试的软件系统已经通过单元测试对各个组件进行了测试,并且这些组件已经通过了单元测试阶段。集成测试的目标是验证组件之间的接口、数据传递和交互是否正常,以及确保整个系统在集成后能够正确运行。

压力测试 软件测试的一种方法,用于评估系统在超出正常工作负载条件下的性能和稳定性。它通过模拟系统面临的高负载、大数据量或高并发等情况,检查系统在这些极端情况下的表现和响应。压力测试的主要目标是找出系统的瓶颈、性能问题和资源耗尽情况,并评估系统在负载增加时是否能够满足性能要求。这种测试方法可以揭示系统的弱点、性能限制和潜在的故障,为系统的优化和调整提供指导。在本单元中,qlm,qbs 等指令时间复杂度较高,可以在数据构造中产生大量此类指令以观察时间性能。

回归测试 软件测试的一种方法,用于确认在进行软件修改、修复或增加新功能后,原有功能是否仍然正常工作,以及新的修改是否引入了新的错误或问题。当对软件进行修改时,无论是修复缺陷、添加新功能还是进行系统配置变更,都存在可能引入新的错误或导致原有功能出现问题的风险。回归测试的目的是在进行修改后,重新运行既有的测试用例,以验证软件系统在修改后的版本中是否仍然具有预期的行为。在本课程中,将迭代后的作业提交至上次作业的强测,观察原有功能是否仍然正常工作。

性能问题及其修复情况

第一次作业

由于五一假期的到来且此次作业较为简单, 在放假前一周并未花太多心思到这次作业上。 周日才发现自己被分到了C房, 强测分数公布后发现得分仅有33.33分…… 也是挺魔幻的, 在最简单的一集写出依托答辩, 体会到强测暴雷的滋味, 也算是给我留了个教训吧, 望后面能引以为戒。
bug也很简单, 就是在 addRelation 时没做好情况的归类, 导致在大部分情况下,会遍历单边Person的关系网,将其所有节点的祖先均进行重置。 这样带来的结果就是开销非常大, 导致七个点TLE。 此外, 在modifyRelation 是也没处理好细节, 导致在某些情况下出错, 这使得一个点WA

第二、三次作业

均未出现bug

规格与实现分离

在此单元中,若只根据JML实现所有方法会导致性能极差,在第一次迭代中就有所体现。因此,为优化代码的性能,我采取了三种方法

  • 直接翻译
    //@ ensures \result == id;
    public int getId() {
        return id;
    }
    
  • 改用容器
    //@ ensures \result == (\exists int i; 0 <= i && i < persons.length; persons[i].getId() == id);
    public boolean containsPerson(int id) {
        return persons.get(id) != null;
    }
    
  • 通过动态维护或优化算法,降低时间复杂度
    /*@ ensures \result ==
      @         (\sum int i; 0 <= i && i < persons.length;
      @             (\sum int j; i < j && j < persons.length;
      @                 (\sum int k; j < k && k < persons.length
      @                    && getPerson(persons[i].getId()).isLinked(getPerson(persons[j].getId()))
      @                    && getPerson(persons[j].getId()).isLinked(getPerson(persons[k].getId()))
      @                    && getPerson(persons[k].getId()).isLinked(getPerson(persons[i].getId()));
      @                     1)));
      @*/
      public int queryTripleSum() {
        return tripleSum;
    }
    

Junit测试

本单元中最为玄学的部分, 且评测结果并没有给出错误代码的示例( 因此只能面向评测机编程

数据构造

第一次作业

参考实验代码, 在测试源代码根目录test下, 构建TripleSumTest类。 使用 @Parameters 构造完全图, 仅对完全图进行 @Test 测试。 测试数据的不全面导致并未全部通过测试点。 舍弃 @Parameters 方法, 只采用 @Test 方法, 一边生成数据一边进行测试。
构造80阶完全图, 一次性加入80个节点。 之后, 每一次使用 addRelation 方法, 便会使用 tempTest 对当下的 network 进行 queryTripleSum 方法的正确性测试。 在生成80阶完全图后, 采用 modifyRelation 方法将 network 的所有边删除, 同理, 每次调用此方法便进行一次正确性测试
在此基础上, case2、 case8因测试开销太大导致运行时出错。 为此, 将80阶完全图改为50阶完全图, 便可全部通过JUnit测试

第二次作业

在第一次的基础上, 严格化isPure检测方法, 用于检测调用方法前后network中的persons数组是否改变

第三次作业

与前两次要求不同, 此次是对新增Message部分的测试
构造五十个emojiId, 通过随机次的 addMessage, sendMessage 操作, 随机分配emojiId的heat。 之后将已发送的message通过 addMessage 重新存入network中, 以便后续对异常方法的检测

实现Junit测试

利用规格信息assginablepure对相应容器修改的限制,来编写断言。先执行一遍正确方法,后执行可能出错的代码,判断调用方法后是否出现非必要的增删、容器中数据改变等一系列错误

学习体会

一些想法

对算法层面的要求较高,不符合“面向对象”的思想。且课程组并未告知要进行优化、动态维护,全靠自己悟
第三次迭代时将 MyNetwork 中方法压至500行是不必要且繁琐的举动

相应收获

熟悉JML语言。编写 JML 规格可以提高设计的正确性和可迭代性,这钟设计方式在未来可能遇到的大型项目团队合作中非常需要
了解基于规格的层次化设计。掌握了数据、方法、类的规格及其设计方法
初步掌握Junit测试方法。与oopre相比掌握更多用法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值