OO第一单元总结

OO第一单元总结

1. 基于度量的程序结构分析

  • 第一次作业

    • (1)作业类图及设计思路分析

      • 本次作业实现目标为幂函数的多项式求导。在动手开始完成代码前,虽说本人也在一定程度上分析了题目要求,并对自己的程序相关实现方式和类的划分做了思考,但由于初次面对面向对象的思想,部分设计还是可能使得程序更加偏于面向过程。

      • 先来看下本次作业最终完成后生成的类图

        • 第一次作业类结构截图
      • 由以上类图,大体分析本次作业程序设计思路如下:

        • 类PowFuc,作为程序中幂函数的代表,仅含有一个indexNum属性,存储指数。
        • 类PolyTerm,继承自PowFuc类,作为变量项的代表,增加coefficient属性,存储变量项最前面幂函数前相乘的带符号整数,作为系数储存在此。
        • 类Polynomial1,包含arrPoly的ArrayList< PolyTerm >属性,用于储存包含多个变量项及常数项的最终表达式存储类。类Polynomial可忽略,为本人在优化程序前的此类的版本。
        • 类ReadTerm,用于读入处理,定义多个Pattern属性,用于正则表达式处理,同时可视此类为工厂类,此类包含根据读入生成Polynomial1类的方法,即创造方法。
        • 类MainClass,调用读入处理,对生成的表达式类进行求导方法调用,最终输出结果。
    • (2)根据数据度量分析程序结构

      • a、先来看看方法复杂度的相关分析:

        • 第一次作业方法复杂度截图

        • 解释一下相关参数的含义(此部分内容参考自讨论区):

          • ev(G)为基本复杂度,用于衡量程序非结构化程度。
          • iv(G)为模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。
          • v(G)为圈复杂度,是用来衡量一个模块判定结构的复杂程度,即合理的预防错误所需测试的最少路径条数。
        • 那么根据以上参数含义,分析本次作业代码发现,有三个方法的这三个复杂度较高,分别是Ploynomial.getPoly(),ReadTerm.getNum(),ReadTerm.getTerm(),所以可以知道本次程序分别在读入操作和获得表达式字符串的操作中有许多不当设计。

      • b、接下来进行类的度量分析

        • 第一次作业类情况截图

        • 同样,先来解释一下相关参数的含义:

          • DIT为继承树深度,如词面描述,该数值用于统计该类继承深度。
          • LOC为类行数,统计该类有效行数。
          • NOAC为该类新添加的方法数,用于统计该类除重写,继承外相对增加的新的方法数量。
          • NOOC为该类重写的方法数,作用如字面意。
          • NAAC为该类新添加的属性数,用于统计该类除继承外相对增加的新的属性数量。
        • 那么根据以上参数含义,分析本次作业代码发现:

          • 类之间的继承关系并不多,即DIT较小,虽然有第一次作业并不复杂的原因,不过主要是因为本人在当时还未意识到三次作业之间的重要联系,也未采用继承性较好的设计,所以这也就造成后两次作业写起来很麻烦的一个重要原因。
          • 重写的方法也并不多,可以看到,除了PolyTerm在有关求导的方法进行过重写,其他类均未进行重写,这主要是在设计方法的作用上。本人在后期进行优化时,将优化操作放进了获取表达式字符串的方法内,这也就造成难以重写toString()方法;而实际上应当将这两部分操作分开,并最终重写toString()方法。
    • (3)本次作业优缺点总结

      • 优点:

        • 在对表达式结构分析上,将读入的表达式结构分成每一个简单的幂函数相加,由此可在高层次的类中直接调用低层次的类,从而使得结构清晰,分层次求导也较为方便。
      • 缺点:

        • 第一次作业未能考虑为后面作业的拓展服务,因而在继承性上做的很差。

        • 部分方法的作用过多过杂,并未合理的将其分开为多个方面的方法,导致这部分方法非结构化程度很高,难以进行维护,同时设计复杂度也因此过高,导致模块的耦合度会很高。

  • 第二次作业

    • (1)作业类图及设计思路分析

      • 本次作业引入了更为清晰的项的层次,同时引入新的函数——三角函数。而在本次作业中,在本人自己看来,所写的程序虽在拓展性上仍有所提高,尤其是在继承这一特性上,但是在减少代码复用和降低代码耦合度这几方面相较上次已经有了很大的进步。

      • 同样,先来看看本次作业最终完成后生成的类图

        • 第二次作业类结构截图

        • 由以上类图,大体分析本次作业程序设计思路如下:

          • 首先设置两个基本类,Poly作为幂函数类,TriGoFunc作为三角函数类,均含有coe系数属性,Poly单含有一个x的指数属性,TriGoFunc含有sin和cos的两个指数属性
          • 其次设置由上面两个基本类组成的本次作业的一个基本单位类,MulTriPoly,任意一个表达式都可拆分为若干个该类的对象相加。
          • 设置最终的表达式类MulTerm,该类的实例化对象储存了最终的表达式。
          • 同样设置ReadTerm类,这次吸取上次教训,为减少耦合,去除了这部分多余的读入处理,使得此类与表达式类的关联减弱。
          • 最终设置MainClass类,面向过程的部分在此实现。
    • (2)根据数据度量分析程序结构

      • a、方法复杂度的相关分析:

        • 由于此次代码中的方法数量较多,所以这里不再提供图片,而是提供如下文件:
        • 首先来看基本复杂度,可以看到基本复杂度相对较高的方法一共有四个。
          • 其中有三个类的equals()方法,其实,个人感觉重写的equals()并不难以维护,目前怀疑是自己习惯性写法的问题,我写的方法中 if-else 结构较多从而导致该复杂度较高,而实际上逻辑并不复杂,维护也不困难。
          • 最后一个基本复杂度高的方法是MulTerm类中对字符串处理并获得表达式对象的方法,至于复杂度高,主要还是因为字符串处理过程中需要考虑较多情况,从而最终导致结构复杂,难以维护。
        • 接下来看模块设计的复杂度,可以看到复杂度较高的方法有两类
          • 第一类是MulTerm类的addTerm()和getMulTerm()两个方法,这两个方法中主要会大量调用下层类的构造函数,所以它会单方面的与下层类耦合度较高,从而导致复杂度较高。
          • 第二类是toString()方法,由于高层次的类会调用它所含有的低层次类的对象的toString()方法,因而导致复杂度较高。
      • b、类的相关数据度量分析:

        • 第二次作业类情况截图

        • 相关参数含义见上面的第一次作业分析

        • 分析结果如下:

          1. DIT的值全部为1,这说明过程中不涉及任何的继承操作,由此可见本次作业的很大一个缺陷就在于拓展能力不强,当然这也是我在第三次作业翻车的一个重要原因。
          2. NOOC的数值相较上次作业有所提高,这说明本次一个关键进步在于分清了各个方法界限,让他们各司其职。
    • (3)本次作业优缺点总结

      • 优点:
        • 本次作业程序同样较为清晰的分开了表达式的结构,将组成表达式的每一项都抽象成$$ a * x^b * sin(x)^c * cos(x)^d $$ 的基本形式,从而可以进一步将每一项分离为幂函数因子和三角函数因子,这样也大大减少了化简过程的工作量。
        • 本次作业还实现了读入,字符串处理,求导,化简,输出等多个过程在类中的分离,从而减少了部分类之间的耦合程度,使得本次作业在一定程度上实现了面向对象这一要求。
      • 缺点:
        • 仍然是在可拓展性上有缺陷,不论是读入合法性判断,还是后面的化为字符串过程,都难以实现进一步的扩展,这一点对高层次类的影响尤为明显。
  • 第三次作业

    • (1)作业类图及设计思路分析

      • 本次作业相较于第二次作业,并未引入新的函数类型,只是使得因子变得可变,即一个相当复杂的表达式,在套上两边括号的情况下,同样视为一个因子处理。那么由于我第二次作业并未在读入处理的方法部分完成这样的可扩展性,这就导致我在这次作业的读入中费了很大的劲来完善处理,甚至放弃了部分内容的优化。但是悲哀的是,我的读入处理最终在强侧中还是有问题。

      • 以下为本次作业最终完成后生成的类图:

        • 第三次作业类结构截图

        • 可以看到,相较于前两次作业,本次作业本人的程序类图异常的复杂,所以接下来的思路介绍,我会尽可能地在较少的文字内将其描述清楚

        • 由以上类图,大体分析本次作业程序设计思路如下:

          • 首先定义抽象类MyFunction,并使之带有coe的系数属性和getDerivative()的抽象求导方法。
          • 定义PowFunction,ComFunction,MulFunction,AddFunction,四个函数类,分别代表幂函数因子,三角函数因子,项函数因子,表达式函数因子,使这四个类均继承自MyFunction,这将利用多态特性,为之后的求导操作减少很多麻烦。
          • 建立工厂接口FunctionFactory,为之后建立各类函数的工厂做准备。
          • 建立PowFunctionFactory,ComFunctionFactory,MulFunctionFactory,AddFunctionFactory四个工厂,使这些工厂均实现工厂接口FunctionFactory,并以AddFunctionFactory表达式函数因子工厂为入口,使得读入字符串进入后即可获得最终表达式。
          • 最后设置读入类和MainClass类,串起整个运行过程。
    • (2)根据数据度量分析程序结构

      • a、方法复杂度的相关分析:

        • 同样由于此次代码中的方法数量较多,所以这里不再提供图片,而是提供如下文件:
        • 可以看到,大部分复杂度较高的方法名称前都带有get前缀,这个前缀在本人的程序中,大部分时间的作用是告知这是有关读入处理的方法。所以,可见在第二次作业中由于我并未考虑读入处理的可拓展性,导致这次的读入处理方法异常艰难,不仅基本复杂度较高,调试十分复杂,而且模块设计的复杂度同样高,几个读入处理方法耦合度高。
        • 而对于求导等其他方法,由于均继承MyFunction抽象类,所以这里的复杂度并不高。
      • b、类的相关数据度量分析:

        • 第三次作业类情况截图

        • 由上图可得分析结果大体如下:

          1. 由于本次采用了继承抽象类,以及实现接口等多种方式,所以本次作业的DIT值还是很正常的,这勉强算是一种进步吧。
          2. 部分类的LOC行数非常大,甚至有200行之多,可以发现这些类均涉及到读入处理,所以由此可以知道这次程序的读入处理是真的失败。
          3. NOOC维持与第二次作业基本相同,而在部分类有增多的情况,也同样说明本次方法的重写还是很有成果的。
    • (3)本次作业优缺点总结

      • 优点:
        • 本次作业采用了继承抽象类和实现接口的方法,并利用多态的性质,一定程度上为函数的总体对象操作简化了相当多的操作。
        • 本次作业采用了工厂方法模式,能减少程序耦合度,有利于扩展。
      • 缺点:
        • 最大的缺点就是读入处理,本次作业采用了不恰当的读入处理方式,在处理的过程中极易因产生bug而导致程序异常,或者错误生成表达式。需要管理的琐碎边界太多,难以通过随机化数据一次将所有bug跑出来。
        • 未能进行表达式优化,由于第二次作业在将表达式转化为最终输出的字符串的方法扩展性不佳,因而本次作业为避免输出表达式格式错误,本人难以进行表达式优化,导致性能分失分严重。

2. 个人bug分析及探测bug策略

  • 个人bug分析

    • 主要分析第三次作业bug
      • bug1
        • 特征:含有带符号的整数的乘法项,本人的程序会将其当成一个含加减号的表达式。
        • 问题所在:AddFunctionFactory类中读入处理方法对独立加减号的判定有误。
      • bug2
        • 特征:首项为三角函数,尾项为带括号的表达式函数或另外的三角函数的相乘项,本人的程序会将此相乘项当成一个整个的三角函数因子处理。
        • 问题所在:ComFunctionFactory类中读入处理方法对三角函数括号边界判定有误。
      • bug3
        • 特征:多括号的相乘或相加减的表达式,本人程序会超时。
        • 问题所在:AddFunction类中未能对简单的幂函数进行化简就去求导。
    • bug位置与设计结构之间的相关性:
      • 本次作业设计结构主要在读入处理方法上有所欠缺,自然bug出现的位置大多位于读入处理方法。可以看到,上述两个bug均出错在读入处理方法。
      • 本次作业设计结构在添加项进入表达式部分的内容缺失,如果添加了这部分内容,自然会使得化简过程方便许多,也就不会再出现超时的bug了。
    • 本次程序设计中的重要问题
      • 本次作业设计,应当独立设置读入处理的类,统一进行处理,这样能大大减少在工厂类中重复而又容易出错的许多操作。
  • 探测bug策略

    • 以下是本人一般采用的按时间先后采用的bug探测策略:
      • 使用之前在中测前测试自己程序的简要自动评测脚本测试他人代码。
        • 有效性:这类测试随机性高,主要测试一般性数据,如果对方代码大体没有问题,这种测试的有效性就不是很高。
      • 查看他人代码,分析部分易错点,手动代入测试易错数据。
        • 有效性:一般都是边界数据,如果没有分析错他人代码,一般都是肯定能让他人的程序跑出错的。
    • 之所以像前面说的,这个分时间先后,是因为只有当前一种难以出现错误时,才会采用后一种更加费时费力的方法。

3. 应用对象创建模式进行重构

  • 第一二次作业

    • 说明:之所以把一二次作业放在一起分析,是因为这两次均未使用工厂模式,但是采用了相似的方式。
    • 具体方式:在函数类中声明静态方法,在静态方法中进行创建对象的相关操作。
    • 重构说明:其实将这两次的创建方法改成工厂类并不复杂,只需要将原有的静态方法中的内容完全复制的到新创立的工厂类的方法中即可。当然在调用的时候,注意改成先调用工厂类。
  • 第三次作业

    • 本次作业的确采用了工厂方法模式来进行创建对象,但是最大的错误就是把部分的字符串处理内容也交给而利工厂类来进行处理,导致工厂类过于复杂冗余,复杂度过高。
    • 重构说明:可采用将字符串处理内容独立出来的相关方法,减少工厂类相关方法的复杂度。

4. 对比和心得体会

  • 问题分析

    • 在学习完优秀代码后,我大概对自己的代码设计进行了以下几个方面的问题分析:
      • 读入处理的代码部分结构混乱,没有采用一个清晰的类和几个清晰的方法进行有条理的处理。而这样的问题存在,自然会导致自己的程序容易出现bug,调试的复杂度也大大增加。
      • 个人认为,本次我采用的函数结构在一定程度上加大了化简的难度,AddFunction和MulFunction都是含有大量种类函数的函数类,种类多了自然很难化简,可以考虑采用优秀代码中的每两个函数之间一个组合的关系来对化简过程进行优化。
  • 本单元心得体会

    • 本单元最大的收获之一就是对面向对象的思想的理解有了进一步的深入,对抽象类,接口等工具的使用也变得更加熟练,这应当为以后的面向对象课程学习奠定了一定的基础。
    • 还有很重要的一点收获是,意识到了代码架构的意义。因为我们的三次作业是不断迭代演化的,如果真的不做好可扩展性的准备,那么每次重新来写真的不容易,尤其是当结构愈加复杂的时候,就比如这次的作业,本人在第二次作业的读入处理方法中没有考虑可扩展性设计,导致第三次作业重新写了这一部分,并且写的异常麻烦,痛苦的是还有bug。总之,下次再做第一次作业的时候一定要想到如何灵活应对接下来的作业的需求变化。
    • 代码能力进一步提升。尤其是当我在第三次作业中没有完全采用正则表达式而是通过复杂的手动处理读入数据后,不仅能够对代码的运行过程情况理解更透彻,还能更加快速的发现自己程序过程中出现的bug的具体位置。
    • 对容器和正则表达式的使用更加熟练。容器等工具相比于我们自己实现某种功能来说大都是非常方便快捷的,也算是体会到“不要重复造轮子”的思想的重要性了吧。
    • 以上就是本单元个人的简要心得体会。
posted @ 2020-03-21 11:01  OmedetoHe  阅读(172)  评论(0编辑  收藏  举报