JML采用javadoc注释的方式来表示规格,且每行以@开头。通过使用//@annotation来进行行注释,使用/*@annotaion@*/来进行块注释。
\result表达式,在方法规格中使用,通过\result来指代返回值。在谓词中使用\result,来表达放回值的限制条件。
在本单元作业中,通过在接口中定义规格变量,来规定要管理的数据。
例如//@public instance model non_null int id;定义了一个规格可见的、引用不为空的、int型实例变量。同理将instance改为static可定义一个静态变量。
通过对类中的字段添加/*@spec_public@*/可以指定该变量为规格可见。
//@invariant P;其中P为一个谓词。该语句规定在所有可见状态下,规格所管理的数据都必须满足谓词P。
//@constraint P;其中P为一个谓词。该语句规定数据在前序可见状态与当前可见状态之间需要满足的约束。
通过对方法修饰/*@ pure @*/,表示该方法仅为查询方法,不会对类的数据做任何修改,使得该方法为规格内可见。
一个方法可以有多个行为,行为分为正常行为与异常行为。一个方法也也可以有多个正常行为与异常行为,但他们的前置条件之间不能有交集。正常行为用normal_behavior表示,异常行为用exceptional_behavior表示。多个行为之间用also连接。
通过使用requires语句实现。格式为requires P。P为一谓词,表示调用该方法时需要满足的限制。一个行为可以有多个requires语句,这些语句之间为且关系,在该行为下需全部被满足。
通过使用ensures语句实现。格式为ensures P。P为一谓词,表示该方法调用结束时需要满足的限制。一个行为可以有多个ensures语句,这些语句之间为且关系,在该行为下需全部被满足。
通过assignable语句或者modifiable语句实现。格式为assignable/modifiable v。其中v表示可以被赋值/修改的变量。特别的,当v为\nothing时表示不能赋值\修改任何变量。
signals语句使用在异常行为下。格式为signals E P。E为要抛出的异常包括异常类型与异常变量名称。P为一谓词,表示当谓词满足时抛出异常。当P与该行为前置条件的P相同时,可简化该语句为signals_only E。
::openjml.bat
@echo off
set java=java -jar "%~dp0openjml.jar" -noPurityCheck
set prove=-prover cvc4 -exec "%~dp0Solvers-windows\cvc4-1.6.exe"
set runtime=-cp %~dp0jmlruntime.jar;
set allparam=
set rac=
:param
set str=%1
if "%str%"=="" (
goto end
)
if "%str%"=="-rac" (
set rac=rac
)
if "%str%"=="-prove" (
set str=%prove%
)
set allparam=%allparam% %str%
shift /0
goto param
:end
if "%allparam%"=="" (
goto eof
)
rem remove left right blank
:intercept_left
if "%allparam:~0,1%"==" " set "allparam=%allparam:~1%"&goto intercept_left
:intercept_right
if "%allparam:~-1%"==" " set "allparam=%allparam:~0,-1%"&goto intercept_right
:eof
%java% %allparam%
set filename=
if "%rac%"=="rac" (
:input
set /p filename=Input file name:
if "%filename%"=="" (
goto input
)
java %runtime% %filename%
)
pause
将上述代码放在下载有JML的文件夹中,再将该文件夹加入环境变量。理论上就可以通过命令行使用openjml
命令了。
查阅多处博客发现OpenJML的局限,比如规格不能存在exist
,forall
。
public class Test {
/*@ public normal_behaviour
@ ensures \result >= num1;
@ ensures \result >= num2;
@ \result >= num3;
*/
public static int compare(int num1, int num2, int num3) {
int max;
if (num1 > num2) {
max = num1;
}
else {
max = num2;
}
if (max < num3) {
max = num3;
}
return max;
}
public static void main(String[] args) {
compare(347276,6234646, 2656435);
}
}
以上使用openjml对代码进行检测
修改了MyGroup类,删除缓存的做法,去掉一些方法,仅为验证JMLUnitNG的功能
以上为使用junitng对代码进行检测。
查阅多处博客发现JMLUnitNG测试主要侧重于边界条件,比如对传入数据的可行域取边界进行测试。
这次测试主要依赖的还是数据生成器以及输出对拍来完成,有些规格或是测试规定的数据范围让这种测试方法更加便捷有效。而且本次作业针对一些代码实际实现可以手动构造一些特殊样例,来进行满载的测试。在类中的实现有时也不能完全按照JML规格完成,比如加入缓存之类的考量,这些使得类变为一个整体,不能单单测试某一个方法。
(由于此次的作业全部都是已知的思路,所以具体的思路以及算法实现等等方面在这里就省略了描写,只写这几次作业中本人出现的bug)
由于第一次作业里面有一个深度优先搜索;然后听说“算法基本上不会在效率上卡大家”,而且听说这次作业没有优化分,所以就直接使用了最暴力的DFS深搜;不想最后却因为时间效率的问题被卡住了;看来最起码的优化还是要有的;
第二次作业的bug还是出在效率上;因为有一个查询算法使用了O(n2)的方法,而没有使用大家常使用的O(1)的方法,所以到最后效率上就差了很多很多;
由于第三次作业吸收了很多前两次的经验,所以就把大量的时间用来优化算法效率;无奈看错了数据范围,然后就对数据的整体特性有了错误的判断;然后就使用了一种在“我以为的数据范围”下效率会很高,但是在“真实数据范围”下效率超级低的算法;所以最后一次花了大量的时间做优化,但是最后还是效率出了问题;强测只过了几个点;偷鸡不成蚀把米,弄巧成拙;
这个单元看起来人畜无害,我却一次强测都没有进。综合来看,我自己也有一部分原因,但也并不是所有的问题都因为我。我得到了错误的传播信息,这导致了我第一次作业效率太低,最终被强测卡住;本地测试与评测机测试的某些差别,导致我第二次作业效率太低,最终被强测卡住;由于错误的理解的数据的范围,导致第三次作业失误,最终也被强测卡住;
虽然这个单元最简单,但是却是我写的最差的一个单元,主要是没有跟学长了解过相关的注意事项,而且跟同学的交流有点少,所以以后要注意从优质信息的来源方面入手来提高成绩,“卷”的精髓就在于找学长学姐 + 跟同学交流 + 提前做准备;
只要把这三点做好就能够拿到一个不错的成绩了吧。
原文:https://www.cnblogs.com/alink61/p/12943900.html