一、规格历史
最初的程序设计是直接面向机器的,代码编写困难、可读性差,当时对于软件开发的需求并不多。随着对于程序规模的需求,出现了面向过程的设计思想,开发者开始忽略底层实现,进行程序设计。对于面向过程设计思想的变革,goto语句成为讨论的热点。Dijkstra于1968年发表著名的《GOTO有害论》,引起了广泛的关注,结构化的设计思想也在此时应运而生。面向对象设计是一种软件设计方法,是一种工程化规范。面向对象设计模式解决的是类与相互通信的对象之间的组织关系,包括它们的角色、职责、协作方式几个方面。程序设计的模块化体现的更为明显,各个部分更加独立,相应地,各个模块之间的交互也更加重要。
正因如此,程序规格化设计越来越被人们重视,对于代码编写的规范能够让程序变得更加易懂,能让使用者轻松地学会如何正确使用这段程序。通过规格的抽象和总结,使用者能够直接使用这段程序而免去浪费大量时间来看懂代码的痛苦过程,代码的维护也变得更加简单。
二、规格bug
在这三次测试中,我遇到的测试者没有对规格进行报错,但这并不代表我的规格书写没有问题,通过自己分析几次规格书写的bug,得到下面的表格
规格bug | 方法行数 |
effects不完整 | 5 |
effects内容为实现算法 | 11 |
requires逻辑错误 | 3 |
modifies不完整 | 3 |
分析:首先,缺少规格设计这样的错误只在第一次书写规格时出现,由于对规格书写了解不够清晰,常常出现应该写规格的地方没有写。我经常出现的错误是effects内容为实现算法,由于常常使用自然语言写effects内容,导致描述算法居多,过度使用自然语言对于规格书写是没有帮助的。Requires逻辑错误的原因,则是由于先写程序后写规格导致的,规格并没能约束程序,程序乱七八糟地修改了,规格却不知怎么跟着修改。
三、一些不好的例子
由于自己随心所欲的书写,出现了许多不好的问题,大部分问题出现在后置条件中,列举如下:
1. public ReqHandler(Request request) {
/**
* @REQUIRES:none;
* @MODIFIES:this;
* @EFFECTS:将请求加到队列中;
*/
this.request = request;
this.taxiSelection = new Vector<>();
}
这是一开始写的规格,对于request不为空没有约束,而后置条件滥用自然语言
修改如下:
/**
* @REQUIRES:requires!=null;
* @MODIFIES:this;
* @EFFECTS:(this.request==request)&&( this.taxiSelection.isEmpty());
*/
2.
public boolean isCross(int x, int y){
/**
* @REQUIRES:
* @MODIFIES: guigv.m.graph;
* @EFFECTS:
* (x<0||x>79||y<0||y>79)==>\result==false;
* (x>0&&guigv.m.graph[x*80+y][(x-1)*80+y]==1)==>cross==cross+1;
* (x<79&&guigv.m.graph[x*80+y][(x+1)*80+y]==1)==>cross==cross+1;
* (y>0&&guigv.m.graph[x*80+y][x*80+y-1]==1)==>cross==cross+1;
* (y<79&&guigv.m.graph[x*80+y][x*80+y+1]==1)==>cross==cross+1;
* (cross>=3)==>\result==true;
* (cross<3)==>\result==false;
*/
if(x<0||x>79||y<0||y>79)
return false;
int cross = 0;
if(x>0&&guigv.m.graph[x*80+y][(x-1)*80+y]==1)
cross++;
if(x<79&&guigv.m.graph[x*80+y][(x+1)*80+y]==1)
cross++;
if(y>0&&guigv.m.graph[x*80+y][x*80+y-1]==1)
cross++;
if(y<79&&guigv.m.graph[x*80+y][x*80+y+1]==1)
cross++;
if(cross>=3)
return true;
else return false;
}
描述内容是实现算法,更改方式应该是从后置条件的含义入手,如下
/**
* @REQUIRES:none;
* @MODIFIES: guigv.m.graph;
* @EFFECTS:
* (x>0&&guigv.m.graph[x*80+y][(x-1)*80+y]==1)==>cross==cross+1;
* (x<79&&guigv.m.graph[x*80+y][(x+1)*80+y]==1)==>cross==cross+1;
* (y>0&&guigv.m.graph[x*80+y][x*80+y-1]==1)==>cross==cross+1;
* (y<79&&guigv.m.graph[x*80+y][x*80+y+1]==1)==>cross==cross+1;
* \result==true ==> (cross>=3);
* \result==false ==>(cross<3)|| (x<0||x>79||y<0||y>79);
*/
3.部分方法需要抛出异常,但是没有体现在规格中。
修改方法:增加exceptional_behavior(InterruptedException);
4.
/**
* @REQUIRES: none;
* @MODIFIES: SchedulerSys.taxis, result.txt;
* @EFFECTS: 根据请求处理原则,选派相应出租车响应,并将响应结果写入文件,同时更改参与抢单出租车的信用值;
* @THREAD_REQUIRES:\locked(finalTaxi);
* @THREAD_EFFECTS:\locked(),整个方法同步;
*/
没有写明具体修改内容
修改方式:增加具体修改内容
5.经过研究其他同学的代码,我发现自己代码有一个很大的问题就是有些方法写的太长了,动辄好几百行,像是面向过程代码,不是一个很好的结构。这是由于书写时没有先写规格,在最后写规格时无从下手。这种规格改正方式,只有推翻重写。
四、聚焦关系
由于规格设计的问题,导致代码功能不清晰,并造成了至今无法查出的crash问题……
作业次数 | 方法名 | 功能bug | 规格bug |
| taxi | 2 | 2 |
9 | hanndleFileReq | 1 | 1 |
10 | getAailaleDir | 1 | 1 |
11 | getAailaleDir | 1 | 1 |
|
|
|
|
五、心得体会
总的来说,这三次作业主要折磨我的还是规格设计的问题。我无法做到先设计规格再根据规格写代码,常常是写完代码了,要花更多地时间去“编”规格。事实上,我无法体会到书写规格的意义,对于我这种底层群众来说,可能书写规格和写注释没什么区别。而且对于规格的规范等等,各人有各人的理解,常常能在朋友圈看到大家被挂7,8个规格错误,然后就是一场撕逼,过去的三个星期虽然一定不是作业量最多的,但一定是大家最生气、戾气最多的时间。说实话,学到现在,有些迷茫了,我不清楚这门课到底要我们干什么,无法理解它和数据结构、c语言的区别,它对于我来说,可能就只是一门很痛苦的课程而已吧。庆幸的是遇见的人都很不错,然而身边见到的不良例子太多了……虽然说最可怕的出租车已经过去了,但是我想每一次撕逼,每一个挂在朋友圈的帖子,都是一把刺在人心上的刀子,伤虽然愈合了,但疤痕永远都在。