规格化设计的大致发展历史


 

1960年代末—1970年代初,出现软件危机:一方面需要大量的软件系统; 另一方面,软件研制周期长,可靠性差,维护困难。编程的重点:希望编写出的程序结构清晰、易阅读、易修改、易验证,即得到好结构的程序。 
 
1968年,北大西洋公约组织(NATO)在西德召开了第一次软件工程会议,分析了危机的局面,研究了问题的根源,第一次提出了用工程学的办法解决软件研制和生产的问题,本次会议可以算做是软件发展史上的一个重要的里程碑。
 
自此以后,随着模块化程序设计的需求日益增长,规格抽象的思想被越来越多的工程人员所接受。
 
为什么得到了人们的重视

·抽象是无形的;没有描述,抽象就没有意义。规格提供了这个描述。
·编写规格使被定义的抽象清楚显示出来,使得注意力迅速集中在不一致、不完全和不明
确这些问题上。它迫使我们仔细关注抽象及其预期用处。
·规格定义了用户和实现者之间的契约:实现者同意提供一个满足规格的实现,用户同意
依赖于不必知道规格满足集的哪个成员用来实现规格。

——《程序开发原理:抽象、规格与面向对象设计》 9.3为什么要有规格

 总结起来,规格可以提高代码的可读性、重用性、规范性,便于团队内部代码维护与修改,也更容易共享代码。

分析自己被报告的规格bug


 

  bug数 bug产生的原因 对应方法的行数
第九次作业 0 N/A N/A
第十次作业 0 N/A N/A
第十一次作业 2 少写了两个方法的规格 4,8

 

列举5个前置条件和后置条件的不好写法,并改进


 

前置条件1:

 

    /**
     * @REQUIRES:None;
     * @MODIFIES:None;
     * @EFFECTS: \result == "("+point.x+","+point.y+")";
     */
    public static String formatPoint(Point point) {
        return "("+point.x+","+point.y+")";
    }

 

改进:缺少对象非空的判断

    /**
     * @REQUIRES:point != null;
     * @MODIFIES:None;
     * @EFFECTS: \result == "("+point.x+","+point.y+")";
     */
    public static String formatPoint(Point point) {
        return "("+point.x+","+point.y+")";
    }

前置条件2:

/**
     * @REQUIRES:(int index; 0 <= index <= 99);
     * @MODIFIES:index, currentPosition, status, credit;
     * @EFFECTS:\this.index == index;
     *          \this.currentPosition == new Point((int) (Math.random() * 80), (int) (Math.random() * 80));
     *          \this.status == Status.WAITING;
     *          \this.credit == 0;
     *          \this.mapForUse = Main.map2;
     */
    public Taxi(int index, int flag) {
    }

改进:缺少谓词逻辑\all

/**
     * @REQUIRES:(\all int index; 0 <= index <= 99);
     * @MODIFIES:index, currentPosition, status, credit;
     * @EFFECTS:\this.index == index;
     *          \this.currentPosition == new Point((int) (Math.random() * 80), (int) (Math.random() * 80));
     *          \this.status == Status.WAITING;
     *          \this.credit == 0;
     *          \this.mapForUse = Main.map2;
     */
    public Taxi(int index, int flag) {
    }

前置条件3:

/**
     * @REQUIRES:(\all int dis, Point des; dis == distance);
     * @MODIFIES:None;
     * @EFFECTS:找到是最短路且流量最小的路;
     * @THREAD_EFFECTS:\locked();
     */
    protected synchronized Point findFlowLeastShortPath(int dis, Point des) {
}

改进:表意不明

/**
     * @REQUIRES:(\all int dis, Point des; dis == des.distance);
     * @MODIFIES:None;
     * @EFFECTS:找到是最短路且流量最小的路;
     * @THREAD_EFFECTS:\locked();
     */
    protected synchronized Point findFlowLeastShortPath(int dis, Point des) {
}

前置条件4:

/**
     * @REQUIRES:None;
     * @MODIFIES:flow;
     * @EFFECTS:this.flow[a][b] == \old(flow) + 1;
     *             this.flow[b][a] == \old(flow) + 1
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void addFlow(int a, int b) {
        this.flow[a][b]++;
        this.flow[b][a]++;
    }

改进:缺少对相邻边的要求

/**
     * @REQUIRES:abs(a-b) == 1;
     * @MODIFIES:flow;
     * @EFFECTS:this.flow[a][b] == \old(flow) + 1;
     *             this.flow[b][a] == \old(flow) + 1
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void addFlow(int a, int b) {
        this.flow[a][b]++;
        this.flow[b][a]++;
    }

前置条件5:

/**
     * @REQUIRES:(\all int a,b; 0 <= a, b <= 6399);
     * @MODIFIES:flow;
     * @EFFECTS:this.flow[a][b] == flow;
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void setFlow(int flow, int a, int b) {
        this.flow[a][b]= flow; 
    }

改进:缺少对flow的约束

/**
     * @REQUIRES:(\all int a,b; 0 <= a, b <= 6399) && (flow >= 0);
     * @MODIFIES:flow;
     * @EFFECTS:this.flow[a][b] == flow;
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void setFlow(int flow, int a, int b) {
        this.flow[a][b]= flow; 
    }

后置条件1:

/**
     * @REQUIRES:point != null;
     * @MODIFIES:None;
     * @EFFECTS: \result == 该点的字符形式;
     */
    public static String formatPoint(Point point) {
        return "("+point.x+","+point.y+")";
    }

改进:不需用自然语言表达

/**
     * @REQUIRES:point != null;
     * @MODIFIES:None;
     * @EFFECTS: \result == "("+point.x+","+point.y+")";
     */
    public static String formatPoint(Point point) {
        return "("+point.x+","+point.y+")";
    }

后置条件2:

/**
     * @REQUIRES:None;
     * @MODIFIES:flow;
     * @EFFECTS:this.flow[a][b] == \old(flow) + 1;
     *             this.flow[b][a] == \old(flow) + 1
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void addFlow(int a, int b) {
        this.flow[a][b]++;
        this.flow[b][a]++;
    }

改进:this和old使用不规范

/**
     * @REQUIRES:None;
     * @MODIFIES:flow;
     * @EFFECTS:\this.flow[a][b] == \old(\this.flow) + 1;
     *             \this.flow[b][a] == \old(\this.flow) + 1
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void addFlow(int a, int b) {
        this.flow[a][b]++;
        this.flow[b][a]++;
    }

后置条件3:

/**
     * @REQUIRES:(\all int a,b; 0 <= a, b <= 6399);
     * @MODIFIES:flow;
     * @EFFECTS:\this.flow[a][b] == flow;
     */
    public synchronized void setFlow(int flow, int a, int b) {
        this.flow[a][b]= flow; 
    }

改进:缺少同步规格

/**
     * @REQUIRES:(\all int a,b; 0 <= a, b <= 6399);
     * @MODIFIES:flow;
     * @EFFECTS:\this.flow[a][b] == flow;
     * @THREAD_EFFECTS:\locked();
     */
    public synchronized void setFlow(int flow, int a, int b) {
        this.flow[a][b]= flow; 
    }

后置条件4:

/**
     * @REQUIRES:request != null;
     * @MODIFIES:request, endflag;
     * @EFFECTS:构造器;
     */
    public Scheduler(Request request) {
        this.request = request;
        this.endflag = false;
    }

改进:构造方法过于简单

/**
     * @REQUIRES:request != null;
     * @MODIFIES:request, endflag;
     * @EFFECTS:this.request == request;
                 this.endflag == false;
     */
    public Scheduler(Request request) {
        \this.request = request;
        \this.endflag = false;
    }

后置条件5:

    /**
     * @REQUIRES:(\all int index; 0 <= index <= 99);
     * @MODIFIES:index, currentPosition, status, credit;
     * @EFFECTS:\this.index == index;
     *          \this.currentPosition == new Point((int) (Math.random() * 80), (int) (Math.random() * 80));
     *          \this.status == Status.WAITING;
     *          \this.credit == 0;
     */
    public Taxi(int index, int flag) {
    }

改进:没有列出所有成员的变化

    /**
     * @REQUIRES:(\all int index; 0 <= index <= 99);
     * @MODIFIES:index, currentPosition, status, credit;
     * @EFFECTS:\this.index == index;
     *          \this.currentPosition == new Point((int) (Math.random() * 80), (int) (Math.random() * 80));
     *          \this.status == Status.WAITING;
     *          \this.credit == 0;
     *          \this.mapForUse = Main.map2;
     */
    public Taxi(int index, int flag) {
    }

 

分析被报的功能bug和规格bug上的聚集关系


由于只有两个因为忘写了而被报的规格bug,这两个bug位于SingleFlow类,而我不存在任何由于流量造成的功能bug,故不存在任何聚集关系。

 

归纳自己在设计规格和撰写规格的基本思路和体会


首先,课程组对于系列作业的安排使得我们在第九次作业时先写代码后写规格,这样的规格写出来是对自己方法的一个抽象概括。并没有体现出规格的威力来。实际上,也没有达到对我们设计规格的训练,绝大部分人在三次作业中都是先写代码后写规格。

基本思路:通常requires和modifies比较好写,也不会被扣。requires就是把对方法的参数的要求,比如对象是否非空,是否是子类,int值的范围是否正确等等。modifies则重点关注类的成员变量的修改,尤其要注意该方法所调用的方法对成员变量的修改。重点在于effects,如果是可以用逻辑表达式写出来的,就参照ppt上给的例子用蕴含式表达出来,如果不行则用自然语言尽量清晰的描述出来。

体会:我感觉我从JSF中学到的东西远不及前面自己作业,反而在给自己的代码补JSF的过程中感觉自己花费了很多时间,只为了互测不被扣分,收获的也不多。而且测我的人也比较佛系,我没被挑出bug,所以我也不知道我JSF哪里写的不好。不过从积极的一方面来想,写规格也是一个归纳我们所写的方法的逻辑的过程,在写规格的同时,我会发现自己有些方法的逻辑写的过于复杂,有些则没有合理的规划从而导致耦合性过高,这是我从写规格的过程中所收获到的。同时,当过了两周后再来看自己的代码,从规格中可以很快回忆起这段代码的作用和逻辑。如果不写的话恐怕就完全不认识了吧。规格在实际的工程代码中必将起到举足轻重的作用,OO的这三次作业让我初步地体会到了这一点。在最近的一次实验中,我也感受到了完整的规格带来的好处,我们可以轻易的根据规格实现代码,也可以通过规格了解到调用这个函数造成的影响和约束,对于我们自己写的代码可能感受不到好处,但是如果拿别人的代码或者把自己的代码给别人看时,就能感受到规格的方便之处,尤其是需要修改添加代码的时候。