OO UNIT 1 个人单元总结

面向对象课程——第一单元个人总结

作业分析

第一次作业
  • 概要

    本次作业主要对简单幂函数的多项式进行求导计算,要点在于对输入字符串的处理,利用正则表达式匹配即可,并且需要对输出表达式的长度进行优化。

  • 度量分析

    Package Name Type Name Method Name LOC CC PC
    buaa.lcy Main main 7 1 1
    buaa.lcy PolyProcessor PolyProcessor 9 1 0
    buaa.lcy PolyProcessor processExpress 18 5 1
    buaa.lcy PolyProcessor printDerivative 10 3 0
    buaa.lcy PolyProcessor ratioProcess 23 5 2
    buaa.lcy PolyProcessor expProcess 10 2 1
    buaa.lcy Poly Poly 3 1 0
    buaa.lcy Poly addNewTerm 9 2 2
    buaa.lcy Poly calcDerivative 47 11 0

    从方法的度量数据中可以看到,本次作业对方法的代码行数和参数都控制的较为合理,大部分方法的CC复杂度都保持在较低的水平,然而对字符串处理的processExpress方法,以及计算导函数的calcDerivative方法在复杂度上都很高,由于calcDerivative方法中将计算导数与输出信息合并的原因,复杂度异常的高,这也是设计中的缺陷所在。

    Package Name Type Name NOF NOM LOC DIT LCOM FANIN FANOUT OCavg
    buaa.lcy Main 0 1 9 0 -1 0 1 1.0
    buaa.lcy PolyProcessor 2 5 74 0 0 1 1 4.7
    buaa.lcy Poly 1 3 62 0 0 1 0 3.2

    由类的度量数据中可以看到,本次类的属性、方法的规模都保持在合理的大小,而方法的平均复杂度有些高。同时,由于暂时没有复杂数据,并没有设计类的继承和接口的实现,而类之间具体的关系,将在UML中分析。

  • UML类图分析

    在此次作业中,Main类主要负责IO的读入、PolyProcessor类的对象的创建和执行,是程序的“启动器”。PolyProcessor类主要用来生成Poly类,并对字符串分析存储进Poly的实例化对象中,最后对Poly对象求导并输出多项式字符串。

  • 公测、互测中个人遇到的Bug

    本次公测分数为100分,但在互测中被hack到一个关于正则表达式匹配的bug,此问题出现在PolyProcessor类中的processExpress方法中,但主要原因在于,对于x的系数的省略情况下,未考虑- x的情况,即无法匹配符号和自变量x中间存在空格的情况,其实发生这种情况的原因在于没有仔细看指导书,本以为这种情况为非法的数据。

  • 互测中发现他人的Bug

    互测中发现两种类型的bug,第一种为正则表达式匹配的错误,在匹配中丢失例如- - x中开头的符号。第二种bug为在大量简单幂函数组合的多项式分析中,出现正则匹配爆栈的情况。

    这两种bug的发现基于两种不同的策略,前者主要依靠对被测试程序源代码中正则表达式的分析、以及求导的逻辑代码的检查,需要真正深入对方的代码,大致走一遍他的思路,并针对出现错误可能性高的部分进行代码分析和测试。而后者主要来源于一些特殊的例子,有针对的检查一些可能出现的错误。

  • 对象创建说明

    本次作业的需求简单,因此没有在设计上进行过多的考量,仅对数据逻辑和处理逻辑进行了类的分离。

第二次作业
  • 概要

    本次作业增加了对于三角函数sin(x)、cos(x)的计算,并且因子之间的连接方式在以往加减的基础上增加了乘法运算,同时,需要对数据的正确性进行判断,在性能方面也有了更多的考量,例如sin(x)**2+cos(x)**2可以优化为1,在数据类型复杂的基础上,运算逻辑也变得更加复杂了。

  • 度量分析

    Package Name Type Name Method Name LOC CC PC
    com.poly.utils ItemFactor create 19 6 4
    com.poly.utils StringProcessor StringProcessor 10 1 0
    com.poly.utils StringProcessor matchString 3 1 1
    com.poly.utils StringProcessor expProcess 10 2 1
    com.poly.utils StringProcessor signProcess 35 10 2
    com.poly.utils StringProcessor constProcess 16 3 1
    com.poly.utils StringProcessor judgeMatchValid 11 3 3
    com.poly.utils StringProcessor isNotValidFormat 12 4 1
    com.poly.tools Power Power 5 1 3
    com.poly.tools Power printItem 22 6 0
    com.poly.tools Item printItem 0 1 0
    com.poly.tools Item calcItemDerivative 25 6 1
    com.poly.tools Item canMultiply 11 3 2
    com.poly.tools Item clone 0 1 0
    com.poly.service Main main 25 3 1
    com.poly.service Func Func 4 1 0
    com.poly.service Func addNewItem 14 3 1
    com.poly.service Func addFunc 3 1 1
    com.poly.service Func calcFuncDer 23 4 0
    com.poly.service Func printFunc 13 4 0
    com.poly.service Func isNotEmptyFunc 3 1 0
    com.poly.service Func getExpsAndRatio 22 7 0
    com.poly.service PolyProcessor PolyProcessor 3 1 0
    com.poly.service PolyProcessor processExpress 52 10 1
    com.poly.service PolyProcessor calcDerivative 3 1 1
    com.poly.service PolyProcessor simplifyExpression 3 1 1
    com.poly.service PolyProcessor printExpression 10 3 1
    com.poly.service Poly Poly 3 1 0
    com.poly.service Poly addNewFunc 12 2 1
    com.poly.service Poly calcDerivative 10 3 0
    com.poly.service Poly simplifyPoly 33 7 0
    com.poly.service Poly printPoly 47 10 0

    从方法的度量数据中可以看到,PolyProcessor类中的processExpress方法,StringProcessor类中的signProcess方法,以及Poly类中的printPoly方法,这些方法都涉及大量的字符串处理,因此需要判断大量的情况,逻辑复杂且不易扩展,但这块部分我在设计中尽量保证其固定性,以此保证正确性。

    Package Name Type Name NOF NOM LOC DIT LCOM FANIN FANOUT OCavg
    com.poly.utils ItemFactor 0 1 21 0 -1 1 5 6.00
    com.poly.utils PolyFormatException 0 0 2 0 -1 5 0
    com.poly.utils StringProcessor 1 7 100 0 0.85714 2 1 3.43
    com.poly.tools Power 0 5 49 1 -1 3 3 2.60
    com.poly.tools Cos 0 5 43 1 -1 4 2 2.20
    com.poly.tools Item 3 10 61 0 0.3 5 4 1.88
    com.poly.tools Sin 0 5 43 1 -1 4 2 2.20
    com.poly.service Main 0 1 27 0 -1 0 4 3.00
    com.poly.service Func 2 13 122 0 0 4 2 2.38
    com.poly.service PolyProcessor 1 5 74 0 0.8 1 5 3.20
    com.poly.service Poly 1 5 108 0 0 3 4 4.60

    从类的度量数据可得,类的规模都保持在一个不是很夸张的程度,并且,类Power、Sin、Cos三个因子类都继承了抽象类Item,因此复用性和复杂度都很低,而专门处理字符串的工具类StringProcessor,处理Poly数据的工具类PolyProcessor,其内聚性都较低,并且代码长度和方法数量都很庞大。

  • UML类图分析

    本次作业构造了ItemFactory类作为工厂类,实现了对Item下的三个因子子类的工厂模式构建,并且,构造了StringProcessor类、PolyProcessor类作为处理相应数据的工具类组织起来。最后,将Item类作为Func类的容器组织类型,Func作为Poly的组织类型,构成了整个多项式的复杂结构。

  • 公测、互测中个人遇到的Bug

    此次公测、互测中未发现bug,但在自己测试中利用自动化工具测试出许多求导中的逻辑bug(这些bug都来源于那些极其复杂的方法,诚然,复杂的逻辑必导致层出不穷的奇怪问题),这些问题不容器构造特殊例子,因此用大量随机数据进行测试成为更好的方法。

  • 互测中发现他人的Bug

    首先构造特殊测试例子发现他未考虑到的部分,如常数为零、指数为零等特殊边界,以及多种符号的判定,和可能的优化带来的错误。

    其次使用我在python平台利用xeger和sympy进行针对正则表达式的随机数据生成和目标多项式的正确求导的检验,在长达一晚的测试中,分析出现错误的例子,剔除重复,进行hack。

    最后,检查源代码中其使用正则表达式的结构正确性与逻辑正确性,并对求导部分和可能的化简部分的代码逻辑做最后的观察,定点爆破。

  • 对象创建说明

    本次实验增加了sin、cos两种因子类型,并且,二者与幂函数因子的属性和特征基本类似,因此,我构造了Item这一抽象类,增加了指数、系数,以及后续作业所需的嵌套这些属性和他们的get、set方法,并设置了抽象的求导、获得内部信息的字符串方法。因为有了一个公共的父类,同时需要构造的基本类型增多,我采用了工厂模式来生成新的对象。对于基础的因子数据,我又设计了Func类利用HashSet储存Item们,并重新修改了Poly类储存Func的对象们,这样,一个具体的多项式解析、存储、计算和输出的框架就此搭建完毕。

    在此次设计中,基本预留好下次试验可扩展的部分。

第三次作业
  • 概要

    本次作业增加了嵌套的数据类型,如:sin(x**2)**-5以及(x-5+sin(x)**2*x)等。由于嵌套的增加,第二次作业的求导方式又要进行改变,然而在第二次作业中,我已经将可能出现嵌套的情况考虑在内,在当时已经将嵌套的求导方式归纳进Power类中,而不是去新增一种新的类,同样需要说明的是,常数也只是指数为零的幂函数而已,将他们都抽象为Power类型,可以降低过多的类导致的复杂性问题。

    由于运算方面已经准备充分,因此这次作业需要细致设计的是字符串的匹配问题。经过慎重的考虑,最终放弃了熟悉但冗杂的自动机式处理方法,也不打算去实现括号的栈处理方式,最终我选择使用正则表达式\\([^()]*?\\)来匹配最内层的括号,我将每个最内层括号内的式子看做一个第二次作业中输入程序的需要处理的字符串,并将其处理为一个Poly对象,使用一个ArrayList容器存储起来。在原字符串中,将匹配到的部分(包括括号)替换为${num},num是当前ArrayList容器的size,用这种替换的方式,逐步消除原字符串中的括号,最终将得到一个包含${num}格式的、但没有括号的、与第二次作业格式相同的多项式字符串,最终回归到第二次作业的处理方式,继续复用原来的方法。这种设计完全不需要改动除了部分正则表达式之外的代码,让代码复用性得到充分的发挥。

    是的,优化部分甚至删除了第二次作业的那一个部分,以求满足输出的正确性,但是,对于多重无用括号的嵌套,我利用Poly类的字符串化和本次作业设计的字符串处理模块,对组合的多项式数据字符串化,在这个过程中,将省略无用括号,并将新的字符串转化回Poly类型,完成基本的括号化简。(真的有用

  • 度量分析

    Package Name Type Name Method Name LOC CC PC
    com.poly.utils ItemFactor create 34 9 4
    com.poly.utils BracketAdapter bracketMatch 20 4 1
    com.poly.utils BracketAdapter getModule 3 1 1
    com.poly.utils BracketAdapter getLastModule 3 1 0
    com.poly.utils StringProcessor StringProcessor 10 1 0
    com.poly.utils StringProcessor matchString 3 1 1
    com.poly.utils StringProcessor expProcess 10 2 1
    com.poly.utils StringProcessor signProcess 35 10 2
    com.poly.utils StringProcessor constProcess 16 3 1
    com.poly.utils StringProcessor constItemJudge 4 1 1
    com.poly.utils StringProcessor judgeMatchValid 11 3 3
    com.poly.utils StringProcessor isNotValidFormat 16 5 1
    com.poly.tools Item printItem 0 1 0
    com.poly.tools Item calcItemDerivative 37 6 1
    com.poly.tools Item canMultiply 11 3 2
    com.poly.service Main main 17 1 1
    com.poly.service Func Func 5 1 0
    com.poly.service Func addNewItem 14 3 1
    com.poly.service Func addFunc 3 1 1
    com.poly.service Func calcFuncDer 23 4 0
    com.poly.service Func printFunc 18 5 0
    com.poly.service Func isNotEmptyFunc 3 1 0
    com.poly.service PolyProcessor processExpress 59 13 1
    com.poly.service PolyProcessor calcDerivative 3 1 1
    com.poly.service PolyProcessor printExpression 13 4 1
    com.poly.service Poly Poly 4 1 0
    com.poly.service Poly addNewFunc 12 2 1
    com.poly.service Poly calcDerivative 10 3 0
    com.poly.service Poly printPoly 47 10 0
    com.poly.service Poly polySize 9 3 0
    com.poly.service Poly clearPoly 3 1 0

    从方法的度量数据中可以看到,依旧是PolyProcessor类中的processExpress方法,StringProcessor类中的signProcess方法,以及Poly类中的printPoly方法超过了10的复杂度,同时,这些方法的代码也越来越庞大的,但总归没有超过60的限制。虽然总体上看各个方法的规模都有在相对合理的范围内,但在不断地迭代中,那几个”老顽固“方法最终变成了代码的屎山,确实无法对他进行进一步的优化和拆分,否则大的框架可能有很多变化,因此,这里成为了设计的顽疾,也成为本次bug出现的一个地方。

    Package Name Type Name NOF NOM LOC DIT LCOM FANIN FANOUT OCavg
    com.poly.utils ItemFactor 0 1 36 0 -1 1 8 9.00
    com.poly.utils PolyFormatException 0 0 2 0 -1 4 0
    com.poly.utils BracketAdapter 1 3 29 0 0 3 3 2.00
    com.poly.utils StringProcessor 1 8 108 0 0.875 3 1 3.25
    com.poly.tools Power 0 5 47 1 -1 3 3 2.40
    com.poly.tools Cos 0 5 43 1 -1 3 3 2.20
    com.poly.tools Item 3 10 73 0 0.3 3 7 1.88
    com.poly.tools Sin 0 5 43 1 -1 3 3 2.20
    com.poly.service Main 0 1 19 0 -1 0 4 1.00
    com.poly.service Func 3 16 142 0 0 4 3 2.25
    com.poly.service PolyProcessor 4 3 81 0 1 6 5 6.00
    com.poly.service Poly 2 11 133 0 0 9 2 3.00

    从类的度量数据可得,平均方法复杂度指标中,ItemFactor、PolyProcessor和StringProcessor都已飘红,虽然将表达式因子和常数因子都归类于Power类是一种我认为的更简练的方案,但是,面对可能出现的多种意义的Power,与他有关的工厂类变得复杂了,可以看到其FANOUT达到了8的新高度。同时,由于更多的Wrong Format和Poly的进一步复杂,与他相关的两个工具类StringProcessor和PolyProcessor都有着极高的复杂度,这也是我在设计中的无奈之举,然而,虽然复杂,但只要逻辑及清晰,bug还是少的(逃。

  • UML类图分析

    本次UML类图中只比第二次多了BracketAdapter括号匹配类,这也是第二次设计充分保证了扩展性的结果。但所欠缺的是,各个类的依赖关系太过于复杂,在改动时牵扯过大,造成了许多困扰。

  • 公测、互测中个人遇到的Bug

    此次公测和互测大翻车,主要bug为:

    1. 未考虑- x中的负号实际为一个省略的因子,而非其幂函数的系数。
    2. 在嵌套类型的输出中,为了化简将只用一个因子的数据省略其括号,然而,没有考虑输出时在开头所乘的系数,实际上是一个因子,这让我的输出变成了Wrong Format。
    3. 意外的在化简时合并了嵌套类型,使其拥有了指数,成为了Wrong Format。

    这些bug主要发生在Poly数据的字符串输出转化中,其实这里的逻辑结构并不复杂,只是个人没有进行详尽的测试的恶果而已,教训只能告诉我,细致的做好每个模块的信息分类,考虑清楚可能产生的值的类型,并不断封装和解耦,来保证代码的清晰,这些都是极为重要的。

  • 互测中发现他人的Bug

    由于强测爆炸的原因,无奈来到了c组,于是我大杀特杀,获得了26杀的恐怖结果(最终判定为14杀),关于他们的程序,无法使用自动测评机进行处理,因为错误太过基础……

    1. 正则表达式匹配问题,依旧是各种符号的丢失情况过于严重。
    2. Wrong Format的判定问题,将正确输入判定为错误,应该也是正则问题
    3. 边界数据的判断,字符串下标越界……

    同之前几次作业相同,采用先进行特殊样例构造,加自动测试(失败),最后结合内部容易出现问题的结构进行人工审查。

  • 对象创建说明

    这次作业与第二次作业的对象创建模式基本相同,特殊之处即在于开头概述中所描述的由内层向外层匹配的括号处理,以及对Item嵌套属性的类型的确定,最终决定使用Poly类,虽然Sin、Cos的嵌套在正确情况下不可能是Poly,但是,将Power的另一种解释设定为表达式因子的我,不得不做这种妥协,现在想来,这样大大增加了设计的复杂度,拆分开来也许是更好的选择。

心得体会

OO的学习中,我真真切切感受到了Java中面向对象的魅力所在,面对从简单到复杂的需求要去实现,我不得不去绞尽脑汁思考选用何种容器才能满足我的需求,并且获得最好的性能。我要去设计一个个类,让它们在体现自己特性的情况下,尽可能提高复用性,并满足高内聚、低耦合的设计思路。同样,也有着丰富的设计模式去供我选择和使用,工厂模式管理着复杂的类的生成,装饰器模式让我们更好地去扩展一个类中的内容,还有着访问者模式、单例模式等等,设计模式是一个模板,为我们更好的抽象类以及类与类之间的关系。面向对象这门课程的中心在于设计,这句话无疑是极为正确的,在面对复杂的数据和实现时,好的设计能够让我们的代码清晰有条理,让我们在迭代中游刃有余,同样的,也使我们的程序拥有更少的bug。对我而言,面对优美的设计方式,是让我感到分外陶醉的,在写代码的过程中,我常常会很久很久的去思考如何设计,如何让类与类之间变得条理明确、结构清晰,过程是艰难的,但最终的成果却让人分外愉悦。在阅读其他同学的代码时,确实让我也饱受启发,每个人对于功能的设计都有自己的独到之处,虽然不是所有地方都设计的那么完美,但那些细节之处的亮点的的确确值得学习。设计不应该是千篇一律的,阅读不同的设计更让我们看到广阔的世界。

在本单元的一次又一次作业里,虽然我在尽力的去设计、构思,让每个对象都各司其职,尽可能少的干扰其他人,而又尽可能多的去隐藏内部信息,封装自己的功能。然而,面对更加庞大的代码,更加复杂的功能,我也确确实实感受到了力不从心,有时,为了满足某一功能,不得不去用一些精巧但复杂的编程方式,这些方式在当时看来的确是那么巧妙(fragile),但相对的也同样脆弱(fragile),在仅仅修改一点具体实现后,不得不对它修修补补,最后面对的就是满是补丁的”破布“。同时,在功能的拆分上,我也不是那么容易去把握,常常生生的将一个功能拆分成多个方法或类,这虽然满足了对方法或类的结构的规模的保持,但是,那些被拆开的代码,总是看上去那么违和,让人心生厌恶,却又无可奈何。第三次作业的翻车,只能说是面对着”屎山“一般的代码的我变得急躁的结果,从错误产生的地方可以发现,错误不多在复杂的逻辑之处,仅仅存在于简单而又容易忽视的地方,把握住复杂代码的逻辑走向也许是我的一个长处,但是,细枝末节的处理确实不尽人意。

第一单元已经结束了,虽然结果有些惨淡,但收获到的确实很多,设计是一种挑战,也是一种美的创造,在后面的学习中,希望自己总结此次的经验,拥抱对象。

猜你喜欢

转载自www.cnblogs.com/celestial39/p/celestial39.html
今日推荐