2024 BUAA-OO Unit3契约式编程JML总结

前言

本单元我们学习了JML规格,以实现一个社交网络的查询和模拟为背景,根据给出的JML的规格来实现程序,本单元学习令我对契约式编程有了更深的体会。

测试过程

黑箱测试

黑盒测试顾名思义,是指测试的代码的设计细节并不清楚,需要通过大规模的随机数据来进行,这要求生成的数据种类要足够多,覆盖到各种情况,同时数据的量也要足够多,做到“质”“量”兼顾。

白箱测试

白箱测试与黑箱测试正好相反,我们可以知道测试代码具体的实现逻辑,可以有针对性的进行测试,所以白箱测试主要为了检查程序的内部结构、逻辑、循环和路径。通过分析测试代码实现中的漏洞,编造数据精准攻击。

单元测试、功能测试、集成测试、压力测试、回归测试

  • 单元测试:对程序中的一个最小单元的测试,例如用Junit对某一个类的某一个方法进行测试。
  • 功能测试:对程序的功能测试,包括单元测试、集成测试、回归测试。
  • 集成测试:单个单元检测无误后,还可以将多个单元组装起来共同测试,这些单元可能会相互影响,单个单元测试无法判断正误,因此需要集成测试一起检测。比如mrarqlm等指令。
  • 回归测试:修改了代码之后,重新进行测试,避免代码的改变带来新的错误。
  • 压力测试:测试较为复杂的数据,人为让程序的内存、CPU性能、磁盘空间等条件不充足,压力测试能够反映程序算法的复杂度,在本单元主要体现在TLE上。

数据构造

  • 避免稀疏图的情况:将ap和ar的指令概率调大,而且ar的数量一般在ap的二倍,而且要保证ap的数量与指令总数量保持一个比例,例如1:6,这样的稠密图数据可以覆盖大部分bug情况。
  • 避免tag数量或者tag内人数量不足的情况:由于第十一次作业涉及到向tag内的人sendMessage的情况,因此生成数据的时候要保证tag的数量和tag的数量够多,因此生成数据时适当调高atatt指令的概率。
  • 测试TLE:针对一些可能导致TLE的方法,例如qtvs,设置专门的数据点生成大量该指令,用来测试TLE的情况。

架构设计

第九次作业

本次作业大部分都是根据JML的编写,例如JML中Person类给出的acquaintance以及value,已经要求实现了图的点和权值。不同之处体现在:

  • 利用Counter计数器类统计各种异常的次数。
  • 为了减少查找等操作的时间开销,JML中的容器全部用HashMap实现。
  • 动态维护连通块数量:本次指令中的queryBlockSumqueryTripleSum,我在MyNetwork中维护两个属性blockSum和tripleSum,每次ap和ar之后更新两个属性。
  • 建立并查集判断isCircle:并查集还采用了路径压缩和按秩合并来进一步提升性能。

本次作业的强测最后一个点出现了TLE,原因可能是因为在modifyRelation进行删边操作的时候我采用的DFS来遍历结点,但我觉得最有可能的原因是我用的ArrayList来实现visited,导致每次判断visited.contains()的时候都需要遍历一遍visited,这个时间复杂度很高,修复的时候我改成了HashSet,同时把DFS改成了BFS。

第十次作业

第十次作业增加了MyTag类,以及queryCoupleSum等方法

  • 动态维护tag中的valueSum和ageSum:每当att的时候valueSum和ageSum和agePowSum都相应增加,dft的时候相应减掉,还要注意在删边的时候也要在所有包括这两个人的tag中减掉value。queryTagAgeVar的时候用完全平方公式计算。

  • 动态维护Person类中的bestAcquaint:每addRelation的时候就和已经保存的bestAcquanitId进行比较,维护bestAcquaintId。

  • queryCoupleSum我采用O(n)的复杂度遍历MyNetwork中的persons,如果person1的bestAcquaint的bestAcquaint是person1,那么ans++。最后由于每个人计算了两遍,因此结果要除以2.

        @Override
        public int queryCoupleSum() {
            int ans = 0;
            for (Person person : persons.values()) {
                if (persons.containsKey(((MyPerson) person).getBestAcquaint())) {
                    MyPerson best = (MyPerson) getPerson(((MyPerson) person).getBestAcquaint());
                    if (person.getId() == best.getBestAcquaint()) {
                        ans++;
                    }
                }
            }
            return ans / 2;
        }
    
  • 最短路径采用双向BFS:queryShortestPath的时候建立两个方向相反的队列,每次遍历size更小的那个队列,直到两个队列遍历的元素相遇结束。双向BFS可以避免单向BFS一直遍历到结尾导致的TLE。

本次作业强测没有出现问题。

第十一次作业

第十一次作业没有涉及到什么特别的算法,实现起来比较朴素。

  • 由于每次addMessage需要将message放到容器的开头,并且getReceivedMessage需要获取messages的前五个对象,因此Person中存储messages我采用链表LinkedList,每次addMessage调用messages.addFirst()方法进行头插。
  • sendMessage中发红包可能出现tag.isEmpty的情况导致除0问题,这里需要特判一下。

本次作业强测没有出现问题。

Junit测试

我总结的junit测试的步骤如下:

  • 生成数据:随机生成数据的时候可以实现一种伪随机,也就是仅仅将一些有效信息随机生成,例如addRelation和modifyRelation的value,但是person的id、name、age以及tag的id等等可以不随机,按照顺序生成。
  • 实现对比方法:在junit中完全按照jml给出的方法规格写一个对比方法,来判断测试代码的对应方法的返回值是否正确。
  • 判断pure和safe:对于pure方法和safe方法,要判断调用方法前后容器中的对象的属性是否发生了变化,可以在生成数据的时候生成属性完全相同的两个对象,判断调用方法后这两个对象的属性是否一致。
  • 针对JML的每一条ensures,设计对应的检验方法,保证测试的覆盖率足够大。

Junit测试可以很好检验代码实现与规格的一致性,可以通过junit找到很多bug。

心得体会

本单元中我学会了阅读JML,并且通过JML给出的契约来实现代码逻辑。感受到了契约式编程的诸多好处,比如高可靠性,便于测试等等。但是根据JML的契约并不等同于完全按照JML来书写,考虑到性能以及实现中的实际情况,我们可能需要自己改变实现的方式,这体现的是一种规格与实现相分离的思想。 规格化设计只是一种 “设计”,具体实现不一定要这样实现,可以自己实现一些规格之外的方法,方便业务代码的实现。

同时我也接触到了更多的算法,例如并查集、双向BFS等等。此外,作业的性能要求也push我学会了很多优化方法,让我进一步体会到了算法的魅力。

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值