BUAAOO_UNIT3

OO第三次博客作业

JML介绍

1. 理论基础

JML是基于java的规格设计表示语言, 是一种行为接口规格语言(Behavior Interface Specification Language,BISL)

JML以javadoc注释的方式来表示规格, 其核心是规格化表述

举例来说, 就像下面的片段描述了JML语言所包含的部分组件

  • 原子表达式
    • \result:非void函数的返回值
    • \not_assigned(x1, x2):变量在方法执行过程中不可被赋值
    • \not_modified(x,y,...):同上
    • \old(expr):进入表达式expr前的取值
  • 包含量词的表达式
    • \forall:全称量词, 对任意
    • \exist:特称量词, 存在
    • \sum:表示给定范围的表达式的和。
    • \exceptional_behavior:满足一些条件需要抛出部分异常, signal同理
  • 方法规格
    • 前置条件:requires语句,表示方法执行的一些前置约束
    • 后置条件:ensures语句,表示方法执行结果的后置(post)要求
    • 作用范围:assignable语句,表示允许方法改变的状态
  • 不变式与约束(类型规格)
    • 不变式(invariant):可见状态下必须满足的状态
    • 约束(constraint):如果发生状态变化, 应该满足怎样的约束

可见JML语法上很简洁, 但是写好规格又是另一码事, 感觉上四舍五入是一门新的编程语言.

2. 工具链

一个概念或想法, 如果没有群众的信念, 没有完整的工具链与行为模式, 终究是空想. 缺失前者那叫脱离群众, 缺失后者那叫不切实际, 而JML......

JML的工具链包含JMLUnit, OpenJML, JMLUnitNG, SMT Solvers, ESC/Java2(OpenJML的前身?), KeY(Eclipse), TACO(检查编译后的静态字节码, 像findbugs) 等 可以归结为Java的SMT(Satisfiability modulo theories) 工具.

SMT是SAT 加上 Theory Solvers的组合体, SAT广泛应用在其他领域, 对于本单元java程序, 相当于JMLUnit, OpenJml这些产物是SMT的框架.

部署

OpenJML

感觉OpenJML不甚完善, 甚至不支持\forall, 运行后一顿报错, OpenJML两周前才更新过, 感觉有点可惜.

JMLUnitNG

为了简化结果, 感受这个工具的用处, 我将GroupTest简化到只包含一个方法, 并且故意写的和JML不一样.

public class TestGroup {
    
    /* @ public normal_behavior
     * @ ensures \result == (x > 0 & y > 0);
     */
    public static boolean over0(int x, int y) {
        return (x + y > 0); // wrong!!!
    }
    
    /* ...... 为简化其他方法被注释掉, 只留下了测试用方法
    			原生方法全是fail, 因为边界数据无法满足require......*/
    
    public static void main(String []args) {
       	// test method
        over0(0, 1);
    }
    
}

首先把jmlunitng.jar放在项目的根目录下.

运行:

扫描二维码关注公众号,回复: 11247830 查看本文章

java -jar jmlunitng.jar test/com/oocourse/spec1/main/TestGroup.java

生成文件后, 执行

javac -cp jmlunitng.jar test/com/oocourse/spec1/main/TestGroup.java

编译后, 执行

java -jar openjml.jar -rac test/com/oocourse/spec1/main/TestGroup.java

生成一个rac文件后, 执行

java -cp jmlunitng.jar test/com/oocourse/spec1/main/TestGroup.java

cmd看到输出如下:

output(cmd): (不知道为啥没有复制换行, 我自己换的行)

[TestNG] Running: Command line suite

Failed: racEnabled()

Passed: constructor Test()

Passed: static main(null) Passed: static main({})

Passed: static over0(-2147483648, -2147483648)

Passed: static over0(0, -2147483648)

Passed: static over0(2147483647, -2147483648)

Passed: static over0(-2147483648, 0)

Passed: static over0(0, 0)

Failed: static over0(2147483647, 0)

Passed: static over0(-2147483648, 2147483647)

Failed: static over0(0, 2147483647)

Failed: static over0(2147483647, 2147483647)

===============================================

Command line suite

Total tests run: 13, Failures: 4, Skips: 0

===============================================

===============================================

可见所谓自动生成, 只是边界数据和0. 不过确实可以根据JML验证正确性.

而且有点难用, 我验证一个文件就需要四条容易报错的指令. 不过可以写脚本编程一个.

我认为没必要为了这几个数据的自动化测试而消耗时间, 自己写Junit更好一些.

作业架构设计

作业九

作业九逻辑简单明了, 直接翻译规格即可, 需要注意的地方如下.

  1. JML使用的数组直接翻译成ArrayList是否会造成性能下降? 通过测试结果可以了解到, 是不会的.
  2. 判断两点是否可达的算法, 我使用了简单的搜索, dfs和bfs都试了, 为了代码简洁使用dfs.
  3. MyPerson中的acquaintance可以使用HashMap进行存储, 减少直接遍历
  4. 由于只有添加两个点之间的联通关系以及添加点, 所以isCircle还可以用简单的并查集维护可达关系

唯一可以进行架构或是面向对象式的优化的地方在应用策略模式实现isCircle, 使用组合而非继承, 来增强扩展性, 从而方便尝试更多的算法, 而不是硬编码的NetWork里面.

这次作业以学习如何阅读JML为主, 正确性检验我使用对拍的方式进行, 要了一份class文件没有发现什么不同.

bug: 没有bug, 也没有发现别人的.

作业十

在第九次作业的基础上增加了Group, 并且需要实现Group内部的查询方法.

新方法有queryAgeMean, queryAgeVar, 这两者直接进行遍历运算即可, 也可以在Group内设置变量进行保存, 需要注意在容量为零的时候进行特判. 计算Var需要注意误差处理.

新方法还有queryConflictSum, 同样可以占用空间进行保存.

relationSum和valueSum的查询都可以进行变量保存以实现\(O(1)\)查询, 不过会导致addPerson与addRealtion的复杂度增加到\(O(n)\), 这些都容易处理. 没有处理的话\(O(n^2)\) 的查询复杂度有点吃不消.

bug: 在对拍的时候数据构造的比较随机, 导致我忽视了corne cases, 忘记特判除零的情况, 导致了严重的测试问题, 修改后通过. 是我的锅, 我背.

作业十一

增加了若干NetWork接口的方法, 涉及到一些新的方法, 和数据结构相关.

重点在Group可以删除人, NetWork可以删除边, 导致并查集进行isCircle复杂度与bfs相同, 故改成了bfs.

查询blockSum的操作可以用并查集进行维护, find时进行优化, 实现\(O(1)\)查询.

minPath使用Dij即可, 为了防止超时进行了堆优化, 堆我使用优先队列容器进行维护. 不过为了更有OO的感觉应该新建图类进行专门的操作, 消除一些耦合度.

strongLink我一开始理解成两点是否在一个环上, 看了讨论区后了解到了点双的概念, 因此获得了易于进行搜索的术语, 了解到了Tarjan算法, 并且进行了实现, 复杂度在\(O(n)\), 本质上就是一个dfs. 感觉边界状况没有很多, 还是容易实现的. 唯一需要注意的地方是, 我是先用Tarjan找到所有的点双连通分量, 再在所有分量中查询两点是否存在. 但是需要注意数学上两个点一个边这种情况也算是点双, 但是在JML的描述里是不算的, 故要删去只有两个点的点双分量.

不过助教给出的方法, 也就是删点的方法, 我认为更加直观... 我没想到是我的问题.

查询年龄区间的算法我直接遍历.

为了尽可能减少预处理的时间, 也就是说减少无用的搜索与维护, 我设置了dirty变量进行处理, 如果没有进行添加点或者删除边, 添加边的操作, 直接查询即可.

由于有删除边和删除Group点的操作, 记得修改对应的relationSum和valueSum.

bug: 没有bug, 在强侧前也进行了对拍. 一切都好. 也没有发现别人的bug, 全屋0hack.

架构展示:

只展示第十一次的UML图, 因为这单元是完全的迭代, 如果类设计合理那么只需扩展即可.

体会与总结

"在人类的社会活动中,契约一般是用于两方,一方(供应者)为另一方(客户)完成一些任务。每一方都期待从契约中获得利益,同时也要接受一些义务".

对于软件开发, class程序员和业务程序员同样是契约关系, 这种抽象的契约作用于两方,每一方都会完成一些任务,从而促成契约的达成,但同时,每一方也会接受一些义务,作为制定契约的前提,有任意一方无视了必尽义的义务,则契约失败.

代码大全里面有写: "契约式编程可以严格区分责任,让每个人都不必为了迁就他人的错误而进行"艰难的编码". 每个人按照契约处理好自己的事情,让损毁契约的人承担责任. "

JML对于契约的支持足够完成契约的设计, 比如先验(pre)的requires, 后置(post)的ensure, 异常的抛出节点等. 所以理论上可以成为足够自动化的验证方式.

不过在编写JML的时候, 需要客户进行严格的思考, 甚至需要探查部分内部的状态, 注意边界情况, 注意类型的状态等. 这就导致了JML难以由没有编程经验的人员进行, 在需求到代码的阶段又多了一层JML, 不过在更进一步的合作开发中, 能够一定的解决准确性问题.

我在第一二次作业中感受到了JML的严谨性, 但是在第三次作业中发现了JML的复杂性, 对于算法的描述, 用JML似乎不太合理, 算法或数据结构有自己独特的名字, 就是为了简化人们的交流成本, 设计模式的各种名字也是如此, 而我认为, JML这种对一些需求算法的方法描述无疑实在增加交流成本.

期待下次的OO作业, 也期待下次能学习到更有用, 更与时俱进的知识.

猜你喜欢

转载自www.cnblogs.com/okifurhere/p/12936444.html