OO学习总结与体会

前言

  经过了对于面向对象程序设计的一个月的学习,我初尝了JAVA以及面向对象程序的魅力。经历了三次难度逐渐加大的课后编程作业,我对于工程化面向对象编程以及调试有了深刻的认识与颇多感想。我写下本篇文章以总结分析这一个月的课后作业的完成情况。

 


 

 

作业总结与分析

多项式加减运算

 

(1)题目简述:

  实现一元多项式的加减运算

 

(2)程序设计:

 

  Class设计:

  • PolyCul类:进行多项式输入,多项式计算调度,多项式输出。

 

  • Poly类:存储多项式信息,进行多项式计算。

 

  • Item类:存储单个项的信息,进行项的计算。

 

  程序规模:

  

 

  程序类图与类演示图:

  

  

(3)程序分析:

  程序的结构化程度(ev)、设计复杂度(lv)和判定结构复杂度(v)情况较好,只有负责输入字符串合法性识别以及多项式组插入的方法的复杂度较高。其他方法的复杂度都处于正常的范围内。输入字符串合法性识别方法复杂度高的原因主要是最初设计时对于指导书的输入需求的理解不够充分,导致了一开始设计的识别算法无法全面地处理输入字符串,使得编程完成后要补充新的判断分支,导致方法代码冗余。多项式组插入方法复杂度高的原因是每次调用方法时需要遍历已有的有序多项式组、判断同次多项式、寻找新多项式在其中的位置扩展多项式组并插入新多项式。

  量化分析结果如下:

  

 

 

(4)出现的问题:

  Bug”-0”字符处理不当。

  在最初的设计中并未考虑输入”-0”的需求,处理这一需求的代码是在程序完成后补充的。由于补充代码时仅考虑了处理”-0”,所以直接使用字符串替换将”-0”替换成了”+0”,导致了程序能够识别以”-0”为前缀的数字,如”-001”。而负数输入是非法的。

  从整个程序来看,该问题导致了程序内会存在非法的数值,虽然就指数计算来看负数引入并不会导致数学上的错误。但由于程序内其他的使用该变量的方法默认处理正数,而不存在处理负数的措施,对于输入判断模块方法的正确性有一定的依赖性。出现入口的错误会使得程序存在崩溃的隐患。

  相关代码:

private void str_input() {
        Scanner s = new Scanner(System.in);
        this.str = s.nextLine().replaceAll(" +", ""); //去除空格。
        this.str = str.replace("-0","+0");//去除-0.
        s.close();
    }

 

简单电梯调度

(1)题目简述:

  实现简单(FAFS)电梯调度系统。

(2)程序设计:

  Class设计:

  Sheduler类:完成请求的输入及处理,根据请求队列调度电梯、输出电梯运动情况。

  ReqQueue类:实现对请求队列内请求的处理,实现重复请求的识别与剔除。

  Request类:格式化存储请求信息。

  Lift类:根据调度类改变电梯的位置、运动情况以及电梯内各按钮的状态(未被使用)。

  Floor类:记录每层楼按钮的状态(未被使用)。

 

  程序规模:

  

 

 

  程序类图与类演示图:

  

 

  

 

(3)程序分析:

  程序的结构化程度(ev)、设计复杂度(lv)和判定结构复杂度(v)情况一般。获取下一执行请求的方法的复杂度最高,此外请求队列遍历操作的相关方法的复杂度也较高。在获取下一执行请求的方法中需要对下一请求的同质性进行判断,导致方法中存在大量的判断语句的嵌套,产生了较多的判断路径。此外,判断语句处于循环中,使得复杂度提高。出现这一情况主要是由于最初的设计不够完善,在编程的过程中不断添加路径,最后使判断逻辑关系比较混乱,也就是模块判断复杂度的提高。其他方法主要由于非结构化代码较多。

  量化分析部分结果如下:

  

 

  

 

(4)出现的问题:

  Bug:输入数字范围限制错误。

  指导书要求输入数字范围为四字节,而程序中限制的是int型变量能表示的非负数。这主要是对于指导书的要求理解错误导致的。该错误对于程序正常运行的影响较小。

  Bug:”&”与”&&”运用错误。

  这一错误的表现形式是当输入的请求存在同质请求时程序会崩溃。经过分析最后确定了是以下代码导致程序崩溃:

if(next_req.get_req_kind().equals("FR") & next_req.get_FR_operator().equals(running_req.get_FR_operator())) {
  next_req.set_fesibility(
false); System.out.println("#第" + next_req.get_input_line() + "个请求与第" + running_req.get_input_line() + "个请求行为相同!"); continue; }

  该段代码应当更改为:

if(next_req.get_req_kind().equals("FR") && next_req.get_FR_operator().equals(running_req.get_FR_operator())) {
    next_req.set_fesibility(false);
    System.out.println("#第" + next_req.get_input_line() + "个请求与第" + running_req.get_input_line() + "个请求行为相同!");
    continue;
}

  “&&”与”&”的区别在于后者会对所有的表达式进行运算,最后取逻辑与得到结果;而前者会顺序对表达式进行运算,若遇到了一个表达式为false则不会对后面的表达式进行运算。而在上面代码中,如第一个表达式为false时,第二个表达式中的方法调用是非法的,若运行就会使程序崩溃。”|”和”||”的原理相同。

  这段代码于程序的其他部分的代码相关性不高,只依赖于Request类的内部结构。电梯请求与楼层请求在格式上的不同使得该段代码需要考虑二者的特性与共性来产生判断分支。出现这一问题主要由于对于其特性的考虑不够全面。

 

优化电梯调度

(1)题目简述:

  实现具有捎带功能的电梯调度系统。

(2)程序设计:

  Class设计:

  Sheduler类:实现简单调度功能,为前一作业的代码。

  AlsSheduler类:完成请求的输入及处理,根据请求队列调度电梯、输出电梯运动情况。

  ReqQueue类:实现对请求队列的基本处理。

  ExeQueue类:实现对执行请求队列的基本处理。

  QueueHandleInterface接口:规范化请求队列处理的方法。

  Request类:格式化存储请求信息。

  Floor类:记录每层楼按钮的状态(未被使用)。

  Lift类:根据调度类改变电梯的位置、运动情况以及电梯内各按钮的状态(未被使用)。

  LiftInterface类:规范电梯类的接口。

 

  程序规模:

  

 

 

  程序类图与类演示图:

  

 

  

 

(3)程序分析:

  程序的结构化程度(ev)、设计复杂度(lv)和判定结构复杂度(v)平均情况一般,但有部分的复杂度相当高。其中主要是调度运行方法以及输入处理的一系列方法。

  在调度运行方法中存在一个循环来模拟时间的流动,并且在每一时间帧中有大量条件判断构建状态机来实现电梯的调度。复杂的逻辑判断导致这一方法的结构十分冗杂,加大了代码的规模,大大提高了复杂度。出现这一问题的主要原因是设计的调度算法未进行优化,较为粗糙,在功能上未对代码进行归类,按照面向过程的思路堆砌判断条件。

  在输入处理方面,相较前两次作业,本次作业的表现并不好。经过后期的分析,在这一部分出现了伪面向对象式的代码。在相关的一系列方法中调用深度过深,实质上是将一个方法分割为多个私有方法碎片,相互耦合的现象较严重。此外,在这次作业对上次作业进行继承后发现输入处理部分的代码应当分离成单独的类,而不应当归属于调度类。

  量化分析部分结果如下:

  

  

 

(4)出现的问题:

  在功能方面尚未检测出问题,但在程序结构的设计方面上暴露出了许多的缺陷。主要体现在:部分类的功能冗余、调度算法需要优化、方法设计不妥当。


 

心得体会

  • 捕捉字里行间的需求

  在这几次的编程作业中,我最先感受到的是充分理解需求的重要性。每一次作业都有一个篇幅较大较为详细的指导书。指导书对于程序需求与功能的描述或多或少有些模糊或者易产生歧义的地方,这对于程序的架构带来了一定的困难。但相较于现实工作中遇到的客户的需求描述,指导书中的描述已经具体了许多。而我们所需要的就是从描述需求的自然语言之中提取出关键的信息,在零散的信息中寻找逻辑上的练习,并建立模型完成程序的架构

  在第一次作业中,需求的捕捉并不算太困难,主要在输入合法性的判断上。而在后两次作业中,需求很明显就复杂了许多。由于指导书的篇幅较长,为避免遗忘,我在通读指导书的同时将发现的关键信息按条记录下来。在多次阅读指导书后,我再将提取到的信息按照其所属的功能部分进行分类,分析实现每个需求所需要的代码或算法。

  此外,在设计时还应当考虑到文中未提及的那些“言外之意”,使程序能够应对指导书中未提及的需求与输入。这需要设计者对需求进行适当的外延与扩展,扩大程序的包容性、兼容性。这在现实的程序设计中具有相当重要的意义。

  • 结构设计模块化

  面向对象编程最重要也是最困难的部分就是设计程序的结构:根据程序需要的操作和数据,将程序分离封装成不同的功能模块,每个模块的聚合度要高,各个模块之间的交集要小。在设计程序时应当自顶向下地思考。若要实现总的需求,程序要具有哪些功能,需要拥有那些数据,需要对数据进行什么操作?根据这些问题以及从指导书捕捉到的信息,我们就能够大致地列出一个清单,再根据其中的聚合关系就能画出程序的结构(类)图。之后在优化的时候还可对设计进行微调,以获得分离度更好地类设计。

  在这三次作业中,我在程序结构设计上表现一般。在编写的程序中已经有了聚合度比较好一些模块,但在将程序建模成类图后同推荐的设计比较还是能发现许多的不足。我在输入识别处理模块的设计上就有失妥当:在这几次作业中我都将这一模块并入了调度模块。这一设计并不符合模块化设计的理念。输入处理操作应当封装成一个独立的模块专门进行相关的操作。而我将其并入调度类,就会使调度类过于繁杂,过于扩大其功能,提高了复杂度,降低了可维护性。从第三次作业继承第二次作业时就能明显感觉到修改代码的工作量较大。

  • 简化方法规模

  在方法的设计上应当尽量精简每个方法的代码量,使每个方法能高效地完成单一的一个任务。应当严格地控制每个方法功能、判断分支、调用深度和代码规模对于降低程序的调试与维护难度有着不错的效果。在我编写的这几个程序中,方法精简的部分最容易进行修改,也最不容易出现问题;出现问题的地方主要是判断分支较多的方法。

  要控制方法规模主要需要对程序中的各个操作进行准确的提炼,获取其中核心的原子操作形成相应的方法。分析我程序中规模较大方法可以发现,这些方法都包含了至少两种操作,一个方法的功能过于繁杂。这样就导致我维护、修改代码时遇到了较多困难,并且容易产生不易发现的问题。

  • 系统化测试代码

  由于输入情况的多样性,简单的功能性测试是远不够的。按照老师课堂的要求,我在测试阶段构建了错误分支树。顶层按照正常功能与异常处理进行分叉,逐渐向下延伸产生错误分支,并为每个分支构造了一至两个测试输入。最后用构造的测试集进行代码测试。通过这样一个比较系统化的测试方式,我比较迅速准确地锁定了存在问题的代码。得益于结构化的代码结构,我也能够较快地优化程序。

 

posted @ 2018-04-04 10:44  Cookize  阅读(190)  评论(0编辑  收藏  举报