OO第一单元总结
目录
作业总体分析
代码结构分析
遇到的bug问题
找到bug的方法
结语
一、作业总体分析
尽管这个单元三次作业都是表达式求导,但我认为每次作业的侧重点是不同的。
对于第一次作业而言,这是我们OO课程的首次作业,所以从难度上讲并不大,仅仅需要完成对幂函数和常函数的求导,其中最主要的部分是完成对表达式的输入处理,即检查出正确的输入格式,这部分的完成情况对于互测是非常重要的。
到了第二次作业,这次的作业内容在第一次作业的基础上增加了对sin(x)和cos(x)的求导。这次作业中大家明显已经对表达式输入的处理有了经验,因此我认为这次作业的重点应该放在优化上。在优化部分,我也尝试着进行了sin(x)^2+cos(x)^2的优化,但是还是有些情况没有考虑到。
第三次作业难度再次提升,在原来的基础上增加了表达式因子和sin、cos嵌套因子的求导。在这次作业中,我认为重点又转移到了如何构造好的继承关系,从而通过递归来解决表达式因子的求导。在这次作业中,我由于多加了优化部分导致CPU运行时间过长,这点会在后续Debug部分进行说明。
经过这三次作业之后,我对于表达式字符串的输入处理有了一定的了解,从第一次作业到第三次作业的代码结构也是有了更明显的划分,但我对于继承关系运用还是不够熟练。
那么先来看一下我这三次作业的代码分析。
二、代码结构分析
1.第一次作业
1.1.类图
包含2个类,6个方法
在这次作业中,我还没有习惯面向对象的思维,所采用的方法类似于面向过程,也就是将所有方法聚合在同一个类中,这就大大缩减了代码的可塑性。
1.2.复杂度分析
在Expression模块中,我进行了多项式匹配以及求导操作,由于方法的冗杂和String类型函数的滥用导致该模块的复杂度较高。
2.第二次作业
2.1.类图
包含4个类,45个方法
在这次作业中,我将代码分成了四个模块,UML图也体现了其中的依赖关系。结构比较清晰,因此这次作业也是我这三次作业中感觉做得最好的一次。
2.2.复杂度分析
Expression与Item类中,做了对表达式字符串的格式处理,以及对每个因子项Factor的排序并进行化简,在一定程序上选择排序算法与sin(x)、cos(x)增加了时间复杂度。
3.第三次作业
3.1.类图
包含9个类,76个方法
这一次的代码结构相较前两次而言复杂很多,但是层次感还是比较明显的,整体呈现一个倒三角结构,其中也涉及到了下级对上级的调用。
3.2.复杂度分析
本次代码复杂度出现的问题还是同前两次的问题一样,因此我特地去查了一下出现这种情况的原因。
4.代码度量总结
OCavg和WMC的指数是与方法复杂度相关的,而在方法复杂度计算中,出现了三个指数,ev(G)、iv(G)、v(G),其中:
ev(G)是基本圈复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解;
iv(G)是模块设计复杂度,与模块与其他模块的调用关系有关,即复杂度高意味着模块耦合度高,将导致模块难以隔离和复用;
v(G)是圈复杂度,与代码中if、else的使用嵌套层数有关。
那么在我第三次作业中,iv(G)出现问题的几个函数都是与嵌套因子、表达式因子的递归求导有关。我发现这其中也涉及到了对ArrayList的多重调用问题。
除此之外,StringBuilder类的delete、append函数的使用,在一定程度上对ev(G)产生了影响。
三、遇到的bug问题
1.replaceall之用法
在学习正则表达式后,我首次接触java中有关regex的函数是replaceall,我在第一次作业中通过这个函数进行表达式输入格式判断,在使用过程中也遇到了一些问题:
在replaceall对字符串处理的过程中,是对字符串进行依次遍历的,也就是说当匹配到合适的字符串时会直接将其替换成相应字符串,再从字符串尾部继续遍历,这样也防止了可能出现的“死循环”。那么在第一次作业中,我通过replaceall有效解决了爆栈的问题,但是在replace的过程中出现了逻辑问题,即先replace变量项的regex与后replace常量项的regex发生冲突,导致形如x+++x+1的式子,我先replace掉变量项x和++x,后replace掉++1,这样就导致错误的格式并没有被检查出来。
2.字符串与ArrayList的操作
在作业进行合并同类项的过程中,会用到ArrayList的remove操作,在通过迭代的方法对ArrayList进行遍历的过程中,我发现如果remove掉ArrayList的最后一项,那么在下一次迭代过程中会出现非法访问内存的错误。原因是在删除最后一项后,下一次迭代会再访问ArrayList原先那项的地址,而这个时候这个内容是不存在的,所以出现访问内存错误的情况。因此,我将for(Factor temp : Itemfac)的迭代方法改成了for(int i = 0; i < Itemfac.size(); i++),在删除一项内容后及时进行i--的操作。
对StringBuilder中deleteCharAt的操作也是如此,且需要在字符串操作之前判断一边该字符串是否为空串。
3.阅读题目需仔细
从这三次我对bug的分析,其实大多数问题出现在对于指导书的要求认识不够彻底。比如说第一次作业指导书:在本次作业中,空白字符包含且仅包含<space>
和\t。在我看到这句话时,与身边的同学讨论了这句话的含义,有同学会觉得这是一个提示而并不是需求,导致使用\s来进行匹配,从而被别人hack到。因此对于题意的理解时非常重要的,我在写代码之前也会先花一个早上把题目的关键信息列举出来,构造好结构之后再开始动手,这样就能够避免因读题不仔细而犯错的情况。
四、找到bug的方法
我在找bug方面并不是一个好手,在第一次作业中则是通过阅读别人的代码来查找bug,这种方法从效率上来看远不如其他方法。但是在这次阅读代码的过程中,我发现别人代码的优点并且在第二次作业中借鉴使用。因此,在后面的作业中,我会先编写好测试数据,再读一些优化做得好的同学的代码,从而做到自我代码水平的提升。(与其说拼死拼活找到别人代码的bug,不如先完善自己的代码,做到万无一失)
而编写数据方面,我采用了讨论区所推荐的Xeger库对正则表达式自动生成表达式,那么这种方法是不能找到WRONG FORMAT的,因此不得不自己想一些比较刁钻的数据进行测试。
PS:在讨论区中,有大佬介绍了通过对拍器的方法同时对多个同学进行测试,这种方法大大缩短了互测的时间,因此我也会尝试在下个单元进行使用。
五、结语
在这三次作业中,我可能大多数情况都是采用了面向过程的思路,并且对于继承与接口的理解不是很深。因此,我计划在这周的空闲中好好学习一下有关继承方面的知识,并且在后面的作业中尝试使用。除此之外,每个单元第一次作业都需要做一个比较完善的构思,这样才能避免后续出现代码重构的问题。
那么经历了第一单元的学习,我对面向对象课程也有了初步的认识,希望在后续的学习中能够真正领悟oo的真谛!