一、规格化设计概述
规格化设计主要为程序设计提供分解、精化和抽象的手段。在撰写代码规格的过程中,需要对其组成部件进行抽象。 否则, 如果每一个组成部件都以自己特有的行为方式运转, 不同的组成部件交织在一起, 那么规格的撰写可能同软件实现一样复杂。
过程抽象和数据抽象是两种有用的抽象方法。过程抽象把一组输入映射到一组输出上, 包含了数据抽象, 并通过数据抽象的行为来定义。数据抽象提供了一组数据以及施加在这组数据上的操作。尽管这些操作也可视为一种过程抽象, 但是把数据抽象当作一个整体更能体现出它和现实世界实体的对应关系。
在软件生命周期的开始阶段采用规格化设计往往要等到以后的阶段中才能显示出它的促进作用。例如, 规格说明划分了软件模块使用者和实现者的责任, 避免了编码阶段可能出现的混乱。所以, 撰写规格时既要考虑到特定开发阶段的特点, 又要联系整个软件生命周期, 统筹规划。
规格化设计是一个自然而然的过程。当人们发现仅靠语言和文档的沟通已经无法满足大规模代码的合作开发需要的时候,规格便应运而生。规格化设计有利于做到设计与实现分离,虽然前期撰写规格花费了一定的时间,但换来了后续代码实现的顺畅,因此得到了人们的重视。
二、作业中的规格Bug
作业次数 | Bug树分支名称 | 所在类/方法 | 所在行数 |
9 | 无 | 无 | 无 |
10 | Overview是否明确抽象对象 | class Taxi | 17 |
11 | Requires不完整 | Map.open | 242 |
11 | Effects内容为实现算法 | Taxi.goTakingOrder | 293 |
三、规格Bug产生的原因
第10次作业中的Overview未明确抽象对象,是因为自己当时对Overview作用的理解并不深刻。后来我参考了一下操作系统实验课的各种函数的Overview,大致明白了Overview在规格化设计中所起到的作用:为该类的使用者营造一个不需要仔细阅读代码就能够理解该类用途和大致使用方式的环境,明确类的职责和其管理并负责的数据;之后,按照Overview去实现类的各种属性和方法,确保该类不会越过自己的职责范围。
第11次作业的Requires不完整是我没有完整考虑到方法里对方法参数的所有要求。在该方法中,我利用传入的两个参数去访问一个数组,如果传入的参数不合适,就会发生数组越界异常。这说明我的REQUIRES限制范围太宽,应加以限制,或在代码中对上述的特殊情况加以处理。
第11次作业的Effects内容为实现算法是因为我的某些方法做的事情太多,更改了许多属性,又调用了许多其他方法,还夹杂了睡眠,导致该方法的耦合度极高,牵一发而动全身,无法用简短的话语概括方法的行为。对此我认为,应该将这种过于复杂的方法加以肢解,使其成为一个个小的方法,这样可以有效减少代码之间的耦合度。
四、前置规格和后置规格的现状与改进
1、前置规格(改进后的前置规格以REQUIRES_NEW在图中标明)
2、后置规格(改进后的后置规格以EFFECTS_NEW在图中标明)
五、功能Bug与规格Bug在方法上的聚集关系
作业次数 | Bug树分支名称 | 所在类/方法 | 功能Bug |
9 | 无 | 无 | 无 |
10 | Overview是否明确抽象对象 | class Taxi | 无 |
11 | Requires不完整 | Map.open | 无 |
11 | Effects内容为实现算法 | Taxi.goTakingOrder | 无 |
由上表可见,功能Bug和规格Bug的聚集关系并不大。一个可能的原因是,功能Bug的测试较为困难,但许多规格Bug是显而易见的,因此测试者偏向于更多地去报规格Bug,而只能在边角处挑一些功能Bug;这些边角功能所在的方法都比较简短,通常不存在规格Bug。这也是我认为目前课程体系互测制度中需要改进的地方:规格Bug的扣分限制过少(或者说性价比太高)且基本无法申诉,使测试者们倾向于扣规格分,而忽视程序在主要功能上的Bug。这可能会导致一些致命的程序设计问题被掩盖,也一定程度上降低了课程的训练效果(无论是对被测者而言还是测试者而言)。
六、思路与体会
使用规格化设计的最佳时机是程序已经有了大致思路、但还没开始落实到代码的时候。通过提前设计好各个类和类中各个方法的规格,我们可以在无需实现的情况下,对整个系统做出一个良好的设计,从而降低后续开发的难度。在这个阶段,规格化设计还可以更好地帮助我们发现自己思路上的问题——例如,如果某一个方法的JSF写不下去了或不知道该如何下手,多半就是没考虑好该方法在整个系统里所起到的作用。从这个角度上来讲,撰写规格虽然花费了一定的精力,但其带来的价值却远超撰写规格的时间开销。
即使是先实现了代码、再补充规格,为方法撰写规格依然能够使我们重新审视自己的设计。如果某一个方法过于庞杂,撰写规格时难以下手,那么这个方法多半耦合性太强,可能需要重构。事后撰写规格,实际上是倒逼我们回顾整个系统,从好的方法(即可以清晰地用规格描述的方法)中得到经验,从坏的方法(即难以撰写规格的方法)中得到教训,这对于我们今后的软件开发生涯有很大的帮助。
然而,目前课程的制度安排上仍然存在一些不足,例如,规格要求太过死板(后置条件是否一定要用布尔表达式?有些时候,自然语言比布尔表达式更能表达出方法的执行效果,也更容易理解,毕竟规格写出来是给人看的),扣分制度有待改善(测试者在报告规格Bug的时候没有约束,可能导致恶意扣分)、各种Bug的权重尚需调整(边角Bug和主要功能上的Bug是否应该具有相同的权重?地图文件没有过滤制表符,和一辆出租车接了两个单,是否应该扣除相同的分数?笔误带来的规格错误和压根牛头不对马嘴的规格错误,这两类问题是否应该采用同一种扣分标准?),这些都是在历次互测中体现出来的问题,也是课程体系中实际存在了许多年、但仍然没有做出有效改进的地方。
作为一名北航计算机学院的学生,我当然是希望面向对象这门课程越来越好,让更多的人不是去吐槽它,而是真正感受到面向对象带来的好处。希望课程组在之后的几次作业中,能够对互测规则做出一些针对性的改进,不要让同学们编程的热情被永无止境的规格扣分上的争辩所消磨殆尽。