BUAAOO Unit3 形式化规格约束下进行编程

§3 形式化规格约束下进行编程

S1 JML相关梳理

P0 作用

1)描述方法的功能,参数要求和输出结果,异常和正常行为

2)结合JML检查工具可以检测代码是否实现了预期功能,或者自动生成测试样例

P1 语法

0)注释格式:

//@ single line
/*@
  @ block
  @*/

1)行为:

  • normal_behavior:正常行为
  • exceptional_behavior:异常行为
    • signals语句:signals(E e) R,当条件R满足时要求抛出异常E
  • 行为发生条件间不应当有交集

2)作用域

  • public:只能用于public方法/字段
  • private
  • package
  • protect
  • spec_public:用于使一个private变量能够在规格中使用

3)前置条件和后置条件

  • requires:前置条件,调用方法前应当满足的条件,通常限制参数
  • ensures:后置条件,方法返回时应当满足的条件,通常限制返回值

4)模型域:JML注释的成员变量 model instance

//@ public model instance JMLObjectBag elementsInQueue
//@ public model static JMLObjectBag elementsInQueue
  • 模型域到实现域的映射描述:represent

5)副作用:方法执行后修改了字段的值

  • assignable直接修饰的arg,或是arg依赖的变量,允许在方法执行中赋值
  • modifiable 允许修改
  • 局部变量和方法形参也允许修改
    • 形参代表的引用可以重新指向,但是不允许修改指向对象的值
  • arg在方法开始时尚未分配,允许分配
  • assign /noting 相当于 pure。assign /everything 表示方法可以修改任何变量

6)类级不变量:进入和退出一个类中每个方法都必须满足的条件

  • invariant:不变量条件

    constraint:变化约束条件,作用类似invariant,限制前后状态关系

  • 分静态与非静态成员的约束:

    //@ public instance invariant
    //@ public static invariant
    

7)布尔表达:

  • 量词:\forall \exists \min
  • 关系: 蕴含==> <== <=>

8)E<:E2 E1是E2 子类/同类

9)集合:new ST {T x| R(x) && P()}

P2 JML关键字与库函数

\result返回值

\old(args)返回调用前args

\not_assigned(args)参数是否在方法执行过程中赋值

\not_modifed(args)参数在方法执行期间取值不变

\nonnullelements(containner)表示continner中存储对象非null

\type(type) 返回type对应的类Class

\typeof(expr) 返回expr的准确类型

\sum product给定范围内表达式求和 积

//@ (\sum int i; 0 <= i && i < 5; i)

\max \min最大\小值

//@ (\max int i; 0 <= i && i < 5; i)

\num_of 满足条件的取值个数

//@ \num_of int x; R(x); P(x)
//equals to:
//@ \sum int x; R(x) && P(x); 1

P3 规格的继承

1)重载方法的规格需要使用 //@also 注释

2)为保证LSP原则,子规格应当满足一定约束:

  • 子规格不变量蕴含父规格不变量
  • 子规格副作用小于父规格副作用
  • 子规格前后置条件蕴含父规格前后置条件

P4 JML相关工具链

1)OpenJML: 开源的JML工具,具有检查JML文档规格语法,逻辑问题的功能。早期我用它进行过JML文档语法的静态测试。

BUAAOO Unit3 形式化规格约束下进行编程

2)JMLUnitNG:基于JML的单元测试工具,支持自动生成测试样例

3)JMLEclipse:Eclipse自带的JML插件

注:关于JML工具链的尝试:

  • OpenJML:许久前用其测过JML文档语法。由于自带说明书,用起来还是比较方便。如果要动态测试,需要编写represent实现模型域到实现域的映射。

  • JMLUnitNG:写了简单的测试程序

    public class Test{
    	/*@ public normal_behavior
    	  @ ensures \result == a+b
    	  @*/
    	public static int add(int a, int b) {
    		return a+b;
    	}
    }
    

    运行结果:
    BUAAOO Unit3 形式化规格约束下进行编程

    JMLUnit主动测试了边界范围的值,并没有做一般性测试。

  • Group的测试。一如既往,JMLUnitNG只会测试边界,由于它不会生成Person,所以它总是丢个null进去

S2 作业分析

1)架构分析:本次作业的规格都已由课程组给出。经过Metric量化分析,发现结构非常合理。

2)算法分析:

  • 在isCircle中使用了并查集算法,在每次加入新成员时,分配一个新的连通块。当加入联系时,若联系的两个人处于不同连通块,则合并这两个连通块。在合并操作中,我选择修改包含成员数少的连通块中成员的连通块号。据了解有另一种做法是维护一个AVL,但是这样做在查找某位成员的连通块号的时候需要进行遍历。经过考虑,我选择了前者。理由是这种实现更快,而且我认为连通块查询频率高于修改频率,这样做更加符合实际。
  • 在queryStrongLink中使用了Tarjan的魔改算法,没有记录所有的点双连通块,仅仅记录根节点(id1)所在的点双连通块,查找其中有无出现过(id2)并且块大小大于2。这样做减少了空间和时间开销。同时不同于常见的点双连通算法将边入栈的做法,我将点入栈,并且在查找到割点时,不将割点退栈。这样实现更快,也不需要添加多余的边类。
  • 在queryMinPath中使用了堆优化DIJ算法。使用了内置的PriorityQueue优先队列,因此实现也很快。
  • 对于Group,使用了效率从属变量方式,保存待查询的变量。这样修改其值很方便。缺点是relationsum这一属性与network是耦合的。

3)测试分析:

  • 第一次作业中,我由于将p.queryValue(q)=0等价于!p.islink(q),导致错了三个点。主要是测试不足
  • 后两次作业中,由于采取充分测试,没有出bug。
  • 测试中结合黑白盒测试。Junit负责测试边界情况。黑盒测试进行压力测试和一般情况测试,并尝试找出白盒测试中的盲点。

S3 心得体会

1)在本单元中,我才初步开始进行测试。在测试中我了解到,白盒测试往往用于功能性检查,黑盒测试往往用于鲁棒性检查。在白盒测试中总是很难做到全面的测试覆盖。

2)对于规格语言的理解:规格语言形式化地描述了程序的行为。我认为规格语言主要在开发之前作为思路梳理,避免后续编程中过于投入而失去整体把握。规格语言有利于产品经理与程序员沟通。同时,也明确定义了需求。但是我觉得JML实际上是为方法编写了一个等价实现。规格不一定要使用JML来描述,我觉得使用Python这样浅显易懂的语言来编写规格文档也可以起到相同的效果。其次,JML工具链并不成熟,许多测试工具没能正常运行。测试工具对于exisit array这样的规格描述基本无能为力。

上一篇:Reactjs-JQuery-Vuejs-Extjs-Angularjs对比


下一篇:Maven常用插件配置和使用