北京航空航天大学2019年OO课程第四次总结

北京航空航天大学2019年OO课程第四次总结

一、UML作业总结

1.1 第十三次作业

本次作业只需要完成一个简单的UML类图解析器,从UmlElement集合中识别出各个元素和元素之间的关系,再执行对应的查询指令即可。

本次作业的难点在于正确理解UML导出文件各个项代表的含义,包括继承与实现的表示,parentId的含义等等。

架构方面,类似邻接表的结构可以方便地实现各类查询,第十三次作业可以分为以下三个步骤:

  • 理解UmlElement各个子类和其属性的含义,理解各个查询是怎样对应到数据结构中的
  • 读取传入的参数,初始化对应的映射(如类到接口、类到类、类到关联对端、类到属性等等)
  • 设计并实现各个查询对应的算法

本次作业有很多获得一个递归集合的模式,因此作业中有很多递归函数。

1.2 第十四次作业

本次作业增加了检查和两幅图,对于新增的图比较简单,和之前基本一样。为了避免将功能集中在Interaction中,可以考虑将各个图隔离为不同的类,主类只需实例化这些类并调用其中对应的函数即可。

比较复杂的是有效性检查,针对三个检查,分别设计如下:

  • 对于命名重复:这个较为简单,对于一个类,其重名检测域为一个类下所有类的属性和类的对端的名字,这个我理解为两个类相关联,类的对端的名字一般是对应类在另一个类中成员变量的名字,所以不能和其它成员属性重名。
  • 对于不能有循环继承:首先要定义研究对象——一个有向图,节点是接口和类,边是继承关系(没有接口实现关系),研究问题就是检测图中的环(包括自环),这里我使用普通的塔尖算法(Tarjan算法)来检测环。因为对于有向图来说,一个强连通分量就是一组循环继承的对象。同时注意Tarjan算法不能检测自环,要额外判断。
  • 对于不能有重复继承:研究对象为一个有向图,节点是接口和类,边是继承关系和实现关系,研究问题是对于图中的任意两个节点,不能有两条不同的路径连接,如果有这样的节点对,则要把源节点输出。这里可以使用反向的做法,把有向图反过来后,对根节点(入度为0的节点)做搜索,如果搜索过程中一个点遍历邻点时该邻点已经被访问,则说明该领点有两条可达路径,其点和所有子孙节点都要输出。这样做可以一次搜索找出所有的重复继承节点,而不需遍历所有的点依次搜索整张图。

二、四个单元中架构设计及OO方法理解的演进

2.1 第一单元 表达式求导

本单元是第一次正式的编写面向对象程序,我第一次有了将各个实体在程序中抽象的想法。我开始能对自然语言中常见的关系进行程序描述和设计。例如求导、运算、复合等等。最终我以语法关系为基础,设计了一套完整的求导程序,并尽可能为更多的设计留出空间(最后我的程序仍可以很方便的支持更多指导书以外的功能)。

除了学习到面向对象的知识,我还更深入了解了Java的一些库和机制,例如BigInterger类和正则表达式类等等,在使用这些包的过程中也能学习到这些开发者的OO知识和设计思想——例如,如何设计一个支持无限大数字运算的类,这个类应该有哪些方法、属性,怎样设计才方便好用等等。其实我们做一个多项式求导体系,和这些库的想法是很像的。

总而言之,第一次作业正式为我打开了面向对象的大门,我开始能够不以过程式的思想设计程序了,还接触到工厂模式等等更系统化的设计方法,更让我认识到设计的重要性。

2.2 第二单元 多线程的电梯设计

本单元是第一次编写多线程程序。从本单元开始我在设计时会着重考虑建模的可扩展性和可延拓性。在第一次作业中我就考虑了后续可能出现的需求。虽然课程组的要求往往超乎想象,导致我不能完全一以贯之的使用一套代码来适应越来越多的需求,但是至少这种思路的出发点是好的。我的代码现在看来已经尽可能地符合开闭原则了。

为什么要编写多线程程序,在我看来,有以下几个原因:

  • 提高资源利用率:这是毋庸置疑的,改变操作顺序可以减少CPU的空闲的时间,避免无意义的等待
  • 简化程序设计:将具有不同职责和时序要求的工作抽象为线程可以大大简化程序难度,方便程序实现
  • 提高程序响应速度: 避免一个程序一直处理其他消息而不开启监听,使服务器等进程响应更快

同时,本次作业还让我理解了一些基本的设计,S.O.L.I.D.是指SRP(单一责任原则)、OCP(开放封闭原则)、LSP(里氏替换原则)、ISP(接口分离原则)和DIP(依赖倒置原则)。 第三次电梯作业中我的调度器履行了过多职责,从捎带到换乘,再到电梯选择和停机,这些工作全部在一个类里完成,不太符合SRP原则——应当分解调度器类或者让电梯承担更多的责任。同时我各个模块之间的依赖非常严重,高层模块和底层模块互相依赖导致代码改动的风险很高,很不符合DIP原则。

2.3 第三单元 JML建模语言

第三单元让我们第一次接触到了程序规格的概念。这对理解程序的模块性和规范性大有裨益。总的来说三次作业就是一个图结构慢慢演变的过程,其中包括图的属性和查询模式的演变。

这次作业我认为最“OO”的地方就是对图的抽象。无论是一个大的地铁系统中每条小的Path,还是各种要求不同的查询所需要的对象,他们本质上都是一个无向图,区别只是大小、结构、拓扑和具体意义。把图抽象为一个专门的类并在里面实现包括各种查询和加速的模块是非常好的一种做法。这次作业我深刻理解了抽象与复用。

当然,作业的主体,JML建模语言也让我大开眼界。这对于以后的团队开发工作很有帮助——如果人和人之间能通过简单的、统一的描述体系沟通,就能提高很多工作效率,省去不必要的阅读代码、解释歧义等等过程。(要是现实生活的人们也像JML这样简单直接就好了)

2.4 第四单元 UML

第四单元在OO层面上的帮助我认为并不是要编写的程序本身。这次布置的作业更像是为了理解UML而设计的——其实代码并没有什么难度与深度,只要正确理解UML还是很好实现的。用UML来描述复杂的代码与代码之间、代码内部的逻辑和时序关系我认为才是最精彩的。其实UML是一个引导设计思路、帮助理解设计内涵很重要的工具:它总结并提炼了类、继承、实现、关联等等面向对象程序中的元素,并用三张图清楚地表达它们的关系。

三、四个单元中测试理解与实践的演进

3.1 第一轮作业

本轮作业为了减少BUG、提高正确性,我从手动和自动两个角度进行调试。手动部分主要针对简单的分类和边界测试。分类测试中需要设计输入分类树,树的分叉点包括各个项的有无、省略形式输入等等。边界测试主要为难以想到的情况和极端情况,例如空输入、v符号、0次项等等。

自动测试主要使用python的自动化正则表达式生成器、subprocess指令和科学函数计算包,这样可以自动进行强度测试,测试点的深度和长度都可以自定义。虽然随机生成测试点质量可能不高,但是数量足够多时也可以提高 程序的可靠性。

这次作业是我按照自己的想法简单设计的测试模式,也是第一次针对自己的程序系统地测试。

3.2 第二轮作业

多线程的测试比普通程序要复杂和困难的多,BUG的发现和排除也是如此。本次作业我只要使用大量测试点和大量重复测试来发现BUG。本次作业我有幸(并不)发现了一个线程BUG,通过艰难的插桩分析我发现了这一BUG:在电梯运行时(例如1到2楼),电梯还未更新自己的所在楼层(如还在1楼),这时如果发生线程切换,调度器会错误的判断指令序列里缺少1-2楼的指令从而添上,在电梯执行完一次1-2楼后又会执行一次,导致ARRIVE-2输出了两次。

我深深感受到了多线程编程的不易和设计原则的重要性。线程之间永远不应该依照可能过期的数据做出行动,如果我认真履行各个编程模板,就不会出现类似问题。多线程的同步问题极难发现,也同样极难解决,在编程的过程中绝不能走先编程后DEBUG的老路不能先污染后治理),程序从设计伊始,就要认真依据happens-before原则以及check-then-act、read-modify-write等模板,梳理清楚线程的执行关系,设计好可靠不易出错的代码架构。

3.3 第三轮作业

一开始我本着“看规说话,合规即美”的思想,简单的根据JML实现最朴素的逻辑(算法都谈不上),错误的低估了课程组的要求。强测时很多点出现了TLE的问题,现在反思,主要有以下原因:

  • 没有充分认识到题目的现实背景:现实中大部分查询系统都是多查少改的结构,每次查询都重新计算有失一个计算机专业学生的素质(尤其是已经学过缓存等等概念了)
  • 没有认真结合数据结构的内容:数据结构是依附算法的一个很重要的程序设计内容,一味的使用简单粗暴的数组是初学者干的事情,程序不仅完成任务,更要完成好任务

如果依据题目要求进行压力测试,我就会发现该问题,这给我留下了深刻地教训。

3.4 第四轮作业

UML的问题主要在于对题目要求的理解和特殊情况的考虑。由于本次作业所有的元素都是统一为一个父类UmlElement,这其实让设计有很多空指针隐患——一个map可能被并不是keyset中的元素访问。这个隐患看似很简单实则很隐蔽,在我和我周围的同学中都有出现。这警醒我绝不应该随意使用可能出现空指针的内容,如map.get()等等。

这里要感谢白心宇同学的多人对拍器,这对测试提供了很多方便。

四、课程收获

  • 设计层面:我不再一拍脑袋就开始写代码,而是会认真思考应该怎样设计一个可靠高效的代码架构。先铺路再前进的习惯对于开发人员来说是很重要的。同时我也学会了什么是抽象、什么是好的抽象、怎样的抽象更具扩展性等等。
  • 算法层面:本课程我认真实现了很多以前只知道伪代码的算法,意识到了实现层面其实和思想层面还有很多区别。OO无疑提高了我的动手能力。
  • 代码层面:我知道了团队协作时可能遇到的问题和应有的解决方式。代码不知是写给自己看的——代码风格、代码规格、代码图在团队各个层次的沟通都是非常重要的。从此不论是代码细节、方法设计和模块设计我都有了和别人沟通的工具。
  • 应用层面:一个好的代码应该稳健而不出错。我认识到了测试的重要性,也通过课程和周围的同学学到了很多测试的方法和要点。

五、改进建议

  • 计算机学院的其他经典O系列(CO,OS)都有一个特点——循序渐进,勇攀高峰。每周作业的难度和深度都要大大高于前一周。作为同样经典的O系列课程OO,难度到电梯就戛然而止未免有些意犹未尽,后面的内容是否可以更有难度一些
  • 课上测试能否更加完善和体系化。过于开放的提问模式有时候会让学生回答错方向,没有答案和反馈也让学生无法及时矫正思想。
  • 每次作业能否都提醒学生以后可能的扩展模式。虽然培养学生写出可扩展的代码的能力很重要,但是一个问题可以发散的方向一般都很多。不告诉学生接下来作业的方向可能导致同学们为后续做出了很多兼容的设计,但课程的要求却根本不是之前考虑的方向,让同学们虽然有意写好代码减少重构却反而在一遍遍重写。

原文地址:https://www.cnblogs.com/parachutes/p/11072306.html