OO第三单元——JML——总结
一、JML的梳理和总结
JML
-
简介
JML(JAVA Modeling Language)是用于对JAVA程序进行规格化设计的一种表示语言。一般具有两种用法:
- 开展规格化设计。其特征为:严格的逻辑性。
- 针对已有的代码实现。书写对应规格,从而提高代码的可维护性。
-
基本表达式结构
-
原子表达式
表达式 含义 \result 方法执行后的返回值 \old(expr) 一个表达式expr在相应方法执行前的取值 \not_assigned(x,y,...) 限制一个方法的实现不能对列表中的变量进行赋值 \not_modified(x.y,...) 限制括号中的变量在方法执行期间的取值未发生变化 \nonnullelements(container) container对象中存储的对象不会有null \type(type) 返回类型type对应的类型(class) \typeof(expr) 返回expr对应的准确类型 -
量化表达式
表达式 含义 \forall 表示对于给定范围内的元素,每个元素都满足相应的约束 \exists 表示对于给定范围内的元素,存在某个元素满足相应的约束 \sum 返回给定范围内的表达式的和 \product 返回给定范围内的表达式的连乘结果 \max 返回给定范围内的表达式的最大值 \min 返回给定范围内的表达式的最小值 \num_of 返回指定变量中满足相应条件的取值个数 -
集合表达式
表达式 含义 new ST {T x|R(x)&&P(x)},其中的R(x)对应集合中x的范围 明确集合中可以包含的元素 -
操作符
表达式 含义 E1<:E2 子类型关系操作符,若为子类型,则为真 b_expr1<==>b_expr2或b_expr1<=!=>b_expr2 等价关系操作符,与java中的==和!=具有相同的效果,优先级更低 b_expr1==>b_expr2 推理操作符,与离散一致 \nothing \everything 变量引用操作符,经常与assignable共同使用
-
-
规格
-
正常行为规格(normal_behavior)
-
前置条件(pre_condition)
通过requires子句来表示:requires P;表达意思是“要求调用者确保P为真”,多个requires子句呈并列关系。
-
后置条件(post_condition)
后置条件通过ensures子句来表示:ensures P; 表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”,同样为并列关系。
-
副作用范围限定(side_effects)
副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。JML提供了副作用约束子句,使用关键词assignable 或者 modifiable 。
/*@ @ ... @ assignable \nothing; @ assignable \everything; @ modifiable elements; @*/
设计中会出现某些纯粹访问性的方法,即不会对对象的状态进行任何改变,也不需要提供输入参数,这样的方法无需描述前置条件,也不会有任何副作用,且执行一定会正常结束。对于这类方法,可以使用简单的(轻量级)方式来描述其规格,即使用 pure 关键词。
public /*@ pure @*/ String getName(); public /*@ pure @*/ int getCredits;
-
-
异常行为规格(exceptional_behavior)
-
signals子句
表达式 含义 signals (***Exception e) b_expr; 当b_expr 为 true 时方法会抛出对应异常e signals_only b_expr; 不强调对象状态条件,强调满足前置条件时抛出相应异常
-
-
JML工具链
-
OpenJML
用于检查JML文档规格语法。
下载地址:http://www.openjml.org/
-
JMLUnitNG/JMLUnit
基于JML的单元测试工具,能够自动生成测试用例,注重边界条件的测试。
下载地址:http://insttech.secretninjaformalmethods.org/software/jmlunitng/
二、SMT Solver
OpenJML
执行语句
java -jar openjml.jar -exec Solvers/z3-4.7.1.exe -esc src/*.java
得到
发现了很多东西完全测不了,只好测Person.java
java -jar openjml.jar -exec Solvers-windows/z3-4.7.1.exe -esc src/Person.java
虽然有很多警告,但总算能跑通了。
三、JMLUnit
JMLUnitNG
文件树如图,然后执行下列4条语句。
java -jar jmlunitng.jar test/Person.java
javac -cp jmlunitng.jar test/*.java
java -jar openjml.jar -rac test/Person.java
java -cp jmlunitng.jar test.Person_JML_Test
得到如下结果:
Passed: racEnabled()
Failed: constructor Person(-2147483648, null, null, -2147483648)
Failed: constructor Person(0, null, null, -2147483648)
Failed: constructor Person(2147483647, null, null, -2147483648)
Failed: constructor Person(-2147483648, , null, -2147483648)
Failed: constructor Person(0, , null, -2147483648)
Failed: constructor Person(2147483647, , null, -2147483648)
Failed: constructor Person(-2147483648, null, null, 0)
Failed: constructor Person(0, null, null, 0)
Failed: constructor Person(2147483647, null, null, 0)
Failed: constructor Person(-2147483648, , null, 0)
Failed: constructor Person(0, , null, 0)
Failed: constructor Person(2147483647, , null, 0)
Failed: constructor Person(-2147483648, null, null, 2147483647)
Failed: constructor Person(0, null, null, 2147483647)
Failed: constructor Person(2147483647, null, null, 2147483647)
Failed: constructor Person(-2147483648, , null, 2147483647)
Failed: constructor Person(0, , null, 2147483647)
Failed: constructor Person(2147483647, , null, 2147483647)
Skipped: <>.compareTo(null)
Skipped: <>.equals(null)
Skipped: <>.equals(java.lang.Object@29444d75)
Skipped: <>.getAcq(-2147483648)
Skipped: <>.getAcq(0)
Skipped: <>.getAcq(2147483647)
Skipped: <>.getAcquaintanceSum()
Skipped: <>.getAge()
Skipped: <>.getCharacter()
Skipped: <>.getId()
Skipped: <>.getName()
Skipped: <>.isLinked(null)
Skipped: <>.queryValue(null)
===============================================
Command line suite
Total tests run: 32, Failures: 18, Skips: 13
===============================================
可以看到验证并不随机,只基于了边界数据的考量,不具有一般性,经过魔改Person类后,跑完的结果是:
Passed: racEnabled()
Failed: constructor Person(-2147483648, null, -2147483648)
Failed: constructor Person(0, null, -2147483648)
Failed: constructor Person(2147483647, null, -2147483648)
Passed: constructor Person(-2147483648, , -2147483648)
Passed: constructor Person(0, , -2147483648)
Passed: constructor Person(2147483647, , -2147483648)
Failed: constructor Person(-2147483648, null, 0)
Failed: constructor Person(0, null, 0)
Failed: constructor Person(2147483647, null, 0)
Passed: constructor Person(-2147483648, , 0)
Passed: constructor Person(0, , 0)
Passed: constructor Person(2147483647, , 0)
Failed: constructor Person(-2147483648, null, 2147483647)
Failed: constructor Person(0, null, 2147483647)
Failed: constructor Person(2147483647, null, 2147483647)
Passed: constructor Person(-2147483648, , 2147483647)
Passed: constructor Person(0, , 2147483647)
Passed: constructor Person(2147483647, , 2147483647)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addAcq(null)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(-2147483648)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(0)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Passed: <>.addValue(2147483647)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Failed: <>.compareTo(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(java.lang.Object@629f0666)
Passed: <>.equals(java.lang.Object@1ff8b8f)
Passed: <>.equals(java.lang.Object@224aed64)
Passed: <>.equals(java.lang.Object@71e7a66b)
Passed: <>.equals(java.lang.Object@5f150435)
Passed: <>.equals(java.lang.Object@50cbc42f)
Passed: <>.equals(java.lang.Object@13b6d03)
Passed: <>.equals(java.lang.Object@73035e27)
Passed: <>.equals(java.lang.Object@4b6995df)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(-2147483648)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(0)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Failed: <>.getAcq(2147483647)
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAcquaintanceSum()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getAge()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Passed: <>.getName()
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Failed: <>.isLinked(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
Passed: <>.queryValue(null)
===============================================
Command line suite
Total tests run: 163, Failures: 54, Skips: 0
===============================================
发现fail的点都是由于null,而null本身也不在设计范围内,因此经过分析测试是成功的,此时的文件树为:
C:.
│ PackageStrategy_int.class
│ PackageStrategy_int.java
│ PackageStrategy_java_lang_Object.class
│ PackageStrategy_java_lang_Object.java
│ PackageStrategy_java_lang_String.class
│ PackageStrategy_java_lang_String.java
│ PackageStrategy_test_Person.class
│ PackageStrategy_test_Person.java
│ Person.class
│ Person.java
│ Person_InstanceStrategy.class
│ Person_InstanceStrategy.java
│ Person_JML_Test.class
│ Person_JML_Test.java
│
└─Person_JML_Data
addAcq__Person_person__5__person.class
addAcq__Person_person__5__person.java
addValue__int_x__0__x.class
addValue__int_x__0__x.java
ClassStrategy_int.class
ClassStrategy_int.java
ClassStrategy_java_lang_Object.class
ClassStrategy_java_lang_Object.java
ClassStrategy_java_lang_String.class
ClassStrategy_java_lang_String.java
ClassStrategy_test_Person.class
ClassStrategy_test_Person.java
compareTo__Person_p2__5__p2.class
compareTo__Person_p2__5__p2.java
equals__Object_obj__10__obj.class
equals__Object_obj__10__obj.java
getAcq__int_x__0__x.class
getAcq__int_x__0__x.java
isLinked__Person_person__5__person.class
isLinked__Person_person__5__person.java
Person__int_id__String_name__int_age__10__age.class
Person__int_id__String_name__int_age__10__age.java
Person__int_id__String_name__int_age__10__id.class
Person__int_id__String_name__int_age__10__id.java
Person__int_id__String_name__int_age__10__name.class
Person__int_id__String_name__int_age__10__name.java
queryValue__Person_person__5__person.class
queryValue__Person_person__5__person.java
四、梳理作业架构设计
由于这次作业架构完全依据课程组的规定,也没有自己创建新的类,类图不再给出。
第九次作业
第一次JML作业可以讨论的不多,主要分为几点:
-
容器问题
在这一次作业中,MyNetwork和Myperson类中我都使用Arraylist作为容器存储,第一个原因是因为这一次作业所实现的功能比较简单,基本按照规格照搬即可,除了iscircle函数,课程组给的数据范围也没有太限制时间和空间,所以直接采用了Arraylist。
-
iscircle的实现问题
这一次作业由于数据弱的原因,再加上写惯了DFS,采用了DFS,但在截止之后注意到DFS在数据过大时,可能会造成内部栈overflow的问题,时间复杂度也是O(n^2),在第二次作业中进行了修改。
Method ev(G) iv(G) v(G) com.oocourse.spec1.main.MyNetwork.circle(int,int) 4.0 4.0 8.0 可以看出,iscircle的实现确实过于臃肿。
-
O(1)还是遍历?
有许多函数是关于类本身的一些参数进行求值的,既可以在addPerson或addRelation的时候进行update再O(1)查询,也可以调用函数时再进行计算,在时间限制不大的时候我选择了后者,也导致了第十次作业的TLE。
第十次作业
这一次作业加入了Group,变得比第九次作业复杂了些。
-
一些取舍
首先是上文提到的O(1)还是遍历的问题。对于relationsum,valuesum和conflictsum,我本想采用维护+查询的方式,但对于agemean和agevar,由于整除的特殊性,每一次的维护可能会出现问题,所以为了统一性,我舍弃了维护的方法,采用了遍历,在互测中出现了bug,在后文会详细说明。
-
容器的更换
由于对people的查询次数增加,再加上时间限制,将arraylist改成了hashmap,用id作为唯一索引。
第十一次作业
这一次作业的重要改变是多了算法层面的考察,对时间的限制显得格外重要。问题非常多。
-
最短路径
使用了spfa算法,结果非常惨烈,多条强测点被卡。
-
querystronglink
想法是先找出一条路径,然后删掉再进行判断是否连通,找路径图方便直接使用了spfa算法,导致了连环tle。
-
queryblocksum
非常傻兮兮的每次计算连通块的个数,导致时间复杂度非常的高。
com.oocourse.spec3.main.MyNetwork.updateBlockSum() 4.0 8.0 8.0 com.oocourse.spec3.main.MyNetwork.queryStrongLinked(int,int) 5.0 6.0 7.0 可以看出,方法的选择具有很大的问题。
五、分析bug及修复情况
第九次作业
未出现bug。
互测由于简单也未找到bug。
第十次作业
强测wa了一个点,互测相关的点也wa了。
解决方法是把relationsum和valuesum,conflictsum的遍历O(n^2)方式改为了维护+查询的方式,降到了O(n)。这个地方的出错也让我第一次明白不能死板地照着规格写代码。
互测同group错的地方主要是relationsum和valuesum更新的时候出错,产生了wronganswer。
第十一次作业
强测错太多了,未进互测。
强测爆炸后我查询资料知道了spfa算法时间复杂度的不确定性,在面对稠密图时其O(VE)的复杂度和dij+堆优化相比差太多,同时题设本身也没有给负路径,本就应该选择dij+堆优化的方式。
更改措施有以下几个:
- 舍弃了spfa算法,改成了dij+堆优化的方式。
- 对blocksum的计算没有再使用updateblocksum对全图进行计算连通块的办法,变成了增加点则blocksum++,增加边时判断两个点之前是否相连,若相连则blocksum--。
- qsl寻找路径时同时也舍弃了spfa寻找路径,改成了dij+堆优化寻找。
六、心得体会
-
对JML的作用的感悟
JML作为规范化编程,给编程可以进行形式化验证提供了一种可能性,我们也可以使用工具分析JML进而进行自动化测试。但它现在远不够成熟,我们一单元的学习也只能算作管中窥豹,但它足以让我们养成一种注重规范化编程的习惯,注重数据边界、异常等。
-
对作业中出现的数据结构的感悟
自我认知在去年的数据结构上学的不是很好,图、表之类的知识掌握的非常薄弱,分数还好但确实没有暴露问题。第十一次作业做的时候发现需要使用一些算法的知识时我显得一筹莫展,疯狂在网上找板子。这个单元的最后结果也算是给我敲响了警钟,算法的学习永远不能停止。