OO游记之三月篇
第一次作业
1.度量分析
根据本次作业的Metric图显示,有一项指标”McCabe Cyclomatic Complexity”标红,即为圈复杂度过高。经查询得知,圈复杂度是一种代码复杂度的衡量标准。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。圈复杂度主要与分支语句(if、else、switch)等的个数成正相关。
看自己的代码发现,自己在输入的时候采用了状态机的方法,根据当前输入字符的情况选择下一步的状态,这样循序渐进的方法势必会导致case与大量if-else的嵌套,因而导致读取时候圈复杂度过高,效率低下并且容易出错。
采用这种写法的缘由是当时自己刚开始接触java,对java包中许多函数的功能不了解。如果上天给我一次重来的机会,我一定会选择用split()方法对输入的字符串进行切分,再用parseLong()获取数字信息。没有充分了解学习java内置函数的使用方法,是我在java学习初期遇到的最坑的事情。
2.类图
在本次作业中,我大致是按照上课所讲述的思路构建了三个类,一个是多项式Poly类,一个是多项式计算ComputePoly类,一个是用于检查字符串是否符合格式的Check类,从功能上来说这次作业没有什么问题,但从面向对象的设计思想方面考虑,这次的设计还是存在些问题,比如没有对输入进行单独封装,使得主函数处在ComputePoly类中显得过于臃肿。对于该类图,带空心菱形的实心线表示聚合关系,是整体与部分的关系,即每个多项式Poly是ComputePoly的一部分。
3.BUG攻防
这次作业的功能很好实现,因而格式检查成了重点。在本次作业中,我细致地按照BUG树进行了测试样例的构造,由于最开始的设计没有什么大问题,所以在自我测试的过程中也没有进行过多的修改以及debug。唯独有一个测试样例,就是所有数字都取最大值的时候,会由于正则匹配递归过深而导致爆栈,经过思考之后,决定将最开始的一个正则表达式匹配一整个式子改为先用一个正则匹配除大括号及大括号以外的内容,在用一个正则匹配大括号里的内容。这样修改之后,压力测试基本可以保证通过。最后互测中也没有被查出bug。
在测试别人的时候,我先是将自己的测试样例全部跑了一遍,那位同学已经错了四个不同的点,细看代码之后,发现他对于好些种情况,特别是一些边界情况的考虑不到位,因而才产生了这样的结果,也算是给自己之后的作业提个醒。
第二次作业
1.度量分析
同第一次作业的原因一样,自己在输入的时候对电梯请求分别进行了多种错误类型的判断,因而使用了大量的if-else结构,不过对于这种情况自己目前依然不知道使用怎样的设计可以避免,或许把情况判断拆分一下放在各个地方会好点?
2.类图
这次的设计总体来说基本符合面向对象的设计思想,按照电梯、楼层、请求、请求队列、调度器进行了类的划分。Elevator类用来执行请求,记录电梯当前的运动状态、所在楼层等;Floor类用来记录该楼层灯的按钮情况,包括时候亮起,熄灭时间;Request用于存储请求;RequestQueue类用来模拟请求的动态数组;Scheduler类用来调度请求并传给电梯执行;Check类用来检查输入格式。在此我才感受到了面向对象的好处,每个类各司其职,众多类协助配合,将一个任务按模块进行划分,以达到提高效率的目的。本次作业不足之处在于,没有将输入输出的主函数从Scheduler类中分割出来,使得在第三次作业继承时还得把主函数部分注释掉,代码的复用性没做到最好。类之间的关系与上次相同,多为聚合关系,由部分构成整体。
3.BUG攻防
本次作业不同于上次作业,需要将格式检查与功能实现并重。好在傻瓜电梯还是比较傻的,只需要按照先入先出的原则同时过滤掉同质请求,就能将功能正确完成。在代码完成之后自己按照BUG树分别构造了测试样例,以及生成了大量的随机数据进行测试比对,没有出现什么问题,后来的互测也平安无事。
在测试别人的bug时,按照bug树小样例测试——随机数据测试比对——读代码“量身定制”的流程,很顺利地没有找出bug。对于Readme中的特殊情况处理也都符合要求,最后我就绝望了,以互交白卷结束这次评测。
第三次作业
1.度量分析
由于和上次采用了相同的输入格式检查,无可避免地导致了圈复杂度再次过高,不仅如此,本次作业需要捎带处理的情况很多,if-else就成为了遍布代码各处的操作,这样看来好像没太好的优化办法。
不同的是,本次作业中的Nested Block Depth也标了红,查询可知是嵌套块过深的缘故。查看自己的代码之后,发现自己在寻找捎带请求的时候使用了多个if嵌套结构,要优化的话,应该把多个深层if条件中的判断放置在浅层当中,会对情况有所改善。
for(int i=0;i<req_queue.size();i++) { tmp=req_queue.get(i); flag_with=false; if((tmp.time_get()<(this.time+(Math.abs(master_r.floor_get()-this.elevator.floor_get())*0.5)) && (tmp.time_get()<(this.time+(Math.abs(tmp.floor_get()-this.elevator.floor_get())*0.5))))){ if(tmp.type_get()==Type.ELEVATOR) { if(elevator.motion_get()==Direction.UP && elevator.floor_get()<tmp.floor_get()) { if(tmp.floor_get()>master_r.floor_get()) { if(this.flag_next==false) { this.flag_next=true; this.next_r=tmp; this.index_next=i; } }
2.类图
本次作业与上次作业的差异只在于功能部分,因此只需要对调度器中的方法进行重写便可完成。类的设计思路与上次大体一致,增加了电梯的接口(虽然暂时不知道有什么用),用ALS_Scheduler继承了上次的Scheduler(虽然觉得这两个调度方法完全不一样不知道有什么继承的必要),并且从调度器中分离出了输入,可以说是更符合面向对象的设计思想了。本次作业的类图中,除了较多的聚合关系外,还有由带三角箭头的实线表示的泛化关系,是一种继承关系,ALS_Scheduler继承了Scheduler类,Elevator继承了Elevator_Move接口。
3.BUG攻防
这次的debug之路异常地艰辛,充分体现出了写代码五分钟,debug两小时的感觉,得好好说说了。
自己在经过不那么严谨的思考和设计之后,匆匆写完了第一个版本,只能进行简单的捎带,然后在后来逐步测试的时候,问题就不断涌现了,每当发现一个自己当初没考虑的情况,都得通过添加if-else来进行条件判断。在修改这个bug的时候,有时候会专注于解决当前的问题而对其他已经设计好的东西再一次造成破坏,通过一步步的调试才慢慢发现自己的问题所在。自己因为几个条件中多加了等于号而痛苦de了一小时bug。后来又用自己程序的结果和同学的结果进行比对,发现很多不一样的地方,通过不断地调试才发现问题所在,回头看来这些问题都是写的时候没有充分考虑情况,在测试的时候才涌现除了大量的bug。可以说是非常难受了。即使是在和同学进行了上万条数据地对拍之后,又偶然发现了自己的很特殊的bug,除非是细察代码,否则通过测试数据很难找出。所幸的是,在互测阶段没有被找出bug。
不过不幸的是,我对所测试者的代码先进行了很细致的小样例评测检查,然后跑了上百万条指令,都没有出错。自己最后耐着性子读了他不是很清晰的结构,也没有什么收获,再一次以互交白卷结束一次作业。
找bug之路
1.Readme
拿到一份程序,当然是从它的Readme开始,从Readme撰写的情况也能大致体现出作者的能力值。我阅读Readme主要是看他对一些指导书中未明确说明部分的描述,以及他的测试要求,这算是前提条件了。
2.bug树测试
自己在编写测试样例的时候大致是按照bug树编写的,这一部分也就是把样例挨个跑一遍,有错误的话应该就可以直接上报了
3.随机大数据测试
既然小样例没测出来,只能随机轰炸了,用大量数据进行测试,与自己的结果比对输出,大概测个那么百十万条,如果还是没错误,就只能开始下一步了。
4.阅读代码
其实前面的测试都属于黑箱测试,简单粗暴,但按课程组设想好像更倾向于我们通过阅读代码的方式对程序中的bug进行精确打击。在第一次作业中,我查看了下被测代码的正则,很明显就有问题;在第二、三次作业中,虽然我看懂了他的代码,不过并没有发现其中的问题。个人认为这个过程是十分需要功力的,除了一些很明显的等于号什么的,要发现对方逻辑上的错误不是那么容易。
心得体会
1.经过三次作业,自己也慢慢了解了面向对象的思想,不过还有很多博大精深的东西需要去探索
2.一个好的设计是多么的重要!!!一定要想好了再开始写,这样可以减少很多调试debug的时间
3.Readme一定要好好写,毕竟找bug是按照Readme来的
4.还是不要有拖延症的好
PS.从周围的情况及朋友圈来看,这门课还是存在着个别问题的,比如对于无效作业的判定。课程组视个人信息为无效本身是为了避免一些低质量的作业因为人际关系等原因而免遭扣分。个人认为一份公测AK,设计良好的作业,因为忘删pdf中的个人信息而被判定无效是很不合理的行为。
PPS.希望这门课在大家的共同努力之下建设地更好!