1, 规格化设计的发展历程
关于规格化设计一词,能找到的资料实在少之又少。在笔者已经找到的资料中,笔者认为,规格化设计来自于历史上第一次软件危机之后,虽然当时已经有了大量的面向过程式的编程语言譬如COBOL,BASIC,C语言等等,但是其中含有的大量的goto语句导致的面条式代码极大地限制了程序规模。
为了解决如上的问题,人们提出了结构化程序设计这一中编程范型。它采用子程序(函数就是一种子程序)、代码区块、for循环以及while循环等结构,来替换传统的goto。希望借此来改善计算机程序的明晰性、质量以及开发时间,并且避免写出面条式代码。
但是,由于硬件的飞速发展,业务的需求越来越复杂,爆发了第二次软件危机。此次危机主要体现在“可扩展性”,“可维护性”两个方面。由于传统面向过程式的编程难以适应快速多变的业务需求,因而产生了面向对象式的编程思想。在笔者看来,规格化设计一词,是在结构化设计的基础上,为了适应程序的“可扩展性”和“可扩展性”而衍生出来的重要设计思想。
一个经过优秀的规格化设计的程序,有着如下的优势:其一,有助于开发者们相互理解对方的程序,方便程序员之间的分工,加快开发速度。如今的工程,往往都是由一个团队共同完成。那么对于团队中的成员来说,互相理解对方的代码是十分重要的,这样才能更好地完成某个项目。其次,对于编写函数库的程序员来说规格化设计更为重要。其原因就在于函数库的使用者并不需要知晓库程序员的代码思路与实现过程,他只需要知道库中的某个函数,它需要的参数是什么,会对什么数据进行修改,修改的效果是什么,也就是我们JSF中的REQUIRES,MODIFIES,EFFECTS三个重要的功能。其二,规格化的设计能很好地提高程序的“可扩展性”与“可维护性”。当开发者清楚自己所写的每一个方法的规格时,他能够很快速地针对每一个bug追本溯源,找出问题所在。其次,在面对如今工程需求快速变化的情况时,开发者通过规格化的设计可以很好地了解自己的程序应该如何进行修改以适应需求的变化。
2,三次作业的规格BUG
笔者的后三次出租车的作业并没有在规格上被测试者报告BUG,但这并不意味着笔者的规格便没有任何的错误,仅仅是遇到了“心地善良”的测试者罢了。
那么笔者便来自己细数自己在规格上出现的纰漏以及修改的方案。
3,JSF不规范的写法以及改进
1)
1 /**
2 * @REQUIRES:(\all String str;str != null,path != null)
3 * @MODIFIES:fw
4 * @EFFECTS:
5 * (\all str != null) ==> fw.write(str+"\n");
6 */
7 public static void appendFile(String Path,String str) {
8 try {
9 File f = new File(path);
10 FileWriter fw = new FileWriter(f, true);
11 fw.write(str + "\n");
12 fw.close();
13 } catch(Exception e) {
14 }
15 }
16 }
该方法的的作用显而易见,便是向文件中写入信息,那么该方法的JSF出了什么纰漏呢?
首先我们对REQUIRES的定义是什么?它的定义是前置条件,也就是执行该方法前对该输入或者系统状态的要求。
该方法的前置条件是字符串str的含义是文件的路径,那么其前置条件便是str是合法的文件路径,而源代码中的前置条件仅仅强调了path != null,显然这并没有满足要求。
而MODIFIES的定义副作用,是对input的修改,即对输入的修改。显而易见的是,本方法并没有对输入进来的str进行任何修改,因此无法满足要求。
最后的EFFECTS的定义是后置条件,即执行后的返回结果或者系统状态应该满足的约束。本方法对系统造成的影响是往文件中写入字符串
因此对该规格的改进为:
1 1 /**
2 2 * @REQUIRES:(str!=null) && (path is a legitimate file path)
3 3 * @MODIFIES:none
4 4 * @EFFECTS:
5 5 * (\all str != null) ==> fw.write(str+"\n");
6 6 */
7 7 public static void appendFile(String Path,String str) {
8 8 try {
9 9 File f = new File(path);
10 10 FileWriter fw = new FileWriter(f, true);
11 11 fw.write(str + "\n");
12 12 fw.close();
13 13 } catch(Exception e) {
14 14 }
15 15 }
16 16 }
2)
1 /**
2 * @REQUIRES:Point p,q;int f;
3 * @MODIFIES:flow[][];
4 * @EFFECTS:
5 * (isAdh(p,q) == false) ==> (/result == false);
6 * (isAdh(p,q) == true) ==> (/result == true)&&(flow[getPointId(p)][getPointId(q)] == flow[getPointId(p)][getPointId(q)]+f)&&(flow[getPointId(q)][getPointId(p)] == flow[getPointId(q)][getPointId(p)]+f);
7 */
8 public boolean addFlow(Point p, Point q, int f) {
9 if(!isAdj(p, q)) {
10 return false;
11 }
12 synchronized(flow) {
13 flow[getPointId(p)][getPointId(q)] += f;
14 flow[getPointId(q)][getPointId(p)] += f;
15 }
16 return true;
17 }
该方法的REQUIRES不符合要求,应该要标明p,q和f的要求等情况
修改方法如下所示
/**
* @REQUIRES:(Point p,q;p!=null,q!=null;int f,f > 0);
* @MODIFIES:flow[][];
* @EFFECTS:
* (isAdh(p,q) == false) ==> (/result == false);
* (isAdh(p,q) == true) ==> (/result == true)&&(flow[getPointId(p)][getPointId(q)] == flow[getPointId(p)][getPointId(q)]+f)&&(flow[getPointId(q)][getPointId(p)] == flow[getPointId(q)][getPointId(p)]+f);
*/
public boolean addFlow(Point p, Point q, int f) {
if(!isAdj(p, q)) {
return false;
}
synchronized(flow) {
flow[getPointId(p)][getPointId(q)] += f;
flow[getPointId(q)][getPointId(p)] += f;
}
return true;
}
3)
/**
* @MODIFIES: None
* @EFFECTS: /result == dist[getPointId(q)];
*/
public int getDistance(Point p, Point q) {
int[] dist = new int[map_size * map_size];
int[] minFlow = new int[map_size * map_size];
Point[] prev = new Point[map_size * map_size];
minPath(p, q, dist, minFlow, prev);
return dist[getPointId(q)];
}
本方法没有写清楚前置条件和副作用
该方法并不仅仅是获得p,q两点之间的距离,它为了得到这个距离寻找了两点之间的最短路径,因此会有一定的副作用。
修改方法如下所示:
1 /**
2 *REQUIRES:(Point p,q;p != null,q != null);
* @MODIFIES: minPath(p, q, dist, minFlow, prev);
3 * @EFFECTS: /result == dist[getPointId(q)];
4 */
5 public int getDistance(Point p, Point q) {
6 int[] dist = new int[map_size * map_size];
7 int[] minFlow = new int[map_size * map_size];
8 Point[] prev = new Point[map_size * map_size];
9 minPath(p, q, dist, minFlow, prev);
10 return dist[getPointId(q)];
11 }
4,功能BUG与规格BUG的聚焦分析
笔者的功能BUG与规格的BUG之间并没有太大的联系,可能原因在于笔者是在写完整个程序之后方才写的规格,而且由于部分方法所完成的功能太多,行数过长,因而导致JSF难以完成,因此这部分的JSF笔者并没有很好地去完善,测试者也并没有认真地去追究这些问题。
3,设计规格和撰写规格的基本思路和体会
笔者在完成这三次作业的时候,是先写完整个程序之后再去写的每个方法和类的规格,仔细分析,有好有坏,但是弊大于利。其好处就在于写程序的时候不需要考虑的规格的问题,因而能提高速度。但是缺点也很明显,由于没有事先设计好规格,没有仔细明确每个方法的要求,目的,以及副作用,在后期DEBUG的时候会造成巨大的困扰——不知道究竟是哪个类,哪个方法的问题。因此,痛定思痛过后,还是应该养成先确定好每个类的目标,即Overview,再确定为了完成这些目标所需要的方法,确定其REQUIRES,MODIFIES,EFFECTS,然后再依据自己的规格去实现自己的代码。我想这样的话能够减少不少DEBUG的时间,也能达到规格化训练的目的。更加重要的是,这样做可以更加方便测试者理解并且测试你的程序,利己利人,何乐而不为呢?
以上便是笔者对于这三次作业的拙见,OO这门课即将到达尾声,一路走来经历了太多,正如那句耳熟能详的话语:OO不易,和谐6系。为了完成作业经常熬夜通宵什么的确实付出了很多,但同样的是,我们收获的,也很多!