第一次作业
一、程序结构分析
(一)Metrics分析结果
(二)类图
第一次作业虽然在Metrics分析结果中没有被标红的项目,但还是能看到其圈复杂度以及块嵌套深度较高。原因主要是在Polynomial类内的构造方法、addPoly方法和subPoly方法中,大量的if/else、for与while的嵌套使用。我把对输入多项式的格式验证分成三个阶段,第二个阶段就是在Polynomial类的构造方法中实现的,而且其中还含有调用Monomial类中的方法生成多项式的代码,所以该方法里的逻辑较复杂,不符合工程规范。此外,这次作业也没有进行代码复用。比如addPoly方法和subPoly方法实现基本一致,只是符号改变了。可当时图省事,就直接复制粘贴后改了改。
在类图里能清楚地看出,ComputePoly类的作用就只是容纳main方法,并没有被很好地利用。
二、分析自己程序的bug
在公测中,我第一个点没过,原因是我在处理系数为0的项时考虑得不周全。由于我在生成多项式的过程中做了升幂排列,而且保留了输入时系数为0的项,在进行多项式加减法时,我直接利用了类似归并排序的方法,对于两多项式指数相同的项进行加减运算,若计算结果系数为0就不存进新的多项式;对于其他项,进行运算后就按指数顺序存进保存计算结果的多项式里,但我忘了在这种情况下指数为0项要舍弃。并且在提交前,我只对计算结果出现系数为0项的情况做了测试,所以一直未能发现这个bug。其实若是在输出位置对项系数进行检查,可以很容易避免这种问题的产生。
在互测中,对于可能爆栈的测试样例,我的程序crash了。问题不算很大,但应该引起足够的重视。老师五次三番强调任何情况下程序都不应该出现crash的错误,而我第一次就出现了。主要原因是对正则表达式的理解不到位,没有去思考正则表达式匹配原理,所以不会想到它会由于递归层数过多而导致爆栈。而且当时不还太会写异常处理,就没有进行异常处理。第一次作业程序对于各种输入不合法情况只需要输出提示信息即可结束,我索性直接system.exit(1)结束程序,没有将异常往上抛并进行集中处理,其结果就是对未预料到的问题,我就束手无策了。
第二次作业
一、程序结构分析
(一)Metrics分析结果
(二)类图
第二次作业圈复杂度的平均下来没第一次高,但被标红了,因为main方法和Request类中的构造方法以及isHomo方法的圈复杂度太高。主要还是if-else嵌套层数太多导致的,但isHomo方法由于if-else语句里条件表达式过于复杂,也促使了圈复杂度的升高。
从类图上看,这次作业类的设计不够均衡,大部分的方法是集中于Request类,而且对于新建的异常处理类和Floor类也没有进行有效利用。之所以没有利用Floor类,是因为我考虑到输入是以请求序列的形式产生的,这就不需要Floor类发出请求了。而且在判断同质请求过程中,我将待执行请求与执行完的请求一一对比,判断是否同质。因此在Floor类里也没有设立button的需要。但很显然,这种处理方式非常偏向面向过程,忽视了类之间的交互,而且对后面电梯新功能的添加也带来了一定的阻碍。
二、分析自己程序的bug
这次作业公测点都过了。
在互测中,我一开始被报了两个bug。一个是error,另一个是crash。第一个是关于楼层号输入格式限制。由于作业指导书中并未严格规定楼层号输入允许前导零和正号,我就在说明里规定楼层号的输入只允许为1到10的整数,否则报错。当时想的是楼层号和请求时间的性质不一样,它类似于UP/DOWN,只不过是有10种选择。但后来仲裁结果是认定为bug的,因为助教通知过这种情况的处理建议向前一次作业继承,因此输入允许有前导零和正号,而我只关注到了“建议”两字,没有太在意这个要求。所以今后会仔细研读助教的所有通知。
第二个是对没有输入“RUN”的测试样例,我的程序crash了。这次是我考虑的异常不完全引起的。在本地Eclipse运行过程中,如果请求数量小于等于100,那程序会一直等待“RUN”的输入,但如果一直没有“RUN”输入,OJ评测机会直接结束输入,我使用的Scanner类中nextline方法由于无法读取下一行内容就抛异常,结果我没有catch,最后程序就崩了。不过好在助教要求测试结果应以本地测试且无手动结束输入(不输入ctrl+z)的结果为准,因此仲裁后,bug被撤了。但这给了我一个提醒,异常可能出现在任何我意料之外的地方,程序尽可能catch到所有可能出现的异常。
第三次作业
一、程序结构分析
(一)Metrics分析结果
(二)类图
这次作业出现了两个红色提示。毫无疑问,仍然是之前两次作业出现过的问题,这次还更加严重了。由于添加了顺路捎带功能,我调度器的实现写得相当复杂,不可避免地用了循环套条件,条件套循环的写法。而上次Request类里不规范的写法也被继续使用,并且main方法内还加入了输入合法性验证,显得臃肿不堪。在思路上,我用了指导书建议的方法。新建子调度器继承父类,子调度器负责将请求按要求进行排序,最后调用上次作业写的父类的Schedule方法执行请求,所以我对上次作业的改动不大,就多写了ALS_Scheduler类。但写得最差的就是这个类,为了正确地实现捎带的功能,我边写边改,因为总有没想到的情况发生,我不得不打很多补丁。最终的结果就是方法的逻辑相当混乱,而且代码的效率低下。
二、分析自己程序的bug
这次作业在公测和互测中都没有被找出bug。
互测找bug所采用策略
1.首先做的是用自己在OJ上添加的测试样例对对方程序进行测试。这些测试样例与分支树上的节点对应,但考虑的情况比公测稍复杂。比如第三次作业测试各种捎带情况时,将电梯能捎带的请求,同质请求判断融合在一起,将捎带请求复杂化,实现主请求需要从捎带集里不断更换。互测时,我发现了对方将需要捎带的请求错误地判断成同质请求而忽略。
2.其次就是浏览代码,重点查看在设计时容易出错的地方。在第一次和第二次互测时,我当时对对方正则表达式的使用有很多疑惑,怀疑它在对输入字符串进行匹配时,只是对子串进行检查,未能实现全字符串匹配,因此构造了包含正确子串的错误字符串的样例对程序进行测试。第一次作业是我多虑了,它对输入的验证是比较严密的。但第二次作业,对方就犯了这样的错误。
3.如果实在找不出bug,就只能用随机生成的样例进行测试。但从目前来看,这种方法的效率不高。因为请求序列的构建需要有一定的逻辑性,利用随机生成的样例无法有效地测试复杂捎带的情况。
心得体会
1.之前没打听过OO作业的细节,经历了第一次作业才觉得这门课对于我来说真不简单。我以为会想计组刚开始一样,有充足的时间让我们适应。可现实恰恰相反,我留出写作业的时间本来就不够,而且第一次作业还需要自学java基础语法以及正则表达式,所以不得不在星期二晚上熬夜做。可结果就是设计考虑得不够充分,公测和互测中都被发现了bug。后来的第二三次作业也同样反映出完成作业需要有合理的时间规划,需要留出充分时间考虑设计的正确性和有效性。
2.在代码编写方面最大的问题就是不够“面向对象”,太偏重解决问题的过程。我写的程序与C程序相比,无非是将函数当成类里的方法,在main里调用各种方法按流程完成任务。这很大程度上是在设计时思维方式的局限,我没有将解决问题的过程看成是信息在各种类之间的传递,而更多的是把精力放在了每个方法具体是怎么实现的,这带来的结果就是关键方法集中了过量的代码,复杂度急剧上升。所以在接下来的几次作业里,我会着重分析类之间相互关系,让方法尽可能均衡有条理地放在各个类中。
3.从Metrics对代码的分析结果来看,很明显我在这三次作业中完全没有考虑过分支和循环互相嵌套带来的后果。毕竟前几次练习目的在于对java语言和面向对象思维编程的熟悉,而对代码规范没有足够重视。因此阅读相关指导手册,规范编程习惯是我今后需要注意的。
4.第二三次作业中我在构造方法里写了过多的代码,使其变得较为复杂。可我是到了后来上课时,老师强调了类的构造方法应该尽量简化才知道有这项规范。因为其简繁情况会直接影响实例对象的创建是否繁琐。在项目开发中,构造方法要做到尽可能不抛异常,尽量不做复杂算法。