OO第三次博客作业
• (1)梳理JML语言的理论基础、应用工具链情况
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言 (Behavior Interface Specifification Language,BISL),基于Larch方法构建。BISL提供了对方法和类型的规格定义, 拥有坚实的理论基础。
使用JML进行规格化设计,需要以类为基本单位,即用户一般把类作为一个整体来使用或重用。类的规格由类型规格与方法规格构成。类型规格规定了类所管理的数据内容,及其有效性条件;方法规格规定了类所提供的操作。开发人员必须确保实现数据内容始终有效,并且任何方法都满足方法本身所定义的规约且不能破坏数据的有效性。
对于数据来说, JML针对类型规格定义了多种限制规则,在我们的OO课程中,主要用到两种:不变式限制(invariant)和约束限制 (constraints)。Invariant是任何时刻数据内容都必须满足的约束条件;constraint是任何时刻对数据内容的修改都必须满足的约束条件。无论哪一种,类型规格都是针对类型中定义的数据成员所定义的限制规则,一旦违反限制规则,就称 相应的状态有错。
对于方法来说,用户有义务保证提供有效的输入以及有效的对象状态,经过方法的执行用户能够获得满足确定条件的输出结果。方法规格的核心内容包括 三个方面,前置条件、后置条件和副作用约定。其中前置条件是对方法输入参数的限制,如果不满足前置条件,方法 执行结果不可预测,或者说不保证方法执行结果的正确性;后置条件是对方法执行结果的限制,如果执行结果满足后 置条件,则表示方法执行正确,否则执行错误。副作用指方法在执行过程中对输入对象或 this 对象进行了修改(对 其成员变量进行了赋值,或者调用其修改方法)。
对于类来说,对于JML的契约,类提供者信任使用者能够确保所有方法的前置条件都能被满足;类使用者信任设计者能够有效管理相应的数据和访问安全。任何一个类都要能够存储和管理规格所定义的数据,始终保持对象的有效性。类可以拒绝为选择自己的表示对象而不受外部约束,拒绝为不满足前置条件的输入提供服务。从设计角度,软件需要适应用户的所有可能输入,因此也需要对不符合前置条件的输入情况进行处理,往往对应 着异常处理。从规格的角度,JML区分是否满足前置条件这两种场景,分别对应正常行为规格(normal_behavior)和异常行为规格 (expcetional_behavior)。
工具链:有与规格化设计相关的工具主要有:OpenJML,JUnit,JUnitNG等等,Openjml主要帮助我们检查规格化语言JML的语法以及基本逻辑正确性,Junit以及JunitUG则是对JML规格进行测试,前者主要用于单元测试以及一定的自动化测试,后者则主要测试一些边界情况。
• (2)部署JMLUnit
按讨论区的提示搭建了openjml和junit后,我测试了一下path类中的size方法:

其实感觉在这种小作业中,JMLUnit这种模式的测试远不如对拍器来得效率高。只能说术业有专攻。
• (3)架构设计梳理
第一次作业中,我使用ArrayList数组储存pList和pidList,使用一个HashMap结构储存图里存在的点,Path类中我使用一个ArrayList类储存路径的点,用一个HashMap记录路径里存在那些点(其实只用HashMap就够了)。
第一次作业基本复杂度很高,原因是我没有专门写方法来处理对点元素的处理,导致一些方法过于臃肿。

第二次作业增加了边的概念,例如containedge方法,求最短路径时也需要用到边。所以需要增加一个结构储存所有边。我采用了HashMap加一个内部类的方法,建立一个邻接表;另外,我使用HashMap套HashMap的方法,建立了一个类似的邻接表以储存两点间的最短距离。计算最短路径使用弗洛伊德算法。同时,新加了更新邻接表的方法,在新加或删除路径时更新最短路径。连通性的判断变为判断两点间是否存在最短路径即可。原有的其他架构没有变化。
第二次作业基本复杂度也不低,而且模块设计复杂度也提高了。我分析是原来的方法会调用更新邻接表的方法,导致耦合度提升。

第三次作业,新加了连通块和三种不同权值下的最短路径,我在原有的架构没有大幅度改动的情况下,新加了一个Calcular类,以完成三种最短路径的求解;另外,我新加了一个求连通块的方法,利用已有的最短路径邻接表,求出连通块数目。
第三次代码UML图如下:


(UML图太大了,得用两张图截下来……)
第三次作业复杂度全线飚红,这是因为为了保证代码长度在500行以内,我新加了Calcular类,调用了其中的方法,而为了保证方法长度在60行之内,我又在方法内调用了更多的方法,导致耦合度进一步提高;另外,我使用了大量中间结构体,导致结构复杂度降不下来(一言以蔽之,就是我太菜了……)。



三次作业的复杂度我都控制得不算很好,尤其是第二次作业,太过偏向数据结构的内容,导致代码非常冗杂,而且难以复用,不但使第二次作业的复杂度变大,还影响了第三次作业的架构。基本上第三次作业的新功能,我都是直接用和第二次作业完全不同的架构完成的。这种增量作业,非常需要一个好的开头,如果基础没打好,会为后面的工作带来极大的麻烦。
• (4)分析bug
前两次作业中,我没有出现bug;第三次作业中,我出现了两个bug,第一是我的测试做的不够充分,在计算权值时遗漏了某些重复的点;第二是程序的优化存在问题,每当增删路径时,都要完全从头开始计算(我为了赶ddl,addpath和removepath的更新方法是相同的),导致CPU运行时长过长。总的来说,就是我太懒了,写代码的时候用了最偷懒的方法,写完代码之后缺乏足够的测试。(对拍器万岁)
为了解决CPU超时问题,我修改了更新最短路径的方法,使其在新加路径时,不需重新构建邻接矩阵,从而优化性能。
• (5)心得体会
jml规格,在我的理解中, jml规格不够简洁,例如在第三次作业中,为了描述得到不同权值下的最短路径的方法,不但使用了冗长的描述,还为了这些方法新加了更多本不需要实现的方法以满足jml规格,我认为在大多数应用场景下,jml规格都没有太多用武之地但从另一方面看,使用规格的目的是在于能够用一种相对于自然语言而言更加严谨、更加没有二义性的方式对一个方法或者类的功能进行描述,这样在多人进行协作开发,而且开发的项目对于正确性与准确性的要求比较严格的时候,更能够保证不会因为不同的成员对于方法或者类的功能理解缠身歧义,导致开发过程中出错。一个好的规格可以帮助程序员在对这个项目没有太多了解的情况下,更好地编写相应功能的代码,也方便对代码的调试和维护。在一些要求比较高的特定工作领域中,如航空航天领域,使用JML更为安全,可靠。
jml规格虽然给我们的程序带来了许多限制,但是仍存在不少的自由度。只要可以满足规格要求,我们可以使用任何方法。规格撰写需要用到面向对象的思想,尽可能忽略中间变量,使用\ensure字句直接对结果做出限制。在我看来,规格撰写就是在编写一个个“黑箱”,内部的处理细节调用者无需关注,只需要关注其输入(require),输出(ensure)和对对象的改变(side-effects)即可。
posted on 2019-05-22 12:18 yezefeng12138 阅读(133) 评论(0) 收藏 举报
浙公网安备 33010602011771号