OO学习体会与阶段总结(设计与实现)

前言

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


作业规格错误汇总

  • 规格错误详细信息:
编号类型所在类方法名称代码行数详细
1前置条件不规范InputHandlerparseOrderReq5未使用形式语言
2前置条件不规范InputHandlerparseRoadChangeReq5未使用形式语言
3前置条件不规范InputHandlerparseSearchTaxiReq4未使用形式语言
4前置条件不规范InputHandlerparseSearchStateReq7未使用形式语言
5后置条件为实现算法LoadFileUnitsetFlow23未使用形式语言表示调用者看到的变化
6后置条件为实现算法LoadFileUnitsetTaxi34未使用形式语言表示调用者看到的变化
7后置条件为实现算法LoadFileUnitsetReq31未使用形式语言表示调用者看到的变化
8后置条件为实现算法MapsetTaxi1未使用形式语言表示调用者看到的变化
9后置条件为实现算法ReqHandlermarkServableTaxi4未使用形式语言表示调用者看到的变化
10后置条件为实现算法ReqHandlerassignTaxi18未使用形式语言表示调用者看到的变化
11后置条件为实现算法ReqHandlerrun12未使用形式语言表示调用者看到的变化
12后置条件逻辑错误TaxiActionrun85,91,931后置条件未描述方法所有的影响
  • 数据汇总:
类型总计平均代码行数最大代码行数
前置条件不规范457
后置条件为实现算法618.534
后置条件逻辑错误189.793
总计1133.693

规格错误分析

第九次作业

  在第九次作业中,根据需求需要加入道路开关功能以及增加用于初始化系统的文件读取指令,并且为所有方法补充过程规格。在这次作业中由于绝大部分代码都是来自上一次作业,许多方法在实现前仅考虑了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. 用于验证有该类生成的对象是否满足该类的不变式。

转载于:https://www.cnblogs.com/Cookize/p/9095427.html

本书由著名编程理论专家所著,是美国麻省理工学院电子工程与计算机科学系的编程实践课程教材。书中讨论了怎样构建具有高可靠性、易于维护和快速修改的软件的开发方法,强调了软件的模块化开发思想,用丰富的实例告诉读者怎样进行模块化并合理地组织各种模块以构成大型软件系统的过程。该书的前8章首次提出编程领域中的一些十分有用的抽象概念,如过程抽象、迭代抽象以及最重要的数据抽象等。此外,本书通过大量的例子,用非形式化的规范来详细定义这些数据抽象,描述模块所需完成的任务,并定义了模块所需的性能边界条件。该书的后7章主要讲述了怎样利用抽象构建大型软件,主要侧重于软件工程的内容,基于类型层次结构提出对于数据抽象的调试、测试、需求分析、自顶向下和迭代的开发过程,还简要介绍了设计模式的概念。 这是一本传授思想的书籍,能使读者透过现象看到本质,从而掌握编写程序的关键。本书非常适合作为软件学院的教材,在低年级即可培养学生对于事物的抽象能力。此外,本书也非常适合软件开发人员参考。 第1章 概述 1 1.1 分解和抽象 1 1.2 抽象 2 1.3 本书其他要旨 8 练习 8 第2章 理解Java中的对象 9 2.1 程序结构 9 2.2 包 10 2.3 对象和变量 11 2.4 类型检查 14 2.5 分派 18 2.6 类型 18 2.7 数据流输入/输出 20 2.8 Java应用程序 20 练习 22 第3章 过程抽象 24 3.1 抽象的好处 24 3.2 规格 25 3.3 过程抽象的规格 26 3.4 实现过程 29 3.5 设计过程抽象 31 3.6 小结 34 练习 35 第4章 异常 36 4.1 规格 37 4.2 Java异常机制 38 4.3 异常编程 42 4.4 设计问题 43 4.5 防御编程 46 4.6 小结 47 练习 47 第5章 数据抽象 48 5.1 数据抽象的规格 49 5.2 使用数据抽象 53 5.3 实现数据抽象 54 5.4 附加方法 59 5.5 用于理解实现的一些帮助 63 5.6 数据抽象实现的属性 69 5.7 推理数据抽象 71 5.8 设计问题 74 5.9 局部性和可更改性 76 5.10 小结 77 练习 77 第6章 迭代抽象 79 6.1 Java中的迭代 81 6.2 迭代器规格 82 6.3 使用迭代器 83 6.4 实现迭代器 85 6.5 发生器的表示式不变量和抽象函数 87 6.6 有序列表 87 6.7 设计问题 91 6.8 小结 92 练习 92 第7章 类型层次 94 7.1 赋值与分派 95 7.2 定义一个类型层次 97 7.3 用Java定义层次 97 7.4 一个简单的例子 98 7.5 异常类型 103 7.6 抽象类 103 7.7 接口 106 7.8 复合实现 107 7.9 子类型的含义 112 7.10 类型层次的讨论 117 7.11 小结 118 练习 119 第8章 多态抽象 121 8.1 多态数据抽象 121 8.2 使用多态数据抽象 123 8.3 重新访问相等性 124 8.4 附加方法 125 8.5 更多灵活性 127 8.6 多态过程 130 8.7 小结 130 练习 131 第9章 规格 133 9.1 规格和规格满足集 133 9.2 规格的某些标准 133 9.3 为什么要有规格 138 9.4 小结 140 练习 140 第10章 测试与调试 141 10.1 测试 142 10.2 测试过程 147 10.3 测试迭代器 147 10.4 测试数据抽象 148 10.5 测试多态抽象 150 10.6 测试一个类型层次 150 10.7 单元和综合测试 152 10.8 测试工具 153 10.9 调试 155 10.10 防御性编程 159 10.11 小结 160 练习 161 第11章 需求分析 163 11.1 软件生命周期 163 11.2 需求分析总结 165 11.3 股票跟踪系统 168 11.4 小结 171 练习 172 第12章 需求规格 173 12.1 数据模型 173 12.2 需求规格 180 12.3 股票跟踪系统的需求规格 183 12.4 搜索引擎的需求规格 187 12.5 小结 190 练习 191 第13章 设计 193 13.1 设计过程纵览 193 13.2 设计笔记本 195 13.3 交互式程序的结构 199 13.4 开始设计 202 13.5 对方法的讨论 208 13.6 继续进行设计 209 13.7 查询抽象 210 13.8 WordTable抽象 214 13.9 最后加工 216 13.10 FP和UI间的交互 216 13.11 模块依赖图表vs数据模型 218 13.12 回顾及讨论 219 13.13 自顶向下的设计 222 13.14 小结 222 练习 223 第14章 从设计实现 224 14.1 评估一个设计 224 14.2 整理程序开发过程 232 14.3 小结 236 练习 237 第15章 设计模式 238 15.1 隐藏对象创建 239 15.2 聪明的对策 242 15.3 桥接器模 248 15.4 过程也必须是对象 249 15.5 复合 252 15.6 间接的力量 258 15.7 发布/订阅 260 15.8 小结 263 练习 263 术语表 264
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值