全文共4909字,推荐阅读时间15~20分钟。

文章共分五个部分:

JML总结

定义

  • JML是一种对Java程序进行规格化设计的表示语言
  • JML是一种行为接口规格语言(Behavior Interface Specification Language,BISL)

注释结构

  • JML和Javadoc的注释方式相同

    • 每行都以@起头
    • 行注释://@ annotation
    • 块注释:/*@ annotation @*/
//@ public model non_null int[] elements

/*@ public normal_behavior
@ ensures \result == elements.length;
@*/
  • model:表示elements数组是规格层次的描述,不是JML语句所在类的组成部分,也不要求该类声明这一变量。
  • non_nullelemants数组对象的引用不能是null
  • 作用域:和Java相同,JML也有自己的作用域,此处是public.
  • 规格中每个子句需要以分号结尾
  • 接口中声明规格变量时,要求明确变量的类别。
    • 静态
    • 实例
// inside an interface
//@ public static model non_null int[] elements
//@ public instance model non_null int[] elements

表达式

  • JML断言中,不可以使用带有赋值语义的操作符。例如,++,--,+=等。

原子表达式

  • \result:表示一个非void的方法的返回值

  • \old(expr):表达式expr在执行相应方法前的取值

    • expr是引用时,表示的是指向的对象(地址)有没有发生变化,而不是对象本身有没有发生变化。因此,当对象发生变化时,expr只要依然指向它,就没有发生变化。
    • map.size() = \old(map).size() != \old(map.size())
  • \not_assigned(x,y,...):括号中变量如果在执行过程中赋值则返回true,否则为false.

  • \not_modified(x,y,...):类似not_assigned(),追踪变量值在执行过程中是否发生变化

    相比\not_assigned(x,y,...)使用较多,通常用在前置条件和后置条件中,断言变量在进入函数前后是否相等。

  • \nonnullelements(container):断言container对象中存储的对象没有null

    container != null && (\forall int i; 0<=i && i < container.length; container[i]!=null)
  • \type(type):返回基本类型type对应的引用类型,例如type(boolean)的值为Boolean.TYPE.

    TYPE等价于java.lang.Class

  • \typeof(expr):返回表达式expr对应的准确类型,例如\typeof(false)的值为Boolean.TYPE.

量化表达式

  • \forall:全称量词修饰的逻辑表达式,表示给定范围内的元素如果都满足相应需求则为真。

    • (\forall int i,j; 0<=i && i < j && j < 10; a[i] < a[j]),当a是严格单增的数组时该表达式为真。
  • \exists:存在量词修饰的逻辑表达式,表示如果存在给定范围内的元素满足相应需求则为真。
    • (\exists int i; 0 <= i && i < 10; a[i] < 0),当数组中有小于0的元素时表达式为真。
  • \sum:返回给定范围内指定的求和表达式的
    • (\sum int i; 0 <= i && i < 5; i)即\(0+1+2+3+4\)
    • (\sum int i; 0 <= i && i < 5; i * i)即\(0^2+1^2+2^2+3^2+4^2\)
  • \product:返回给定范围内指定的求积表达式的
  • \max:返回给定范围内指定表达式的最大值
    • (\max int i; 0 <= i && i < 5; i)
  • \min:返回给定范围内指定表达式的最小值
  • \num_of:返回给定范围内满足相应条件的表达式个数
    • (\num_of int x; 0 < x && x <=20; x % 2 ==0)

集合表达式

JML规格中构造一个局部的集合(容器),明确集合中可以包含的元素。

  • new ST {T x | R(x) && P(x)},其中R(x)对应集合中x的范围,P(x)对应x取值的约束。

    • new JMLObjectSet {Integer i | s.contains(i) && 0 < i.intValue() }

    s指java程序中构造的容器,如ArrayList.

操作符

\(JML操作符=Java操作符+子类型关系+等价关系+推理+变量引用\)

  • 子类型关系操作符:E1<:E2,当E1是E2的子类型时,表达式结果为真。(包括两者是相同类型)

  • 等价关系操作符:b_expr1<==>b_expr2b_expr1<=!=>b_expr2,其中表达式均为布尔表达式

    <==><=!=>的优先级低于==!=

  • 推理操作符:b_expr1==>b_expr2b_expr1<==b_expr2,和离散中的谓词表达式的真值判断相同。

  • 变量引用操作符:JML规格语句可以直接引用Java代码或者JML规格中定义的变量,使用\nothing,\everything来概括作用域,常出现在assignable句子中。

    • assignable \nothing表示当前作用域下每个变量可以在方法执行过程中被赋值。

模型域(model)

理解为规格中的成员变量

//@ public model instance JMLObject elemQueue;

创建了一个公开的、类型为JMLObject的、名叫elemQueue模型实例

  • instance:虽然这个模型是在接口中被定义出来的,但是每个实现接口的类都有各自的模型域。
  • 声明的模型域和Java代码无关,不可以在Java代码中被引用。
  • model为Java代码提供了一个建议,如果Java代码中实现了相应的变量,那么就可以使用JML进行检查,但是如果没有实现,则只是提供一个建议

模型域的取值(represents)

用于沟通JML和Java

//@ private represents jmlHeap <- javaHeap;

实现了jmlHeap和javaHeap的同步,每次永不需要手动操作。

方法规格

基本概念

  • 前置条件:对方法输入参数的限制
  • 后置条件:对方法执行结果的限制
  • 副作用:方法在执行过程中对输入对象或者this对象进行了修改(赋值等操作)

前置条件(pre-condition)

  • 通过requires子句表示

    • requires P;,其中P是逻辑表达式(可以包含逻辑连接词如&&,||,!

后置条件(post-condition)

  • 通过ensures子句表示

    • ensures P;,其中P是逻辑表达式

副作用(side-effects)

  • 约束副作用范围:assignable,modifiable(没有\

    • assignable elements,max,min,多个变量时使用,隔开。
  • JML不允许在约束语句中指定规格中声明的变量数据
  • 二者可以替换,但是通常使用assignable.
  • 纯粹访问:pure

    public /*@ pure @*/ int getCredits();

    方法规格描述可以省略\assignable

类型规格

对Java程序中定义的数据类型设计的限制规则,通常是对类、接口设计的约束规则。

不变式限制(invariant)

不变式是要求在所有可见状态下都要满足的特性。

  • invariant Pinvariant是关键词,P是谓词。

  • 可见状态(visible state):凡是会修改成员变量(静态+实例)的方法的执行期间,对象都处于不可见状态。

    这些方法在执行时,对象状态不稳定,因此不可见。

public class Path {
private /*@ spec_public @*/ ArrayList<Integer> seq_nodes;
private /*@ spec_public @*/ Integer start_node;
private /*@ spec_public @*/ Integer end_node;
/*@ invariant seq_nodes !=null &&
@ seq_nodes[0] == start_node &&
@ seq_nodes[seq_nodes.length - 1] == end_node &&
@ seq_nodes.length >= 2;
@*/
}
  • spec_public表示private属性的成员变量在规格中对调用者可见,在Java程序中的可见性不受影响。
  • invariant语句最后以;结尾
  • 不变式中可以直接引用pure形态的方法

  • 静态不变式(static invariant):只约束静态成员变量

  • 实例不变式(instance invariant):约束静态+非静态成员变量

  • 实例

    • 前置条件调用pure方法

      /*@ requires c >= 0;
      @ ensures getCredits() == \old(getCredits()) + c;
      @*/
      public void addCredits(int c);
    • 使用量化表达式进行限制

      /*@ ensures !contains(elem);
      @ ensures (\forall int e; e != elem; contains(e) <==> \old(contains(e)));
      @ ensures \old(contains(elem)) ==> size == \old(size) - 1;
      @ ensures !\old(contains(elem)) ==> size == \old(size);
      @*/
      public void remove(int elem);
    • signals子句&signals_only子句

      public abstract class Student {
      // public model non_null int[] credits;
      /*@ public normal_behavior
      @ requires z >= 0 && z <= 100;
      @ assignable \nothing;
      @ ensures \result == credits.length; @ also
      @ public exceptional_behavior
      @ requires z < 0;
      @ assignable \nothing;
      @ signals_only IllegalArgumentException; @ also
      @ public exceptional_behavior
      @ requires z > 100;
      @ assignable \nothing;
      @ signals (OverFlowException e) true;
      @*/
      public abstract int recordCredit(int z) throws IllegalArgumentException,OverFlowException;
      }
      • normal_behavior/exceptional_behavior:后面不用带;

      • also使用情景:

        • 子类覆写父类方法后,进行规格补充时。
        • 分隔正常功能和异常功能

        also各个块的前置条件不能有重合,因为各部分不是串行的。

      • signals (Exception e) b_expr:当设置b_expr为true时,方法需要抛出相应的异常。

        在异常功能中,ensures语句一样可以使用。

      • signals_only Exception:不关心什么条件下会抛出,只要方法抛出即可。

状态变化约束(constraint)

状态变化约束是在限制状态变化的方式,在前序可见状态当前可见状态中生效。

public class ServiceCounter {
private /*@spec_public@*/ long counter;
//@ invariant counter >= 0;
//@ constraint counter == \old(counter) + 1;
}

constraint限制可以在每个改变counter的方法的后置条件中实现,但是相比直接在变量本身添加限制会更繁琐。

  • 静态状态约束(static constraint)
  • 实例状态约束(instance constraint)

方法规格&类型规格

  • 当一个类是不变类时,构造方法的后置条件和成员变量的不定式选一个实现即可。

JML FAQ

  • JML只对纯方法支持断言确认

  • 当方法修改引用的具体域时,需要指明是哪个域。

    /*@ assignable obj.x
    @*/
    public void foo(Bar obj){
    // TODO
    }
  • JML==equals的区别

    两者的区别和Java中的区别相同

  • exceptional_behavior情况下,可以定义方法的前置条件、副作用和异常抛出,但不能定义方法的后置条件。

    使用signals时,可以不需要定义exceptional_behavior的前置条件。

  • JML中是否可以引用Java中的变量

    • JML无论何时都可以调用Java中的pure方法,但是并不是随时都能够引用变量。
    • 如果Java中的变量在JML对应方法的参数列表方法体中出现,那么就可以引用。
    • /*@ spec_public @*/的作用就是让不满足第二条的变量能够被调用者JML引用。
    • 如果第三条也不满足,即JML对应的方法以及其调用的方法中都没有出现需要的变量,则不能够引用相应的变量。

    引用变量则可以使用其方法

JML工具链

SMT solver

Z3 Theorem Prover is a cross-platform satisfiability modulo theories (SMT) solver by Microsoft.

​ --Wikipedia

因为openjml开发较早,所以导致很多我们现在使用的JML语法无法被识别,因此实现了基本的四则运算进行验证。

方法如下:

public class Test {

	// @ensures \result == a + b;
public static int add(int a, int b) {
return a + b;
} // @ensures \result == a - b;
public static int sub(int a, int b) {
return a - b;
} // @ensures \result == a * b;
public static int mult(int a, int b) {
return a * b;
} // @ensures \result == a / b;
public static int div(int a, int b) {
return a / b;
}
}

SMT solver反馈结果如下:

java -jar .\openjml.jar -esc -prover .\z3-4.7.1.exe -exec .\z3-4.7.1.exe .\Test.java

错误: An error while executing a proof script for add: (error "Error writing to solver: (set-logic ALL) java.io.IOException: 管道正在被关闭。")
错误: An error while executing a proof script for div: (error "Error writing to solver: (set-option :AUTO_CONFIG false) java.io.IOException: 管道正在被关闭。")
错误: An error while executing a proof script for mult: (error "Error writing to solver: (set-option :smt.MBQI false) java.io.IOException: 管道正在被关闭。")
错误: An error while executing a proof script for sub: (error "Error writing to solver: (declare-sort REF 0) java.io.IOException: 管道正在被关闭。")
4 个错误

JMLUnitNG部署与测试

由于JMLUnitNG支持的JML语法较少,与现在我们使用的大部分语法并不兼容,因此将Group中的部分方法及其规格进行简化后进行了自动生成数据并测试的试验。

依次键入以下命令:

java -jar jmlunitng-1_4.jar Group.java
javac -cp jmlunitng-1_4.jar *.java
openjml -rac Group.java
java -cp jmlunitng-1_4.jar Group_JML_Test

add方法得到如下反馈结果

[TestNG] Running:
Command line suite
Passed: racEnabled()
Passed: constructor Group()
Failed: static add(-2147483648, -2147483648)
Passed: static add(0, -2147483648)
Passed: static add(2147483647, -2147483648)
Passed: static add(-2147483648, 0)
Passed: static add(0, 0)
Passed: static add(2147483647, 0)
Passed: static add(-2147483648, 2147483647)
Passed: static add(0, 2147483647)
Failed: static add(2147483647, 2147483647)
===============================================
Command line suite
Total tests run: 11, Failures: 2, Skips: 0
===============================================

可以看到自动生成的数据均为边界数据,对程序的极限条件检查还是很到位的。

作业分析

Unit3要求我们模拟现实生活中的社交网络,迭代主要是增加不同的功能。本单元的作业分析有独特的地方——因为三次作业都采用了一致的架构,因此以第三次作业的UML图为例即可了解整个单元迭代开发的过程。

从UML图可以很直接地看出来整个系统的模拟思路:通过分包策略,让网络(graph)、人(vertex)、算法(algo)之间实现“透明调度”。如此清晰的架构得益于课程组的精心设计,让我真真切切感受到了JML规范在Java语言中的强大。

这里说一点题外话,今天在知乎上看到一个问题:

如果让你来创建一种全新的语言,你会希望它具有什么特征?

高赞回答中提到了

能够通过一种程序员和用户都能看懂的格式抽象出所有运行流程的逻辑语义。

这不就是JML最擅长的吗?这样看来,JML早已悄悄地跑到了大多数语言前面。

第一次作业

第一次作业要求我们实现一个支持可达性判断的图模拟社交网络。

UML图如下

  • 代码结构

结构大致可以分为三层:graph->vertex->algo.在算法层面不用考虑每个人具体的情况,只需要知道这是个点,并且图是无向的,那么就可以在接口一致的情况下调用封装好的算法了。(这和Java自带的数据结构非常相似)

  • 复杂度分析


从反馈结果可以看出,因为课程组为我们提供的良好架构,因此复杂度都体现在算法层面,而不是系统本身的逻辑架构上。这么说或许有些抽象,进一步阐释就是说,复杂度的升高并不是因为架构的复杂,而是来自算法对逻辑和数据结构要求的提高。

第二次作业

第二次作业要求我们实现可以增加子图,并在子图中进行查询的社交网络模拟系统。

UML图如下

  • 代码结构

代码架构基本延续了第一次的设计风格,虽然加入了SocialGroup这个子图结构,但是可以发现UML图的三层结构几乎没有变化。

  • 复杂度分析

从反馈结果可以看出,这次的复杂度依然集中体现在算法中,而不是架构上,JML的威力足够让人吃惊。

第三次作业

第三次作业要求我们进一步实现可以进行高级查询操作的社交网络模拟系统。

UML图如下

  • 代码结构

代码架构基本延续了第一次的设计风格,但是出于查询要求需要,增加了一些新的算法。

  • 复杂度分析


从反馈结果可以看到,由于queryStrongLinked的出现,导致了最高复杂度的“易主”,因为采用的是Menger定理的枚举思想,因此循环较多,在圈复杂度上体现得尤为明显。

评测相关

重难点聚焦

从三次开发和互测的过程来看,问题和上一单元相同,主要都在CPU时间上。但是这一单元出现这个问题的原因不是来自大家的陌生,就我自己而言,是对某些算法的陌生和复杂度的估计不准确。

在第三次作业中,因为是根据Menger定理枚举获得分离集进行queryStrongLinked的判断,虽然采用了数组和BFS代替普通的数据结构和递归进行加速,但是还是出现了超时的情况。讲道理复杂度和标程的\(O(n^2)\)相比应该是会小一点的,可能因为标程在其他的部分做的更加完善,所以能够不超时。

之后将算法改为了Tarjan来进行判断,时间复杂度确实下降不少。

权当这是个教训吧,还是要时刻牢记:

要射下太阳的人,他的箭头总会比太阳高一些的。

Hack所用的策略

主要有两种方式:

  • 使用完成作业时有意义的样例进行测试
  • 利用评测机自己生成数据

评测机简要介绍

这是评测机的working directory

  • center:存放评测的核心控制代码,用于组织编译->运行->反馈功能

  • data:存放自动生成的数据

  • download_data:存放测试中出现问题的数据,可以用于回归测试。

  • factory:存放数据生成代码

  • output:存放各个测试代码的输出

  • result:存放各个测试代码的结果

  • ruler:存放标程

  • src:存放源码

  • filter.py:用于格式化数据以提交

重构策略

本单元的一大特点就是规格化编程,同时得益于课程组的精心设计,在架构上不需要我们进行过多的自主设计。课程组已经为我们设计好了相当完备和精巧的架构,因此本单元的重构问题其实是被巧妙地规避了,在这里衷心地向老师和助教们的辛苦构思表示感谢。

课程体验感受

这一单元的迭代开发让我接触到了Java工程的超现实主义开发方法:面向JML规格编程。请原谅我给它取了一个略显中二的名字,但是它的与众不同的确让人无法抗拒。

我们很多时候都会面临读别人写的代码的情况——无论是在此基础上继续开发,还是借鉴思想另起炉灶,对代码执行逻辑的准确理解都是相当重要的。因此,养成良好的阅读习惯不仅有助于我们去理解别人,更有助于我们规范自己的风格。这一单元的学习在我看来是非常有必要的,因为它让大多数人第一次不是仅仅为了完成功能而写代码,而是开始为了搭建一个系统并构建一个良好的可扩展的生态,这是让我醉心于OO的一大原因,也是OO的魅力所在。

最后,再次感谢一路上陪伴的老师、助教、同学,谢谢大家!

Unit3-窝窝社交圈的更多相关文章

  1. HipHop算法:利用微博互动关系挖掘社交圈

    /* 版权声明:可以任意转载,转载时请务必标明文章原始出处和作者信息 .*/                  CopyMiddle: 张俊林 TimeStamp:2012年3 月 在微博环境下,如何 ...

  2. 百度大脑UNIT3.0智能对话技术全面解析

    智能客服.智能家居.智能助手.智能车机.智能政务……赋予产品智能对话能力是提升产品智能化体验.高效服务的重要手段,已经开始被越来越多的企业关注并布局.然而,智能对话系统搭建涉及NLP.知识图谱.语音等 ...

  3. 百度大脑UNIT3.0详解之嵌入式对话理解技术

    相信很多人都体验过手机没有网时的焦虑,没有网什么也做不了.而机器人也会遇到这样的时刻,没有网或者网络环境不好的情况下,无法识别用户在说什么,也无法回复用户.在AIoT(AI+物联网)飞速普及的现在,智 ...

  4. 百度大脑UNIT3.0详解之知识图谱与对话

    如今,越来越多的企业想要在电商客服.法律顾问等领域做一套包含行业知识的智能对话系统,而行业或领域知识的积累.构建.抽取等工作对于企业来说是个不小的难题,百度大脑UNIT3.0推出「我的知识」版块专门为 ...

  5. 百度大脑UNIT3.0详解之数据生产工具DataKit

    在智能对话项目搭建的过程中,高效筛选.处理对话日志并将其转化为新的训练数据,是对话系统效果持续提升的重要环节,也是当前开发者面临的难题之一.为此百度大脑UNIT推出学习反馈闭环机制,提供数据获取.辅助 ...

  6. 百度大脑UNIT3.0解读之对话式文档问答——上传文档获取对话能力

    在日常生活中,用户会经常碰到很多复杂的规章制度.规则条款.比如:乘坐飞机时,能不能带宠物上飞机,3岁小朋友是否需要买票等.在工作中,也会面对公司多样的规定制度和报销政策.比如:商业保险理赔需要什么材料 ...

  7. 百度大脑UNIT3.0详解之语音语义一体化方案

    在电话客服场景里,用户和机器人交流的过程中,经常会出现沉默.打断机器人.噪声等情况,机器人在应对这些异常情况的时候,需要语音和语义理解技术进行处理,才能实现用户和机器人的流畅交谈.而这些能力的获取与应 ...

  8. JML规格编程系列——OO Unit3分析和总结

    本文是BUAA OO课程Unit3在课程讲授.三次作业完成.自测和互测时发现的问题,以及倾听别人的思路分享所引起个人的一些思考的总结性博客.主要包含JML相关梳理.SMT Solver验证.JML单元 ...

  9. LuoguP5464 缩小社交圈

    LuoguP5464 缩小社交圈 背景:洛谷七月月赛T4 题目大意给定\(n\)个点,每个点的权值对应着一个区间\([l_i,r_i]\),两个点\(i,j\)有边当且仅当他们权值的并集不为空集,问有 ...

随机推荐

  1. MySQL slave状态之Seconds_Behind_Master zz

    在MySQL的主从环境中,我们可以通过在slave上执行show slave status来查看slave的一些状态信息,其中有一个比较重要的参数Seconds_Behind_Master.那么你是否 ...

  2. 【面试考】【入门】决策树算法ID3,C4.5和CART

    关于决策树的purity的计算方法可以参考: 决策树purity/基尼系数/信息增益 Decision Trees 如果有不懂得可以私信我,我给你讲. ID3 用下面的例子来理解这个算法: 下图为我们 ...

  3. vscode格式化Vue出现的问题:单引号变双引号 格式化去掉分号

    学习vue框架时,发现在使用vscode格式化vue代码时,出现单引号变成了双引号问题(导致和EsLint要求不一致),从而导致报错!!!!好坑啊!!! 解决方法如下 在文件根目录下创建 .prett ...

  4. thymeleaf将对象Model数据抛到HTML页面

    thymeleaf名称空间  <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymelea ...

  5. 常用的body和通用css设置

    body{ min-width: 320px; width: 15rem; margin: 0 auto; line-height: 1.5; background: #f2f2f2; overflo ...

  6. Xilinx的DocNav文件导航中的文档开头字母缩写都是什么意思?

    在安装Xilinx的开发软件后都会附带安装一个用于查阅Xilinx技术文档的文件导航工具DocNav. 在DocNav中可以找到几乎所有对我们开发Xilinx FPGA有用的技术文档,其中的文档数量更 ...

  7. 编辑器、编译器、文件、IDE等常见概念辨析

    一.编辑器与编译器 1.编辑器与编译器有什么区别? 简单讲,编译器就是将"一种语言(通常为高级语言)"翻译为"另一种语言(通常为低级语言)"的程序.一个现代编译 ...

  8. 技术大佬:我去,这个容易被忽略的小程序Image图片属性,竟然这么屌!

    前段时间在做“高清壁纸推荐”小程序优化的时候,发现一个很实用的图片属性——能够实现最大化压缩图片大小.且图片质量最小的损失,在此之前一直没有注意.今天跟大家分享一下这个属性的用法,主要是让大家能够,意 ...

  9. Emiya家今天的饭 NOIP2019 (CSP?) 类DP好题 luoguP5664

    luogu题目传送门! 首先,硬求可行方案数并不现实,因为不好求(去年考场就这么挂的,虽然那时候比现在更蒟). 在硬搞可行方案数不行之后,对题目要求的目标进行转换: 可行方案数 = 总方案数 - 不合 ...

  10. Shell概述1

    Shell概述1 脚本文件内容(vim ex2) #!/bin/bash #If no arguments,then listing the current directory. #Otherwise ...