OO_Unit3_JML


OO_Unit3_JML

JML认识

JML 介绍

JML (Java Modeling Language),Java 建模语言,指在消灭自然语言中的二义性,比如一个著名的程序员的梗:

请求: 去买一个西瓜,如果看到西红柿,那就买两个
普通人:一个西瓜,两个西红柿
程序员:两个西瓜

那就买两个,这就是自然语言的二义性,因为省略了宾语,所以这导致了二义性。

JML 就能消除这种二义性,防止代码实现的偏差导致的故障,只要完全按照 JML 写,那么一定能符合要求。

JML个人认识

JML 能完全消除二义性,但是其也有一些问题:

  • 实现复杂:光是一个删除操作,写成 JML 特别长, 如果和算法有关,比如 最小生成树的算法,写出 JML 更是苦难。
  • 阅读困难:因为 JML 实现复杂, 导致其描述不简洁,对于程序员来说阅读障碍太大,甚至看不懂想干什么。 比如最小生成树算法的那个函数,我光看 JML 并没有看懂到底想干什么。
  • 出现错误后果严重:如果 JML 错了,那么实现也全是错的,程序员并不知道怎么做才是对的,所以 JML 的正确性及其重要,但是前面两个缺点导致错误出现很难避免。

对于我来说,可能后续不会写 JML,因为实在是太复杂了,但是其对于规格的思想确实很重要,在平时的 code 过程中,其实自己也在写规格 (不会有人不思考就直接开始写吧,不会吧) ,比如这个类要干什么,这个方法要做什么,它应该返回一个什么给我?我自己规格的确定是自然语言和 Java 语言的混合,自然语言描述天然的概括性高,加上 Java 语言的无二义性。

代码设计

最后一次作业的总的JML:
异常类:
Exceptions

每个异常类都有一个 staticCounter 成员变量,用于统计该种异常发生次数,和对应 id 造成的异常次数。

消息类:
Messages
除了 MyMessage 外, 其余类都继承于 MyMessage,免去因为 EmojiMessage 等接口继承 Message 接口导致的代码重复。

主体逻辑:
Main

主题部分是我主要做优化的地方,现对每个类进行讲解:

MyPerson

该类继承了 Peron 接口,对其主要的优化是使用 HashMap 存储 idPerson 的键值对,对于 acquaitance 中每个人,使用 id2Value 存储其对应的 value

这个优化主要是减少查询时间

MyGroup

该类继承 Group 接口,对其的优化一个是 HashMap 简化查询,还有使用 ageSumvalueSum 维护每个 Group 中成员的总价值和总年龄,将查询分摊到每一次的修改上,保证查询指令O(1)的复杂度。

MyNetwork

该类继承 Network 接口,对其优化一个是使用 HashMap 简化查询,还有一个就是使用并查集算法(实现 isCircle 方法),Prim 算法(实现 getMinTree 方法),Dijkstra 算法(实现 sendIndirectiveMessage 方法)。对于这些算法也有优化。

  • 并查集: 每一次查询都更新记录
  • Prim: 使用堆优化,每次找边只在只图里面找,更新的时候通过获得新增节点的 acquaitance 属性简化查询。
  • Dijkstra:算法和 Prim 类似,所以优化思路类似。

另外,因为 emojiMessage 的特殊性,我使用了多种容器:

  • emojiId2EmojiHeat:方便查询每种 emoji 的受欢迎程度
  • emojiId2Id:装每一种emojiId 对应那些 message,方便 clearColdMessage 的时候更快查询到需要删除的 message

其中的MyPersonMap是用于存储person之间关系的类,并实现了 PrimDijkstra 算法,提供方法让 MyNetwork 对象调用。

Edge 是用于辅助堆优化而创建的类,堆中存储的是 Edge 对象,Edge 有关于边的两个端点和边权值的属性。

测试和debug

测试

本次测试主要是和其他人对拍进行测试,整体数据生成逻辑分为所有指令槽随机枚举测正确性,和特定指令池枚举测试复杂度(比如sim指令测试复杂度,指令池主要是 aparsimam 指令)。在测试中发现测试只能发现 bug,不能证明程序正确。示例如下:

  • sim: 该指令我代码的INF太小,在强测中被找出,但是课下测试因为数据枚举随机性高,没有发现此类错误。

可见,即使有 JML,实现正确性还是有些细节方面的雕琢

bug

  • 第一次作业bug: 第一次作业没有被测试出bug,但是第一次作业留下了隐患,那就是我的并查集没有更新记录,导致后续作业数据量增大出现了 TLE 的情况
  • 第二次作业:第二次作业比较惨烈,先是 qbs 指令因为并查集的问题爆 T,又是函数调用错误出现 bug,再是因为没有维护组的valueSum爆 T。
  • 第三次作业:第三次作业性能做了全面优化,可惜 INF 太小,导致出错。

Network 拓展

需求分析

这次拓展主要是对 Person 类进行了拓展,多了如下四种人:

  • Advertiser
  • Producer
  • Customer
  • Person

其中 Person 可以继续使用之前的实现,新增的三个类可以看成一个 group,ProducerAdvertiser 发送信息,Advertiser 就是一个分发器,负责把信息发给订阅了该种类商品的 CustomerCustomer 要购买,那就向自己订阅了的 Advertiser 发送信息,Advertiser 把对应的信息发送给 Producer

至于统计销售量,只需要在每次购买之后,修改 Producer 维护的变量,销售路径则是从 ProducerCustomer,这些就交给 Person 来维护关系图,NetWork 只需要知道 Producer 是谁.

主要接口方法


public interface Network {
    /*@ public instance model non_null Person[] producers;
      @ public instance model non_null Person[] advertisers;
      @ public instance model non_null Person[] customers;
    @*/


    /*@ public normal_behavior
      @ assignable messages,getMessage(id).getPerson2().messages
      @ ensures getMessage(id).getPerson2().messages.length = (getMessage(id).getPerson2().messages.length) + 1 
      @ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size());
      @          \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i)));
      @ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id)));
      @ ensure (\forall i; 0 <= i && i < customers.length; customers[i].isLinked(getMessage(id).getPerson2()) => 
      @     customers[i].messages.length = (customers[i].messages.length) + 1 &&
      @     (\forall int i; 0 <= i && i < \old(customers[i].getMessages().size()) 
      @          \old(customers[i].getMessages().get(i+1) == \old(customers[i].getMessages().get(i))) &&
      @      \old(customers[i].getMessages().get(0).equals(\old(getMessage(id))))
      @ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &
      @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
      @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
      @ also
      @ public exceptional_behavior
      @ signals (MessageIdNotFoundException e) 
      @ !containsMessage(id) ||
      @ containsMessage(id) && getMessage(id).getType() == 1;
      @ signals (RelationNotFoundException e) 
      @ containsMessage(id) && getMessage(id).getType() == 0 &&
      @ !(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()));
      @ signals (PersonIdNotFoundException e) 
      @ (\forall i; 0 <= i && i < producers.length;!producers[i].equals(getMessage(id).getPerson1())) ||
      @ (\forall i; 0 <= i && i < advertisers.length;!advertisers[i].equals(getMessage(id).getPerson2()))
      @*/
    void postAdvertise(int id);


    /*@ public normal_behavior
      @ ensures \result == producer.money
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) 
      @ (\forall i; 0 <= i && i < producers.length; !producers[i].equals(producer))
      @*/
    int querySaleVolume(Person producer);

    int querySalePath(Person producer);

    /*@ public normal_behavior
      @ ensure
      @ (\exist i; 0 <= i && i < advertisers.length;advertisers[i].isLinked(getMessage(id).getPerson2())) && advertisers[i].isLinked(getMessage(id).getPerson1()))
      @ assignable getMessage(id).getPerson2().messages
      @ ensures getMessage(id).getPerson2().messages.length = (getMessage(id).getPerson2().messages.length) + 1 
      @ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size());
      @          \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i)));
      @ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id)));
      @ assignable getMessage(id).getPerson2().money
      @ getMessage(id).getPerson2().money = \old(getMessage(id).getPerson2().money) + getMessage(id).getsocialValue()
     @ ensures &!containsMessage(id) && messages.length == \old(messages.length) - 1 &
      @         (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;
      @         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
      @ also
      @ public exceptional_behavior
      @ signals (PersonIdNotFoundException e) 
      @ (\forall i; 0 <= i && i < customers.length;!customers[i].equals(getMessage(id).getPerson1())) ||
      @ (\forall i; 0 <= i && i < producers.length;!producers[i].equals(getMessage(id).getPerson2()))
      @ signals (RelationNotFoundException e) 
      @ (\forall i; 0 <= i && i < advertisers.length;!advertisers[i].isLinked(getMessage(id).getPerson2())) || !advertisers[i].isLinked(getMessage(id).getPerson1()))
      @*/
    void buyGood(int id);

}

心得体会

面向对象这门课本身就是让我们关注事务的抽象认知,关注功能并对其分类,而不是细究实现的方法,JML 的编写也确实就是这样,屏蔽了实现,只关注方法的最终结果,很 OO。

但是这个单元还是有不是很 OO 的地方,对于一些指令,比如 ar,在 person 接口中竟然没有实现的方法,使得 acquaitance 属性在接口提供的方法里面无法被更改,我不得不自己创建方法,然后再向下转型,这是很不 OO 的。接口就应该让调用者认为自己能实现所有功能,而不是还得依靠类自身独立的属性。


Author: Dovahkiin
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Dovahkiin !
  TOC