OO第一次总结
引子
虽然对OO这门课的大名早有耳闻,也做好了心理准备,但经过开学来这四周的磨练,不禁让我怀念起了去年计组的快乐时光(误)。即使是P7这种公认的玄学领域指导书,与目前OO的任何一份指导书相比也难分伯仲;即使是课上测试前一天开强测,也不会让我像这几周的周一周二那样熬到凌晨两三点。作为一位非酋玩家,分到三次大佬作业,直到现在分数还基本处于零和状态,怀着这样的心情写下这篇博客。
1.多项式加减运算
1.1程序分析
可以看到整体来看第一次作业的设计还算合理,但是部分方法复杂度较高,具体分析上面标红的方法发现这些这些方法都在通过正则表达式处理输入,由于该程序本身计算难度不大,主要的工作在处理输入上,再加上正则表达式处理输入本身需要很多行,导致了这些方法复杂性较高,但总体上讲这次作业基本实现了高内聚低耦合的目标,层次结构比较清晰。
1.2BUG分析
在第一次作业中,让我印象最深的BUG就属正则表达式溢出了。由于是临时学习的正则表达式,对正则表达式内部的处理机制并不熟悉,再加上自行构造过长的数据难度较大,前期写代码的时候没有注意这个问题,直到提交前一天晚上睡觉前,用自己写的python脚本生成了一个合格的(20x50)多项式把自己程序炸了才意识到问题的严峻性。接下来就是一晚上的极限操作,经过在网上搜索解决方案,发现是因为自己在正则表达式中使用了太多的嵌套,我原本使用了一个超长的正则表达式,把多项式匹配单项式匹配数字格式等这些一次性解决。这样的正则表达式在读到过长的输入,甚至是符合指导书要求的20x50的多项式时就会发生正则表达式溢出错误。最终我通过把匹配工作分为两部分,先匹配多项式两边的大括号,提取出每个多项式再匹配多项式中的单项式,从而解决了缓冲区溢出问题,也成功达成了OO第一次作业就熬到半夜两点的成就。
本以为改完这个bug就万事大吉了,就没再管这个de过bug的代码,第二天也用了全部的公测,但是事情并不像我想象的这么简单。分配测试任务后几个小时一个红方块就出现在了我的分支树上,一看测试样例发现是没有处理多项式前面的负号。回头看自己写的代码,原来是在半夜debug的过程中分部处理了多项式,由于第一个多项式的格式与其他多项式不同(第一个多项式前面可以没有符号),就把第一个多项式单独提出来处理。偏偏就在这个地方偷了个懒直接把处理后面多项式部分的代码拷贝了过来,又偏偏没有拷贝处理符号的部分,直接导致第一个多项式的符号默认为正。这也给我提了个醒,在debug的过程中可能会产生更多的bug。
第一次互评,就收到了某巨佬的作业(windows下pdf右键删除信息有漏洞,虽然在windows下无法看到信息,但是在linux下原形毕露,后两次作业大家注意到后就没有再出现类似问题),由于这份作业使用有限状态机来匹配格式,用哈希表来记录,在咨询了同样使用有限状态机和哈希表的室友后成功虎口拔牙发现了一个小bug。如果输入的某一个单项式系数为0,在哈希表上不会有显式的变化。可以在后面再输入一个指数相同的多项式也不会报格式错误。这也是使用哈希表的同学们都要面对的问题,可以通过对每一个指数增加访问位来解决该问题。
1.3心得体会
这个程序也是我使用Java写的第一个算得上规模的程序,在这次作业中使用到了Java正则表达式以及try/catch语句块,通过啃《Thinking in JAVA》我在这两个方面收获颇丰。通过使用catch(Throwable t)实现了写出永远也不会crash的程序,也基本掌握了使用简短的正则表达式来检查输入格式并从输入中提取需要的信息,相比那个状态极多的有限状态机节省了不少时间精力。
2.傻瓜电梯
2.1程序分析
本次作业在设计过程中,我希望能够完全模拟真实电梯的运行状态,但同时又受到课件上提供的设计模式的影响,最终综合起来就得到了上面UML图所示的这个庞然大物,从我设计的角度来看,各个类的分布基本均衡,每个类各司其职,但是其中有相当多的冗余部分,并没有起到实际作用,而且我为了让每个类管理好自己的属性,把对象到处传递,在UML图像上就展现出了十分复杂的关系网络,整体耦合度较高,但是每个类的内聚性还算合理,体现出我对面向对象的理解还不透彻,刻板地认为模拟真实情况就算是面向对象,而且过多的冗余代码导致这个不怎么复杂的程序被我写得看起来很复杂。
2.2BUG分析
在这次作业由于实现的功能较为简单,在我编写的过程中实际上并没有遇到多少bug,更多的时间是在应对频繁变化的指导书(再次吐槽指导书...)。这次作业我写的代码非常臃肿,有很多冗余的部分,虽说表面上又臭又长,但功能上讲我个人认为是没有问题的。之后的公测以及互测阶段的结果也证明了这一点。
而这次互测我估计又是拿到一位大佬的代码,用我构造的各种刁钻数据测试也没有出现任何问题,代码写的也很简洁明了,不像我写的那么臃肿,我认真学习了这位大佬的代码,发现从逻辑上确实是无懈可击,我也把其中一些思想用到了我下一次的作业中。但是估计是偷懒这个程序对于无效请求没有任何输出,错误输入只输出ERROR也不输出提示,报了个incomplete,算是实现了0的突破/(ㄒoㄒ)/~~。
2.3心得体会
这次作业涉及到的类众多,我也是经过了长时间的构思才开始写这个程序,但是并没有将构思过程中的想法记录下来,分析整理。最终由于自身的逻辑条理确实不是很清晰,对面向对象编程也不是很熟练,把多个想法混合实现在了一起,写了一堆臃肿丑陋的代码,其中有面向过程的、面向对象的、还有四不像的,比如在楼层类里增加了电梯间,希望通过楼层类的电梯间来调用电梯,也就是通过调度器调度楼层再调度电梯,后来发现太麻烦又改成了直接调用电梯,造成了电梯间这一引用实际上没什么用,这次的代码中还有相当多类似的问题,从上面UML图的复杂度以及程序的复杂度分析结果也可以看出这一点。这也让我下定决心在下次作业中大刀阔斧删掉那些没有用的东西,同时也让我注意到了提前做好规划的重要性。
3.ALS电梯
3.1程序分析
由于这次作业需要继承上一次作业的部分类,所以看上去UML图变得更加复杂了,但实际上并非如此。本次作业在上次作业的基础上进行了大幅度的简化,但是由于调度类需要实现的功能太多有一些不均衡。但这些改进也带来了另一些问题,比如我把作业2中楼层和电梯中的按钮全部转移到了新的调度器内部,虽然减少了运行过程中调度器与楼层和电梯的交互,降低了耦合度,但是这也造成了类的内聚性的降低。同时在这次作业中出现了一个god类ALS_Scheduler,这个类无所不知无所不晓,可以随意改变其他类对象的属性,这应该是在面向对象编程中极力避免的。
3.2BUG分析
这次作业相比前两次难度和复杂度明显提升,直观的表现就是如何捎带这个问题指导书自己都说不清楚(第三次吐槽指导书...希望不会有第四次)。在最终通过Git上的问答基本解答了心中的疑惑之后,吸取上次作业经验做了个规划,并学习了互测阶段拿到的那份作业,在第二次作业的结构上做了大规模的简化。通过实现电梯和楼层按钮,每次运行一层,并在调度器中实现主请求,等待请求队列和捎带请求队列,得到了一个较为令我满意的版本。然而问题来了,使用主请求以及队列有一个不可避免的问题,就是一层多个捎带指令的输出顺序混乱。看似是一个小bug但实际改起来却伤筋动骨,如果单独执行主请求势必会导致主请求与捎带请求的顺序难以严格按照输入顺序,但是如果抛弃主请求模式,又会导致当下整个调度机制的完全瘫痪。所以最终只得将主请求降级为捎带请求加入捎带队列,但保留住请求的引用,并在给每个请求增加序号在捎带队列中进行排序后执行。但是这个补丁打得仿佛是在拆东墙补西墙,de一个bug出一堆bug。究其原因是结构的改变导致了很多之前规划中并没有考虑到的问题,再加上代码本身已经相当复杂,修改过程很容易出现纰漏。总而言之,还是前期工作不足。只能用时间来弥补设计上的缺陷,最终通过熬夜爆肝,de完了所有bug,整体感觉就像是在补丁上打补丁。我还吸取第一次作业的经验,经行了完整的各项测试,确保没有新的BUG出现。不过这个代码改到最后可读性已经变的非常差,我自己几乎都看不懂(心疼一下互测阶段拿到我程序的同学)。
或许是很多同学都遇到了我上面的这种问题,也或许是由于我的运气太背(面向运气的编程对我不友好),在互测阶段我拿到的这份代码,虽然写的比我简洁一些,但同样可读性极差,几乎是不知所云,在各个类之间跳来跳去,跳的我眼花缭乱。想从readme入手理个头绪,发现readme中并没有有关程序具体实现方法的描述,无奈只得用我收集的和自行构造的近60个经过精挑细选的几乎是覆盖的测试样例轰炸了一轮,没有出现任何问题,然后又经历了读代码从入门到放弃的过程,最终第三次作业又以零和收场。
3.3心得体会
第三次作业可以说是真正体现出了面向对象编程优势所在。前几次作业也许可以用面向过程的方法写出来,但是这次真的很难想象不用面向对象的方法如何编写。但是由于我个人对面向对象的理解和掌握还存在很多问题,导致写出来的代码复杂度很高,维护困难,bug频出。这也是在将来的作业中需要重点改进的。
4.总结
从前三次作业结果来看,我自己的作业公测全过,三次被测人的公测也是全过,互测总共被找了一个bug,找到别人一个bug,找到别人一个输出不完整,总分+1。我也意识到了我在发现程序bug的能力上确实存在缺陷。大部分情况下都只能通过自己构造或是从同学们那里搜集测试数据进行测试,在测试中发现bug,然后再打补丁式地修改,这种现象随着作业难度的上升变得越来越普遍,同时修改的难度也在不断增大,debug带来的新bug也越来越多。
在互测阶段寻找对方的bug时,我也基本遵循着这一套路,用大量的测试数据进行狂轰滥炸,如果炸完没有出现问题基本就代表这个代码是真的没有问题了,之后我也会抱着侥幸心理去看看readme,看能不能找出一些自相矛盾的地方(比如像第二次作业的互评),如果上面两步都没有问题(比如第一次和第三次),就基本可以认定是大佬的作业无疑,就只能读代码,学习一下对方是怎么写的,同时看看有没有逻辑上的漏洞,(比如第一次作业就发现了这样的漏洞)。但是对于可读性差的代码,只能说读懂对方的代码比自己写代码还难,我表示直接弃坑(比如第三次作业),这也算是OO互评的一个bug吧,毕竟如果写出来的代码没有可读性,会使评测者无处下手,只能通过原始的狂轰滥炸来发现bug,大大增加了测试者的测试难度。
具体分析我前几次的代码,可以发现最主要的问题在于把各个类之间的关系编制得过于复杂,这从第二次和第三次作业的UML图上就可以看出来,如何实现高内聚低耦合一直是我的一个老大难问题,需要对我目前程序的结构进行彻底重构才能实现。在第四次课上我也进一步了解了什么是面向对象以及怎样才能写出面向对象的程序,我也已经做好了构思,在写多线程之前大刀阔斧整改我之前的程序。
最后盗一张图作为结语(逃...)