• 面向对象第三单元博客作业


    面向对象第三单元博客作业

    针对第三单元的三次作业和课程内容,撰写技术博客

    • (1)梳理JML语言的理论基础、应用工具链情况

    • (2)部署SMT Solver,至少选择3个主要方法来尝试进行验证,报告结果

    ​ • 有可能要补充JML规格

    • (3)部署JMLUnitNG/JMLUnit,针对Graph接口的实现自动生成测试用例, 并结合规格对生成的测试用例和数据进行简要分析

    • (4)按照作业梳理自己的架构设计,并特别分析迭代中对架构的重构

    • (5)按照作业分析代码实现的bug和修复情况

    • (6)阐述对规格撰写和理解上的心得体会

    JML Language

    理论基础

    JML是一种形式化的、面向JAVA的行为接口规格语言,基于Larch方法构建。其最重要的特点是契约式设计(Design by contract)

    笔者个人的理解是,JML结合了契约式设计和规格,其理论基础是数理逻辑,可以由形式化推理工具验证正确性。

    工具链

    一般来说,JML的工具链有JML检查工具,编译器,自动构造测试用例工具,SMT Solver等工具。

    JML官方主页

    http://www.eecs.ucf.edu/~leavens/JML//index.shtml

    不幸的是,官方预先编译好的工具jmlc、jmlunit、jmldoc在Java 8无法正常工作。对于Java8,推荐使用openjml

    Users who want to work with Java 1.5, 1.6, 1.7, or later sources should consider using OpenJML first.

    OPENJML包含了编译器,SMT Solver,命令行前端等。

    另外,OPENJML提供了Eclipse插件,可以直接在IDE使用OPENJML的功能,包括但不仅限于JML规格的语法检查、ESC静态检查、RAC动态检查。

    生成测试的工具有jmlunit,JMLUnitNG等。不过经过笔者测试,在使用Java 8的情况下,jmlunit无法正常工作,而JMLUnitNG可以生成测试代码。

    SMT Solver

    部署

    SMT Solver使用Microsoft的Z3 4.3.1

    最简单的例子

    Student.java

    public class Student {
        private /*@ spec_public @*/ String name;
        //@ public invariant credits >= 0;
        private /*@ spec_public @*/ int credits;
        /*@ public invariant credits < 180 ==> !master &&
          @ credits >= 180 ==> master;
          @*/
        private /*@ spec_public @*/    boolean master;
        /*@ requires sname != null;
          @ assignable everything;
          @ ensures name == sname && credits == 0 && master == false;
          @*/
        public Student (String sname) {
            name = sname;
            credits = 0;
            master = false;
        }
        /*@ requires c >= 0;
          @ ensures credits == old(credits) + c;
          @ assignable credits, master;
          @ ensures (credits > 180) ==> master;
          @*/
        public void addCredits(int c) {
            updateCredits(c);
            if (credits >= 180) {
                changeToMaster();
            }
        }
        /*@ requires c >= 0;
          @ ensures credits == old(credits) + c;
          @ assignable credits;
          @*/
        private void updateCredits(int c) {
            credits += c;
        }
        /*@ requires credits >= 180;
          @ ensures master;
          @ assignable master;
          @*/
        private void changeToMaster() {
            master = true;
        }
        /*@ ensures this.name == name;
          @ assignable this.name;
          @*/
        public void setName(String name) {
            this.name = name;
        }
        /*@ ensures 
    esult == name;
          @*/
        public /*@ pure @*/ String getName() {
            return name;
        }
    }
    
    

    运行ESC检查:

    java -jar openjml.jar -encoding utf-8 -esc Student.java
    

    得到如下输出

    Student.java:34: 警告: The prover cannot establish an assertion (ArithmeticOperationRange)
    in method updateCredits:  overflow in int sum
            credits += c;
                    ^
    

    显然,当credits==2147483467且c>0时,credits会发生溢出。

    JMLUnitNG

    测试Graph

    java -jar $HOME/jmlunitng-1_4.jar -cp src:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -sp $HOME/specs-homework-2-opensource/src/main/java -d test src/MyGraph.java
    javac -cp src:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -d test src/**/*.java
    java -jar $HOME/openjml.jar -cp src:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -d test -rac -racShowSource src/MyGraph.java
    javac -cp test:$HOME/jmlunitng-1_4.jar:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -d test test/*.java
    java -cp test:$HOME/jmlunitng-1_4.jar:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar MyGraph_JML_Test
    
    

    注意:这里的OPENJML没有指定规格路径,是由于Graph的规格导致OPENJML出现以下错误:

    error: A catastrophic JML internal error occurred.  Please report the bug with as much information as you can.
      Reason: Unexpected call of JmlFlow.visitJmlTypeClauseConstraint
    1 error
    

    不指定Graph规格,也能生成测试,但是只盲目测试了边界。

    [TestNG] Running:
      Command line suite
    
    Passed: racEnabled()
    Passed: constructor MyGraph()
    Passed: <<MyGraph@18ef96>>.addPath(null)
    Passed: <<MyGraph@ba4d54>>.containsEdge(-2147483648, -2147483648)
    Passed: <<MyGraph@de0a01f>>.containsEdge(0, -2147483648)
    Passed: <<MyGraph@4c75cab9>>.containsEdge(2147483647, -2147483648)
    Passed: <<MyGraph@1ef7fe8e>>.containsEdge(-2147483648, 0)
    Passed: <<MyGraph@6f79caec>>.containsEdge(0, 0)
    Passed: <<MyGraph@67117f44>>.containsEdge(2147483647, 0)
    Passed: <<MyGraph@5d3411d>>.containsEdge(-2147483648, 2147483647)
    Passed: <<MyGraph@2471cca7>>.containsEdge(0, 2147483647)
    Passed: <<MyGraph@5fe5c6f>>.containsEdge(2147483647, 2147483647)
    Passed: <<MyGraph@6979e8cb>>.containsNode(-2147483648)
    Passed: <<MyGraph@763d9750>>.containsNode(0)
    Passed: <<MyGraph@2be94b0f>>.containsNode(2147483647)
    Passed: <<MyGraph@d70c109>>.containsPathId(-2147483648)
    Passed: <<MyGraph@17ed40e0>>.containsPathId(0)
    Passed: <<MyGraph@50675690>>.containsPathId(2147483647)
    Skipped: <<MyGraph@31b7dea0>>.containsPath(null)
    Passed: <<MyGraph@3ac42916>>.getDistinctNodeCount()
    Failed: <<MyGraph@47d384ee>>.getPathById(-2147483648)
    Failed: <<MyGraph@2d6a9952>>.getPathById(0)
    Failed: <<MyGraph@22a71081>>.getPathById(2147483647)
    Failed: <<MyGraph@3930015a>>.getPathId(null)
    Failed: <<MyGraph@629f0666>>.getShortestPathLength(-2147483648, -2147483648)
    Failed: <<MyGraph@1bc6a36e>>.getShortestPathLength(0, -2147483648)
    Failed: <<MyGraph@1ff8b8f>>.getShortestPathLength(2147483647, -2147483648)
    Failed: <<MyGraph@387c703b>>.getShortestPathLength(-2147483648, 0)
    Failed: <<MyGraph@224aed64>>.getShortestPathLength(0, 0)
    Failed: <<MyGraph@c39f790>>.getShortestPathLength(2147483647, 0)
    Failed: <<MyGraph@71e7a66b>>.getShortestPathLength(-2147483648, 2147483647)
    Failed: <<MyGraph@2ac1fdc4>>.getShortestPathLength(0, 2147483647)
    Failed: <<MyGraph@5f150435>>.getShortestPathLength(2147483647, 2147483647)
    Failed: <<MyGraph@1c53fd30>>.isConnected(-2147483648, -2147483648)
    Failed: <<MyGraph@50cbc42f>>.isConnected(0, -2147483648)
    Failed: <<MyGraph@75412c2f>>.isConnected(2147483647, -2147483648)
    Failed: <<MyGraph@282ba1e>>.isConnected(-2147483648, 0)
    Failed: <<MyGraph@13b6d03>>.isConnected(0, 0)
    Failed: <<MyGraph@f5f2bb7>>.isConnected(2147483647, 0)
    Failed: <<MyGraph@73035e27>>.isConnected(-2147483648, 2147483647)
    Failed: <<MyGraph@64c64813>>.isConnected(0, 2147483647)
    Failed: <<MyGraph@3ecf72fd>>.isConnected(2147483647, 2147483647)
    Failed: <<MyGraph@483bf400>>.removePathById(-2147483648)
    Failed: <<MyGraph@21a06946>>.removePathById(0)
    Failed: <<MyGraph@77f03bb1>>.removePathById(2147483647)
    Failed: <<MyGraph@326de728>>.removePath(null)
    Passed: <<MyGraph@25618e91>>.size()
    
    ===============================================
    Command line suite
    Total tests run: 47, Failures: 26, Skips: 1
    ===============================================
    

    测试Path

    这里的OPENJML依然无法指定规格路径,否则会抛出Internal JML bug - please report. 。jmlunitng可以指定规格,但是不支持Java 8的新语法。

    jmlunitng_jar=$HOME/openjml/jmlunitng-1_4.jar
    
    java -jar $jmlunitng_jar -cp src:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -sp $HOME/specs-homework-2-opensource/src/main/java -d test src/MyPath.java
    javac -cp src:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -d test src/**/*.java
    openjml -cp src:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -d test -rac -racShowSource src/MyPath.java
    javac -cp test:$jmlunitng_jar:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar -d test test/*.java
    java -cp test:$jmlunitng_jar:$HOME/specs-homework-2-1.2-raw-jar-with-dependencies.jar MyPath_JML_Test
    
    

    结果即使指定了规格,自动生成的测试仍然存在一定程度的盲目,甚至出现忽视前置条件的情况

    [TestNG] Running:
      Command line suite
    
    Passed: racEnabled()
    Failed: constructor MyPath(null)
    Passed: constructor MyPath({})
    Failed: <<MyPath@1>>.compareTo(null)
    Passed: <<MyPath@1>>.containsNode(-2147483648)
    Passed: <<MyPath@1>>.containsNode(0)
    Passed: <<MyPath@1>>.containsNode(2147483647)
    Passed: <<MyPath@1>>.equals(null)
    Passed: <<MyPath@1>>.equals(java.lang.Object@1c2c22f3)
    Passed: <<MyPath@1>>.getDistinctNodeCount()
    Failed: <<MyPath@1>>.getNode(-2147483648)
    Failed: <<MyPath@1>>.getNode(0)
    Failed: <<MyPath@1>>.getNode(2147483647)
    Passed: <<MyPath@1>>.hashCode()
    Passed: <<MyPath@1>>.isValid()
    Passed: <<MyPath@1>>.iterator()
    Passed: <<MyPath@1>>.size()
    
    ===============================================
    Command line suite
    Total tests run: 17, Failures: 5, Skips: 0
    ===============================================
    

    从以上例子可以看出,OPENJML以及JML自动生成测试的工具仍然有很大的改进空间。

    架构设计

    1

    这次作业要求实现Path和PathContainer接口,直接按照题目要求实现对应的类。

    2

    这次作业新加了一个Graph接口,由于在增加、删除路径需要对已经建立的图进行重构,为了方便笔者直接将PathContainer合并到Graph的实现代码。实际上这严重违背了单一功能的原则。

    3

    这次作业笔者放弃了之前把PathContainerGraph的实现写在一起的方法,将Graph和PathContainer的实现代码分离,MyRailwaySystem继承MyGraph实现RailwaySystem接口。

    Graph的实现中并没有包含图的存储和算法,而是新建一个类,存储图的数据结构和封装最短路算法(Floyd算法),对外提供求任意两点最短路径、求连通块等方法。

    RailwaySystem也同理,构造函数传入PathContainer、问题类型,建立不同的图,内部用Dijkstra或SPFA算法求最短路,实现缓存机制存储已经计算过的最短路长度。(不推荐笔者将问题类型传入构造函数的这种方式。这里应该实现一个Factory,根据问题类型构图)

    bug和修复情况

    1

    这次作业代码在Path类的compareTo存在1个bug:

    return getNode(i) - b.getNode(i);
    

    显然这样直接相减,会出现整数运算溢出,导致原本大于关系应该返回正数,实际上返回负数。事实上,运行ESC检查很容易发现。

    src/MyPath.java:84: warning: The prover cannot establish an assertion (ArithmeticOperationRange) in method compareTo:  underflow in int difference
                return getNode(i) - b.getNode(i);
                                  ^
    src/MyPath.java:84: warning: The prover cannot establish an assertion (ArithmeticOperationRange) in method compareTo:  overflow in int difference
                return getNode(i) - b.getNode(i);
    

    由于compare方法溢出,强测只剩60分。令人遗憾的是,互测中没有人发现我的这个bug。

    修复:改成使用Integer类的compare

    return Integer.compare(getNode(i), b.getNode(i));
    

    2

    强测100分,互测也未发现bug。

    3

    由于拆点后添加的边数过多(最坏情况下,边数为(120^3)),而笔者使用的Dijkstra算法复杂度为(O(m log n)) ,强测中有1个测试点超时。互测中,拆点后的构建的图出现最坏情况(形成稠密图)出现超时。

    修复方法是:在仍然采用拆点的前提下,调整了构图策略,由原来换乘站拆出来的点之间相互连接(A_i---A_j)修改为连接到对应的中间节点(A_i---M_A),减少边数。同时,由于拆出的点之间增加了中间节点,边(A_i--M_A)权重相应变为原来(A_i---A_j)的一半。

    修复后,最坏情况下边数为(120^2),比修复之前减少很多,成功解决了超时的问题。

    对规格撰写和理解上的心得体会

    1. 规格的作用是约束,只关心状态变化,而不关心实现细节。

      在第1次JML作业中,笔者尚未充分理解JML,对PathContainer的规格困惑了很久。后来才认识到JML规格中//@ public instance model non_null Path[] pList;只是一种形式,用来描述对结果的约束。

    2. 对于方法规格的撰写必须跳出面向过程的思维,不考虑实现细节,而是用JML描述函数的作用,包括结果必须满足的条件、修改的变量、参数的要求、异常等。

    3. 数据规格的编写主要是不变式invariant 状态变化约束constraint ,同时注意到这些约束只针对可见状态,而不是任意时刻。

  • 相关阅读:
    HDU 5363 Key Set(快速幂取模)
    HDU 5339 Untitled(暴搜)
    POJ 2406 Power Strings
    Dedecms备份还原网站有效方法
    DEDECMS文章列表每隔8行文章添加分隔虚线
    DEDECMS突破TAG和关键字长度的限制
    为织梦dedecms制作全文RSS订阅源
    DedeCms中Channel用typeid无效
    织梦CMS/Dedecms添加自定义函数
    sql批量换dedecms文章来源和作者
  • 原文地址:https://www.cnblogs.com/yspjack/p/10883689.html
Copyright © 2020-2023  润新知