BUAA_OO_第一单元作业总结_多项式求导

OO第一单元作业-----多项式求导

一、概述:

  • 第一次作业是仅含有常数因子和幂函数因子的多项式求导
  • 第二次在第一次的基础上增加了表达式因子(qwq)和三角函数因子
  • 第三次在第二次的基础上增加了三角函数内部的表达式因子嵌套和格式检查

二、程序结构

1.第一次作业

1.1.架构思路

​ 第一次作业在进行架构设计时,定义了Expression类,Item 类,VaribleFactor类,ConstantFactor类,SimplifyOutput类和MainClass类,并没有很好地实现面向对象的思想,主要是通过Expression类处理输入,利用正则表达式将其分割为Item ,再在Item 的内部类中实例化了ConstantFactor类和VaribleFactor类(以为这样是面向对象的设计),在ConstantFactor类和VaribleFactor类中通过正则表达式识别出常数因子大小和幂函数的指数大小。而在求导时,也十分面向过程,在Item中定义了求导方法,对实例化的ConstantFactor类和VaribleFactor类直接进行系数和指数的赋值,最后在Expression类中定义一个泛型为item的链表,将实例化的item放入链表,定义求导方法将链表中的各个item输出。

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

  1. 类的耦合度分析

    class OCavg OCmax WMC
    VaribleFactor 1.67 3.0 5.0
    SimplifyOutput 13.0 13.0 26.0
    MainClass 1.0 1.0 1.0
    Item 3.2 12.0 16.0
    Expression 3.67 5.0 11.0
    ConstantFactor 1.67 3.0 5.0
    Total 64.0
    Average 3.76 6.17 10.67
    • OCavg代表类的方法的平均循环复杂度。

    • OCmax代表类的方法的最高循环复杂度。

    • WMC代表类的总循环复杂度。

    • 本次作业可以看出对于SimplifyOutput类,其复杂度达到了13.0,其原因还是缺乏面向对象思想,在进行输出时,并没有给每一个类实现tostring方法,而是直接新建该类,对每一个项,因子等进行大量的if...else...判断,然后逐一进行输出,导致该类的复杂度过高,代码阅读起来也不是很清晰。

    • Expression类的复杂度主要是体现其中的求导和合并方法。其中mergelist方法通过循环来进行合并相同的item,并且还犯了老师上课特意强调的不规范错误,即在进行循环不可以对循环变量进行重新赋值或操作。

      method ev(G) iv(G) v(G) CogC
      Expression.expressiondiff() 3.0 6.0 6.0 8.0
      Expression.divideitem(String) 1.0 2.0 2.0 1.0
      Expression.mergelist() 1.0 4.0 4.0 6.0
      Total 5.0 12.0 12.0 15.0
      Average 1.67 4.0 4.0 5.0
      • ev(G):基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。
      • iv(G): 模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。
      • v(G):是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护。
      • CogC:认知复杂度

      分析:可以看到在Expression.expressiondiff()方法的iv(G)值较高,其原因是在该类中大量调用了另一个SimplifyOutput类中的方法,导致该模块耦合度过高。

  2. UML类图

    • 类间的架构关系:直接通过Mainclass类读取字符串,经Expression类分割为Item的各字符串,并交由Item类进行处理,直接实例化ConstantFactor因子类和VaribleFactor因子类,在item,Expression类里定义了求导方法,最后经SimplifyOutput类进行输出。
    • 架构缺点:
      • 架构的耦合度过高,每一个类的求导方法并没有实现完全独立,输出也是需要调用其他类的方法。
      • 类的结构化程度不高,比如在item里面处理因子类的赋值和求导,最终的求导和输出混在一起执行。
      • 简化和输出的方法复杂度过高,不易于阅读。
      • 使用大正则表达式处理,容易导致爆栈问题
  3. 类和方法规模分析

    • 类规模分析
    class 属性个数 方法个数 总代码规模
    SimplifyOutput 0 2 112
    Expression 1(List<Item> list) 3 67
    Item 3 4 75
    ConstantFactor 1 3 35
    VaribleFactor 1 3 28
    MainClass 0 1 12
    • 方法规模分析(只选取主要方法进行分析)
    method 方法规模 方法控制分支数
    ConstantFactor.ConstantFactor(String) 8 1
    Expression.expressiondiff() 16 3
    Expression.mergelist() 13 3
    Item.Item(String) 45 10+
    Item.itemdiff() 2 0
    SimplifyOutput.operate(List,int) 50 15+
    VaribleFactor.VaribleFactor(String) 9 2

1.3.程序bug分析

​ 本次作业在强测和互测中出现了一个同质bug,即在优化时,为考虑对指数为0或常数因子为0的项求导应该忽略输出,但是却忽略都为0时,最终应该输出0的情况,所以导致了强测的一个数据点错误,并在互测中被反复鞭尸。

method ev(G) iv(G) v(G) CogC
SimplifyOutput.firstoperate(List,int) 1.0 13.0 13.0 46.0
SimplifyOutput.operate(List,int) 1.0 13.0 13.0 46.0

​ 可以看到包含该bug的方法的耦合度和复杂度极高。

2.第二次作业

2.1.架构思路

  1. 问题引入:在本次作业中引入了三角函数的求导和表达式因子的求导,这也导致了第一次作业时面向过程的架构设计对表达式因子无法进行很好的处理,所以在本次作业中进行了重构;并且由于表达式因子和三角函数的引入,使得通过正则表达式来进行解析多项式结构变得更加困难。

  2. 架构设计

    • 数据处理

      • 由于此次实验中并没有非法表达式,因此首先去掉所有的空白符。
      • 将'+'和'-'的不同组合,进行相应的替换,比如对'++-'替换为'-'。
      • 为了区分*和乘方**,将**替换为,即x**2变为x2从而可以更方便地进行正则表达式的提取识别。
      • 由于表达式因子和三角函数因子的引入,为达到区分表达式因子外层括号、三角函数括号和表达式因子内部括号的目的,首先进行识别出sin(x),cos(x),将其更改为cos<x>,sin<x>,并且通过栈来找出表达式因子最外层的括号,将其替换为[,],即(x+sin(x))变为[x+sin<x>]
    • 架构实现

      • 定义一个simplifyinput类对输入的字符串进行预处理后,交给我定义的多项式poly类,在内部通过正则表达式分割出各个项的部分,并将其传给项类,在poly中定义一个list,将项类进行实例化存入Arraylist中。
      • item类根据题意可以看出是由因子类的乘积构成,因此在item类中定义了一个list来存放factor类型的因子,又因为因子包括常数因子,变量因子(幂函数因子和三角函数因子),和表达式因子,所以令factor为父类,令常数因子,变量因子,表达式因子分别继承自factor类。
      • 在item的构造方法中运用了工厂模式,根据不同继承自factor类的不同类型,new一个不同类型的factor返回。
    • 求导实现:因为factor类均需要实现求导,因此自然想到通过在幂函数类,三角函数类等子类中覆写父类的求导方法,从而实现各个因子的求导。老师在声明面向对象要让中间过程在类的内部实现,不能对外透明显现,并且tostring功能和deri求导功能要区分出来,所以改变了求导直接输出字符串的方法,以x**2求导为例,在对这一因子求导时,要返回一个list其中存放一个常数因子类“2”,和幂函数类“x**1“。

      • 对于表达式因子,我们发现其求导的法则与其他因子大相径庭,但是我们观察到表达式因子内部仍是一个表达式,因此我们完全可以将表达式因子去掉括号成为一个表达式,将其丢给poly类进行递归调用求导,在这里我又遇到一个问题,对表达式因子的求导也要返回一个因子类的链表,才能在item类中和其他因子类进行合并,但是我们对表达式因子求导要调用表达式的求导方法,显然我们无法将poly类的求导定义为返回一个factor类型的链表。为解决这一问题,我们在表达式因子类中定义了一个String字段来存储每一次传进来的表达式因子,并且令poly类求导方法返回值为一个字符串,这样就可以在表达式因子类的内部求导方法中,对自身的表达式因子字符串进行求导,将返回值存入一个新的实例化的表达式因子类中的string类型的字段中,最终返回这个新实例化因子。
      • 对于item求导,比如对xyz...求导,我们直接通过将x'因子和yz..因子进行合并组成一个新的项,y'的因子和xz...因子进行合并组成新的项...,将这些实例化的项通过tostring方法直接输出。

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

  1. 类的耦合度分析

    Class OCavg OCmax WMC
    Factory 6.0 6.0 6.0
    Item 4.57 15.0 32.0
    SimplifyInput 3.5 6.0 7.0
    Xfactor 2.67 6.0 24.0
    Poly 2.5 3.0 5.0
    Cosfactor 2.44 6.0 22.0
    Sinfactor 2.44 6.0 22.0
    ExpressionFactor 2.2 3.0 11.0
    Cfactor 1.0 1.0 7.0
    Factor 1.0 1.0 9.0
    MainClass 1.0 1.0 1.0
    Total 146.0
    Average 2.39 4.91 13.27
    • 其中Factory类的复杂度最高,其原因大致是通过一个方法来实现多个因子的创建,但是应该也在情理之中;SimplifyInput复杂度较高应该也是在一个方法中进行了大量替换,如果将不同种类的替换分在不同的方法中可能有所缓解。

    • Item类:

      method ev(G) iv(G) v(G) CogC
      Item.getderi() 1.0 9.0 10.0 19.0
      Item.getFactorslist() 1.0 1.0 1.0 0.0
      Item.Item() 1.0 1.0 1.0 0.0
      Item.Item(String) 1.0 2.0 2.0 1.0
      Item.merge(ArrayList) 1.0 1.0 1.0 0.0
      Item.merge(Factor) 3.0 4.0 13.0 9.0
      Item.toString() 1.0 3.0 3.0 4.0
      • Item.getderi():该方法的iv(G)值和Cogc值过高。
        • iv(G)过高主要是因为在求导过程中大量调用了不同factor类的求导方法,使得该类的耦合度较其他类较大。
        • 其Cogc值过高主要在于在求导的过程中,同时进行了优化,处理含常数因子为0的项,并且为方便输出直接在该方法内部将刚刚项求导后产生的存在链表里的项转变为了String类型。
      • Item.merge(Factor):该方法的Cogc值过高主要是因为为了优化实现合并,要合并同类的项,因此进行了大量的switch()...case...来判断是哪个类;并且在发现没有相同类因子时,进行深拷贝时,再次进行了类型判断,从而导致了复杂度过高。
  2. UML类图

    • 类的架构分析:
      • Mainclass类:程序主要输入输出和中间控制的流程
      • SimplifyInput类:简化输入模块,处理读入的字符串
      • Poly类:对读入的表达式字符串,将其处理为项字符串,交给Item类处理,实现求导方法
      • Item类:对读入的项字符串,将其处理为因子字符串,交给工厂类实现产生相对的因子类,实现求导方法,合并因子求导返回各因子的方法,toString方法
      • Factory类:对传入的因子字符串,利用正则表达式识别,判断类型后,产生相应的因子类
      • Factor类:作为所有具体因子类的父类,实现求导方法和toString等方法方便子类覆写
      • Cfactor类:常数因子类,实现求导方法和toString方法,继承Factor类
      • Xfactor类:幂函数类,实现求导方法和toString方法,继承Factor类
      • Cosfactor类:三角函数cos类,实现求导方法和toString方法,继承Factor类
      • Sinfactor类:三角函数sin类,实现求导方法和toString方法,继承Factor类
      • ExpressionFactor类:表达式因子类,实现求导方法和toString方法,继承Factor类
    • 架构优点:
      • 因子类通过继承同一个父类,并覆写求导和toString方法,各因子类的求导在其内部执行,耦合度较低
      • 将poly类看作加法运算法则,Item类看作乘法运算法则,对其分别实现相对应的求导法则,因子内部又有自己的求导法则,可以更好地支持各种嵌套求导等的需求。
      • 采用了工厂模式,减少了项的构造方法的复杂度和耦合度。
    • 架构缺点:
      • 在Poly类和Item类的求导方法中直接进行输出字符串,从而导致在优化输出结果时比较困难
      • 对读入数据采用了大量的正则表达式,并且先进行了预处理字符串的操作,这部分操作比较面向过程
  3. 类和方法规模分析

    • 类规模分析
    class 属性个数 方法个数 总代码规模
    Cfactor.java 2 7 39
    Cosfactor.java 3 9 112
    ExpressionFactor.java 3 5 73
    Factor.java 0 9 37
    Factory.java 0 1 49
    Item.java 1(ArrayList<Factor>) 7 144
    MainClass.java 0 1 12
    Poly.java 1(ArrayList<Item>) 2 45
    SimplifyInput.java 0 2 50
    Sinfactor.java 3 9 112
    Xfactor.java 3 9 113
    • 方法规模分析
    method lines ev(G) iv(G) v(G) Cogc
    Cfactor.Cfactor(String) 2 1.0 1.0 1.0 0.0
    Cfactor.getderi() 3 1.0 1.0 1.0 0.0
    Cfactor.toString() 1 1.0 1.0 1.0 0.0
    Cosfactor.Cosfactor(String) 15 1.0 4.0 4.0 7.0
    Cosfactor.getderi() 27 1.0 6.0 6.0 10.0
    Cosfactor.toString() 13 4.0 4.0 4.0 8.0
    ExpressionFactor.ExpressionFactor(String) 13 1.0 3.0 3.0 4.0
    ExpressionFactor.getderi() 11 1.0 1.0 2.0 2.0
    ExpressionFactor.toString() 10 2.0 1.0 2.0 2.0
    Item.getderi() 32 1.0 9.0 10.0 19.0
    Item.Item(String) 6 1.0 2.0 2.0 1.0
    Item.merge(ArrayList) 1 1.0 1.0 1.0 0.0
    Item.merge(Factor) 50 3.0 4.0 13.0 9.0
    Item.toString() 8 1.0 3.0 3.0 4.0
    Poly.getderi() 8 1.0 3.0 3.0 4.0
    Poly.Poly(String) 7 1.0 2.0 2.0 1.0
    SimplifyInput.changebrack(String) 23 1.0 5.0 6.0 10.0
    SimplifyInput.simplify(String) 17 1.0 1.0 1.0 0.0
    Sinfactor.getderi() 27 1.0 6.0 6.0 10.0
    Sinfactor.Sinfactor(String) 15 1.0 4.0 4.0 7.0
    Sinfactor.toString() 12 4.0 4.0 4.0 8.0
    Xfactor.getderi() 23 1.0 6.0 6.0 10.0
    Xfactor.toString() 17 6.0 6.0 6.0 10.0
    Xfactor.Xfactor(String) 14 1.0 4.0 4.0 7.0

2.3.程序bug分析

​ 本次作业在强测和互测过程中没有发现bug。

3.第三次作业

3.1.架构思路

  1. 第三次作业相对于第二次作业增加了三角函数内因子的嵌套,和格式检查。在第二次作业的设计架构下,实现这种嵌套在三角函数内的因子求导只需将三角函数内的因子字符串解析出来,通过工厂类新生成一个因子,并重新交给相对应的因子类的求导方法进行递归地求导处理。

  2. 而对格式的检查因为没有找到很好的资料学习递归下降法的分析,于是仍旧通过大正则来进行匹配检查,这里有一个问题,就是我们在进行表达式求导时,首先对字符串进行了预处理,所以要在进行预处理之前,对字符串进行合法性检查。

    • 对空白项进行预处理检查。

      • 常数各位之间有空白符
      • 三个连续正负号最后一个和整数之间有空白符
      • 三角函数名之间有空白符
      • 指数**之间有空格
      • 非第一个因子符号与数字之间有空格
      • 三角函数内部数字和符号之间有空格,即sin(+ 50)为非法
    • 对加减号进行预处理检查

      • 四个以上正负号相连
      • 三个加减号后相连的不是常数
      • 两个以上正负号相连的不是第一个因子
      • 指数有多个正负号
    • 利用栈对括号匹配进行预处理检查

    • 对非法字符进行检查

    • 在表达式类里通过正则表达式进行匹配,如果匹配不到,则检查为格式错误。其中大正则我们的指导书中已经给出了很清楚的形式化描述。

    • 在我们上一步的检查中并不能有效检查出比如表达式因子内部和三角函数嵌套内部的格式,因为我们在外部匹配表达式因子和三角函数因子,只通过识别最外部的特殊替换的括号,而不会检查内部嵌套的因子,因此我们可以直接在工厂类生成因子的过程直接对因子内部进行格式检查,检查方法仍旧是通过正则表达式,这样我们在递归地进行求导的过程中就可以成功检查内部各因子的格式。

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

  1. 类的耦合度分析

    class OCavg OCmax WMC
    Factory 12.0 12.0 12.0
    SimplifyInput 6.75 12.0 27.0
    Item 4.63 15.0 37.0
    Poly 3.5 4.0 7.0
    Xfactor 2.67 6.0 24.0
    CheckPoly 2.43 6.0 17.0
    Cosfactor 2.3 6.0 23.0
    Sinfactor 2.3 6.0 23.0
    ExpressionFactor 2.2 3.0 11.0
    MainClass 2.0 2.0 2.0
    Cfactor 1.0 1.0 7.0
    Factor 1.0 1.0 11.0
    Total 201.0
    Average 2.68 6.17 16.75
    • 依旧是Factory类,Item类的,SimplifyInput类耦合度过高,不再赘述
  2. UML类图

    • 类的架构分析
      • 其他类基本与第二次作业相同,不再赘述
      • 添加了新CheckPoly类,主要来检查空白符,加减号,括号,非法字符等的问题,从而保证预处理前后的字符串没有问题,不会漏失格式错误类型的检查
    • 架构优缺点分析:
      • 基本与第二次作业类似,但是在进行格式检查时采用正则表达式来进行匹配检查,使程序复杂度有所增加。
  3. 类和方法规模分析

    • 类的规模分析(由于基本与第二次作业类似,只选取部分类)

      class 属性个数 方法个数 总代码规模
      CheckPoly.java 1 7 114
      Cosfactor.java 4 10 131
      Sinfactor.java 4 10 131
    • 方法规模分析(只选取部分方法)

      method lines ev(G) iv(G) v(G) Cogc
      CheckPoly.checkblank() 16 1.0 8.0 8.0 2.0
      CheckPoly.checkbracket() 17 6.0 5.0 6.0 10.0
      CheckPoly.checkkey() 2 1.0 1.0 1.0 0.0
      CheckPoly.checkplminus() 10 1.0 5.0 5.0 1.0
      CheckPoly.checkpoly() 13 5.0 1.0 5.0 4.0
      Cosfactor.getderi() 31 1.0 6.0 6.0 10.0
      Sinfactor.getderi() 31 1.0 6.0 6.0 10.0

3.3.程序bug分析

​ 在强测中出现了一个bug,因为在检查三角函数内部空格时,正则表达式内部的\\d没有加+(无语子),导致只能识别出sin( + 5)这样的错误,却不能识别出sin( + 030 )。

method ev(G) iv(G) v(G) CogC
CheckPoly.checkpoly() 5.0 1.0 5.0 4.0

​ 可以看到该出现bug的方法复杂度较其他未出现的方法的bug复杂度较高

三、hack其他程序bug策略

  1. 第一次作业:
    • 没有本地搭建评测机,通过手动构造测试样例,并且因为正则表达式较容易出现爆栈问题,所以试图构造一些易造成爆栈的数据,比如x*x*x*x*x...等
  2. 第二次作业和第三次作业
    • 通过正则表达式,借助python本身自带的库,基于Sympy随机产生表达式字符串,并将同组者的代码打包,同时进行评测机黑盒测试,主要是在第二次和第三次作业中,代码较为复杂,每个人实现思路也大相径庭,对于阅读他人代码发现问题不太现实,因此采用了黑箱自动化测试。

四、重构经历总结

​ 在第二次作业时,遇到了表达式因子的嵌套求导,而第一次作业的架构也体现出来了面向过程的弊端,没有很好的扩展性,所以进行了重构,但是在重构时,仍然感觉最大的难点是处理怎么很好地读入表达式,识别出项,因子;其次在处理表达式因子的时候也思考了很久,怎么能对表达式因子中的内容进行递归求导?最后考虑到老师上课举的例子,在写面向对象的程序时,每一个类都有自己的功能,所以自然地想到交给表达式类继续进行递归求导。其实这一次的重构,也是让自己能更好地区分面向过程和面向对象的两种不同思想的一个很好的机会,初窥到了面向对象的特性。

project v(G)avg v(G)tot
homework1 3.76 64.0
homework2 2.38 145.0

​ 可以看到第二次作业重构后,项目的耦合度和复杂度有了明显的下降。

五、心得体会

​ 第一单元的作业让我更加了解了程序的架构和面向对象在我们的程序设计中的重要性,如果起初架构搭建的不符合面向对象的思想,那么在后面的迭代过程中一定是很困难的。

​ 其次,本次表达式的读入,使用正则表达式来识别处理,贯穿了三次作业,是对我的一个较大的挑战(因为不会递归下降,只能通过大正则来匹配项,因子,检查格式)。

​ 并且在进行了为期三周的迭代作业开发,让我对java的一些继承,多态,接口,容器等知识有了更加深入的了解。

​ 但是,在本次作业还是有很多遗憾,比如通过了中测后没有进行过多的优化,也没有学会递归下降分析法等等,希望可以在后面的作业迭代过程中尝试不同的方法来进行训练,也希望能将对面向对象的理解更进一步。0_0

posted @ 2021-03-27 22:40  做个废柴呐  阅读(85)  评论(0编辑  收藏  举报