本文是BUAA OO课程Unit3在课程讲授、三次作业完成、自测和互测时发现的问题,以及倾听别人的思路分享所引起个人的一些思考的总结性博客。主要包含JML相关梳理、SMT Solver验证、JML单元测试、设计策略、代码度量、BUG测试和心得体会等内容。

目录

一、JML简单梳理

二、SMT Solver的部署和验证

三、JML单元测试

四、架构设计梳理

五、测试与BUG分析

六、心得体会


一、JML简单梳理

1.1 JML语言理论基础

  The Java Modeling Language (JML) is a behavioral interface specification language that can be used to specify the behavior of Java modules. It combines the design by contract approach of Eiffel and the model-based specification approach of the Larch family of interface specification languages, with some elements of the refinement calculus.

  JML(Java建模语言)是一种进行详细设计的符号语言,它鼓励我们用一种全新的方式看待Java的类和方法。面对对象的分析和设计的一个终于原则就是过程性的思考应该尽可能地推迟。Java建模语言在Java代码中增加了一些符号,这些符号用来标识一个方法是干什么的,却不关心它的实现。通过这种方式,JML把过程性的思考延迟到了方法设计中,从而拓展了面对对象的这个原则,同时通过引入大量用于描述行为的结构,比如有模型域、量词、断言可视范围、预处理、后处理、正常行为与异常行为等等,这些结构就使得JML非常强大了。通过分析JML,我们能够更为精确地了解代码的用途,减小引入bug的几率,更高效地检测并纠正代码的bug等。

1.2 JML应用工具链

  除了OpenJML、JML Editing、JMLUnitNG以外,常用的JML工具还包括以下:

  • The AspectJML tool is able to specify and do runtime assertion checking for both Java and AspectJ programs. Ajmlc uses aspect-oriented compilation techniques to provide significantly improved runtime assertion violation messages, and also works with JavaME.
  • The jml4c tool is a JML compiler built on the Eclipse Java compiler. It translates a significant subset of JML specifications into runtime checks. Notable features of JML4c include (1) support for Java 5 features such as generics and enhanced for loops, (2) support for nested classes, (3) improved compilation speed, and (4) improved error messages. Using JML4c, one can now verify Java 5 classes annotated with JML specifications.
  • Sireum/Kiasan for Java, which is a JML contract-based automatic verification and test case generation tool-set for Java program units.
  • JMLEclipse, which is a pre-alpha version of the JML tool-suite developed on top of Eclipse's JDT compiler infrastructure.
  • JMLOK is a tool that uses random tests to check Java code against JML specifications and suggest likely causes for non-conformance problems it finds.

返回目录


二、SMT Solver的部署和验证

  因OpenJML不能支持\forall、\exists,本次作业中无法完成测试验证,略。

返回目录


三、JML单元测试

  为了规避\forall和\exists,作业中的其他方法过于简单,改为使用以下测试代码进行测试:

package test;

public class Test extends Exception{
public static void main(String[] args) {
try {
divide(35, 7);
} catch (Exception e) {}
} /*@ public normal_behavior
@ requires b != 0;
@ ensures \result == a / b;
@ also
@ public exceptional_behavior
@ requires b == 0;
@ signals (Exception e);
@*/
public static int divide(int a, int b) throws Exception {
if (b == 0) {
throw new Exception("Divided by zero!");
} else {
return a / b;
}
}
}

  即一个简单的除法(整除),要求除数为零时报错,否则需要输出正确的答案。

  测试的结果如下:

JML规格编程系列——OO Unit3分析和总结-LMLPHP

  可见自动构造的测试数据是使用0,最大正数和最大负数等几个极端数据生成的,这一定程度的和易出现bug的情况相符。

返回目录


四、架构设计梳理

4.1 第一次作业

4.1.1 类图

JML规格编程系列——OO Unit3分析和总结-LMLPHP

4.1.2 简析

  第一次作业较为简单,从互测来看同学们的实现也较为类似,基本上按照规格实现功能不需要做很大的改动,选择合适的容器即可完成任务。即对于Path,使用ArrayList即可;对于PathContainer,我采用了双ArrayList,现在来看,实际上使用双HashMap在编写时更直观清晰一些。

4.2 第二次作业

4.2.1 类图

JML规格编程系列——OO Unit3分析和总结-LMLPHP

4.2.2 简析

  本次作业继承了PathContainer,在保持其原有功能的同时增加了两点间的距离计算,此为本次核心任务。大致有两种不同的设计理念,其一是在add Path和remove Path时使用Dijstra、Floyd等算法计算并缓存所有点之间的距离等信息,可以使查询在瞬间完成,另一种是在查询时才使用DFS等算法来计算距离,则对图结构影响的操作执行起来更快。我采取的是前者,主要在影响图结构的操作执行时更新所有路径容器、距离矩阵、自环统计这三个容器。在修复的时候,为了提升性能,我映入了映射机制,将输入的结点映射到1到250并改用静态数组。因此,除了增加映射机制从而改变了Path的容器和添加、删除方法之外,其他方面基本上无需对上一次的代码进行重构,而增加新功能。

4.3 第三次作业

4.3.1 类图

JML规格编程系列——OO Unit3分析和总结-LMLPHP

4.3.2 简析

  本次作业继承了Graph,在保持其原有功能的同时增加了更多的要求,基本上都可理解为带权路径的相关最小路径求解,以及需要求联通块数。仍然存在刚才提到的两种设计理念。我仍然使用在图的结构改变时计算所有结果的理念,并继续使用了映射机制。本次对最小路径、最少换乘、最少费用、最低不满意度的计算,实际上是拥有相似性的,都可以通过单程最近距离+换乘两步使用两次Floyd算法完成,因此我将上一次的代码重构为工厂模式,将进行某种特定距离的矩阵的运算封装成产品。联通块数目则单独使用一种算法,由于距离矩阵已经被计算,也可以非常简单地算出。

返回目录


五、测试与BUG分析

5.1 第一次作业

  本次作业中强测出现了超时的问题,但功能是正确的,原因在于代码设计不够合理。原先采用的设计是每次调用计算distinct nodes把所有点加入HashSet重新计算一遍,这是没有必要的,应该直接设置一个HashSet容器来存储所有distinct nodes,并改成每次加入或删除Path时就更新一次该容器,则查询时不再需要重新计算,从而节约CPU运行时间。

  本次作业的互测中没有发现bug。

5.2 第二次作业

  本次作业中强测也出现了超时的问题,但功能是正确的,原因在于使用的算法不够合理。使用的是一种添桥式的动态规划算法,但由于是每增加一条边更新一次,效率实在太慢,因为增加一条路径时可能有很多条边,而标准的Floyd算法只需要在添加后更新一次即可,就节约了CPU时间,从而不会超时。

  由于超时严重,在本次作业的互测分组中发现了较多的bug,主要有使用了错误的算法、容器选择不合理等。主要根据测试的限制构造几乎达到上限的随机数据进行测试即可测出主要的bug。

5.3 第三次作业

  本次作业的强测中没有出现bug,互测时也没有找到别人的bug。

返回目录


六、心得体会

  本单元的学习中,我感到规格的特点和好处,尤其在大型项目中,规格是顶层的设计对底层实现的一种很好的规范和约束,免去了思考具体实现,却又能保证正确性。不仅如此,规格能够清晰界定每个模块的功能,这非常有利于高内聚低耦合化的开发。结合单元测试,规格能使程序具有更好的健壮性和可维护性,规格和文档相结合对团队的合作也有较好的帮助。

  我感到撰写规格的时候和写程序时既有相似又有不同之处。相似之处在于写规格时也用到了很多相似度很高的语句,而最大不同之处在于规格的目的并不是分析问题解决问题,而是描述某一事物的所有可能性和状态。如正常情况、异常情况,何种条件下会导致怎么样的结果,对哪些数据有影响,哪些数据没有改变。如果想要准确的描述,还应保证语句的规范,例如对数理逻辑中全称量词和存在量词的使用,才能使规格达到要求。为了能使用OpenJML编写时还应注意一些语法格式,由于这方面工具的不充分(没有自动补全、高亮、纠错等),需要格外注意。

  本单元中运用的数据结构技巧也很多,我认为最为精巧的设计方法是第三次作业中将票价、最少换乘、最低不满意度、最近距离问题全部转化为单程最近距离+换乘两步使用两次Floyd算法,仅仅有细节上的差异,因此可以使用工厂模式来进行求值,从而使所有查询都能在极短时间内完成。

返回目录

05-12 06:02