TakiP

导航

BUAAOO 第一单元总结

BUAAOO 第一单元总结

前言

面向对象课程实践会是什么样子?真正开始 coding 前,只有从学长学姐那里获得的模糊印象。

——是对面向对象思想的应用吸收。望向不存在什么设计方法与范式的、存在硬盘角落里的过往写过的码,望向空无一人的身边,留给各种意义上都对所谓对象毫无概念的我的只有茫然。

——是四个不同课题组织起的工程结构实践。但在此之前,只是零零散散写过一些跑起来便是万事大吉的mountain of s**t 的,从未写过恰当组织起来的、成型的工程的我,尚对工程两个字所蕴含的设计巧思一无所知。

——是让你有作为程序员的自信乃至自慢,当然也可能只是自我宽慰(小声)的闯关。熟识的学长如是说。他同时补充说,这是他认为大学数年间,最体现计算机学科之工科特质的课程之一。无论是一往直前一路披荆斩棘直达顶峰,还是挂着氧气瓶左右碰壁进退失据挣扎着爬完崇山峻岭,对于工程能力都能有相当大的提升。

然后,春天开始了,对象降临到我身边。


三周之后,第一单元的OO工程练习就此告一段落。对于我来说,我找到了适用于这个单元的,对于文首疑问的答案。——一场被 DDL 追逐,被重构包裹,被收获宽慰,怀抱遗憾但充满希望的大冒险。

第一次作业

猪突猛进,而后坠入深井。

第一次作业的主要需求是,对简单多项式导函数的求解。

——太简单了。看到题目的第一反应是有劲儿没处使,一心就冲着把分数拿好拿满去,简单的设计之后,紧随其后的就是浮躁与冲动驱动的 coding 之夜。


项目类结构说明

第一次作业的类图及目录结构如上所示。

在初期设计分析需求时,首先从处理之数据的层次结构入手,设计了表达式(和式) Expression -项(乘式) Term -因子 Factor 的三层结构。通过层次上的梳理,这次作业的业务逻辑设计变得易如反掌——求导、化简两个分任务,抽象成每一层次暴露的方法接口,使用时逐级调用即可满足需求。

这样的抽象层次结构设计,剥离了各层次之间的相互杂糅,建立起单向的交互关系。同时,对单独的解析器模块的需要也大大降低——有限次的逐层解析过程,完全可以放心交给各层次的构造方法。

至于可扩展性?当时的我所考虑到的扩展,不过是因子会引入更多种类的函数,对于这种意义上的扩展,我所采取的办法是为因子增加类型,在新因子加入时,通过判断因子类型,便可做针对性的化简。

类度量及说明

类度量量表格如上所示。

在纷繁复杂上独占鳌头的是 Expression 类,主要原因在于作为处于整个项目结构中最外层的类,在调用下层与交互上,存在额外开销。事实上,较高的平均操作复杂度所提示的是,应该对部分业务需求做合适的分类与剥离,并聚合成工具类。

方法度量及说明

方法度量分析表格如上。

可见,除去需要进行多次判断的格式化输出 toString 方法外,复杂的方法主要是文法解析方法。事实上,回过头来看这部分的代码,构造方法中甚至还混杂了一段几乎完全与化简方法相同的业务逻辑。当时的我一心考虑的是,一步到位,“多快好省“地解决问题。殊不知,此处的处理正显示出了业务逻辑的混乱与相互杂糅,导致代码的可复用性大打折扣。


一气呵成之后,看着毫无破绽的运行结果,自然是心满意足地沉入梦乡。

愚者暗于成事,知者见于未萌。——《商君书・更法》

在花果香甜氤氲的密林之中,毫无自觉地落入陷阱的野熊显然是前者。那时的我不会想到,等待我的是噩梦般的第二周。

第二次作业

进退维谷,挣扎无底深渊。

毫无预兆地在第二次作业的需求描述里登场的,是犹如五雷轰顶一般的嵌套表达式。这不仅意味着原先的层次结构设计全盘崩溃,更意味着没有抽象出解析器等模块的我连可以复用的代码都难以寻觅。嵌套破坏了单向的、自顶向下的类间交互方式,引入了互相包裹的复杂联系。

重构如同垫满陷阱的网一般将我包围,然后自下而上提起吊在不知名的歪脖子树上。


项目类结构说明


紧缩成一团!无数次的删改之后,最先想到的脱困方法便是如此。曾经单向传递的抽象层次结构不见了,取而代之的是一整个大类——表达式类。无论是曾经的项还是因子,都因为嵌套关系的到来成为了表达式。解析部分的逻辑变成了建立表达式树的过程,因此每一个表达式都拥有了嵌套子节点的能力,唯一涉及三层层次结构的地方只剩下解析逻辑中,运算符的优先级。

因为嵌套的引入,曾经的正则识别不再堪用,因此抽象出了用于匹配的两个工具类,这是自救中唯一有暇他顾的点。

类度量及说明

类度量量由于大类的建构,就此没眼看了。究其原因,没有对大类进行解耦,各种本质上有差异的结构,例如嵌套、组合、因子等等,被形式上统一而逻辑上分离,导致了各类业务方法的复杂化。仅仅是构造方法就要多次判断以适应不同情况,求导、化简等方法同样坠入深渊,使得方法冗长、无法灵活运用复用。

方法度量及说明

此处极高的方法复杂度揭示了另一个隐患——由于项的定义杂糅,格式化输出变得臃肿不堪。而在化简上,大量运用 toString 进行文本匹配,而没有进行结构化的存储,骤增的复杂度使得嵌套复杂时,程序落入递归陷阱。效率大幅下降。


接下来的一周跟压线提交的那一晚一样,飘飘乎仿佛如梦中,好的架构是什么?我沉浸在对这一问题的迷茫和求索里。

第三次作业

待从头、收拾旧山河,朝天阙。

有了第二次的经验,我意识到,面对接下来的项目设计,所要做的很简单——实现 Expression 类的解耦。面对新增的验证格式需求,这次我采用的是新增 Verifier 模块的方式来解决——吃一堑长一智之后,我决定选择更具模块化思维的方式来构建项目。


项目类结构说明

几经考虑之后,项目的设计终于成型。可以看到,最大的改变在于对工具类的抽象——匹配、生成、验证三块逻辑就此分离单独成类,适应大一统的项类的需要。项类进一步细分成组合项和因子项,对应组合关系和两种函数,从而实现了第二次作业中大类的解耦。

类度量及说明

解耦后的类,复杂度较前次大幅下降。然而独立成模块的验证器,难以有效利用数据解析中的额外上下文信息,导致做了更多的无用功。

但总体而言,活用继承的结构设计模式,给了类更大的自由度,增加了可复用代码量。抽象出的工厂结构,也有效缓解了类本身的复杂度。

方法度量及说明

此处略去了大量较为简单的方法。

可以看到,用于数据解析与构造的方法复杂度仍较高。此处,由于解析器事实上还是有所缺失,因此此部分未能实现合理的代码复用。不过抽象出的子类共同分担了这部分的逻辑复杂度,使得整体方法复杂度下降明显。


完成了——长舒一口气之后,我终于从密林里的陷阱完全挣脱出,回归自由——虽不能尽善尽美,但终于得到了一个相对合理的架构。Level up!

Bug分析及Hack思路

第一次作业

足够简单的层次化设计虽然最终把我一路送向了泥淖,但至少一时让我得以在可能的 Bug 间左右横跳,避开坑点。本次作业在公测和互测中均无 Bug 。

Hack 的部分,使用了构造的自动评测机作为辅助,但主要还是通过分析同学项目的构造,阅读其关键逻辑的具体实现入手。然而,第一次作业的架构大抵简单,并未能发现可能的 Bug 。

第二次作业

在互测的起始阶段,递归陷阱所带来的超时问题就暴露无疑。本次作业的主要问题便来源于此。掺杂在化简、合并逻辑中的大量字符串方法带来了极大的性能开销,从而导致超时。

此外,由于对符号判断部分逻辑的解耦考虑不足,出现了特定情况下符号异常的问题,这是导致 WA 频现的罪魁祸首。

由于自己符号处理失误,又落入了嵌套分析的递归陷阱之中,在 Hack 时也着重考虑构造此类样例试图 Hack ,效果拔群!然而自顾不暇的我难以匀出时间细致分析他人的代码,导致事实上错失了学习的好机会。

第三次作业

本次作业经历了前两次的磨砺,虽然仍进行了项目的大重构,但是各方面的考虑都成熟了许多。因此,最终的问题大大减少,局限于从第二次作业处理逻辑里遗留而来的小符号问题。

Hack 时的情况大抵如是,这次我主要采用了分析代码的方式来试图 Hack ,好过再像上周一样无头苍蝇般乱撞。然而互测屋的室友们代码质量都很高,并未能发现问题。

重构经历总结与心得体会

三次作业,两次重构,OO的下马威让我至今回想起来仍是收益满满。通过上文展示的三次作业的类图可以看到,在项目结构的设计上,走过了螺旋上升的过程。第一次的简单层次结构设计源始于对短期需求的充分认识,但欠缺了工程设计中相当核心的思想——可扩展性;第二次是重压之下压出的垃圾山,裹成一团的类结构之后,唯一的可取之处在于,第一次认识到了嵌套所带来的本质改变——层级结构不再,取而代之的是抽象的、共通的”同种存在“;也因此,第三次作业才终于得以收拾旧山河,通过活用继承,合理规划顶层设计,完成了复用性和可扩展性更好的代码。

虽然在重构上耗费了大量的时间,使得化简这一需求没能有机会做到极致,但也正是重构的经历让我对工程结构设计有了更多的了解与体会。好的顶层设计,带来的是对后续需求更改的强适应性,也使得工程迭代有了更好的弹性。

第一单元的OO大冒险结束了,初出茅庐的刺激,余韵悠长,让我对未来的三个月更多抱了一份期冀。我认识到文首的那位学长似乎自相矛盾的描述之后,包囊着的确实是每一个有幸参与这门课学习的过来人的真情实感。OO大冒险,堂堂连载,且待下周继续勇者斗恶龙!

posted on 2021-03-30 19:52  KumaXX  阅读(132)  评论(1编辑  收藏  举报