OO规格作业总结

本文介绍了JML语言,它用于Java程序规格化设计。阐述了其理论基础、应用工具链,还展示了JMLUnit测试。详细讲述三次作业的架构设计,包括数据结构和算法。分析了作业中出现的CPU TIME爆炸问题及修复方法,最后分享了学习JML的心得体会。

一、

1.JML语言理论基础

    JML,全称为Java Modeling Language,根据名字可以容易的知道JML是对Java程序进行规格化设计的一门语言。JML是一种行为接口规格语言,可用于指定Java模块的行为。它结合了Eiffel的契约方法设计和Larch系列接口规范语言的基于模型的规范方法,以及细化演算的一些元素。

    通过对JML的阅读,我们可以了解一个方法的前提和作用结果,以及其产生的副作用。

示例:

 1     /*@ normal_behavior
 2       @ requires (\exists Path path; path.isValid() && containsPath(path); path.containsNode(fromNodeId)) &&
 3       @          (\exists Path path; path.isValid() && containsPath(path); path.containsNode(toNodeId));
 4       @ assignable \nothing;
 5       @ ensures (fromNodeId != toNodeId) ==> \result == (\exists int[] npath; npath.length >= 2 && npath[0] == fromNodeId && npath[npath.length - 1] == toNodeId;
 6       @                     (\forall int i; 0 <= i && (i < npath.length - 1); containsEdge(npath[i], npath[i + 1])));
 7       @ ensures (fromNodeId == toNodeId) ==> \result == true;
 8       @ also
 9       @ exceptional_behavior
10       @ signals (NodeIdNotFoundException e) (\forall Path path; containsPath(path); !path.containsNode(fromNodeId)); 
11       @ signals (NodeIdNotFoundException e) (\forall Path path; containsPath(path); !path.containsNode(toNodeId));
12       @*/
13     public boolean isConnected(int fromNodeId, int toNodeId) throws NodeIdNotFoundException;

    normal_behavior是正常方法的作用;

    exceptional_behavior是抛出异常;

    requires是一些要求,在本例中,要求存在包含了fromNodeIdtoNodeId的有效路径,否则抛出相应的异常

    assignable是产生的副作用,在本例中无副作用(\nothing

    ensures是方法结束后满足的状态,在本例是判断了fromNodeIdtoNodeId是否连通。

2.应用工具链

    官方网站中,提供了断言检查编译器(jmlc)、单元测试工具(jmlunit),以及了解JML规范的javadocjmldoc)。实际使用中,我们常常使用OpenJML检查JML的规范性,使用JMLUnit或者JMLUnitNG生成数据测试代码。

二、JMLUnit测试

测试代码:

 

 1 public class Atest {
 2     public int add(int a, int b) {
 3         return a + b;
 4     }
 5     public int sub(int a, int b) {
 6         return a - b;
 7     }
 8     public int multi(int a, int b) {
 9         return a * b;
10     }
11     public int div(int a, int b) {
12         return a / b;
13     }
14     public int and(int a, int b) {
15         return a & b;
16     }
17     public int or(int a, int b) {
18         return a | b;
19     }
20 }

 

测试结果:

三、架构设计

1.第一次作业:

 

第一次作业比较简单,只需要按照JML描述补充相应代码即可。

Path类中,利用ArrayList存储path的具体结点情况,利用HashSet存储path中存在的结点。

PathContainer类中,用一个int型变量产生pathid,每次加入一条路径,id++。用HashMap存储路径,keypathidvaluepath。用HashMap存储所有的结点,key为结点,value为该结点的数目。本次作业最容易产生复杂度的方法就是getDistinctCount。我采取的做法是在每次改变路径时,即增加或删去一条path时,调用相关的adddistinct(Path)subdistinct(Path)方法及时更新存储结点的HashMap,如此该HashMap的元素的数量便是所有不同结点的数量。

2.第二次作业:

 

 

本次作业在第一次作业的基础上新加入AdjacencyList类,实现邻接表,在每次改变图结构时通过addadjacencysubadjacency方法更新邻接表。

AdjacencyList类:用一个HashMap实现邻接表,key为结点,value为存储该结点的邻接点的ArrayList,注意,ArrayList中可以出现相同的元素,因为一个结点可以通过不同的路径而与另一个结点邻接。addremove方法用于更新邻接表。通过bfs实现了判断两结点是否连通的方法isConnected以及求两点的最短路径的shortestLength

Graph类中只需调用其他类中已经实现的相关方法即完成目标。

3.第三次作业:

 

 

在本次作业中,相比第二次作业,主要是增加了带权值的图的最短“路径”。思路也很暴力,每次更新图时,通过Floyd更新最短路径、最低不满意度。

最低价格:先将所有不需要换乘的情况加入,再通过Floyd进行更新,每次对某个值进行更新相当于进行了一次换乘,则加上换乘的花费即可。

最低换乘次数:利用bfs,每次更新获得这一次换乘能到的所有站,直到找到目的地站即可。

最低不满意度:与最低价格类似。

联通块数目:每次改变路径时,更新联通块,联通块以一个HashMap存储,key为结点所在的块的序号,value为结点。每次更新时,先判断这条路径中是否存在已经知道块号的结点,已知则把所有结点放到该块中,没有则新建一个块(增加块号)。

四、bug以及修复情况

    1. 第一次作业:强测中CPU TIME爆炸,主要是distinctcount方法复杂度太高,修复后,引入HashMap存储某个结点的数目,并将复杂度分散到每次改变图结构时。例如,在addPath时,增加结点数目。
       1   public int addPath(Path path) {
       2         if (path != null && path.isValid()) {
       3             if (!this.containsPath(path)) {
       4                 this.pathHashMap.put(id, path);
       5                 id++;
       6                 this.adddistinct(path);
       7                 return id - 1;
       8             } else {
       9                 try {
      10                     return this.getPathId(path);
      11                 } catch (PathNotFoundException e) {
      12                     return 0;
      13                 }
      14             }
      15         } else {
      16             return 0;
      17         }
      18     }

      第6行即this.adddistinct(path)可实现目的,具体方法代码如下。

       1     private void adddistinct(Path path) {
       2         for (Iterator nodes = path.iterator(); nodes.hasNext();) {
       3             int node = (Integer) nodes.next();
       4             if (this.nodeamount.containsKey(node)) {
       5                 this.nodeamount.replace(node, this.nodeamount.get(node) + 1);
       6             } else {
       7                 this.nodeamount.put(node, 1);
       8             }
       9         }
      10     }
  1. 第二次作业:未发现bug
  2. 第三次作业:依然是CPU TIME爆炸,据我分析是Floyd的原因,尤其是求最低价格和最低不满意度时,修复需要引入新方法来实现相关函数,借鉴讨论区中提出的拆点的方法。

五、心得体会

本次作业,我第一次接触了规格以及JML,同时经历了配置OpenJML的痛苦。我认为,一定要多与其他人沟通,对于其他人的方法进行思考,是否比自己的复杂度更低。本次作业,我的面向对象思想更进一步,不再像以前的作业次次重构,本单元也是由于作业的特点,我重构的部分并不是很多,大多数是在原来基础上增添一点点方法即可。JML作为规格语言,在描述程序功能方面的作用很大,掌握好规格,更有利于完善自己的架构能力。总体来说,我认为本单元我的收获非常多,进步也很大,要继续保持。

转载于:https://www.cnblogs.com/jerryleeo/p/10902494.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值