OO第三单元作业总结-JML

OO第三单元作业总结-JML

17375097 张文浩

1.梳理JML语言理论基础以及应用工具链

java建模语言(Java Modeling Language)即JML是一种规范详细设计规格的符号语言。通过使用JML,我们可以尽可能的推迟代码具体实现方式的构思,明确各个模块的要求和责任,从而达到规范性设计的目的。

(1).JML语言理论基础

有关JML语言理论基础,其实在我们的JML手册中已经说的比较详细了(JML手册点击这里,我就不一一列举了,在这里就简单的稍讲一下:

JML语言的组成分为表达式、方法规格、和类规格:

表达式分为原子表达式,量化表达式,集合表达式和操作符;

方法规格包括前置条件(requires)、后置条件(ensures)、作用限定 (assignable modifiable)和signals子句;

类规格有状态变化约束 (constraint)和不变式(invariant)。

(2).JML应用工具链

说实话应用工具确实不是很好找,我使用百度和必应搜索jml工具链,出来的都是我们OO课程的作业hhh

不过从官网上我们可以找到官方推荐的工具:

如jmlc, jmlUnit, jmlDoc;除此之还可以找到其他工具如openjml, jmle等等。

2.SMT SOLVER

SMT(Satisfiability modulo theories)is a decision problem for logical formulas with respect to combinations ofbackground theories expressed in classical first-order logic with equality.

我尝试了部署SMT SOLVER(但水平有限但也仅限于尝试了

哪位大佬做出来了我去围观

3.部署JMLUnitNG/JMLUnit

JMLUnit − Generate file for running JUnit tests on JML annotated Java files

所以说JMLUnit就是在JUnit上跑JML相关测试嘛~

因此,仿照我们的实验课的JMLUnit使用方法,进行了简单的测试(确实很简单的测试hhhh)如下:

首先对我们可爱的path类:

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class MyPathTest {
    private MyPath path1, path2, path3;

    @Before
    public void before() {
        path1 = new MyPath(1, 2, 3, 4);
        path2 = new MyPath(1, 2, 3, 4);
        path3 = new MyPath(1, 2, 3, 4, 5);
    }

    @After
    public void after() {
        // do nothing
    }

    @Test
    public void testEquals() throws Exception {
        Assert.assertEquals(path1, path2);
        Assert.assertNotEquals(path1, path3);
        Assert.assertTrue(path1.equals(path1));
        Assert.assertTrue(path1.equals(path2));
        Assert.assertFalse(path1.equals(path3));
        Assert.assertTrue(path2.equals(path1));
        Assert.assertTrue(path2.equals(path2));
        Assert.assertFalse(path2.equals(path3));
        Assert.assertFalse(path3.equals(path1));
        Assert.assertFalse(path3.equals(path2));
        Assert.assertTrue(path3.equals(path3));
    }
}

PASS:

再对我们的Graph类来一遍:

import com.oocourse.specs1.models.Path;
import com.oocourse.specs1.models.PathContainer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class MyGraphTest {
    private final PathContainer graph = new MyGraph();
    private Path path1, path2, path3;
    

    @Before
    public void before() {
        path1 = new MyPath(1, 2, 3, 4);
        path2 = new MyPath(1, 2, 3, 4);
        path3 = new MyPath(1, 2, 3, 4, 5);
    }

    @After
    public void after() {
        // do something here
    }

    @Test
    public void testAddPath() throws Exception {
        Assert.assertEquals(1, graph.addPath(path1), 1);
        Assert.assertTrue(graph.containsPathId(1));
        Assert.assertEquals(path1, graph.getPathById(1));
        Assert.assertEquals(1, graph.size());

        Assert.assertEquals(1, graph.addPath(path2));
        Assert.assertTrue(graph.containsPathId(1));
        Assert.assertEquals(path2, graph.getPathById(1));
        Assert.assertEquals(1, graph.size());

        Assert.assertEquals(2, graph.addPath(path3));
        Assert.assertTrue(graph.containsPathId(2));
        Assert.assertEquals(path3, graph.getPathById(2));
        Assert.assertEquals(2, graph.size());

    }
}

PASS:

至于JMLUnitNG,我在配置过程中出现了一些问题,不过虽然没有部署成功,也算是有了一些了解,明白了JMLUnitNG是神马东西。

4.作业架构设计梳理

(1).第一次作业

非常简单hh,基本上不存在什么架构设计问题。不过要注意有关复杂度的要求,特别是不同点的个数,如果使用数组莽夫的话是很容易爆炸的哦~

因此我使用了HashMap<Node, Interger>,记录点的情况,path加入时其中每个点的对应值加一(不存在对应值则新建对应值为1),path remove时减一(减为零则移除)。

(2).第二次作业

储存结构沿用了上次的思路,不过因为有了更多要求,我对于每条边也采用了相同的方法,采用HashMap存储,使之能够方便使用。

我在已给定的类中多加了个MyEdge类用于存放我的边;不过也不是很难,使用HashMap将nodeMap中每一个点对应一个下标,只要一个Floyd就可以解决啦:

(3).第三次作业

对于这次作业,emmm,我心情复杂

我被我自己蠢到了,至于怎么蠢到的暂且不表请见下一小节中的BUG部分QAQ

总之我写了两个版本的MyRailwaysystem,算法相同。

图内最短路径算法:使用HashMap每个点对应一个下标,就可以采用弗洛伊德算法。

连通块个数算法:使用BFS。

第一个版本使用的是Path内单独建图,Path之间再建一个图,设计结构同第二次差别不大,只是将一些重复使用的方法独立出来了:

另外一个版本采用了拆点的做法,这个版本我重构了一下。而为了拆点,我需要将不同path中的同一个点独立出来,因此创建了MyNode,持有点的id和path的id。而在MyNode基础上又新建了NodeEdge,为一条边持有两个MyNode点。此外扩充写了几个重复使用多次的方法至MyMethed中:

5.bug的发现及修复情况

(1).第一次作业

第一次作业确实比较简单,我没有出现BUG,互测屋中也没有出现BUG

(2).第二次作业

第二次作业稍微难了一点,不过我依旧没有出现BUG,全互测屋中有人发现了其他人的一个正确性问题,不过我当时没有对拍器并没有测试出来。

(3).第三次作业

这次作业是我的一个大教训。

我身边的人都写得是拆点,因此我写完了第一个版本后又写了拆点的版本。我对两个版本都进行了正确性的验证(对拍),但是并没有加上运行时间一项,心想大家都是拆点应该没问题吧,就交了拆点的版本。

然后进入了互测阶段,下载了其他同学的代码,对拍。发现每拍一次至少4个人有BUG。于是我大概知道自己进了c屋,随便刀了几刀就开始反省自己了。

于是我对我自己的两个版本进行了运行时间测试:

新版120s,旧版1.6s

emmm,看来我应该交旧版的。

我想了想就想通了:

旧版的复杂度同新版拆点一样,同为O($$n^3$$):

旧版需要path内建图所以一次add或remove操作,复杂度是80^3 + 1203,其中80为path内最大点数,path内建图需要803,而120为Graph内最大点数。

新版不需要path内建图,因此是120^3 (啊呸)

新版虽然不需要path内建图,但实际最大节点数是50条path * 一条path 80个节点 = 4000个节点,复杂度是4000^3!!!

看来我强测炸的不冤啊。

后来修改bug的时候直接交了第一个版本即path内建图版本,AC...

所以我为了图省事没有自己测运行时间出了大事啊。算是给自己买了个教训吧。

6.心得体会

有关JML:

· 通过规格化设计一个单元的学习,我们了解了规格化设计在企业开发中的潜力,对于我们代码的正确性也能合理的提出相应的方式进行规范,确实有可取之处。相信在合作开发的项目中,或者是有较高正确性要求的航空航天领域中,可以采用相关的规格明确责任,确保正确性。

· 通过JML语言描述,能较好的解决二义性问题。

· 不过从另一个角度来讲,JML语音依旧有许多不足:

· JML相关的工具发展不是很完备(有些工具仅限特定Java版本),不用户友善,执行起来有很多问题。

· 为了追求没有二义性,反而导致描述效率不如自然语言。

有关个人经验:

· 不要犯懒,自己应该做完备的测试(而且要自己做),不然迟早有一天会出事。

· 也要“犯懒”。这里指的“犯懒”不是做某些工作时偷工减料,而是应该在着手做一件事之前,应该先对这件事有一个较为透彻的思考。比如因为我并没有正确的思考看待算法复杂度的问题,导致我重构拆点算法既导致自己公测17个TLE,又浪费了一下午的时间去写一份不如旧版的代码。

总之,以后多看多学多练多想。圣斗士是不会被同一招打败两次的!吸取教训才能走得更远。

最后依旧希望OO课程在助教们、老师们和同学们的共同努力之下,能越办越好!
posted @ 2019-05-22 16:16  garyzhang99  阅读(162)  评论(0编辑  收藏  举报