zoukankan      html  css  js  c++  java
  • OO第三单元总结--根据JML写代码

    一. JML语言

    1. 理论基础

      首先,JML不是JAVA的一部分,它是一群研究者为JAVA设计的扩展部分,但还没有得到官方的支持。因此,JAVA编译器并不支持JML,所以要想JML起作用,只能采用类似openJML这样的第三方来编译,将JML 规格编译为运行时检查的语句,即RAC code(runtime assertion checking)。如果代码实现与其JML规格不一致,将引发运行时JML exception。

      JML遵从契约式设计范式(DBC),Design by contract是软件开发的一种方法,核心是类与其客户之间达成契约。JML是一种形式化的、 面向 JAVA的行为接口规格语言。

      推荐一篇step by step以一个比较复杂的例子来讲解JML语法和设计的教程:https://www.ibm.com/developerworks/library/j-jml/

    2. 应用工具链

    • jmlrac: test for violations of assertions during execution
    • ESC/Java2: static verification; compile-time proving that contracts are never violated
    • jmldoc: javadoc-style documentation
    • jmlc: assertion-checking compiler
    • jml4c: a new JML compiler built upon the Eclipse JDT open-source platform

      上述工具很多都已经不再维护(跟不上java的升级,大多支持到java 1.5), 看大家抱怨openJML坑,就想找找有没有更好用的JML工具,结果发现openJML竟然是最好用的。

    • openJML:目前对JML支持最好,维护最积极的JML编译器了 
    • jmlunit/jmlunitNG: unit testing tool

    二. 部署SMT Solver

      maven+openJML+Eclipse试了一下午还是报错,命令行没试过,不知道怎么样,这一部分只好放弃了。

    二. JMLUnitNG

      下述过程可复现,代码和运行结果是一致的,如果有兴趣,可以参照我的步骤在本地试一试。

      配置过程参见讨论区https://course.buaaoo.top/assignment/71/discussion/199  ,为了保证配置成功,我也是在Linux下配置。

       Graph接口方法的规格几乎全部含有 exists 或 old,根本搞不了,于是只能退而求其次,去验证Path类。Path类里面的sum等语法也不支持,于是我就模仿MyPath,写了个比减法稍复杂的demo。

     1 // yifan/MyPath.java
     2 package yifan;
     3 
     4 public class MyPath {
     5     private/*@ spec_public @*/ int[] nodes;
     6 
     7     public MyPath(int[] nodeList) {
     8         nodes = new int[nodeList.length];
     9         for (int i = 0; i < nodeList.length; i++) {
    10             nodes[i]=nodeList[i];
    11         }
    12     }
    13 
    14     //@ ensures 
    esult == nodes.length;
    15     public /*@pure@*/ int size() {
    16         return nodes.length;
    17     }
    18 
    19     /*@ requires index >= 0 && index < size();
    20       @ assignable 
    othing;
    21       @ ensures 
    esult == nodes[index];
    22       @*/
    23     public /*@pure@*/ int getNode(int index) {
    24         return nodes[index];
    25     }
    26 
    27     //@ ensures 
    esult == (nodes.length >= 2);
    28     public /*@pure@*/ boolean isValid() {
    29         return nodes.length >= 2;
    30     }
    31 
    32     public static void main(String args[]) {
    33         return;
    34     }
    35 }

        运行过程 step-by-step:

    ./jmlunitng yifan/MyPath.java
    javac -cp jmlunitng.jar yifan/**/*.java
    ./openjml -rac yifan/MyPath.java
    javac -cp jmlunitng.jar yifan/MyPath_InstanceStrategy.java  
    java -cp jmlunitng.jar yifan.MyPath_JML_Test

    运行前目录

     

    运行后目录

     运行结果

    结果讨论

    • MyPath(null),MyPath.java中会调用length,错误,意料之中;
    • 明明已经requires index>=0了,为什么生成的测试例子里还会有负数?就算刚开始nodes=new int[10],避免nodes为null的情况还是这种结果;
    • 似乎JmlUnitNG只会生成int的边界值和0,不管requires? 这只是我的猜想。

    有个问题需要讨论一下:

    • 为什么不写成
    //@ public instance model non_null int[] nodes;
        private ArrayList<Integer> nodes;

            openJML会把规格和实现里的nodes当成一个nodes,会报错。

            那这么写不就行了吗?

    //@ public instance model non_null int[] nodes;
        private ArrayList<Integer> myNodes;

            非也,会报下述错误

    yifan/MyPath.java:6: 警告: JML model field is not implemented: nodes
        //@ public model int[] nodes;
                               ^

            nodes没实现?对的。要想解决这个问题,就得写抽象函数,可以看看这篇论文https://digitalcommons.utep.edu/cgi/viewcontent.cgi?referer=https://www.google.com.hk/&httpsredir=1&article=2073&context=cs_techrep ,我大概写了个抽象函数,没bug,但也没起作用,还是报nodes没实现的错误,所以这里就不贴我的抽象函数了。

    从上图可以看到如果不实现nodes,大多数方法都被skip掉了。

    三. 架构设计

    1. 第一次作业

     直接继承接口,简单地实现了两个类。

    2. 第二次作业

     

     为了更改方便,直接ctrl+v把MyPathContiner的代码复制到MyGraph。

    3. 第三次作业

     

    由于第二次作业比较复杂,再去动很可能出bug,于是在写第三次作业的时候对于第二次作业已有的代码我一行都没动,只是在MyGraph类里加了求连通块个数的Public的函数。这样一来bug少了,但新加的架构和已有的架构看起来很不协调。

    看了std码之后,惊呼:我之前竟将所有代码都直接放在src文件夹下。好的分层设计应该像标程一样,起码得有多个文件夹吧,比如base,core,util,grpha等。

    四. bug和修复情况

    三次作业均无bug。

    五. 心得体会

    撰写:规格的撰写用到了很多离散数学的知识,掌握常见的几种模式后,就能够比较容易地写出一些简单函数的规格。以我目前的水平看,写代码还是要比写规格来得容易。

    理解:实操中,我实际上是先看的指导书,对于含混的地方,(比如起点和终点相同的情况下,算不算换乘,最小费用算多少?同一路径中如果有环该怎么算?)我会去详细阅读规格,因为规格严谨的描述了某一个方法该干什么。(但这次直觉上的理解实际上更靠谱,比如同一路径中有环的情况,直觉上的理解是不用非得绕着环走一圈多余的路,但死扣规格,确实得绕,事后证明是老师或助教的规格写错了)。

    JML和JmlunitNG:JML设计期望太高,其目的是写出规格后,就可以自动生成测试用例,还可直接检查代码是否准确实现了规格;但相关工具链及其难用,功能及其有限,目前我只能写个简单的demo探索一下它的性质,体验一下整个流程。

  • 相关阅读:
    详解ASP.NET MVC 2中强类型HTML辅助方法
    C#3.0规范(四)Lambda 表达式
    HTTP Status 检测器 : SEO在线工具 : SEO 网站优化推广
    CSS 命名规范
    数据库表及字段命名、设计规范
    C#变量命名中的[匈牙利命名法][骆驼命名法][帕斯卡(pascal)命名法]
    C#命名规范
    推荐磁盘分区管理工具Acronis Disk Director Suite 10.0(有效注册码+汉化补丁)
    经典海量jQuery插件
    GET PUT POST的含义(Http)
  • 原文地址:https://www.cnblogs.com/yifan-liu/p/10898286.html
Copyright © 2011-2022 走看看