BUAA-OO 第四单元 & 课程总结

BUAA-OO 第四单元 & 课程总结

前言

不知不觉,OO课程的学习即将结束,回想每次作业,有面对指导书毫无思绪,也有理清架构后的豁然开朗;有debug到濒临崩溃,也有评测AC后的成就感,在这个艰难的过程中自己也学到了很多,希望能就最后一次博客作业好好总结。

一、第四单元作业架构

(一)树形层次结构

考虑到类图不能很好地表现出抽象层次结构,而且较为繁琐,这里用一个树形层次图来表示这单元中的架构设计。

架构设计主要遵循了UML图中元素的树形层次结构。顶层MyImplementation管理所有UML元模型,而每个元素则管理着对应的下层元素,如UMLClass管理其对应的UMLAttributeUMLOperation。这种树形层次化结构的好处在于,一方面可以根据元素和元素之间的逻辑关系建模,另一方面,各层的分工较为明确,层与层之间的耦合度大大降低,还可以逐层向上或向下找到所需元素的信息。

当然,为了便于层次化结构的建模和使用,在作业中我没有完全按照UML元素的管理层次结构建模,如UMLGeneralizationparentId无限制,但是在作业中由子类的UMLClassUMLInterface管理,从而在类图中又建立起了一个继承关系树,这样无论是搜索继承深度还是检查循环继承、重复继承等都是很有好处的。

(二)面向对象编程

在这单元第一次作业中我深刻感受到面向对象思想带来的极大便利。由于需要对各个元素类添加自己的方法和属性成员,又不能直接在官方包中修改,因此我用MyUMLXXX来将UMLXXX进行封装。一开始我的想法是只对部分必要的类进行封装,但是这样就会出现MyUMLXXX类和其他的UMLXXX类的交织混合,这对于对象的管理和调用是非常恶心的一件事(必须加入无数instanceof判断,以及各种类型的容器)。

于是我转而将所有UMLXXX都封装为MyUMLXXX,尤其是父类MyUMLElement封装UMLElement,然后再在MyUMLXXX的类中建立上述层次化结构。上述封装过程看似繁琐,但是由于面向对象的思想和方法,总的工作量还是比较小的,比如在MyUmlElement类中实现getName()获取封装的UMLElementname后,其他子类MyUMLXXX则会自动继承。甚至是equals()hashcode()函数等都能根据多态的性质自动绑定为对象实际类型的函数,大大提高了代码的复用性。在将所有元素都进行封装后,就可以使用同一的容器进行管理,在对象调用时的逻辑也大大简化。

(缺点可能是类太多了hhh)

此外,我将根据输入生成MyUMLXXX元素封装成了MyUmlCreator,根据上述层次依次循环读取相应的元素(一开始creator的功能是全部都塞到MyImplementation,直到第二次作业被CheckStyle制裁。。。

第三次作业涉及到众多规则的检验,我实现了MyChecker类对各个规则进行检查。其中一些规则是在建模的过程中检查的,如R001(name不为空),R002(attribute、associationEnd不重复)等,此时MyChecker的作用主要是对违背规则的异常情况进行记录。另一些则是最后建完模型后进行检查,如R003、R004的循环继承和重复继承,此时MyChecker的作用主要是调用相应元素的check()函数来进行模型的检查,并记录非法情况。

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

(一)架构设计

还记得第一堂OO课,老师就提出了层次化架构,在此之后,几乎每次作业都是以层次化架构为基础完成的。

(1)第一单元——神奇的表达式因子

第一单元我在课上听着层次化架构似懂非懂,一做作业果然不懂()

在第一单元中,我尝试着建立表达式的层次化结构——表达式、项、因子。但是表达式因子这种神奇的东西,既是表达式,又是因子,当时使我很困扰。随着递归下降方法的学习和使用,我逐渐建立起了整个表达式树的结构,但是这时候的架构思想还是比较不成熟的,一方面思维中没有层次化模型,仅仅是代码上的层次化,另一方面,建立起了层次化结构后,并不知道怎么去应用这个结构。第一单元应用层次化结构的重点在于递归,而自己在一开始没有意识到递归能够很好地在层次化架构中解决表达式因子这个难题,因此第一周的拆括号十分艰难。

(2)第二单元——逐层分派的请求

我在第二单元才算是真正意义上的将思维的层次化结构和代码的层次化结构结合起来了

在第二单元中,从第一次作业开始,我就尝试着先建立整体结构,再考虑具体细节实现,因为如果一开始就考虑细节,很可能整个结构都是错的,此时重构在所难免。在第一次作业中,我建立了看似比较繁琐的层次化结构(虽然第一次作业每栋楼就一台电梯),但是这种层次化结构能够很好地适应后面两次作业,后面作业的结构基本上没有太大变化,没有使本就艰难的第二单元雪上加霜。

(3)第三单元和第四单元——层次化架构的熟练运用

为什么将这两个难兄难弟放一起呢?因为这两个单元的层次化架构实在是不太烧脑(相比前两个单元)

第三单元的层次化架构基本上由JML规格给出了,而且涉及到的层次较少(也就三四层,实在不能和第二单元七八层的相比),因此整体的层次化架构还是很容易建立的。第四单元的层次化架构也可以由UML图的一些基本定义推出(当然,老师上课也重点讲了),因此第四单元的层次化结构也不难,就是元素有点多 😃

虽然这两个单元的层次化架构简单,但是我认为可能是经过了前两个单元的锻炼(zhe mo),自己对层次化架构有了比较深的体会,应用能力也提高了一些,因此在第三和第四单元中建立和运用层次化结构能够比较熟练。

(二)OO方法的理解

(0)Pre——和OO相遇的美好起点

在Pre的训练中,我初步接触了OO的方法,其中影响最深刻的是将Adventurer和Equipment都视为价值体(实现Valuable接口),然后就能够进行统一管理的方法。其中的一些继承、多态也使我开始对OO有了一个基本的认识。但是,Pre中的任务主要还是在指导书的提示下完成的。

(1)第一单元——从0到0.5

第一单元我以为会跟Pre差不多,刚好让我运用上从Pre中获得的新鲜知识,没想到一开始就被第一单元来了当头一棒。

第一单元我的设计可以说非常不OO,如表达式的化简、表达式的计算、函数的替换等等,都是在某几个关键类中写了一大坨方法,很有点面向对象中面向过程的意思。这也导致了某几个关键方法的规模急剧膨胀,复杂度也非常高,对于调试、迭代开发都会造成很大的麻烦,而且对象之间的分工与协作也很不明确。

(2)第二单元——对象解耦与协作

第二单元我在设计时,就限制了各个对象的功能的范围,使得对象之间的耦合度控制在一定范围内。同时,在设计对象的一些关键方法时,会思考各个方法之间是否存在共性部分,可以提取出来,提高代码的复用性。

在第二单元中,我加深了对OO方法的理解——OO不仅仅是将代码进行分类和封装,更强调由对象之间来协作完成某一个任务,比如请求的逐级分派就需要依赖各级Dispatcher和相应队列之间的协作。

当然,在第二单元的后两次作业中,电梯策略类Strategy没能保持住第一次作业强调的限制对象的耦合度,每次都是想到哪直接改上去,以至于最终的Strategy类就是一坨能跑但毫不优雅的**。

(3)第三单元——对象协助2.0

第三单元则更加深了我在第二单元中对oo的理解,即对象间的协作。这个特点在第三单元中体现的非常明显,基本上每条指令都对应一个任务,每个任务都涉及到若干个对象之间的协作,因而在设计、实现和测试的过程中,都围绕着相关对象的行为进行,同时注重控制好对象之间的耦合度,因此第三单元的整体设计还算比较简洁和清晰(当然,与第三单元比较简单也有关系

同时,第三单元还加深了我对继承的理解,包括继承中子类和父类的共性与特性。

(4)第四单元——OO的便利

第四单元我对oo方法的理解主要是多态和继承,以及真正感受到oo方法带来的便利

在封装自己的UML元素类中,我体会到了多态的好处,可以避免很多不必要的对象类型判断;而继承关系则使得代码的复用性大大提高,代码更为简洁,这个结构也更为清晰。

第四单元中,遵循着对象解耦和对象协作的思想,我实现了MyUmlCreator类和MyChecker类来满足迭代开发的需求,保证了前面作业的整体层次化结构没有太大改变。

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

(一)第一单元——漏判的伤痛

第一单元中,我对测试的理解主要是自动测试的流程——数据生成、运行待测程序、正确性检验。其中,生成数据时,既要保证数据的合法性(符合形式化定义),又要尽可能全面地覆盖多种情况,正确性检验需要严格覆盖所有要求

在第三次作业中,我的正确性检验程序就缺少了对输出形式的判断,最终在强测中挂了两个点(依然记得那个周六晚上19点50多,电脑放教室,在宿舍摸鱼了一会后慢悠悠地走去教室,路上突然想到这个地方可能会有问题,结果时间离八点不到5分钟了,跑着去教室也来不及了,由此总结了摸鱼会被刀的血泪经验)

除了摸鱼会被刀的惨痛教训,我还明白了不能完全依赖自动测试,因为自己对自己的程序的理解一定比机器更深刻,自己也要进行手动测试来弥补自动测试的不足,如极端边界情况等。

(二)第二单元——测试贯穿开发全过程

第二单元测试给我带来最深的体会就是测试是贯穿开发的全过程,而不是完成设计后再用数据狂轰滥炸。

第二单元不仅需要保证基本功能正确(如线程安全),还要保证真正实现了自己预期的功能我们应该在设计某些特定功能时,就构造出该特定功能的测试样例。这些测试样例具有非常强的针对性,将是我们验证特定程序功能的利器。

此外,这单元的自动测试中,最难的一部分在于spj。前两次作业自己写的spj虽然勉强能用,但是代码惨不忍睹,极其恶心。第三次我决定用面向对象的方法写一个评测机。在spj程序中,我实现了电梯类和Person类,根据输出来模拟电梯系统的运行和人员进出,并对每一次操作进行合法性检查,于是整个程序无论是逻辑上还是架构上都更为清晰和简洁。

而对于测试的流程,我实现了Creator、Runner和Checker类来完成数据生成、运行待测程序、正确性检验,其中Runner对外提供了addName方法,可以运行多个程序,同时对多个程序进行测试(互测AOE),而且改改后面两单元还能用hhh

(三)第三单元——以对象为单位进行测试和数据质量

第三单元中,正确性检验主要采取对拍,关键在于测试数据的生成。

由于第三单元中,每一条指令都涉及到若干对象的合作,因此我首先根据对象将指令分类,然后开展以对象为单位的单元测试。以对象为单位展开测试,一方面测试数据的针对性比较强,能够高强度地测试某一个对象的具体功能,同时debug时也能精准锁定错误位置;另一方面,相比于传统的单元测试(以每个方法为单元),又能很好地覆盖一个对象所涉及到的一系列方法

同时,第三单元中引入了异常机制,因此需要特别注意测试数据的质量,否则只会引发大量异常,导致测试的强度减弱。我尝试采用了先设计测试方案,然后根据测试方案生成测试数据,再运行待测程序,并检验测试效果,最后修改测试方案并重新进行测试的闭环测试方法。

img

(四)第四单元——艰难的自动测试

第四单元的自动测试真是一言难尽。。。

前两次作业,勉强还能写出数据生成器模拟UML图的元素,第三次作业碰上烤漆,就没写数据生成器了,手动构造了一些测试样例跑一跑,再用homework14的数据生成器改几个参数触发一些异常,进行比较弱的自动测试(希望不会寄)。当然,测试的过程中还是遵循前三个单元的经验,如测试方案、单元测试等。

(我还有过一个大胆的想法,就是用脚本来控制鼠标随机点几个startUML的图出来,不过这个原始的想法很快就被抛弃了hhh)

这单元的测试我想重点还是在于保证数据能够全面覆盖各种情况,同时检查指导书确保没有漏掉一些特殊的情况。

(五)在心中埋下一颗测试的种子

经过四个单元的锻炼,自己的测试水平有了比较大的提升,但是还有很多不足,也还有很多地方可以优化,如检验测试对各个方法的覆盖率、自动回归测试、单元测试等,未来都可以继续深入地了解和应用。虽然OO课即将结束,但是我相信在未来的学习中测试一定是开发过程中的一个重点,正如吴际老师说的,“在心中埋下一颗测试的种子”,希望OO课埋下的这颗种子未来能够成长为参天大树!

四、课程收获

  • 重视架构。虽然在计组的学习中,老师就说必须先思考,再动手写代码,但是OO课使我明白,思考究竟要思考什么。我想,思考的重点,正是整体的架构,思考的过程中我们可以忽略细节的实现,但是一定要重视架构,并在设计的基础上先开展对于架构的形式化验证,确保整个架构无误后,再设计具体细节,因为一个好的架构无论对于实现还是迭代都是至关重要的。
  • 从对象的角度考虑分工与解耦。或许应该叫“面向对象的思维方法”,但我认为自己的理解远不到那么高深,实在不敢用这么一个词来说自己的收获。经过一学期的学习,我关于面向对象最深刻的体会和最大的收获就是,要充分发挥对象的特点,从对象的角度来考虑对于一个任务,需要几个对象通过什么样的协作来完成,每个对象负责哪一部分功能,能不能做到充分的封装和解耦。在其他的课程中(如OS),我有时也会尝试从对象的角度来解决问题,实践证明,这种编程方法在结构等各个方面都具有非常大的优势。
  • 对测试的逐步深入的理解。从第一单元刚接触自动测试的兴奋,到第二单元的测试贯穿全过程,再到第三单元的测试方案闭环测试,最后到第四单元的自动测试和形式化验证结合,可以说自己从一个测试小白成长成为一个“测试大白”(●—●),在测试的过程中,有对判断程序不完善的懊悔,也有严格测试后的踏实。我想测试是我OO课程学习过程中非常重要的一部分,也给我带来了很多乐趣。
  • 知识的获取。在OO课的学习中,我认为知识的获取是学习过程中非常重要的一个环节。知识的来源可以是课堂,可以是同学,也可以是网上的众多资料。在后面的几次作业中,我会先阅读往届学长学姐的博客,对于架构和测试中的一些问题也会和同学交流,相比于计组的单打独斗,这在很大程度上帮助我解决了很多问题。
  • 写博客总结经验。在OO课之前,我没有尝试过写博客来总结自己的学习,但是OO课独有的作业要求push了我一把后,我发现通过写博客,一方面能够把自己的经验分享给别人,另一方面能够回顾自己的学习历程,总结自己的学习经验和教训,因此在其他课程中我也尝试着开始写博客来总结。我认为总结是学习过程中非常重要的一个环节,而写博客这种方式刚好适合计算机专业学习过程中的学习总结。
  • 一颗坚强的心:)。回首OO课,虽然有时候真的因为架构混乱,灵异bug而濒临崩溃,但是仔细想想,生活嘛不只有OO,别人能完成,自己也能完成,只要下功夫认真地思考,就能够克服前进道路上的困难,由此也锻炼了一颗坚强的心。未来,如果遇到什么困难,就想想“OO课都过了,还有什么过不去呢?”

五、改进建议

  1. 第三单元的公测可以改成“伪单元测试”而不是黑盒测试。

    第三单元虽然强调了JML规格的理解和应用,但是课下测试同学们基本上都是采用黑盒对拍测试,不会过多考虑JML规格和单元测试。这其中的主要矛盾就在于课程组本身的测试方法就是黑盒测试,而学习目标却是具体到细节的JML规格。说实话,第三单元的JML规格,我只在一开始阅读需求的时候用到,阅读的时候甚至还把JML规格转化成自然语言描述,然后对着自然语言进行实现(不得不承认,这样的方法比单纯读JML规格实现快多了)。因此,本应是重点的JML规格,在第三单元中的地位却不如黑盒测试,实在是有点可惜。

    我想,解决上述问题的关键在于解决黑盒测试和JML规格之间的矛盾。可以把黑盒测试换成更细致的单元测试。但是,单元测试必须依赖于具体的实现,因此单元测试在课程中不可能作为公测的标准;不过我们可以想想,同样是对建模的检验,为什么第四单元我们可以做到对一个抽象的图模型的检验?原因是我们提供了很多对图元素进行检验的接口。通过这些接口,我们可以查询元素的状态而不必考虑具体实现,因此,第三单元同样可以在每个对象中实现多个状态查询的方法,在公测时突出调用每个方法前后查询对象的状态是否符合JML规格,而不是简单的只关心每个方法的输出结果是否正确,将这种“伪单元测试”和当前的宏观测试相结合,才能更好地发挥JML规格在这单元中的作用。

  2. 第三单元三次作业的层次化递进关系可以设计得更加明显。

    第三单元的重点是JML规格,但是三次作业涉及到的JML规格,在第一次作业中就全部都体现出来了,无非是require、ensure、exceptionnal behavior和量词,后面两次作业仅仅是功能上的拓展,三次作业在JML规格上的递进关系不够明显。而且JML规格中涉及到的其他方面如不变式,只在实验中体现,在作业中并没有体现过。

    我想,第三单元未来或许可以设计成第一次作业主要涉及前后置条件,第二次作业涉及异常,第三次作业涉及不变式和副作用等等,这样三次作业在JML规格上有更深层次的递进而不仅仅是功能上的拓展。

  3. 第一单元稍微降低难度,并突出如何应用层次化结构

    不得不说,从Pre到第一单元的跨度实在是有点大,很容易在一开始就击碎对OO的美好幻想。不妨第一次作业降低些难度,后面两次再逐渐拔高。

    此外,虽然第一节课老师就在强调层次化结构非常重要,但是建起来层次化结构以后,还是不知道怎么用。表达式树建完了,拆括号怎么拆?碰到表达式因子这种跨越好几层的神奇的东西,既可能是在最高层,有可能在最底层,这种神奇的跨层次循环怎么处理?后来,经过讨论区同学的提示,我才想到要用递归。虽然课程组在训练中很温馨地给出了递归下降的demo,但是对于拆括号的递归处理、函数参数替换的递归处理并没有给出提示,给出的这个基准思路也没能体现出如何应用层次化结构。

    或许未来在第一单元中除了强调如何建立起层次化结构以外,还可以对如何应用层次化结构给出更多的引导

写在最后

一学期的OO课很快就过去了,OO课真的带给自己很大的收获,是为数不多的带给我切身感受的一门课。过程虽然曲折充满艰辛,但是回头看还是有很多乐趣和成就感的。我想OO课带给我的不仅仅是编程上的知识和技巧,还有思考现实世界的许多方法,以及一颗坚强的心 😃

非常感谢老师和助教们在OO课中的奉献,也非常感谢许多同学给予的帮助。祝OO课越办越好!!!

posted @ 2022-06-26 17:37  Booooomb  阅读(23)  评论(0编辑  收藏  举报