一、总结介绍规格化设计的大致发展历史和为什么得到了人们的重视

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

规格化设计,又称契约式设计,最早由Bertrand Meyer于1986年提出,出自于《面向对象软件构造》。其基础是形式验证、形式规格和Hoare logic。

2、规格化设计得到重视的原因

在大型软件工程开发中,协同工作能力的重要性日益凸显。“对于一个大型系统来说,光保证它的各组成成分的质量是不够的。而最有价值的是确保在任何两个组成部分的交接处设计明晰的彼此义务和权利规范,即所谓契约。”(《面向对象软件构造》)

Bertrand Meyer曾在访谈中提到:“对于软件和软件设计者来说,为了保证各方面的正确性和健壮性,他们就必须懂得通讯的准确约束规范。在这个地方,我们就将商业中的契约概念应用到软件中。……商业中,我们会通过契约——对我们期望的彼此义务和权利的准确表述——的方式彼此协调。对于软件来说,我们撰写客户和服务例程时,都必须严格遵循同样的契约表述。”

《契约式设计的收益》一文中将规格化设计的优点概括为以下5方面:获得更优秀的设计、得到更出色的文档、支持复用、提高可靠性、帮助调试。

二、表格分析规格bug

由于没有被发现规格bug,此环节略过。

三、分析自己规格bug的产生原因

由于没有被发现规格bug,此环节略过。

四、分别列举5个前置条件和5个后置条件的不好写法,并给出改进写法

1、前置条件的不好写法

1-1:方法将形如”(x,y)”或”x,y”的字符串转化成一个坐标点对象,原写法:

(\exist String in; in == str1 + "," + str2);

更好的写法是:

in == String.format(“(%d,%d)”, x, y) || in == String.format(“%d,%d”, x, y) ;

0 <= x < 80; 0 <= y < 80;

明确指出格式具体是个什么样子。

1-2:普通出租车的构造方法。原写法:

int i, SafeFile fp, TaxiGUI g;

更好的写法:

\exist int i; 0 <= i < 100;

\exist SafeFile fp; fp != null;

\exist TaxiGUI g; g != null;

方法实际上没有对输入进行检查,因此前置条件必须有更严格的约束。

1-3:普通出租车的run()方法。原写法:

(\exist int status; 0<=status<=3);

更好的写法:

none;

不变式的内容是默认成立的,没必要写在这里。

1-4:这个方法写出来完全没有意义:

/**

* @REQUIRES:(\exist int i; 0<=i<6400);

* @MODIFIES:none;

* @EFFECTS:(the point that i represents is in this city)==>(\result == true);

* (the point that i represents is not in this city)==>(\result == true);

*/

public boolean incity(int i) {

int x = i / 80;

int y = i % 80;

return (x >= 0 && x < 80 && y >= 0 && y < 80);

}

如果认真检查,会发现前置条件满足时,即使什么都不做,后置条件也满足。这部分代码一定是神志不清时写出来的。

2、后置条件的不好写法

2-1:方法传入一个点坐标,判断当前对象代表的坐标是否位于传入坐标周围的4*4区域内,即是否需要发送给该出租车。原写法:

(\result == (src.within()))

更好的写法:

\result == (src.x – 2 <= x <= src.x + 2 && src.y – 2 <= y <= src.y + 2)

这种简单的布尔类型判断方法在写后置条件时难免照抄代码原文。从工程角度,这样的方法不写规格也无大碍,但是作为作业训练,还是要写。

2-2:重写了点坐标类的toString方法。原写法:

(\exist String re; re == "(x,y)"); (\result == re);

更好的写法:

(\result == ret)==>(ret == “(” + x + “,” + y + “)”);

对后置条件的理解出现偏差。

2-3:针对控制台输入的格式检查。原写法:

(\exists byte op);

* (in.matches(req))==>(op == 1);

* (in.matches(load))==>(op == 2);

* (in.matches(road))==>(op == 3);

* (in == end)==>(op == 4);

* else (op == -1);

* (\result == op);

更好的写法:

(\result == 0)==>(in == “END”);

(\result == 1)==>(in.matches(req));

(\result == 2)==>(in.matches(load));

(\result == 3)==>(in.matches(road));

(\result == -1)==>otherwise;

首先,JSF应该避免使用中间变量。原来的写法暴露了实现细节。

其次,更推荐将\result作为蕴含式的前件。

最后,所谓“更好的写法”应该避免使用otherwise。这里保留是考虑程序的可扩展性,即新增控制台指令。然而,这就要求程序员必须头脑清醒,明确otherwise何时被触发。

2-4:向队列加入一条请求。原写法:

(queue.length == \old(queue)+1);

(queue[length-1] == req);

更好的写法:

(queue.size == \old(queue).size + 1) && (queue.contains(req));

原文第二行属于暴露细节的写法。出现相似问题的还有从队列取出请求的方法。

2-5:SafeFile类的构造方法。原写法:

(!fname.valid())==>(throw Exception);

(fname.valid)==>(create the file and its writer);

更好的写法:

(fname.valid)==>(this.file == File(fname));

( ! fname.valid)==>(exceptional_behavior(IOException));

原文写法没有遵循JSF语法。

五、按照作业分析被报的功能bug与规格bug在方法上的聚集关系

第九次作业:道路临时开关、道路流量

没有被发现任何bug

第十次作业:红绿灯系统

BUG10-1:出租车crash

奇怪,这个BUG无法复现。

第十一次作业:可追踪出租车

BUG11-1:出租车回头问题,流量处理不当

相关方法:public void run_edge()

方法规格没被报告bug,但是规格是拿自然语言表述的。可见自己在一个方法里面加入了过多功能,代码行数达到50行。

BUG11-2:远距离出租车接客

相关方法:

public int dispatch(Request r)

public boolean receive(Request r)

public boolean within(_Point src)

方法dispatch的规格经自己检查与实际方法代码有出入,可见自己没有按照规格编写方法。自己对于“什么样的出租车可以接单”这个问题在规格设计时就没想清楚,写出来的代码自然是有漏洞的。

BUG11-3:出租车寻找最短路径时数组越界

相关方法:public void search(int dst)

规格写得太简略,而且逻辑有问题。下一个点必须在城市地图内。这一点不但写规格时没写,而且写代码时也没注意,不出问题才怪。

BUG11-4:不同时的相同位置请求冲突。

相关方法:public void search(int dst)

不完全是这个方法的问题。没有认真读下发的gui.java代码,使用里面的函数时没有仔细分析其实现逻辑。

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

1、针对方法规格

方法规格主要包含REQUIRES, MODIFIES, EFFECTS三部分。

REQUIRES即前置条件,是调用者传入参数等方面所必须满足的约定。如果需要传对象或容器,一般都会要求对象不为空,容器有元素。此外,我还会思考数据具有的实际含义。运行在一个城市的出租车系统,传入的坐标点显然不能超出城市范围,出租车的位置和状态必须有效,等等。不做格式检查的输入提取,也要在前置条件写清楚输入格式。

MODIFIES写方法会改变的数据。这里的数据应该理解为用户(或使用你代码的程序员)关心的数据,因此局部变量的改变大可不必写在这里。类属性的改变也没有必要全写,只需要填写呈现给用户看的那部分数据的改变。

EFFECTS即后置条件,是方法执行完毕返回结果、类属性等数据所满足的约定。初学者(包括我)往往容易将部分算法逻辑展示在里面,但这是不可取的行为。实际上,EFFECTS向方法调用者屏蔽了部分细节,进行了一层抽象。调用者和用户只关心方法执行后的效果、返回值满足的条件,并不关心值是如何得到的。另外,EFFECTS内容为实现算法也往往暗示着作者是在方法完成之后再补充的规格。根据Head First Java的讲法,正确的顺序首先是写伪代码和测试代码,即首先想清楚方法的执行效果,然后才是具体实现(写正常代码)。本末倒置往往是这类问题的深层原因。

初学者写面向对象程序,容易出现所谓“面条代码”的问题,将所有业务逻辑放在一个方法里面。我自己提炼出一个规律:方法的代码超过了50行,EFFECTS部分往往也超过5行。如果在写规格时感到写起来非常困难(不知如何概括),或是写了太多(超过5行),往往方法的功能还能进行分拆。因此,先写EFFECTS的另一个好处是倒逼程序员对业务逻辑进行拆分整合,避免“面条代码”。

2、针对类规格

类规格的核心是编写OVERVIEW,对于子类可以编写INHERIT,不变式也可以写在INVARIANT部分。

OVERVIEW阐明类的目标或整体轮廓。OO程序的类大体可以分为以方法为核心和以数据为核心两种。前者的OVERVIEW侧重类的目标:提供什么方法、如何处理数据。后者的OVERVIEW侧重整体轮廓:管理什么数据。对于后者,应该避免我互测环节拿到的将所有属性列举出来的错误。类需要对用户进行一层抽象,给用户看到的应该是他关心的、抽象过后的数据。把所有细节展示给用户实则是不安全的行为,代码可维护性也不好。就拿普通出租车而言,用户关心的顶多是出租车编号、当前状态、当前位置。其它一些标记抢单之类的变量没必要让用户知道和看到。

INVARIANT即表示不变式,是判定对象是否有效的布尔表达式,对应boolean repOK()方法。在实现其它方法前写出表示不变式和repOK方法,可以倒逼设计者思考类所构造的对象的实际意义,在编写方法过程中绷紧一根弦,确保方法调用前后对象的有效性。写不变式时应抓住程序的实际应用场景,以指导书规定和尝试辅助判断。

3、杂感

首先谈谈自己出现的问题:越来越懒得做自我测试。用来处理测试文件输入的那个类竟然一组样例也没跑过,最后肯定是崩。尽管自己可以找冯如杯比赛忙、操作系统难写之类的理由,但是测试者从来不会心慈手软。这也不是将来走上工作岗位应该有的态度。自己编写测试用例时,也不能对自己太好。为了测试多线程的正确性,两条请求之间间隔的时间不能太长,否则很多潜在的问题查不出来。(例如BUG11-2)总之,写完代码之后要多进行自我测试,写出来的程序错误才更少。

其次谈谈普遍出现的问题:要求用JSF写规格后,很多人没有把它当成训练面向对象思维的途径,而当成了增加扣分收入的法宝。他们对拿到的互测代码大量地报告规格BUG,其中很多BUG未必真实。这实际上曲解了老师的本意,浪费了提升自己能力的机会,得不偿失。一方面,希望这些学生端正学习态度,切实锻炼自身能力,而不是追求一个外在的分数。另一方面,希望教学组增加对乱扣分行为的检查和惩处力度。

总之,编写规格的质量不要仅依赖测试者的评价,要多结合课件、文档说明,找自己的不足并改进。

OO博客作业3:第9-11周作业总结的更多相关文章

  1. OO博客总结——OO落下帷幕

    OO博客总结--OO落下帷幕 凡此过往,皆为序章. 不知不觉OO课程即将落下帷幕,一路坎坎坷坷磕磕绊绊,可算是要结束了,心里终于松了一口气,也有小小的不甘和遗憾.凡此过往,皆为序章.特殊的线上OO课程 ...

  2. OO博客作业-《JML之卷》

    OO第三单元小结 一.JML语言理论基础以及应用工具链情况梳理 一句话来说,JML就是用于对JAVA程序设计逻辑的预先约定的一种语言,以便正确严格高效地完成程序以及展开测试,这在不能容忍细微错误的工程 ...

  3. OO博客作业1:第1-3周作业总结

    (1)基于度量来分析自己的程序结构 注:UML图中每个划分了的圆角矩形代表一个类或接口,箭头可代表创建.访问数据等行为.类的图形内部分为3个部分,从上到下依次是类的名称.类包含的实例变量(属性).类实 ...

  4. 第二次oo博客作业--多线程电梯

    这次的系列作业是写一个电梯调度,主要目的是让我们熟悉多线程. 第一次作业是一个傻瓜电梯的调度问题,要求也很简单,即每次接一个人就行了.我只用了两个线程,一个是输入线程,一个是电梯线程,输入线程负责从标 ...

  5. OO博客作业2:第5-7周作业总结

    (1)从多线程的协同和同步控制方面,分析和总结自己三次作业来的设计策略及其变化. 第5次作业:多线程电梯 基本照搬了课件上“生产者-消费者”模型的设计策略,将InputHandler设计为生产者线程, ...

  6. 小菜鸡儿的第三次OO博客

    规格化设计历史 规格化设计的历史目前网上的资料并不多,百度谷歌必应也表示无能为力...... 在这里结合现实情况讲一讲自己对程序规格化的理解,首先代码规格化对代码的影响是间接的,或许它不能让你代码里面 ...

  7. 接着继续(OO博客第四弹)

    .测试与JSF正确性论证 测试和JSF正确性论证是对一个程序进行检验的两种方式.测试是来的最直接的,输入合法的输入给出正确的提示,输入非法的输入给出错误信息反馈,直接就能很容易的了解程序的运行情况.但 ...

  8. OO博客作业4:第13-14周作业总结

    一.论述测试与正确性论证的效果差异,比较其优缺点 测试是设计若干组测试用例,运行程序并检验其是否完成预期功能.测试是一种直接发现BUG的方法,可以准确断定什么样的BUG会发生,并通过辅助调试进一步确定 ...

  9. 第四次oo博客作业

    (1)本单元是撰写UML数据分析器,架构大致如下,在指导书要求的函数外,对于UmlClass类,Umlinterface类,以及状态机,顺序图这四个类重现构造一个类,这个类里有他们所需要的全部信息,另 ...

随机推荐

  1. c/c++ 模板与STL小例子系列<三> traits

    c/c++ 模板与STL小例子系列 traits 对这个概念,还是处于懵逼的状态,初步体会就是,为了解决类型之间的转换问题. 从一个类型为A的指针,转化到类型为B的指针,中间需要用void*来作为中介 ...

  2. 数据执行保护呈灰色无法开启 用命令BCEDIT无效 请问怎么解决?

    1.默认的管理员账号是关闭开不了的. 2.你重新创建个管理员账户. 3.然后登陆这个账号. 4.然后打开cmd 输入 bcdedit /set nx optin alwaysoff 这行指令.然后回车 ...

  3. LeetCode算法题-Convert a Number to Hexadecimal(Java实现)

    这是悦乐书的第219次更新,第231篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第86题(顺位题号是405).给定一个整数,写一个算法将其转换为十六进制.对于负整数,使 ...

  4. 挂载KVM Guest操作系统磁盘

    使用虚拟机时, 发现想要修改虚拟机中的文件非常麻烦, 需要启动虚拟机, 然后再登录进去修改. 对于已经关闭的虚拟机, 为了修改一个文件而启动, 非常耽误时间. 对于一个无法启动的虚拟机(比如启动文件损 ...

  5. February 22nd, 2018 Week 8th Thursday

    Confine yourself to the present. 着眼当下. The morning wind spreads its fresh smell, we should get up an ...

  6. VMware安装系统时"无法创建新虚拟机: 不具备执行此操作的权限"的解决方案

    作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 在VMware中安装操作系统时,遇到以下这种情况 问题主要出在虚拟机文件的位置选择上,不应该选在VMwa ...

  7. Linux for Python教程01

    目录 1. Linux和操作系统 1.1. 什么是操作系统 1.2. 现有操作系统 1.3. Linux用户目录 1.4. Linux文件权限 2. Linux命令 2.1 文件管理相关 (1).ls ...

  8. 面试总结——Java篇

    前言:前期对Java基础的相关知识点进行了总结,具体参看:Java基础和面试知识点.近期由于笔者正在换工作(ing),因此下面将笔者在面试过程中或笔者朋友面试过程中反馈的题目进行总结,相信弄清楚下面题 ...

  9. Scrapy 框架 安装 五大核心组件 settings 配置 管道存储

    scrapy 框架的使用 博客: https://www.cnblogs.com/bobo-zhang/p/10561617.html 安装: pip install wheel 下载 Twisted ...

  10. SQLite也可能出现死锁

    提到锁就不得不说到死锁的问题,而SQLite也可能出现死锁.下面举个例子:连接1:BEGIN (UNLOCKED)连接1:SELECT ... (SHARED)连接1:INSERT ... (RESE ...