面向对象第三单元总结

JML语言

面向对象的分析和设计(OOAD)的一个重要原则就是过程性的思考应该尽可能地推迟,不过遵循这个原则也不过是把这个原则适用到方法实现这个级别上。一旦设计好了类和接口,下面的事情自然就是实现其中定义的方法了。对呀,还能做什么呢?还有什么其它方法可以使用吗?毕竟,用Java进行程序设计和用其他语言进行程序设计一样,都要一步一步地实现每一个方法。

标记本身只是表示如何做一个事情(how to do something),根本不管我们希望做什么。如果我们在做一个事情之前就能够知道我们能够达到什么样的结果是非常好的,不过Java语言并没有给我们提供一个可以显示地把这些信息插入到我们程序代码的方法。

这个单元所接触得到的Java建模语言(Java Modeling Language,JML)在Java代码中增加了一些符号,这些符号用来标识一个方法是干什么的,却并不关心它的实现,能够描述一个方法的预期的功能而不管他如何实现。通过这种方式,JML把过程性的思考延迟到方法设计中,从而扩展了面向对象设计的这个原则。

JML引入了大量用于描述行为的结构,比如有模型域、量词、断言可视范围、预处理、后处理、条件继承以及正常行为(与异常行为相对)规范等等,并且可以使用openJML进行检查。通过JML的规约来检查代码静态、动态时候的正确性。

除了openJML,还有很多可以编译JML的编译器,如

  • jmlrac: test for violations of assertions during execution
  • ESC/Java2: static verification; compile-time proving that contracts are never violated
  • jmldoc: javadoc-style documentation
  • jmlc: assertion-checking compiler
  • jml4c: a new JML compiler built upon the Eclipse JDT open-source platform

但openJML是目前JML支持最好,维护最积极的JML编译器了 ,我在idea中下载并安装了openJML,并尝试运用体验了一番,感觉还是非常赞的。

 

JUnit测试

JUNIT是一个用于模块化测试的环境,在本单元第二次作业时我尝试对MyGraph编写对模块进行测试,代码如下:

import com.oocourse.specs2.models.NodeIdNotFoundException;
import com.oocourse.specs2.models.NodeNotConnectedException;
import com.oocourse.specs2.models.PathIdNotFoundException;
import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;

public class MyGraphTest {
    private MyGraph tmp;
    private MyPath p1;
    private MyPath p2;

    @Before
    public void setUp() throws Exception {
        int[] a = {0,1,2};
        int[] b = {4,10,24};
        p1 = new MyPath(a);
        p2 = new MyPath(b);
        tmp = new MyGraph();
        tmp.addPath(p1);
    }

    @Test
    public void size() {
        tmp.addPath(p2);
        assertEquals(2,tmp.size());
    }

    @Test
    public void containsPath() {
        assertTrue(tmp.containsPath(p1));
        assertFalse(tmp.containsPath(p2));
    }

    @Test
    public void getPathById() {
        tmp.addPath(p2);
        try {
            assertEquals(p1,tmp.getPathById(1));
        } catch (PathIdNotFoundException e) {
            e.printStackTrace();
        }
        try {
            assertEquals(p2,tmp.getPathById(2));
        } catch (PathIdNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void getDistinctNodeCount() {
        tmp.addPath(p2);
        assertEquals(6,tmp.getDistinctNodeCount());
    }

    @Test
    public void containsNode() {
        assertTrue(tmp.containsNode(0));
        assertFalse(tmp.containsNode(-2));
    }

    @Test
    public void containsEdge() {
        assertTrue(tmp.containsEdge(1,0));
        assertFalse(tmp.containsEdge(0,10));
    }

    @Test
    public void isConnected() {
        try {
            assertTrue(tmp.isConnected(1,0));
        } catch (NodeIdNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void getShortestPathLength() {
        try {
            assertEquals(2,tmp.getShortestPathLength(2,0));
        } catch (NodeIdNotFoundException e) {
            e.printStackTrace();
        } catch (NodeNotConnectedException e) {
            e.printStackTrace();
        }
    }
}

运行后结果如下:

每个模块运行的结果与期望值相等,则测试用例通过,若一个模块未通过测试,则去该模块中寻找错误,错误定位快速准确。

 

JMLUnitNG实现自动生成测试用例

由于用JMLUnitNG尝试自动生成测试用例一直报错,于是自己写了一个简单的类尝试运用JMLUnitNG,类的代码如下:

public class Caculate {
    /*@
      @ ensures (a <= b) ==> (\result == a);
      @ ensures (a > b) ==> (\result == b);
      @*/
    public static int min(int a, int b) {
        return Integer.min(a, b);
    }
    /*@
      @ ensures (a >= b) ==> (\result == a);
      @ ensures (a < b) ==> (\result == b);
      @*/
    public static int max(int a, int b) {
        return Integer.max(a, b);
    }
}

按照讨论区的方法,输入以下命令:

会自动生成以下文件:

最后运行自动生成的测试,会得到如下结果:

发现运行成功,没有报错,生成的测试样例基本都是边界条件,对程序在比较极端条件下的正确性做了测试,还是有一定的可用性的,之后可以将其运用在自己的debug环节。

 

架构设计

这个单元作业最大的特点就是每次要实现的需求都是在上一次的需求之上再添加新的需求,这对架构的可扩展性做了很高的要求,也是训练我们学会考虑架构的可扩展性,不要只想着完成每一次的作业就好,而要多考虑考虑以后,要想不每一次作业都重构,就要提高自己程序的靠扩展性。

这个单元的第一次作业我完成的两次,进行了重构,主要是最开始没有考虑到复杂度的问题,只是进行了简单的ArrayList遍历算法,时间复杂度十分的高,后来在室友的提醒下,才注意到了这个问题,进行了重构(没有想到一上来就要重写,aaa我的内心是崩溃的)。我又重新阅读了一遍指导书,发现虽然指令数总共有好几千,很吓人,但add和remove指令其实是非常有限的,因此我只需要尽量降低查询的复杂度,让其尽可能是o(1),就能避免超时,于是我采用了三个HashMap的结构(如下图所示),一个由pathId对应Path,一个由Path对应PathId,还有一个用来存容器中不同的点数,三个HashMap在add和remove时进行维护,虽然add和remove的复杂度相比之前有一定的升高,但查询的复杂度瞬间降至o(1),可以说是非常划算和正确的做法。

 

第二次作业在第一次作业的基础上增加了一些关于图的操作,吸取了上一次的经验,我首先非常认真的阅读了作业指导书,发现这次与上次比较相同的一点是add和remove指令数非常有限,因此这次的大致思路与上次的相同,也应尽量在add和remove方法中维护一些方便查询的数据结构,以尽量降低其他查询指令的时间复杂度。首先有关于图的操作,我先去复习了大一下所学的数据结构中关于图的部分。确定了图的存储结构应为邻接表,再考虑这次需求中最复杂的方法寻找两点间的最短路径所该应用的算法,我锁定了两个方向,一是运用多源查找最短路径,即遍历一次将所有点的路径都查好;二是单源查找最短路径,即遍历一次只能将一个点到其他点的最短距离算出来,当然两种算法都要将结果保存下来以复用。经过再三的考虑,我选择了后者,因为我觉得前者做了很多无用功,明明有些点可能不会被查到,却进行了无用的计算,浪费了cpu的时间;而后者最大限度的保证了查什么算什么,而且不会重复查询,因为结果都保存了下来,因此我认为在设计上后者一定优于前者。下图就是我这次作业采用的架构。

 

第三次作业是在第二次作业图的基础上实现一个地铁系统,要计算最少票价,最少换成次数,最少的不满意度和联通块数。拿到需求后,我想了非常非常久,但一直没有想出到底该如何计算三个最少,联通块数倒是在第二次作业的基础上直接实现了。直到周一晚上看到了讨论区中拆点的算法才有了一点思路。算法有了,接下来就是实现了。由于这次每种图边的权值都是不一样的,因此存图时也得存三个,每次在add和remove操作之后要重新更新图的信息,之后运用拆点法加迪杰斯特拉斯的算法就能算出三个最少,实现这次的需求。

 

总之,除了第一次作业,这次没有进行重构,每次都在上次的基础上进行了扩展,觉得自己相比于之前还是有了非常大的进步,渐渐不再满足于只完成本次的作业,而是为之后做打算。

 

Bug和修复

先说说这三次的评测结果,前两次在评测及互测中均为发现bug,最后一次在评测中没有发现bug,但在互测中被🔪了。第三次出现的错误主要为对图的一个更新忘记写了导致了wrong answer,还有一个未修复的是cpu超时,那组数据是我所采用的算法的最坏情况,不幸的是,我的算法未能在最坏情况下满足题目的要求,除了重构,我想大概是束手无策了。

从这几次的情况来说,我还是比较满意的,当然这也归功于课下自己比较充分的测试,我采用的方法是与室友互拍,然后用sublime中的sublimerge工具进行比对,这种debug方式我认为对这单元大量的数据是非常有效的。

 

关于规格撰写

这一单元是我第一次接触关于规格撰写,我们主要运用的是JML语言。开始时我非常不能理解,觉得写代码就已经够累的了,何苦还要为难自己,绞尽脑汁的写规格。但通过老师上课的讲解,我渐渐理解了规格撰写的意义,在编程中加入规格的设计,可以有以下的好处:

  • 能够更为精确地描述这些代码是做什么的
  • 能够高效地发现和修正程序中的bug
  • 可以在程序升级时降低引入bug的机会
  • 可以提早发现客户代码对类的错误使用
  • 可以提供与应用程序代码完全一致的JML格式的文档

前三点是我在本单元编程中深切体会到的,而后两点是通过老师上课向我们分享自己的工作经历所提到的。总之规格设计在真正工作场景中是十分重要的,规格写好后,真正的实现都不是事儿。老师在课上讲我们与那些世界上大牛的做软件的差距其实就在于规格的撰写,将大量时间花费在规格撰写上看似是浪费了很多时间,但实际上好的规格编写完了以后会沿用非常久的时间,并且不会出现在实现过程中突然发现设计层面出现了问题需要重来,诸如此类的无用功会被大大减少。

由此看来,学习规格的撰写还是非常必要的,它也逼迫我们在真正动手实践之前先设计好,考虑清楚每个模块需要完成的事情,有效的避免了bug的出现。bug出现时也只需检查各模块是否满足了规格的要求即可。

这个单元对于规格的学习我们只是浅尝了一下,只学了一点皮毛,今后还有很长一段路要走。随着第三单元的结束,马上将要迎来最后一个单元的挑战,也给有点疲倦的自己打打气,加油💪

 

posted @ 2019-05-22 11:19  Lisabo  阅读(153)  评论(0编辑  收藏  举报