OO第一单元总结

OO第一单元总结

        这是oo课的第一个单元,也是我写的第一个博客。无论是在对象的构建,还是数据测试等方面,我都还刚刚入门。这次的博客就权当对之前一段时间的总结,也希望其中总结的经验教训可以发挥更大的作用。

第一次作业

    设计思路:

        第一次作业的主要要求从功能上分析为实现多项式的读入、解析、最后进行求导、输出。所以如果落到类的构建上,我首先设计了一个名为H1的class,作为顶层的模块,控制输入输出等;其次是叫做polynomial的类,里面的方法包括了解析、求导、以及输出;最后是一个term类,里面存储的是多项式的每一个项,具有成员变量系数和指数,并且也实现了输出、求导、解析的相应方法。

    UML类图:

        

    度量分析:

        经过分析,复杂度主要产生原因:

        1、由对系数和指数部分的解析产生;

        2、由在相加合并同类项的时候,多次循环遍历产生;

        这次的作业比较简单,复杂度也不是很高,但是我还是没有优化到很好的程度,并且可扩展性差,为下一次的重构埋下了伏笔。

    BUG分析:

        由于这次作业复杂度比较低,所以我在强测和互测阶段均没有被找到问题。但是我发现了别人有一个bug,这个bug在于他并没有使用BigInteger来存储系数和指数,而是采用了java.long。这样刚好可以通过中测的数据,但是当互测时,遇到真正很大的数字程序就会抛出异常。

第二次作业:

    设计思路:

        第二次作业的主要要求与第一次的主要区别在于加入了三角函数的混合运算,过程仍然是读入、解析、求导、输出。但是由于我之前一次作业没有很好地做到面向对象,所以遇到了扩展性的严重限制。我在之前一次作业中,一项内只对应了一个系数一个指数,这就导致我在本次作业中,完全没办法沿用之前的设计,只能采取重构。究其原因,是因为我把项和因子这两个东西混为一谈了,这是之后构建对象的时候应该引以为戒的。

        所以在这次设计中,我采用了如下的类设计:H2作为顶层模块,控制输入输出等;然后建立expression这一class,作为表达式的类;Term这一class作为表达式各项的类;Factor作为Term中各个因子的抽象接口,分别由常数类(const)、三角函数类(sin、cos)、多项式类(poly)实现。这些class中也均有相应的化简(simplify)、求导(derivative)、toString等必要方法。

    UML类图:

        (为了简洁,我略去了其中极其复杂的调用关系)

    度量分析:

        利用idea自带的分析,得到了如下的分析:

        总的来说,耦合度复杂度并不算高。我仔细看了复杂度较高的部分,发现主要的原因在于:

            1、判断是否是同类项时,首先要判断A的因子全部是B的因子,在判断B的因子全部是A的因子,这是很消耗资源的;现在想想实际上不必要,因为如果是化简好的,只需要判断A的因子全部是B的因子并且AB因子数相同就可以了。

            2、在H2中,存在一个大的状态机,这也会显著提升复杂度,但似乎没有很好的解决方法。

            3、工厂模式的复杂度似乎也比较大。

    Bug分析:

        这次的设计中,我由于过分追求优化出现了一个bug,就是在进行化简合并的时候,判断是否能够合并的方法出现了问题,在某种特殊的情况下,会把不能合并的项误判为可以合并,这就导致了一些错误的结果。

        对于别人,也存在一些bug,比如符号的处理不正确(如---1等)。

第三次作业:

    设计思路:

        这次作业主要的不同在于允许了表达式的嵌套。从过程上来说,仍然是输入、解析、求导、输出的过程。然后在类的设计方面,我的改动不大。我将嵌套视为另一种因子,即表达式因子(ExpFactor),这样一来如(x+1)*(x+2)的表达式就可以和之前一样进行解析了;至于sin和cos之中的嵌套,则可以在其中添加成员变量son来解决,然后重写求导方法即可。

        这次作业还涉及到了WF的判断,我的思路是:将WF分成两类,一种是空格位置错误;另一种是其他WF。为了使得我之前的解析方法仍然适用(即删掉所有空格),我将第一种错误的判断提前到了最开始,如果没有这种错误,才进行接下来的判断和解析;而第二种错误则放到解析的过程中抛出FormatException。我觉得这种方法还是很好的,避免了大面积重构的同时,也比较符合高聚低耦的原则。

    UML类图:

       ( 为了简便,省略了复杂的调用关系)

    度量分析:

        在这次的作业中,我写的很难受,因为出现了很多始料未及的的情况,这也就导致了复杂度的显著上升。

        1、在Expression类中,因为要考虑是不是要加括号来保证格式合法,就必须访问里面Term的成员,这就导致来来回回访问,复杂度提高。

        2、在解析表达式的时候,同样是为了括号的问题,由于我在之前设计的时候,直接会对表达式进行化简处理,所以原本的输入就丢失了,不能判断之前是不是WF,所以我只能采取了一遍检查格式,一遍解析的方法,总共对输入遍历了两次,非常复杂。但事实上,如果建立一个参数来记录初始的数量,又会导致copy方法的大幅重写,可能会产生很多Bug。所以我当时考虑再三,选择了这种牺牲复杂度的方法。

    Bug分析:

        这次我出现了一个bug,在于没有理解好题意。就比如sin(- 1)这种写法的含义是里面有两个因子,是错误的格式,但我直接把它判断为一个因子了所以产生了误判;相似地我也把sin(+x)判断为正确格式了。

        这次别人也出现了一些bug,比如有几个人对于多个正负号加上空格的表达式会给出错误判断(WF)但是事实上这些是正确的样例,这可能是他们的正则表达式中出现了一些问题。

思考

    应用对象创建模式

        从第二次起,由于学习了工厂模式,我就在程序中加上了利用工厂模式构建Factors的方法,感觉这样的方法非常好,一方面可以减少代码量,简化程序;另一方面也使得封装性更好,程序可读性加强。

    本地测试

        由于很多大佬的分享,我也逐渐意识到了本地测试的重要性,所以在测试的时候,我采用了sympy进行比对测试。但是必须要说的是,这样的检测仍然不能保证一定是正确的,一方面当然有数据覆盖性不好的情况;另一方面,如果自己本身对题目的理解就有错误,那么这种问题就是不能通过这个方法检查出来的,这也就体现出和同学讨论的重要性;用sympy测试还有一个隐性的bug,就是如果输出的导函数不符合题目规范的表达式但是仍然具有合理的数学形式,这种错误也是没办法查出来的。而针对这种问题,我采用的方法是:把结果要输出的字符串再作为输入进行一次解析,如果没有异常,就说明了解析和输出格式上的一致性。

    自动机

        在写代码的时候,越是到后面的作业,我越发意识到,采用大正则可能会爆栈而且会有很多难以预料的bug。所以在后面的作业中,我采用了自动机加栈的方法,也就是只解析当括号栈空时的表达式部分,这样我发现写起来会得心应手很多。

    求导方法

        在阅读大家的很多代码时,我发现很多人求导的方法都是新建了一个类,专门负责求导用;而我是将求导看作各个类中的一个方法。我觉得他们的方法是很有道理的,我在设计的时候主要想的是式子和求导它们在逻辑上是紧密相连的,所以就把求导视为类中的一个方法。但是他们在进行单独的求导时,就可以通过同样的函数名对所有类型的无论表达式、项还是因子进行求导,是一种很好的想法。

    发现Bug的思路

        在寻找Bug方面,我采用了评测机随机生成数据和自己手动测试边界数据、异常数据的方法。总的来说,每次我都可以通过这样的方法在互测阶段找到别人的bug,但我感觉这样的方法还不是效率最高的,我也在进一步摸索相关的办法。

对比与心得

    在阅读了一些优秀代码之后,我发现很多大佬都采用了二叉树形式的表达式树构建。这种方法确实非常简便,尤其是在解析、求导、输出等方面确实复杂度很低;但是这种方法在化简方面则非常复杂,他们的代码在这些方面也确实非常值得我的借鉴和参考。也有优秀代码采用和我相似的自动机、栈的方法,他们的思路虽然和我相似,但是不得不说,他们的代码风格确实要优于我,我也从中学到了很多的技巧。

    最后是个人的一点心得体会,我感觉OO课确实还是很有难度,也对我们的编程能力提升很大的一门课,我还是应该更努力地去学习。另外,在写代码的时候,我往往会为了追求速度而忽视掉一些代码的细节,比如注释,风格等等。这些问题虽然看似无足轻重,但是很可能导致后面编程的时候思维出现漏洞,从而写出bug来,这是我将来需要避免的。

原文地址:https://www.cnblogs.com/kzxzvbk/p/12532867.html