BUAAOO第三单元总结

BUAA面向对象课程JML单元总结

JML语言

理论基础

JML全称Java Modeling Language,是一种行为接口规格语言(Behavior Interface Specification Language, BISL),用于对java程序进行规格化设计。

JML本身支持java语言,在此基础上进行了语法拓展,主要有原子表达式、量化表达式、集合表达式、操作符,此处不再赘述。

JML规格包括方法规格和类型规格,无论程序内部如何组织,只要外部接口满足JML规格,就可认定正确。JML既可以先于程序完成,用以开展逻辑严格的规格化设计,也可以针对已有代码实现,从而提高代码可维护性。

工具链

由于JML采用了严格的数学化描述,使得自动化验证成为可能。目前已有不少基于JML的程序验证工具:

  • OpenJml集成了JML编译和规范检查,用以编译含有JML标记的代码。

  • SMT Solver可以部署在OpenJml中,用以对代码进行静态的等价性验证。

  • JML UnitNG可以基于JML生成一个java测试类框架,对代码进行动态的自动化测试。

SMT Solver验证

pass ^_^

JML UnitNG测试

由于自动化工具对部分JML语法的支持可能存在问题,这里使用自定义的JML规范和类来进行测试。

下面是一个自建的坐标类Coordinate,支持的方法有判断是否为原点、交换xy、判断相等,均按照相应的JML实现。

// src/com/Coordinate.java
package com;

public class Coordinate {

   private /*@spec_public@*/ int x;
   private /*@spec_public@*/ int y;

   public Coordinate(int x, int y) {
       this.x = x;
       this.y = y;
  }

   //@ ensures \result == ((x == 0) && (y == 0));
   public /*@pure@*/ boolean isOrigin() {
       return x == 0 && y == 0;
  }

   //@ assignable x, y;
   //@ ensures x == \old(y);
   //@ ensures y == \old(x);
   public void swap() {
       int temp = x;
       x = y;
       y = temp;
  }

   /*@ also
     @ public normal_behavior
     @ requires obj != null && obj instanceof Coordinate;
     @ assignable \nothing;
     @ ensures \result == (x == ((Coordinate) obj).x && y == ((Coordinate) obj).y);
     @ also
     @ public normal_behavior
     @ requires obj == null || !(obj instanceof Coordinate);
     @ assignable \nothing;
     @ ensures \result == false;
     @*/
   @Override
   public /*@pure@*/ boolean equals(Object obj) {
       if (obj instanceof Coordinate) {
           return x == ((Coordinate) obj).x && y == ((Coordinate) obj).y;
      }
       return false;
  }
}

经过一番折腾(此处感谢伦佬的讨论区教程),配置好工具进行测试,结果如下:

[TestNG] Running:
Command line suite

Failed: racEnabled()
Passed: constructor Coordinate(-2147483648, -2147483648)
Passed: constructor Coordinate(0, -2147483648)
Passed: constructor Coordinate(2147483647, -2147483648)
Passed: constructor Coordinate(-2147483648, 0)
Passed: constructor Coordinate(0, 0)
Passed: constructor Coordinate(2147483647, 0)
Passed: constructor Coordinate(-2147483648, 2147483647)
Passed: constructor Coordinate(0, 2147483647)
Passed: constructor Coordinate(2147483647, 2147483647)
Passed: <<com.Coordinate@29444d75>>.equals(null)
Passed: <<com.Coordinate@2280cdac>>.equals(null)
Passed: <<com.Coordinate@44e81672>>.equals(null)
Passed: <<com.Coordinate@60215eee>>.equals(null)
Passed: <<com.Coordinate@4ca8195f>>.equals(null)
Passed: <<com.Coordinate@65e579dc>>.equals(null)
Passed: <<com.Coordinate@61baa894>>.equals(null)
Passed: <<com.Coordinate@b065c63>>.equals(null)
Passed: <<com.Coordinate@768debd>>.equals(null)
Passed: <<com.Coordinate@490d6c15>>.equals(java.lang.Object@7d4793a8)
Passed: <<com.Coordinate@449b2d27>>.equals(java.lang.Object@5479e3f)
Passed: <<com.Coordinate@27082746>>.equals(java.lang.Object@66133adc)
Passed: <<com.Coordinate@7bfcd12c>>.equals(java.lang.Object@42f30e0a)
Passed: <<com.Coordinate@24273305>>.equals(java.lang.Object@5b1d2887)
Passed: <<com.Coordinate@46f5f779>>.equals(java.lang.Object@1c2c22f3)
Passed: <<com.Coordinate@33e5ccce>>.equals(java.lang.Object@5a42bbf4)
Passed: <<com.Coordinate@270421f5>>.equals(java.lang.Object@52d455b8)
Passed: <<com.Coordinate@18ef96>>.equals(java.lang.Object@6956de9)
Passed: <<com.Coordinate@769c9116>>.isOrigin()
Passed: <<com.Coordinate@6aceb1a5>>.isOrigin()
Passed: <<com.Coordinate@2d6d8735>>.isOrigin()
Passed: <<com.Coordinate@ba4d54>>.isOrigin()
Passed: <<com.Coordinate@12bc6874>>.isOrigin()
Passed: <<com.Coordinate@4c75cab9>>.isOrigin()
Passed: <<com.Coordinate@1ef7fe8e>>.isOrigin()
Passed: <<com.Coordinate@6f79caec>>.isOrigin()
Passed: <<com.Coordinate@67117f44>>.isOrigin()
Passed: <<com.Coordinate@5d3411d>>.swap()
Passed: <<com.Coordinate@2471cca7>>.swap()
Passed: <<com.Coordinate@5fe5c6f>>.swap()
Passed: <<com.Coordinate@6979e8cb>>.swap()
Passed: <<com.Coordinate@763d9750>>.swap()
Passed: <<com.Coordinate@5c0369c4>>.swap()
Passed: <<com.Coordinate@2be94b0f>>.swap()
Passed: <<com.Coordinate@d70c109>>.swap()
Passed: <<com.Coordinate@17ed40e0>>.swap()

===============================================
Command line suite
Total tests run: 46, Failures: 1, Skips: 0
===============================================


Process finished with exit code 0

分析结果,发现以下几点:

  • 除了第一个莫名其妙的rac失败,其余方法均通过验证。

  • 有关int的样例,几乎全部是溢出边界数据的测试,连随机数据都不弄几组,看起来十分的人工智障……

  • 这才三个极其简单的方法就这么一大坨信息,要是测个地铁系统,那岂不是……

最后感觉自己对工具仍然一头雾水,停留在抄讨论区大佬的阶段,深入研究真的既无心也无力……

架构设计

第一次

第一次结构较为简单,作业要求的两个类+一个Main完事,类接口也完全是指导书要求什么就做什么。

 

第二次

第二次作业除了Main和作业要求的两个类(换了个艺术点的名字,HighwayToHell和MasterGraph),还使用了一个GraphInfo内部类用于存储无权的无向图,并实现图的一些操作。这样两个主要类均不涉及大段代码,个人认为算是一个不错的架构实现。

 

第三次

第三次作业采用和第二次作业相同的思路,尽量不在两个主要类(换了个现实点的名字,ChangpingLine和BeijingRaiway)里涉及复杂逻辑,两个类总代码量控制在350行以内。根据不同需要,建立了两个图类,各自附送一个工具类。

 

重构情况

首先辩解一下,没有做继承并不是因为重构量过大,单纯就是因为不想同时在几个不同的文件里看代码,所以直接复制粘贴了……

个人对本单元的重构情况较为满意,第一次到第二次几乎没有任何重构,粘贴过去再加东西就解决了;第二次到第三次重构的点在于图类需要支持的操作不同,但重构幅度也不大。

总之,虽然没有做成标程那样漂亮的架构,但重构情况比起前几个单元已经有了巨大的改善,个人比较满意。

BUG及其修复

PathContainer

  • 中测

    • 对构造ArrayList的容量参数理解有误,认为可以对带参初始化的ArrayList直接进行set操作,导致构造方法就爆出异常……实际上该参数只是对ArrayList内部性能做了优化,带参初始化的ArrayList仍是一个空列表,外部操作与不带参无异。

  • 强测/互测未见bug

Graph

  • 中测

    • 最初未考虑自环,加/减边时对自环直接跳过。后经群友提醒,加入了自环的情况。

  • 强测/互测未见bug

RailwaySystem

  • 中测

    • 在带权图中采用了一个动态二维数组来实现Floyd算法,但初始化二维数组时未判断相应点是否已被删除,加入判断条件后修复。

    • 最有意思的一个bug,是关于1 2 3 4 2 5 6的问题。我的程序对换乘的处理采取的是建边方式,将同一条线路上的结点全部相连。由于1 2 5 6被判定为可直达,在建边时需要更复杂的权值计算方式。我最初依照1 2 3 4 2 5 6样例思考,认为只需扫描一遍,将重复结点中间的部分跳过即可。但很快我发现这种做法无法处理诸如0 1 2 3 4 1这样的环路,正确的计算方法应为:为每一条线路建立一个子图,将子图中某两点的最短路径作为这两点的建图权值。

  • 强测/互测

    • 一失足成千古恨,因为删除边时漏写了一个“+2”,强测惨变25分……抛开马虎粗心的借口,这次翻车一来反映出我一直以来测试能力的不足,二来也让我明白了防错性编程的重要性,这些内容将在心得体会部分详细说明。

心得体会

关于可拓展性

在本单元作业中,我特别注意了一些类的可拓展性。比如在地铁系统中我将图结构开类存储,这个图结构含有类型变量,且与作业的任务逻辑不相关。凡是支持hashcode和equals方法的类,都可以利用这一图结构进行管理,从而完成查找边、计算最短路径等操作。

关于测试

这几次作业其实暴露出我的测试能力十分薄弱。首先是缺乏精力去搭建评测程序,而地铁系统作业则带给我一个更加难以解决的问题:如何判断正确性?因为没有标程,判断正确性的唯一方法似乎就是手画手算了……但这样只能构造规模较小的用例,我地铁系统作业的致命笔误之所以没有被查出,固然有构造用例能力弱的原因,但也有一部分是因为只能构造小用例,导致随机用例的边过于稠密,删边的bug便难以被查出。看来如何全面、巧妙地构造测试用例,真的是一个需要仔细琢磨的问题。

关于防错性编程

除去细心和构造强测试用例,其实还有一道防线本能避免这次地铁系统的致命bug,那就是防错性编程。回到bug本身,某些情况下在图结构中删除边时使用的权值参数比正确权值少了2,造成的后果可能是删除了另一条边,也可能是删除了不存在的边,对路径少但单条路径中结点多的随机数据,后者的可能性应该更高。我作为图结构的设计者,对于删除不存在的边这一未定义操作,应该通过异常等手段通知调用者才对。但我出于谜之自信,或者说,懒,对不存在的边直接return了事。这导致在测试过程中可能多次由于bug删除了不存在的边,而我浑然不知,最后把bug带到了强测……

一点吐槽

本单元的设计思路很好,但是实际效果似乎不尽如人意……

  • 指导书和JML似乎都成并行的了……大部分情况下只看指导书就能完成任务,JML真正仔细看的只有具体怎么抛出异常……我承认我是有点懒,有时候看完指导书觉得很明白了就不去仔细看规格,但规格经常出错改来改去最后绕的不行,我真的不如看指导书……

  • JML不能跑/有错/官方提供反面教材。官方建议使用自动化工具验证JML,但实际上很多规格跑不起来;官方提供的规格有错误,比如地铁系统改了好几次才改对,但是已经改到抽象得不成样子了;课上实验的JML,括号不匹配、限制条件不满足、甚至还出现了和PPT上反面教材一摸一样的代码。总之感觉课程组准备不是很充分,有点云教学的意思。

一点建议

如同很多群友所说,我也建议将JML提前到第一单元。一方面是有助于同学们先建立工程思想,另一方面对JML单元本身的教学效果也有裨益。

之所以这样说,是我认为JML重在思想而非细节。如果放在后面,由于课程本身的进度,任务不可能过于简单。而对复杂任务写起JML,正如昂神所说“这玩意真写起来挺烧脑的”。牵涉到过多细节,到地铁系统甚至连老师们也一度翻车,改了好几版,我们看起来更是痛苦的要命,这样成为数理逻辑课显然不是JML单元教学的目的。

因此,将JML单元提前,顺便降低任务难度、简化JML代码,也许是一个不错的思路。

转载于:https://www.cnblogs.com/AbyssGazeaAlso/p/10902997.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值