自序
庚子鼠年,阳春三月。江南草长,万物复苏。盼望着,盼望着,OO来了,春天的脚步近了。
素闻OO课程的难度和考核模式,虽然早有心理准备但在亲身经历时仍难免手忙脚乱。从pre到第一单元一路走来,特别是回首开学以来的三次作业和前前后后的心路历程,纵然“泪添九曲黄河溢,恨压三峰华岳低”,也算是跌跌撞撞地入了门。昆仑巍峨,我已攀登过公格尔九别矣。
闲言少叙,且容我将我这三次作业的情况娓娓道来,然后总述自己的重构策略和心得体会,立此存照,如有不正之处请多指教。
第一次作业
程序结构分析
如下面的类图所示,在第一次作业中,由于仅需要实现对幂函数的求导,我只构建了主类和一个表达式类Poly,并用两个Arrylist分别存储各项的系数和幂,实现了幂函数的逐项求导和输出表达式信息,主类用一个大正则读取输入数据,现在看来结构较为简单,无需多言。
代码行数统计,仅有113行,各类的行数分别还相对均匀。
以下是我使用IDEA插件MetricsReloaded分析的方法和类的各项复杂度情况。可以发现,我的方法的结构复杂度、模块耦合度和循环复杂度都还控制在一个比较合理的范围内,但是类的循环复杂度超标,暴露问题,在初次编写面向对象程序时考虑欠缺,有待优化。
我的bug
我仅在中测第一次提交时出现了一个bug(忘记具体内容了),并且很快就得到了修复。由于我做了充分的手动测试和竭尽全力的性能(即输出长度)优化,加上第一次作业较为简单,我取得了强测满分的“开门红”。在互测中也没有被hack到bug。
别人的bug
因为时间和个人能力所限,我没有使用评测机,所以我debug和互测找别人bug的过程只能全部靠手动完成。互测刚开始时,我通过尝试系数和幂分别为-1、0、1等容易出错的特殊边界情况,不幸地找到了一位同学的bug,这位同学在幂为-1时求导的符号出错,令人惋惜。这些特殊数据列举完后,因为第一次作业的代码量不大,我便仔细阅读了屋内“室友“的代码,尤其关注他们正则表达式的写法,果然又先后发现了两个人的bug。可见,大正则真的很容易出错,这也为我敲响了警钟。
第二次作业
程序结构分析
随着第二次作业新增了三角函数的求导和判断WF的需求,我不得不进行了OO作业的第一次重构。但是第二次作业的整体思路还是延续了第一次作业,只写了三个类:主类、表达式类Poly和项类Item,如下面的类图所示。此时的我还没有掌握对象的层次结构相关的知识,因此这几个对象之间并无清晰的层次关系和逻辑结构,面向对象的思想尚不完全。因此每个类的实现都较为臃肿,尤其主类中的main方法甚至接近了checkstyle60行的上限。不免还是延续了C语言的面向过程编程思想,当时觉得这无伤大雅,当时现在想来是极不妥当的,虽然类数、总方法数和代码总行数较少,却显得极为杂糅混乱,给调试和维护都带来了不便。另外大正则的继续使用也是一个极大的缺点,不仅不易阅读维护,还极易发生爆栈等不可知的危险情况。另外我为了合并化简的方便,不再使用Arraylist,而改用了Treemap容器,为此我还专门研究了一番容器的选择,仿佛也算一个小小的进步。具体地,我采用了指导书上推荐的策略,将幂函数、正弦函数和余弦函数的指数构建Item类,并以此作为key。总体上看,虽然这次作业完成了要求,但是代码风格不佳,可扩展性不高(注定了再次重构的命运),不够”面向对象“,因此私以为这不能算是一次成功的作业。
通过观察可以证实,随着作业难度的增加和设计思想的缺陷,类复杂度和方法复杂度都急剧增加,尤其是主类MainClass,类复杂度达到了令人发指的11,耦合度也有10之多,深刻揭露了我一main到底的不良后果。
我的bug
虽然我的代码不忍卒读,但我竟然侥幸地在中测和强测中没有被发现bug,这令我感到甚是惊讶。不过,我这次只做了简单的优化,优化并不充分,和大佬相比还存在着很大的差距,因此性能分不佳也在情理之中。
很遗憾,我在互测中被发现了一个小bug,原来是我幂函数指数忘了考虑前导零的情况所致,想来一是阅读指导书不够认真,二是苦大正则久矣。
别人的bug
我在第一次研讨课中受到大佬的启发,使用了一个比较简陋的评测机,但是仍然没有发现别人的bug,反而因为上述bug被hack9次,一开始尚不知这是同质bug,几近崩溃。不过事后得知寒舍包括鄙人在内共有3个bug,其中有一位仁兄的bug和我相同,我竟然后知后觉。
第三次作业
程序结构分析
第一单元的第三次作业在OO课程中算是第一次打BOSS,因为前两次的设计思路不佳,重构已刻不容缓。我从指导书上的提示得到启发,采取化整为零的思想,对每种函数(常数、幂函数、三角函数)和每种函数组合规则(乘法、加减法、嵌套)分别建立类,实现具体的求导、化简等方法,并实现统一的poly接口。在构造表达式树时,采取工厂模式用小正则解析输入格式,递归构造相应的对象,如下面的类图所示,在面向对象和层次化设计方面比前两次作业已有了很大的进步。
但是,通过观察行数和类复杂度的统计数据可以发现,还是出现了一些较为复杂的类,比如工厂Factory类和乘法Multiply类等,方法复杂度的情况也基本类似,因为方法过多表格较长故按下不表。可见我分而治之、化整为零的程度还不够,面向对象的思想还不完全,每个类完成的功能和任务也不甚明确,甚至出现把不同的功能放在一个类中杂糅实现的情况,比如我把正则表达式判断格式解析输入数据等功能杂糅进了Factory类实现,把表达式的化简等功能杂糅进了Multiply类实现,导致这些类的语句臃肿,结构复杂,极易出错,是设计的一大缺陷所在。此外还有一个缺陷是我在解析表达式时,采用的是面向过程的笨方法,即用下标变量i去遍历字符串,因为实在想不到更高级的方法了所以才出此下策,似有用JAVA写C的嫌疑,无疑也增大了类的复杂度。这些为我出现tle的bug埋下了伏笔。
我的bug
我在中测第一次提交时出现了一个bug,是因为在调用函数嵌套的Function类时忘记加括号所致。此后我还自行debug发现了一些bug,比如符号处理等。另外我在课下也做了力所能及的优化,如简化符号系数合并同类项等,并进行了主动的测试。
我在强测中没有出bug,得到了较为满意的分数。但是在互测中没能逃过同学的火眼金睛,被发现了很多bug,鲜血淋漓。经过我的仔细比对分析,这些bug由以下两个原因引起,一是在往Multiply类中添加Add类时忽略了该类等于0的情况导致运行时报错,考虑不周只需略作修改即可,另一个就是很多人犯的递归过深导致的tle错误,被hack到的样例也是广为流传的 -x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x)))))))))))))),我曾因为不知如何修复而苦恼不已,一度想放弃,后来经人点拨把不必要的括号去掉、减少递归层数即可通过,为此我大幅修改了原代码。
别人的bug
我在第三次寻找“室友”bug时无心阅读他们的代码,直接采取了自动化测试方法,将每个人的代码套上while扔进评测机运行,发现了一些人的bug(甚至发现了自己的bug)。我记得有一位同学是运行三角函数嵌套常数连乘时出错,还有一位是输入0幂时出错,一位是运行时非法访问报出和我一样奇怪的红色错误信息。测试刚结束时我hack成功5次,第二天又更正为3次,无奈入不敷出。
对象创建模式与重构
代码千万行,重构第一行。作为一名忠实的重构选手,我在每次编写程序时本着“杀鸡焉用牛刀”的思想,选择最简单、最容易想到但不一定是最合适的模式来创建对象、存储对象、使用对象,比如在能用int的地方我绝不用Biginteger(虽然这给我带来了一些麻烦),在第一和第二次作业中我都只采取了容器来存储对象,在遇到第三次作业的复杂需求时寸步难行,毫无拓展性可言。我当时只知道下一次作业有需要重构的可能,并没有主动预测下一次作业的具体要求是什么,即使从别人那里听到一点风声也没有及时止损,而是想着先把这次作业做完下周的事情下周再说。第三次作业我推倒重来了所有的类,虽然有了初步的面向对象思想,具有一定的可拓展性,但是由于上述设计的种种缺陷我想如果有更复杂的第四次作业还是免不了重构至少一半的代码。我现在认识到,要想在以后的作业中尽量避免重构,就需要逐步建立面向对象和层次化设计的思想,控制代码的复杂度和耦合度等,明确每个类的功能,学会做减法,并且积极主动预测用户需求的变化,在设计中留有余地。
对比与心得体会
和互测中发现的优秀代码以及课程组公布的优秀代码相比,我想我在许多方面的差距都是巨大的。这些代码虽然各自的风格不同,也各有各的优点,但有一点都是相同的,那就是层次分明、逻辑清晰,一看就知道每个类、每个方法、每一行代码是做什么的,读来赏心悦目,是美的享受。而我的代码就完全是一团乱麻,连我自己看都要费解半天,更何况别人阅读时的体验。具体来说,我的代码功能分配很不合理,也没有通过分包等手段把类管理起来建立层次化结构,变量方法等的命名也比较混乱,意义和功能都不甚明确。如果要我改进的话,我想不妨把预处理、判WF和化简等环节单独抽象成一个类,使它们之间的分工更加明确。此外,虽然我在debug中已经解决了tle的问题,但我仍然认为我的递归代码复杂度高,还有进一步优化的空间,这可能需要扎实的数据结构和算法知识,是我和大佬相比的硬伤所在。
对我而言,互测也是一个很好的学习别人优秀代码的机会,这些优美的代码既能一看就排除hack目标,也能观照自身反省设计缺陷,在下次作业中注意改进。和优秀的同龄人学习是提升自我的一条最快的途径。我想,课程组把7~8个人分在同一间ROOM中的用意也正在于此,广泛地阅读他人的代码,取长补短,在适度的竞争氛围中携手并进。
比起刚开学时,我对JAVA语言、对面向对象、对工程化等等的认识都有了一个质的飞跃,虽然过程有些痛苦,每周在各个环节都要投入大量的时间和精力,但是正如玉琢成器、蚌病生珠,我深知要想写出优美的面向对象代码绝非一朝一日之功,需要大量累进的练习,在高强度的OO课程和优秀老师助教同学的帮助之下,我相信我们都能快速地成长起来,攀上昆仑课程的一座又一座高峰。
跋
明代正德年间一位日本使臣游西湖后写过这样一首诗:“昔年曾见此湖图,不信人间有此湖。今日打从湖上过,画工还欠费工夫。”希望我在OO课程顺利结束之时,也能拍着胸脯笑道“OO还欠费工夫”。
庚子仲春 于金陵