正向建模与开发
根据百度出来的结果,在面向对象编程中,正向建模与开发是指从需求分析到系统实现的一种开发方法。本单元的UML教学与作业的核心目的之一应当就是让我们形成根据需求设计构造,画UML图,最后才写代码实现我们的需求。这样的开发带来的好处是巨大的。在之前的多次作业当中,我的设计顺序都是读题,脑补一个设计,实现,但是可能会出现各种各样的bug,需要进行大大小小的修改,最后算下来很有可能写了很多无效代码。虽然最终这样任务也完成了,但是这样的开发流程相当低效,在实际上的生产中会带来巨大的效益损失。UML在整个开发流程中相当于实现了一个缓冲,把我们脑中的架构先进行一个简单而抽象的设计,如果有什么大的架构上的问题,在前期UML设计时我们就能发现,这样就避免了陷入后期代码已经完成了好大一截,却发现代码架构上有一些无法回避的问题的窘境。虽然本人和周围的同学在实际完成作业时或多或少有一边写代码一边修改UML的情况,毕竟初期UML实现时可能无法考虑到那么细,但是UML的出现还是给整个作业开发带来了方便。
架构设计
本单元的三次迭代中没有添加很复杂的模拟过程,设计架构上也比较单一,整个过程比较顺利。UML类图如下:
本人的设计追求外部关联简单,把关联复杂的部分放到类的内部去处理。主要的管理类为LibraryManagement
,类的内部开设各种容器表示书的不同位置,包括个人,书架,借还处,预约处。为了处理预借请求,还设置了一个容器requests
,表示还没有把书送到预约处的借书请求,并在每天晚上遍历这个容器,筛选可以完成的请求,并把书运到预约处。在第二次作业中,又加入了容器流浪角,并在借还处添加了一个HashMap
记录每本非正式图书的成功借还次数。在第三次作业中,为了完成新的用户信用分系统,首先再加入了一个HashMap
记录用户的分数,其次为了实现在用户捐赠的书成为正式书目时给用户加分的需求,又加入了一个HashMap
记录每本非正式书目是谁捐赠的。为了其他维护的方便,设置了两个简单的数据类Order
和Keep
,这两个类的主要需求仅是记录信息,不涉及复杂交换。
可以看到,本人的架构中书的状态是由其存在于哪一个容器当中表示的。根据各种操作对于书状态的改变我们可以画出书的状态图如下:
在书的预约到取书流程中,用户首先发出取书请求,然后调用方法检查合法性,包括信用分是否合法,书籍限制是否满足。如果都合法,加入request
等待晚上移书。然后,等用户发出取书请求时,图书管理系统首先查找到预约处为此用户预留的这本书,然后检查书籍限制,如果合法,允许用户结束。流程图如下。
尽管真实的工作流程当中应该是先完成UML图的设计再写代码,但是由于能力有限,实际完成作业时候两头是共同推进的,写代码期间也会对UML图修修补补。最终的代码实现和UML图是相辅相成的,应该效果也比直接写代码好不少。
四个单元的演进
第一个单元的内容是表达式化简,主打的思想是层次化设计,递归下降直到表达式的复杂度足够单次处理为止。整个设计的结构像是先把表达式展开为一棵树,再设计表达式规范化结构,用规范化结构在树上求值,算出答案。由于性能由长度衡量,最后的优化设计中还包含了一些算法上的优化,比如提取指数的公因式,指数拆分,设计搜索和最优化的一些算法。整体上看,第一单元想要给同学们介绍的思想重点还是面向对象的递归层次化设计,类与类互相关联,形成一个互相关联的结构。这一单元依靠上个学期的面向对象先导还可以勉强应付。
第二个单元是电梯的调度与运行策略设计,核心是多线程安全。这一单元的内容依靠先导课的基础就很难扛住了,这也是我认为四个单元中强度最大的一个单元,大概有第一单元的1.5倍,第三四单元两倍的工作量。多线程的核心问题是不确定性,需要使用同步和锁这样的工具来完成线程之间的一些交互。一旦设计有难以察觉的小问题,就会出现死锁、轮询等等问题。不仅如此,由于多线程的不可复现性以及本单元一个测试点就需要大约一分钟运行的特点,某一次代码中出现的bug可能不会复现,gdb调试无法使用,如果bug比较难复现可能需要跑5到10次才能复刻一次,这使得对症下药基本不可能。因此调试非常强调我们根据自己的代码推理观察,思考何处可能出现问题的能力。
第三个单元是JML教学,内容是设计一个图模型抽象模拟一个现实中的社交网络。经历了二单元的折磨,第三单元的难度显得十分简单。核心难点在于JML的理解。JML是Java的一种模型语言,其只规定一个方法或类的外部表现,而不关心内部的实现,主打的就是一个抽象。这种抽象带来的好处多多,例如,便于测试,可以根据JML直接翻译出Junit测试代码,也便于优化,我们可以先根据JML实现功能需求,再考虑如何优化,等等。说到优化,这个单元的另一难点就是复杂度优化。很多同学都出现了TLE的问题。我设计了一个BFS与并查集找生成树的缝合乱搞,这样尽管还是每次上界边集大小的复杂度,但尽可能进行了懒惰处理,实际上跑的很快。
刚刚结束的第四单元是UML教学,核心是让我们学会整体架构规划。代码难度并不大,甚至我第三次迭代所有类最后加起来也就三四百行样子,虽然额这可能是我尝试使用室友教的流式编程带来的好处。然而,UML设计的思想给整个开发流程带来了一些改变。上文已经有所提及这里不重复了。
对于测试,首先感谢民间评测机dpo拯救苍生,可以直接拍出有问题的数据并下载调试,给了大家一个比较方便的调试途径。但dpo的数据是比较随机的,强度不够高,也很难对个人的代码对症下药。为了保证自己代码的正确性,最好自己再进行测试。对于第一个单元,有同学在讨论区给出了非常简单的评测方法:要判断两个表达式A和B是否相等,只需要找第三人的代码计算A-B是不是0即可。对于第二单元,由于多线程先天就是反调试圣体,本人也并没有想到什么好方法测试。不过有同学在讨论区提出了排除死锁的方法,可以用作盯代码时的参考。第三单元的JML本身就是最好的调试方法:对比较复杂没有的方法严格按照JML写Junit就好。对于第四单元,可以写Junit,但是随机生成的测试可能也效果不好,可能更好的做法是针对易错细节,比如各种日期过期,各种信用分的加减条件手动生成数据分析,结合dpo进行测试,可以达到比较好的效果。
课程收获与建议
16周16个作业12次迭代4次博客的OO课,写完这篇博文大概就算是全部完结了。OO课能教给我什么呢?说实话我并不打算未来当写代码的程序员,而是希望能找个大学教书,OO课的作业甚至可能是我为数不多写Java的机会。尽管如此,我觉得面向对象的思想还是对我有很大的帮助。往简单里说,这门课教会了我怎么写java,怎么用idea这个强大的工具,怎么用starUML这个开源软件画图;深入了讲,学会了怎么测试,怎么工程化的完成工作;再深一点,学会了如何把面向对象的这些思想应用到编码中,锻炼了代码风格,提升了调试的耐心和遇到困难的抗压能力。简言之这门课使我收获颇丰。
对于这门课的建议,我最大的建议是加学分。这门课的强度我只能说至少跟OS相当,就我而言我花在OO的时间比OS更多,应该加学分不然对不起同学们老师们助教们的努力。此外还有一些杂七杂八的建议我简单列举:
-
取消互测保底分。意义不大。
-
每单元的第一次作业建议把指导书写的更详细,帮助同学们上手。很多时候同学们不是不会,而是不知道该干什么。每次本人都要花很多时间研究这个单元的作业到底要写成啥样子。
-
助教提前测试提前检查,比如第三第四单元,减少各种出锅。理解第三单元助教的好心,是想减轻同学们的无意义负担,但屡次对指导书和JML做大大小小的修改也拉低了体验。
-
中测强度再多上一点,不用上的太多,但也希望能测试到题目中的每一个点。因为OO课程的作业细节确实太多,可能有一些细节被一笔带过,同学自己脑补了一些细节的处理,但是跟助教想的不一样,都没有意识到。
-
强测要有数据梯度。那个因为调试信息输出太多导致全TLE的同学实在太惨。rip。
我的OO课结束了,虽然经历了种种困难但也是颇有收获。希望今后的OO课也能更友好更充实。