BUAA-OO-Unit4
BUAA-OO-Unit4
本单元所实践的正向建模与开发
本单元主要是完成了一个图书管理系统的设计,实际上就是一个大模拟。
所谓 正 向设计简单来说就是从概念——实物,这一过程利用绘图或建模等手段预先做出产品设计原型,然后根据原型制造产品。
而在这一单元,课程组的目标也是鼓励我们借助 UML 图进行正向建模设计与开发。因此,整个单元作业的完成步骤也是,先画出大致的 UML 图,然后根据 UML 图进行代码的编写。
不过,实际上在接触 UML 之前,除了第三单元 JML 单元只需要完成方法的编写而不需要进行整体架构上的设计,其它的作业都是需要从头开始一点一点搭建架构的,也就是说,在此之前我们的开发,也一定是先建模,至少确定大致的架构,再进行编码。第四单元和此前的不同主要在于引入了 UML 图,借助 UML 图,它可以让我们的架构设想不仅仅停留在脑海中的抽象,还可以落到实处,用更加具象的形式描述出来。
每单元的第一次作业往往都十分关键,从零开始搭建架构本身就需要诸多考量,如果稍有不慎走了歪路,很可能后续开发会困难重重,甚至牺牲大量时间进行重构。这单元的架构在第一次作业时就已经被确定下来了,主要是用一个 Runner
类进行统筹,管理诸多部门,并且处理相关的指令,并调用相应的相应处理方法,向下向图书馆的各个部门传递消息。
而具体的建模方法,我延续了一贯的做大模拟题目的思路:像 dfs 那样,一点一点把每一个操作处理好,并在处理每一个操作的过程种,给每个类一点一点添加必要的属性与方法。这样基本上可以保证模拟的正确性,同时也可以慢慢的对整个题目需求建立起全面的认知。
本单元作业的架构设计
最终的 UML 类图:
整个架构主要包括了三层:最顶层的 Runner
类统筹管理所有图书馆的部门,第二层是各种图书馆的部门,第三层是最底层的 Book
和 User
类,是最底层被管理的个体。
整个架构的执行流程其实也很简单,主要是 Runner
类进行操作的识别,识别后调用相应的 handle
方法进行处理,每种指令的 handle
方法实际上也是严格根据题目描述中相应操作的实现流程来执行的——用户在什么位置、相应的书在什么位置、什么部门在干什么样的事,明确了这些,也就可以根据实现流程来写方法了。
我这次作业主要是 自顶而下 的设计,从顶层开始一步步往下模拟,并在模拟的过程中慢慢填充每个类的必要成员变量和方法,最终搭建好整个完整的架构。
最终完成之后,对比 UML 图和代码,二者最后差别并不大,部分实现细节方面的有小改动。主要是因为,画 UML 图的时候就是按照以往的做题思路一点一点画下来的,只是把直接上手写代码换成了画图,所以最终的设计也是差不多的。虽然还是有一些小的变化,不过也可以理解,毕竟实际写代码的时候还是会遇到一些画图时没有考虑到的问题。
架构设计思维的演进
在最开始,我对“面向对象”的认知比较简单粗暴,认为其实就是“封装”,把各种各样的操作大量封装成方法,然后在实现过程中大量调用方法即可。不可否认,这一认知在从初入门程序设计到接触面向对象的这一个阶段,有一定道理,并且确实可以一定程度上写出面向对象风格的代码。
然而,在经过了一个学期面向对象课程的学习之后,我对面向对象有了更加深刻的认知。除了它的三个基本特征封装、继承、多态之外,我还深刻认知到了“高内聚、低耦合”的架构及其重要性。这一认知主要是在每个单元的三次迭代开发过程中,同时在互测过程中,下载查阅其他同学的代码,对比自己的代码进行分析之后,得到的感受。如果架构没有做到高内聚,那么整个框架可能会显得比较臃肿,可能会陷入“为了封装而封装”的误区,影响整体的观感,也给迭代带来困难呢;如果架构没有做到低耦合,在迭代实现新需求的时候,对代码的改动幅度比较大,经常需要顺着方法调用一路改,工作量大,出现错误的可能性也变大了。
每个单元的第一次作业往往都是比较困难和重要的:第一次作业需要从零进行架构设计,需要从头在平地上打地基盖房子,而且还是之前从来没有接触过的内容和知识,这本身就非常有挑战性,需要我们在短时间内学习新知识并进行架构设计。除此之外,第一次作业的架构设计基本上奠定了此后迭代的风格和工作量,如果设计的架构不好,后续就有可能需要重构,带来极大的工作量和心理负担。
不过,好心的课程组为我们提供了“训练”和“实验”。训练栏目有助于帮助我们对新知识建立起一定的概念体系,并且给予一些代码片段,让我们初步对新知识进行一些小练习;实验栏目往往会提供我们一个完整的架构,让我们进行代码填空,而进行代码填空就必然要从头到尾的通读整个代码,并且在此过程中分析架构。我一直以来都认为,阅读他人优秀的代码可以给人带来极大的提高,特别是在面向对象课程的完成过程中,这样的感受尤为深刻——如果没有实验栏目给我们提供一个优秀的代码框架作为示例,我很难在短时间内就完成一个优秀的架构设计,并进行代码实现。所以我每个单元的第一次作业,都会在实验课之后,借鉴实验中给出的代码,针对题目进一步进行修改和加工,最后转变成我自己的架构设计。这一个思路看起来还是很不错的,我在整个过程中没有进行过重构,而且每个单元迭代开发的耗时相对来说也比较少。
具体到每一个单元的思维演进,第一个单元是递归下降解析表达式,说实话整个顶层框架基本都是和实验代码差不多,分三类对表达式进行层次化描述和解析,具体在写的过程中,改一些识别和运算等方法就行,没有动过架构。不过,在实现各种功能的过程中,有时也需要进行递归下降的处理,例如两个表达式的判等,这实际上就是一个递归下降的过程,我在实现这些功能的过程中又对递归下降有了更深的理解和认知。第二单元是电梯,第一次接触到多线程,收获非常大。整个的架构是依靠生产者-消费者模式搭建的,主要的调度策略设在了电梯类里。不过,由于第一次作业时对于电梯的策略设计的比较朴素,到后续迭代的时候也有点害怕修改,我这一单元的性能分比较低,不过正确性非常有保证。这个单元最后的代码其实不是那么的“面向对象”,虽然倒是挺“高内聚”的,但是代码全部扎堆挤在电梯类里,非常难看。第三单元,主要是根据 JML 写代码,并不需要自己进行架构设计,读懂要求之后自己写方法即可,相对来说比较简单。第四单元,训练目标主要就是自己设计架构,然后根据设计好的框架完成程序设计。这一次我确定了 自顶而下 的设计思路,根据需求一点一点完成设计。
测试思维的演进
以往我对“测试”的认知仅体现在“通过数据点评测”就是对的了,然而,这一思想并不具有实用性:在进行有实际意义的开发的时候,有时并不能有那么全面的测试数据,在功能上线公测之前,只有一些小的测试;而真正的“强测”,得等到系统上线公测后,才能够有足够强、覆盖面足够广的测试数据,然而,如果程序的 bug 到这个时候才被发现,那么也就为时已晚了。在面向对象课程中,也是类似的——在中测的时候,我们有机会面向数据来修改代码,但此时的测试数据强度有限,通过这一部分测试并不意味着代码没有问题,而在强测和互测的时候,如果没能通过测试,将会有很惨痛的后果。
面向对象课程的先导课时,让我对单元测试有了一定的认知。这也是我从程设、数据结构课程的独立编写一段代码,进化到更高级的开发的转变,给我带来的思维的进步——前者只需要测试整个代码最后的结果就行了,毕竟全部工作都是自己完成,整个代码的体量也比较小,而后者则不然,有的时候自己只需要负责整个项目的一部分,而如何测试自己负责的这一部分是否出现问题呢,这就需要进行“单元测试”。也是从这个时候,我开始了解了 junit 单元测试,并且给予了一定的应用。
在第一单元时,我起初在测试的过程中只关注了我表达式“值”的正确性,而忽略的自己输出结果是否符合形式化表述,从而造成了一次强测的寄。这使我认知到了,测试不仅要保证测试数据的全面性,还需要保证验证程序的全面性,应当把所有条件都考虑进去,否则将会出现问题。
第二单元,多线程,主要的问题是测出来的 bug 可能无法复现。这有点折磨,往往要通过阅读源码来修改 bug,而且最痛苦的是改完之后,由于 bug 没法复现,也不知道自己到底改对了没有。总之感觉还是得对代码有深刻理解,以及知道自己为什么要改。这不像有的时候修 bug,随便改几个地方试试,没准就改对了。
第三单元,主要是针对契约写 junit。这又让我的测试思维更进了一步——对于 pure 方法,还要测一下它不能改的东西到底改了没有。这对我而言在此之前是完全没有纳入我的测试思维范围内的,但对于一个契约式的编程来说,却是非常必要的——如果忽略了这一点,虽然可能方法 return 的值是正确的,但是团队的另一个人基于此进行进一步开发的时候,就有可能因为前人的隐蔽错误,产生莫名其妙的 bug,从而带来巨大的调试成本。
第四单元的测试主要是交互性的,比较有意思,不过主要是依赖同学的评测机来测的,自己并没有实际的体会一下这个特殊的测试,还是有点遗憾的。
总得来说,OO 让我对测试这一工作有了更深更全面的认知,对我非常有帮助!
课程收获
最直接的收获自然是,在强度不低的代码实践中,对面向对象有了更深的认知,对 java 语言有了更多的了解…
另外 OO 课程还给了我与更多优秀的同学进行交流的机会,在和他们的交流过程中,我增进了和同学之间的感情,也从他们身上学到了很多。
总得来说,感谢课程组对课程的精心设计,感谢一路上同学们的陪伴和帮助!