前言

  在最近的一个月的课程中,笔者对于规格化编程进行了深入的学习。运用面向对象抽象思想对编写的程序进行过程抽象、异常处理、数据抽象、类的层次规格与迭代等等规格设计,使得程序结构化程度提高,具有更好的可维护性和复用性。本文通过分析并总结近三次作业规格设计情况,分享我在规格化程序设计上的见解与体会。


作业规格错误汇总

  • 规格错误详细信息:
编号 类型 所在类 方法名称 代码行数 详细
1 前置条件不规范 InputHandler parseOrderReq 5 未使用形式语言
2 前置条件不规范 InputHandler parseRoadChangeReq 5 未使用形式语言
3 前置条件不规范 InputHandler parseSearchTaxiReq 4 未使用形式语言
4 前置条件不规范 InputHandler parseSearchStateReq 7 未使用形式语言
5 后置条件为实现算法 LoadFileUnit setFlow 23 未使用形式语言表示调用者看到的变化
6 后置条件为实现算法 LoadFileUnit setTaxi 34 未使用形式语言表示调用者看到的变化
7 后置条件为实现算法 LoadFileUnit setReq 31 未使用形式语言表示调用者看到的变化
8 后置条件为实现算法 Map setTaxi 1 未使用形式语言表示调用者看到的变化
9 后置条件为实现算法 ReqHandler markServableTaxi 4 未使用形式语言表示调用者看到的变化
10 后置条件为实现算法 ReqHandler assignTaxi 18 未使用形式语言表示调用者看到的变化
11 后置条件为实现算法 ReqHandler run 12 未使用形式语言表示调用者看到的变化
12 后置条件逻辑错误 TaxiAction run 85,91,93[1] 后置条件未描述方法所有的影响
  • 数据汇总:
类型 总计 平均代码行数 最大代码行数
前置条件不规范 4 5 7
后置条件为实现算法 6 18.5 34
后置条件逻辑错误 1 89.7 93
总计 11 33.6 93

规格错误分析

第九次作业

  在第九次作业中,根据需求需要加入道路开关功能以及增加用于初始化系统的文件读取指令,并且为所有方法补充过程规格。在这次作业中由于绝大部分代码都是来自上一次作业,许多方法在实现前仅考虑了SOLID原则[2]而未考虑规格设计,每个方法的规格都是在实现后补充上去的(这在顺序上是倒置的)。有一小部分的方法在功能与产生的作用比较繁杂,难以用形式语言进行描述,最后只能用自然语言作为替换。但对于这些方法使用自然语言也不太好说清楚其后置条件,最后导致了后置条件为算法是实现的过程的错误。此外,在使用形式语言描述的时候,对于一些方法的处理边界的描述上存在一些缺陷。

  在完成这次作业前,笔者学习了使用异常抛出来区分正常情况与异常情况。但由于在此之前设计代码时未考虑使用反射机制来处理异常情况,而使用形如null等变量来当作异常情况的返回。如果要加入这一功能需要重构大部分方法中的讨论情况。由于时间关系,在这次作业中只在新加入的部分使用了异常处理机制。因而在旧代码的部分的规格设计中对于异常情况没有显示表示,而是返回一些无意义的数据(从调用者的角度来看是不友好的)。

  在测试别人的程序时发现的规格问题基本与我的相似,基本是方法的冗杂致使规格的后置条件为实现过程或者后置条件有遗漏。

第十次作业

  在这次作业中,根据需求仅需增加路口的红绿灯功能,工作量较少(虽然计算时间和流量比较困难),因此笔者将之前写的代码根据过程抽象原则进行了优化。对功能较多的方法进行了重构,将其功能进行了分割,分散至不同类或者方法当中。在此之后程序中绝大多数的方法都能够用较为简洁的形式语言描述。因此在这次作业的测试阶段,对方未报告规格错误。

  此外,需求要求补充每个类的类规格、抽象函数以及对象有效性验证方法[3]。由于最初设计时着重考虑了SOLID原则,程序中每个类的功能是比较明确的。因而增加类规格的困难不大,在互测阶段也没被报告错误。

第十一次作业

  在这次作业中,根据需求需要对出租车种类进行扩充,增加一种能满足新需求(不赘述)的出租车,以及实现迭代输出服务记录的功能。在继承前一种出租车的同时要着重考虑里氏替换原则,实现有效的子类设计,并且还需附有有效性论证。具体体现在子类方法与父类方法的前置条件与后置条件的空间上。在我的程序中,子类重写父类方法过程时仅对后置条件进行了扩充,使其能满足里氏替换原则。因而在互测阶段未被报告错误。在我测试的程序中,设计者将所有父类的方法复制到类子类中,并对细节进行了改动。虽然这种做法有很多赘余,但通过论证也未发现问题。


规格优化

  • 后置条件为实现过程。

 优化前:

public synchronized void setTaxi(int index, int locaX, int locaY, TaxiState state)
/**
* @REQUIRES: 0<=index<100;0<=locaX<=79;0<=locaY<=79;
* @MODIFIES: gui
* @EFFECTS: (在GUI中将编号为index的出租车设置为state状态,移至(locaX,locaY)位置);
* @THREAD_REQUIRES:\locked(this);
*/

 优化后:

public synchronized void setTaxi(int index, int locaX, int locaY, TaxiState state)
/**
* @REQUIRES: (0<=index<100);(0<=locaX<=79);(0<=locaY<=79);
* @MODIFIES: gui
* @EFFECTS: (gui.taxi[index].locaX==locaX)&&(gui.taxi[index].locaY==locaY)&&(gui.taxi[index].state==state);
* @THREAD_REQUIRES: \locked(this);
*/
  • 前置条件可以扩展,后置条件可以对异常进行处理。

 优化前:

public RoadChangeReq parseRoadChangeReq(String input, long time){
/**
* @REQUIRES: input符合道路更改请求格式;
* @EFFECTS: \result==解析后的道路更改请求对象;
*/
Matcher roadReqMatcher = this.roadReqPattern.matcher(input);
if(roadReqMatcher.matches()){
return new RoadChangeReq(roadReqMatcher.group(1),
roadReqMatcher.group(2),
roadReqMatcher.group(3),
roadReqMatcher.group(4),
roadReqMatcher.group(5),
time);
}
else
return null;
}

 优化后:

public RoadChangeReq parseRoadChangeReq(String input, long time) throws Exception{
/**
* @REQUIRES: input!=null;
* @EFFECTS: (!roadReqPattern.match(input))==>(\result==解析后的道路更改请求对象);
* (!roadReqPattern.match(input))==>exception_behavior(Exception);
*/
Matcher roadReqMatcher = this.roadReqPattern.matcher(input);
if(roadReqMatcher.matches()){
return new RoadChangeReq(roadReqMatcher.group(1),
roadReqMatcher.group(2),
roadReqMatcher.group(3),
roadReqMatcher.group(4),
roadReqMatcher.group(5),
time);
}
throw new Exception("不符合道路更改请求格式");// 可以自定义异常。
}
  • 后置条件应为具体现象。

 优化前:

public void setTaxi(int taxiNo, TaxiState state, int credit, int locaX, int locaY){
/**
* @REQUIRES: 0<=taxiNo<100;credit>=0;0<=locaX<79;0<=locaY<79;
* @MODIFIES: this.set,gui
* @EFFECTS: 更新出租车位置。
*/
this.set[taxiNo].setTaxiInfo(state, credit, locaX, locaY);
}

 优化后:

public void setTaxi(int taxiNo, TaxiState state, int credit, int locaX, int locaY){
/**
* @REQUIRES: 0<=taxiNo<100;credit>=0;0<=locaX<79;0<=locaY<79;
* @MODIFIES: this.set,gui
* @EFFECTS: this.set[taxiNo].locaX==locaX;
* this.set[taxiNo].locaY==locaY;
* this.set[taxiNo].credit==credit;
* this.set[taxiNo].state==state;
* gui.taxi[taxiNo].locaX==locaX;
* gui.taxi[taxiNo].locaY==locaY;
*/
this.set[taxiNo].setTaxiInfo(state, credit, locaX, locaY);
}
  • 后置条件应为具体现象。

 优化前:

class InputListener implements Runnable {

	private ReqBuffer reqBuffer;
private TaxiInfoSet taxis;
private Map map; ...... @Override
public void run() {
/**
* @MODIFIES: this.map,this.reqBuffer,this.taxis,System.out
* @EFFECTS: (监控到乘客请求)==>(解析并将请求对象加入reqBuffer);
* (监控到道路更改请求)==>(解析并更改map中的道路);
* (监控到出租车搜索请求)==>(解析并System.out相应信息);
* (监控到出租车状态搜索请求)==>(解析并System.out相应信息);
*/

 优化后:

class InputListener implements Runnable {

	private ReqBuffer reqBuffer;
private TaxiInfoSet taxis;
private Map map;
/**
(省略正则表达式)
public static String REQREGEX;
public static String ROADREQREGEX;
public static String SEARCHTAXIREGEX;
public static String SEARCHSTATEREGEX;
*/
...... @Override
public void run() {
/**
* @MODIFIES: this.map,this.reqBuffer,System.out
* @EFFECTS: (System.in.match(REQREGEX))
* ==>(this.reqBuffer.contains(new Request(System.in)));
* (System.in.match(ROADREQREGEX))
* ==>(this.map.road.status==System.in.status);
* (System.in.match(SEARCHTAXIREGEX))
* ==>(System.out==this.taxis[System.in.taxiNo].info);
* (System.in.match(SEARCHSTATEREGEX))
* ==>(\all Taxi taxi;
* this.taxis.contains(taxi)
* &&taxi.state==System.in.taxiState;
* System.out.contains(taxi.info));
*/
  • 后置条件可以写为形式语言。

 优化前:

private void markServableTaxi(ReqWin reqWin)
/**
* @ REQUIRES: reqWin!=null;
* @ MODIFIES: rewWin;
* @ EFFECTS: 将符合抢单条件的出租车加入至reqWin的taxiSet中;
*/

 优化后:

private void markServableTaxi(ReqWin reqWin)
/**
* @ REQUIRES: reqWin!=null;
* @ MODIFIES: rewWin;
* @ EFFECTS: (\all TaxiInfo taxi;
* this.taxiSet.contains(taxi)
* &&taxi.isin(reqWin.district);
* reqWin.taxiSet.contains(taxi));
*/

作业功能错误汇总

第九次作业

  在这次作业中笔者被报告了以下三个错误:

  • 错误现象:当读取的文件中有多条乘客请求时程序会死锁。
  • 错误分析:在程序的设计中,初始化乘客请求是通过系统启动前就将文件中的请求解析并加入至请求缓存区中;在此之后调度器将缓存区中的请求取出再按调度策略分配服务的出租车。在最初的程序中,这部分的代码如下:
public class SysMain {
public static void main(String[] argv) {
......
LoadFileUnit loadFileUnit = new LoadFileUnit(); // 构造文件读取器
loadFileUnit.checkLoad(); // 检查指令合法性
......
ReqBuffer reqBuffer = new ReqBuffer(); // 构造请求缓存区
......
......
// 构造调度器
ReqHandler reqHandler = new ReqHandler(reqBuffer, taxiSet, map);
loadFileUnit.setReq(reqBuffer, map); // 逐个加入请求
new Thread(reqHandler).start(); // 启动调度线程
......
}

在此之中请求缓存区的容量为1。进而很明显当请求数大于1时由于此时调度线程还未启动,没有线程能够消耗缓存区的请求,导致了主线程一直等待缓存区为空。而这种情况不会发生,最后导致了死锁。

  • 错误改正:将调度线程的启动时机提前即可。

----------分割线----------

  • 错误现象:多辆出租车同时计算路径时有一定几率报出地图不连通,程序退出并结束程序。
  • 错误分析:在出租车计算路径是需要使用公用的矩阵存储广度遍历的结果,程序中共享资源的互斥存在缺陷导致计算进入了错误的步骤,得出了地图不连通的结果。笔者在原程序中通过对课程组提供的路径计算方法增加synchronized关键词加锁实现资源的互斥,但由于课程组提供的GUI包中大多数类的封装问题,导致该互斥操作不完善,最终导致该错误发生(虽然我测了好久也没出现这种情况)。
  • 错误改正:构造Map类(线程安全类)包装地图操作。

----------分割线----------

  • 错误现象:未排除相同请求。
  • 错误分析:相关代码如下:
// 请求窗口集合类的插入方法
public void append(Object req) {
Request newReq = (Request)req;
System.out.println(newReq);
for(int i= 0; i < this.length; i++) {
int[] loca = this.list[i].getReq().getLoca();
int[] aim = this.list[i].getReq().getAim();
long time = this.list[i].getReq().getMakeTime();
if(newReq.getLoca()[0] == loca[0]
&& newReq.getLoca()[1] == loca[1]
&& newReq.getAim()[0] == aim[0]
&& newReq.getAim()[1] == aim[1]
&& newReq.getMakeTime() == time) {
System.out.println("相同请求 : " + newReq);
// 标记 //
}
}
// 在末尾插入。
this.list = Arrays.copyOf(this.list, ++this.length);
this.list[this.length - 1] = new ReqWin(newReq); }

  在对集合中已有的请求进行遍历并判断为相同请求后为中止方法,使相同请求也能被插入至请求队列。

  • 错误更改:在标记处增加return;

----------分割线----------

  在第十与十一次作业时笔者未被报告程序错误。

  在测试别人程序的过程中,笔者测试的这三位同学的程序都无法正常地运行多条乘客请求。在第九次作业时,被测试的程序在运行多条请求时会出现请求间信息错位的现象,初步认定是请求共享部分的互斥工作存在缺陷。在第十次作业时,被测试的程序在运行3至6条程序时会异常地停止运行,无任何反应,但不会崩溃退出。在阅读代码后大致由于路径计算时耗时过多,以致多辆出租车同时计算长距离请求时延迟较大。当运行超过7条请求时,程序会有崩溃的可能,几率随请求数的增加而增加,崩溃的原因是堆栈溢出。在第十一次作业的时候,被测试的程序在运行多条请求时会出现严重的延迟现象。由于这位同学的代码的可读性实在太差,笔者没能找出导致错误的代码。


思考与体会

  在经过这三次作业之后,我对于规格化程序设计的重要性有了亲身体会。在编写面向对象程序前就应当对程序中的数据和处理过程进行抽象,定义出对数据的操作以及数据管理的方式,归纳出程序中需要进行的行为,限定各个操作的边界以及用户可见的内容。再结合上个阶段学的面向对象程序设计原则,对于程序中的类设计做出相应限定,使得编写的类具有更好的延展性、可维护性与鲁棒性。

  经过总结,对于程序中方法的设计过程大致分为以下几步:

1. 明确方法存在的意义。

2. 明确方法结果正确的判定条件。

3. 明确方法对调用者提出的条件,以保证结果正确。

4. 明确方法执行期间修改的数据。

5. 按照要求的方式整理前置条件、修改数据、后置条件。

  经过了短暂的一个月的实践,笔者虽对这些思想有了不少的体会,但还有待更多的实践深化。


  1. 分别为三次作业的代码行数。 ↩︎

  2. Solid原则分别指:单一职责原则、 开闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。 ↩︎

  3. 用于验证有该类生成的对象是否满足该类的不变式。 ↩︎

OO学习体会与阶段总结(设计与实现)的更多相关文章

  1. OO学习体会与阶段总结(多线程程序)

    前言 在最近一个月的面向对象编程学习中,我们进入了编写多线程程序的阶段.线程的创建.调度和信息传递,共享对象的处理,线程安全类的编写,各种有关于线程的操作在一定程度上增加了近三次作业的复杂度与难度,带 ...

  2. OO学习体会与阶段总结(测试与论证)

    前言   随着期末的到来,对于面向对象程序设计课程的学习也迎来了尾声.在最后一个月的从课程中,笔者对于面向对象程序规格实现层面的单元测试.正确性论证以及使用UML图描述程序的设计进行了深入的学习.通过 ...

  3. Java程序猿学习当中各个阶段的建议

    回答阿里社招面试如何准备,顺便谈谈对于Java程序猿学习当中各个阶段的建议   引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的 ...

  4. 深度学习在推断阶段(inference)的硬件实现方法概述

    推断(Inference),就是深度学习把从训练中学习到的能力应用到工作中去. 精心调整权值之后的神经网络基本上就是个笨重.巨大的数据库.为了充分利用训练的结果,完成现实社会的任务,我们需要的是一个能 ...

  5. 【OO学习】OO第四单元作业总结及OO课程总结

    [OO学习]OO第四单元作业总结及OO课程总结 第四单元作业架构设计 第十三次作业 第十四次作业 总结 这两次作业架构思路上是一样的. 通过将需要使用的UmlElement,封装成Element的子类 ...

  6. 【OO学习】OO第三单元作业总结

    [OO学习]OO第三单元作业总结 第三单元,我们学习了JML语言,用来进行形式化设计.本单元包括三次作业,通过给定的JML来实行了一个对路径的管理系统,最后完成了一个地铁系统,来管理不同的线路,求得关 ...

  7. Alink漫谈(十二) :在线学习算法FTRL 之 整体设计

    Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 目录 Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 0x00 摘要 0x01概念 1.1 逻辑回归 1.1.1 推导过程 ...

  8. Python学习的十个阶段,学完神功大成,对应一下看看你自己在哪个阶段

    大家好,我是白云. 今天给大家整理了Python学习的十个阶段内容,看看你现在正处于哪个阶段,想学习的朋友也可以根据这个阶段规划学习. 阶段一:Python基础[ 初入江湖] Linux基础 目标: ...

  9. OO第三单元——JML规格化设计

    OO第三单元--JML规格化设计 JML语言的理论基础以及应用工具链情况 理论基础 JML是对JAVA程序进行规格化设计的一种表示语言,是一种行为接口规格语言.JML整合了Java和JAVAdoc,并 ...

随机推荐

  1. [NOIp2009] $Hankson$の趣味题

    \(23333\)这是最近第二份在时间上吊打\(yjk\)的代码--啊哈哈哈哈哈哈哈 嗯,其实遇到这种单纯的\(gcd \ \ or \ \ lcm\)的题,我们都可以用一种比较简单的方法分析:唯一分 ...

  2. [图解tensorflow源码] Simple Placer节点布放算法

  3. Oracle透明网关访问SQLServer数据库

    针对oracle数据库不同实例之间的数据访问,我们可以直接通过dblink访问,如果oracle数据库想访问mysql/sqlserver等数据库的数据,我们可以通过配置oracle透明网关实现异构数 ...

  4. React-Native使用极光进行消息推送

    推送作为APP几乎必备的功能,不论是什么产品都免不了需要消息推送功能,一般做RN开发的可能都是前端出身(比如我),关于android ios 都不是很懂

  5. Django:settings中关于static静态文件目录的设置

    django项目settings中关于静态资源存放位置的设置 主要涉及以下3项:STATIC_URL.STATICFILES_DIR和STATIC_ROOT 1.STATIC_URL 这项是必须配置的 ...

  6. Delphi泛型动态数组的扩展--转贴

    此文章转载于http://www.raysoftware.cn/?p=278&tdsourcetag=s_pcqq_aiomsg的博客 从Delphi支持泛型的第一天起就有了一种新的动态数组类 ...

  7. c++ 指针访问数组

    用指针访问一维数组 用指针访问二维数组 用指针访问三维数组 一. 用指针访问一维数组 //代码 ; ]={,}; int *p=&a; //int *p=&a[0]; printf(& ...

  8. CF 1114 C. Trailing Loves (or L'oeufs?)

    C. Trailing Loves (or L'oeufs?) 链接 题意: 问n!化成b进制后,末尾的0的个数. 分析: 考虑十进制的时候怎么求的,类比一下. 十进制转化b进制的过程中是不断mod ...

  9. 用docsify快速构建文档,并用GitHub Pages展示

    什么是docsify 无需构建,写完 markdown 直接发布成文档,写说明文档的极佳选择. 快速上手 安装 npm i docsify-cli -g docsify init docs 创建项目 ...

  10. 【总结】详细说说Html.ActionLink的用法

    Html.ActionLink概述 在MVC的Rasor视图引擎中,微软采用一种全新的方式来表示从前的超链接方式,它代替了从前的繁杂的超链接标签,让代码看起来更加简洁,通过浏览器依然会解析成传统的a标 ...