一、 JML理论基础
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。
对于JML,个人觉得JML相比于自然语言的优势在于,它更为严谨准确,避免了自然语言容易引起歧义的缺点。但同样,JML也有一些缺点。首先它的效率更低,相比于自然语言,表达同样意思的语句,JML语句比自然语言要长很多,而且JML的写作者和阅读者都需要花费更多的时间精力来写准确(读明白)JML语句。其次,JML不会引起歧义是建立在写作者准确的表达,和阅读者正确的阅读的基础上的。而事实上,由于表达中经常会出现几个\forall \exist \sum语句的嵌套,以及大量的括号,导致写作者和阅读者犯错的概率相比于自然语言大大增加。
JML常用语法语句:
表达式 |
含义 |
\result |
方法的返回值(非void类型) |
\old(expr) |
表达式expr在方法执行前的取值 |
\not_assigned(x, y, ...) |
括号中的变量在方法执行过程中未被赋值 |
\not_modified(x, y, ...) |
括号中的变量在方法执行期间的取值未发生变化 |
(\forall expr) |
全称量词 |
(\exists expr) |
存在量词 |
(\sum expr) |
求和 |
(\max T x; R(x); expr) |
返回给定范围内的表达式的最大值 |
(\min T x; R(x); expr) |
返回给定范围内的表达式的最小值 |
\nothing |
空集 |
方法规格 |
含义 |
Requires |
条件。 |
Ensures |
结果。 |
Assignable |
指方法在执行过程中会修改对象的属性数据或者类的静态成员数据。 |
public normal_behavior |
正常功能。 |
public exceptional_behavior |
异常功能。 |
Signals |
表示抛出异常 |
二、 应用工具链
使用JMLUnitNG进行自动测试
Failed: racEnabled()
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(2147483647)
Passed: <>.addPerson(null)
Passed: <>.addPerson(null)
Passed: <>.addPerson(null)
Passed: <>.addPerson(java.lang.Object@4ca6665f)
Passed: <>.addPerson(java.lang.Object@66caa894)
Passed: <>.addPerson(java.lang.Object@49011bd5)
Passed: <>.addrelation()
Passed: <>.addrelation()
Passed: <>.addrelation()
===============================================
Command line suite
Total tests run: 12, Failures: 0, Skips: 0
===============================================
结果如上,可以看到JMLUnitNG测试的数量很少,而且测试的都是边界条件,这个测试显然不太靠谱,基本上就是胡乱输几个数进去差不多的水平。
三、 架构设计
架构设计这部分其实没有太多可说的,因为主要架构都是根据JML定的,没有多大的变动空间。我的代码架构上,比较值得一说的地方是:我把算法单独分出来了一个类,里面都是static方法,这样就可以实现算法与类中的具体方法的分离,还有全局变量也单独分出一个类。
另外,对于queryStrongLinked方法,由于涉及到了两次dfs算法,采用单独分一个类的做法,以减少方法中的代码量。
四、 Bug分析
这次作业的bug主要是在阅读JML时不够仔细造成的,比如第一次作业看错了islinked里/exist的括号位置,第三次作业queryBlockSum看错了最外面括号的位置。
还有一个问题是之前对于数据结构不太重视,因为之前的数据量都比较小,在第二次作业强测之后,才意识到这个问题。之后换成hashmap与hashset后速度显著加快,看来正确选择数据结构与算法对于大数据量的情况是必须的。
除了bug之外,bug测试也是一大困扰我的问题,对于数据量比较小的情况,我可以手动写测试代码,但对于数据量很大的情况,如何进行测试就是很大的问题了。可能只能靠自己估计时间复杂度和空间复杂度的方式,对于数据量很大时的情况进行大概的估计。
五、心得体会
本单元与之前两个单元有了很大的不同,之前的两次基本上都是给出目标问题,自己进行设计、自己编写方法来解决目标问题,很多时候由于在设计之初没有考虑清楚,导致写到后面需要把之前的一部分代码推倒重写,这种情况可以说是家常便饭。而这次的JML基本上就是按图索骥的感觉了,从设计的难度上几乎没有,只是自己选择合适的数据结构和算法来解决问题。大概这种情况才是现代社会高度分工、流水线生产所真正使用的模式。敲代码的人无需考虑程序的架构、程序根本上的目的是什么,只是把自己的部分按照要求写对就好,从效率的角度这样做无可厚非。但是对于个人来讲这样似乎有点把人当作机器的意味。基层程序员似乎和东南亚那些血汗工厂里日复一日重复着同样的操作的工人没什么区别了。我甚至觉得,基层程序员相比于血汗工厂里的员工,会是更早被强人工智能淘汰的,因为想要复制体力劳动还要受到机械工艺的限制,而在计算机上敲代码,这只是AI能力的问题。辅助驾驶如今已经实现,辅助敲代码估计也不会多远吧,到那时候,给定JML规格,程序直接生成代码,至少这听起来还不错。