1.规格化设计的发展历程
看到这个问题,先是去原题目搜索,结果发现全是同学们自己写的博客或者是提问。便思考一下,觉得这个问题的另一种问法应该是软件工程的发展过程?果然,通过软件工程的发展历程自然可以看出其中的重要一部分,也就是规格化设计的发展历程。从第一次软件危机开始至今,大概有四个时期:
①软件危机
从60年代中期到70年代中期是计算机系统发展的第二个时期,在这一时期软件开始作为一种产品被广泛使用,出现了“软件作坊”专职应别人的需求写软件。这一软件开发的方法基本上仍然沿用早期的个体化软件开发方式,但软件的数量急剧膨胀,软件需求日趋复杂,维护的难度越来越大,开发成本令人吃惊地高,而失败的软件开发项目却屡见不鲜。“软件危机”就这样开始了!
“软件危机”使得人们开始对软件及其特性进行更深一步的研究,人们改变了早期对软件的不正确看法。早期那些被认为是优秀的程序常常很难被别人看懂,通篇充满了程序技巧。现在人们普遍认为优秀的程序除了功能正确,性能优良之外,还应该容易看懂、容易使用、容易修改和扩充。
1968年北大西洋公约组织的计算机科学家在联邦德国召开的国际学术会议上第一次提出了“软件危机”这个名词。 概括来说,软件危机包含两方面问题:一、如何开发软件,以满足不断增长,日趋复杂的需求;二、如何维护数量不断膨胀的软件产品。
②软件工程的提出
1968年秋季,NATO(北约)的科技委员会召集了近50名一流的编程人员、计算机科学家和工业界巨头,讨论和制定摆脱“软件危机”的对策。在那次会议上第一次提出了软件工程这个概念。
软件工程是一门研究如何用系统化、规范化、数量化等工程原则和方法去进行软件的开发和维护的学科。软件工程包括两方面内容:软件开发技术和软件项目管理。软件开发技术包括软件开发方法学、软件工具和软件工程环境。软件项目管理包括软件度量、项目估算、进度控制、人员组织、配置管理、项目计划等。
③传统软件工程
为迎接软件危机的挑战,人们进行了不懈的努力。这些努力大致上是沿着两个方向同时进行的。
一是从管理的角度,希望实现软件开发过程的工程化。这方面最为著名的成果就是提出了大家都很熟悉的“瀑布式”生命周期模型。它是在60年代末“软件危机”后出现的第一个生命周期模型。如下所示:
分析 → 设计 → 编码 → 测试 → 维护
后来,又有人针对该模型的不足,提出了快速原型法、螺旋模型、喷泉模型等对“瀑布式”生命周期模型进行补充。现在,它们在软件开发的实践中被广泛采用。
这方面的努力,还使人们认识到了文档的标准以及开发者之间、开发者与用户之间的交流方式的重要性。一些重要文档格式的标准被确定下来,包括变量、符号的命名规则以及原代码的规范式。
软件工程发展的第二个方向,侧重与对软件开发过程中分析、设计的方法的研究。这方面的重要成果就是在70年代风靡一时的结构化开发方法,即PO(面向过程的开发或结构化方法)以及结构化的分析、设计和相应的测试方法。
④现代软件工程
早期的软件开发仅考虑人的因素,传统的软件工程强调物性的规律,现代软件工程最根本的就是人跟物的关系,就是人和机器(工具、自动化)在不同层次的不断循环发展的关系。
面向对象的分析、设计方法(OOA和OOD)的出现使传统的开发方法发生了翻天覆地的变化。随之而来的是面向对象建模语言(以UML为代表)、软件复用、基于组件的软件开发等新的方法和领域。
为了更系统化、规范化地管理工程的开展,有了上述软件工程的发展思路作为积淀,规格化设计这一的解决方案理所应当地就被人们逐渐接受并加以重视了。
2.规格类BUG反思
JSF-BUG | repOK-BUG | Overview-Bug | |
第9次 | 1个: 没有写异常处理 | 本次作业未要求 | 本次作业未要求 |
第10次 | 2个: modified未写全/部分effects使用自然语言 | 无 | 无 |
第11次 | 无 | 无 | 无 |
关于规格Bug,大概是人品好,看我代码的同学都没有狠抓JSF吧,不过自认为写的也还算说得过去?除了第一次JSF没有写异常处理是没有意识到,比较清晰;剩下的错误具体在哪里我也不好说,不过后来两次作业对规格又重新改改加加,所以到最后一次应该还是挺全的,而且没有再使用自然语言,所以没有规格上的Bug。
3.前置条件和后置条件的改进
1. @requires:None; ==> @requires: 0 <= x_now < 80, 0 <= y_now < 80;
2. @requires:None; ==> @requires: request.Object == Request;
3. @requires:None; ==> @requires: this.lightmap != null;
4. @requires:None; ==> @requires: Time >= 0;
5. @requires:None; ==> @requires: requestList != null;
1. @effects: \exist one Request for taxi.cantake() ==> complete this request; ==>
@effects: \exist one Request for taxi.cantake() ==> complete this request; !(\exist one Request for taxi.cantake()) ==> hang around;
2. @effects: Find a random way base on the Map; ==>
@effects:
(dir == Element.Up && (Map.map[x_now - 1][y_now] == 3 || Map.map[x_now - 1][y_now] == 2)) ==> \result == true;
(dir == Element.Down && (Map.map[x_now][y_now] == 3 || Map.map[x_now][y_now] == 2)) ==> \result == true;
(dir == Element.Left && (Map.map[x_now][y_now - 1] == 3 || Map.map[x_now][y_now - 1] == 1)) ==> \result == true;
(dir == Element.Right && (Map.map[x_now][y_now] == 3 || Map.map[x_now][y_now] == 1)) ==> \result == true;
3. @effects: \result == isArrived(); ==>
@effects:
(status == Element.Picking || status == Element.Serving) && x_now == x_destination && y_now == y_destination) ==> \result == true;
(else == true)==> \result == false;
4. @effects: list.add(request) == true; ==>
@effects: list.size == \old(list).size + 1 && list.contains(request);
5. @effects:list.remove(request) == true; ==>
@effects:list.size == \old(list).size – 1 && !list.contains(request);
4.功能 bug 与规格 bug 在方法上的聚类关系
个人来说,这三次出现的功能BUG均是来源于事前不知道要这么实现,或者是由于重点放在了JSF上,某个小功能忘了实现了导致的,所以功能Bug与JSF基本无聚类关系。
而且无聚类关系的另一个原因是,本次出租车作业的第一次实现为上一个周期,那个时候并没有JSF等设计规格的要求,所以当我们第一次接触JSF之后,都是根据之前的代码实现来完善JSF,顺序完全是反过来的,所以硬说可能有聚类关系的话也无法反驳,不过很可能是其他同学先在功能上有bug导致规格也是错的。
5.设计规格和撰写规格的基本思路和体会
首先肯定,规格设计是必须的,而且大有裨益,这点所有人都认同。
但是,以OO课的授课流程说的话,即先完成绝大部分代码,再要求实现规格设计,这样的设计思路与规格设计的初衷大相径庭,导致对于我们完成9、10、11次作业基本无益处可言。
设计规格的基本思路,应该是在实现代码之前,先进行规格设计。但是究竟是需要在多久之前进行规格设计呢?是一口气把程序全部设计完,还是设计一部分实现一部分,这是我在作业中无法体会出来的。个人的理解是,涉及一部分,实现一部分吧?因为我感觉设计出来的东西可能并不一定容易实现,可能设计的思路就是有问题的,所以需要通过实现去归正设计思路。
如果能够严格按照上述思路实现的话,一定会是很容易阅读的代码,而且具有良好的可扩展性,不过这就要求比单纯实现更多的时间投入了,而且对应到各次作业上来,容易出现每周工作量相差过多的情况。
所以呢,对于政教来说,上帝的归上帝,凯撒的归凯撒。对于课程来说,就是作业的归作业,学习的归学习。