Object-oriented first unit summary

Object-oriented first unit summary

Design analysis

first assignment

Overall architecture

The Main class is responsible for reading and outputting, passing the read string into the method class Regexfunc. The Regexfunc class is specially used for regular expression processing, and returns the expression Expression after the string has been parsed; there is a Polyitem in the Expression The hashmap is used to save each item of the expression; and each Polyitem contains a Factor, which is the factor of the item.

class analysis

Metric analysis

The following table is the metric analysis taken for each method. Except for the two methods of derivation and regular expression parsing, which have higher cognitive complexity, the others are at a lower level.

Method CogC ev(G) iv(G) v(G)
Expression.Expression() 0 1 1 1
Expression.addPolyitem(Polyitem) 2 1 2 2
Expression.derivative() 4 1 3 3
Expression.toString() 23 2 12 13
Factor.Factor() 0 1 1 1
Factor.Factor(BigInteger,BigInteger) 0 1 1 1
Factor.getCoefficient() 0 1 1 1
Factor.getIndex() 0 1 1 1
Factor.much(Factor) 0 1 1 1
Factor.setCoefficient(BigInteger) 0 1 1 1
Factor.setIndex(BigInteger) 0 1 1 1
Main.main(String[]) 3 1 2 3
Polyitem.Polyitem() 0 1 1 1
Polyitem.Polyitem(BigInteger,BigInteger) 0 1 1 1
Polyitem.addFactor(Factor) 0 1 1 1
Polyitem.getFactor() 0 1 1 1
Polyitem.setFactor(Factor) 0 1 1 1
Regexfunc.getitem(String) 33 1 16 16

The following table shows the measurement analysis for each class. It can be seen that Expression and Regexfunc are the most complex. This is the same as the above analysis, which is caused by the complexity of the derivation method and regular expression parsing.

Class OCavg OCmax WMC
Expression 4.75 13 19
Factor 1 1 7
Main 3 3 3
Polyite 1 1 5
Regexfunc 12 12 12
class diagram analysis

  • Main

Due to the design of decoupling, Main is only responsible for reading in strings and outputting results.

  • Regexfunc

This class is a method class that handles regular expressions, with only one method for parsing a string and returning the expression.

The following is how to write a regular expression. According to the formal expression given in the title, write from bottom to top, that is, the regular expression above can "call" the regular expression below:

String addorsub = "[+-]";//加减
String white = "[ \\t]";//空白
String whiteItem = "(" + white + "*" + ")";//空白项
String integer = "(" + "[0-9]+" + ")";//整数
String signedInteger = "(" + addorsub + "?" + integer + ")";//带符号整数
String powerfunction = "(" + "x" + "(" + whiteItem + "\\*\\*" + whiteItem + signedInteger + ")?" + ")";//...
...

The resulting regular expression looks like this:

  • Expression

This class is based on the topic, and the expression is obtained by adding a series of items, so there is only one attribute in it, that is, a hashmap used to store each item of the expression. In addition, since all items can be obtained in this class, the derivation method is written in this class.

  • Polyite

This class is the item class, which holds the factors in the item. Therefore, its attribute has only one Factor class, corresponding to its factor. Its method is only a very simple method addFactor to add Factor.

  • Factor

This is a factor class, which stores the attributes corresponding to a factor: coefficient coefficient, index index. In this class there is an additional mult method for multiplying two Factors to get the resulting factor Factor.

Advantages and disadvantages analysis
  • advantage
  1. Each part is decoupled, and each function design that needs to be completed is separated into different classes, which is conducive to subsequent expansion.
  2. Due to the decoupling design, when debugging, you only need to observe which functional part has a problem, and you can quickly locate the relevant parts of the project, and the debugging is fast.
  • shortcoming
  1. Factor and Polyitem are actually equivalent, that is, Polyitem is not actually needed, and there is only one Factor in Polyitem, which brings some troubles to the expression derivation—you need to obtain Polyitem first and then Factor, which affects the project operating efficiency.
  2. The parsing and writing of regular expressions is confusing, and it takes a lot of time to debug.

second assignment

Overall architecture

The Main class is responsible for reading and outputting, passing the read strings into NodeFactory to obtain expressions, where Nodefactory is a method class specially used for expression parsing, and then NodeFactory will create various Node nodes to form an expression tree and return the root node. Other classes inherit directly or indirectly from Node. Node leads to two types of nodes: Relation and Factor, which represent the relationship and factor in the expression tree respectively. The relationship is further divided into the additive relationship AddRelation and the multiplicative relationship MultRelation, and the factor is further divided into the triangular factor TrigFactor, the power factor PowerFactor, and the constant factor ConstFactor.

expression parsing

Since the expression has a nested relationship, and the regular expression is only equivalent to a finite state machine, it cannot complete the expression matching with an indefinite number of nesting times, so the push-down automaton is used, that is, adding a stack to cooperate with the regular expression formula to complete the parsing of the expression.

Here's a flowchart of my expression read in:

getNode(String input)

First pass the expression as a parameter into the method getNode, this method returns a new node as an expression. The first step of this method is to change the outermost parentheses in the expression to braces . Here I use a simplified stack for this operation, so that the expression factors of the next layer can be expressed through regular expressions After the formula is recognized, each item is separated and passed to the next method getItem after loop matching.

eg: x+(x**2+1+sin(x))*x**2After the character string is read in, it will become after the repalceBracket x+{x**2+1+sin(x)}*x**2, so that the expression factor can be identified by providing a regular expression when matching. In addition, since the parentheses of sin and cos will also be subjected to the same operation, I have changed the matching of sin and cos to sin{x} or sin(x).

getItem(String input)

After being processed by getNode, this method is responsible for returning the node corresponding to the item. The first is to perform further segmentation, identify and segment the factors in the item through regular expressions, and then pass it to the getFactor method to obtain the node corresponding to the item and return it.

getFactor(String input)

This method returns the node corresponding to the factor through the factory pattern. The first is to judge whether the factor is an expression factor. If it is an expression factor, pass the string of the factor to getNode to obtain the node of the expression. In other cases, return the corresponding factor type.

class analysis

Metric analysis

The following table is the measurement analysis of each method. Except for the toString method output by the expression, which has a high cognitive complexity, the others are at a low level.

Method CogC ev(G) iv(G) v(G)
AddRelation.AddRelation(Node,Node) 0 1 1 1
AddRelation.derivative() 0 1 1 1
AddRelation.normalize() 6 5 3 5
AddRelation.toItem() 11 7 7 10
AddRelation.toString() 0 1 1 1
ConstFactor.ConstFactor(BigInteger) 0 1 1 1
ConstFactor.derivative() 0 1 1 1
ConstFactor.getNumber() 0 1 1 1
ConstFactor.normalize() 0 1 1 1
ConstFactor.setNumber(BigInteger) 0 1 1 1
ConstFactor.toItem() 0 1 1 1
ConstFactor.toString() 0 1 1 1
Item.Item() 0 1 1 1
Item.Item(BigInteger,BigInteger,BigInteger,BigInteger) 0 1 1 1
Item.addItem(Item) 0 1 1 1
Item.derivative() 0 1 1 1
Item.getCoef() 0 1 1 1
Item.getCosIndex() 0 1 1 1
Item.getPowerIndex() 0 1 1 1
Item.getSinIndex() 0 1 1 1
Item.multItem(Item) 0 1 1 1
Item.normalize() 0 1 1 1
Item.setCoef(BigInteger) 0 1 1 1
Item.setCosIndex(BigInteger) 0 1 1 1
Item.setPowerIndex(BigInteger) 0 1 1 1
Item.setSinIndex(BigInteger) 0 1 1 1
Item.toItem() 0 1 1 1
Item.toString() 31 1 12 16
Main.main(String[]) 3 1 3 4
MultRelation.MultRelation(Node,Node) 0 1 1 1
MultRelation.derivative() 0 1 1 1
MultRelation.normalize() 12 7 9 13
MultRelation.toItem() 13 7 11 15
MultRelation.toString() 0 1 1 1
NodeFactory.getFactor(String) 10 7 8 10
NodeFactory.getItem(String) 3 1 4 4
NodeFactory.getNode(String) 1 1 2 2
PowerFactor.PowerFactor(BigInteger) 0 1 1 1
PowerFactor.derivative() 3 3 3 3
PowerFactor.getIndex() 0 1 1 1
PowerFactor.normalize() 0 1 1 1
PowerFactor.setIndex(BigInteger) 0 1 1 1
PowerFactor.toItem() 0 1 1 1
PowerFactor.toString() 1 2 1 2
RegexFunction.replaceBracket(String) 10 1 6 6
Relation.Relation(Node,Node) 0 1 1 1
Relation.getSubNodeA() 0 1 1 1
Relation.getSubNodeB() 0 1 1 1
Relation.setSubNodeA(Node) 0 1 1 1
Relation.setSubNodeB(Node) 0 1 1 1
TrigFactor.TrigFactor(TrigType,BigInteger) 0 1 1 1
TrigFactor.derivative() 7 4 5 5
TrigFactor.getIndex() 0 1 1 1
TrigFactor.getTrigType() 0 1 1 1
TrigFactor.normalize() 0 1 1 1
TrigFactor.setIndex(BigInteger) 0 1 1 1
TrigFactor.setTrigType(TrigType) 0 1 1 1
TrigFactor.toItem() 1 2 1 2
TrigFactor.toString() 1 2 1 2

下表为对每一个类的度量分析,可见复杂度最高的是RegexFunction和NodeFactory,这是由于对表达式处理时的复杂性所导致的。其他类均在一个较为合理的水平上。

Class OCavg OCmax WMC
AddRelation 3 7 15
ConstFactor 1 1 7
Item 1.81 14 29
Main 3 3 3
MultRelation 3.4 7 17
NodeFactory 4.33 8 13
PowerFactor 1.43 3 10
RegexFunction 6 6 6
Relation 1 1 5
TrigFactor 1.67 5 15
类图分析

  • Main

出于解耦的设计,Main只负责读入字符串与输出结果。

  • NodeFactory

这个类是工厂类,用于根据输入的表达式字符串返回相对应的表达式。其具体操作已经在上面的表达式解析中详细说明了。

  • AddRelation

这个类是一个加法关系,其储存了两个子节点,这两个子节点可以是任何Node,求导操作也非常简单,返回一个新的加法关系,其中两个子节点为之前的两个子节点的求导。

  • MultRelation

这个类是一个乘法关系,其储存了两个子节点,这两个子节点可以是任何Node,求导操作也非常简单,返回一个新的加法关系,其中两个子节点为两个乘法关系,其中一个乘法关系为子节点A与子节点B的求导,另一个为子节点A的求导与子节点B。

  • xxxFactor

3种因子,分别储存了其应含有的属性,TrigFactor有trigType和index,分别表示三角函数的类型(sin还是cos?)和三角函数的指数;PowerFactor有index,表示指数;ConstFactor有num,表示常数大小。

在拥有上面5种类后,我们即可将一个表达式树构建出来。如1+x*(x*sin(x)+1)+x**2可表示为:

  • Item

这个是专门用于优化的类,他包含了4个属性:coef、powerindex、sinindex、cosindex。即这个item可以把多个乘积合并为一个项,每一个节点都进行toItem()操作后,将把所有的节点替换为Item,由此实现优化操作。

优缺点分析
  • 优点
  1. 采用了表达式树的数据结构,每一个节点实际上都是等价的,符合面对对象的思想,也十分有利于程序的扩展,即只需要添加新的节点类型即可扩展新的类型。
  2. 所有节点都实现了求导方法derivative(),在进行求导时只需要对根节点进行调用derivative(),后续求导操作都会递归调用,完成表达式的求导。
  • 缺点
  1. 在优化时效率不高,需要遍历所有节点且优化程度低,基本的拆括号等等操作都没有实现。
  2. 仅仅采用了二叉树,出现大量嵌套时,表达式树会出现层数非常多的情况,不利于操作。

第三次作业

总体架构

这次作业加入了三角函数内可包含表达式因子,在第二次的基础上稍加修改即可,为三角函数类TrigFactor添加一个新的属性var,类型为Node,作为sin中的变量。表达式的读入基本与第二次相同。另外题目还要求了格式检查,而由于我是使用正则表达式进行表达式读入的,一旦输入不能与正则表达式匹配,说明格式有误。另外我对第二次的架构进行了稍微的修改,将之前的二叉树表示表达式树更改成了多叉树,这样可以提高表达式的化简效率,降低化简的难度。

表达式解析

在第二次的基础上进行更新,添加了TrigFactor中因子的判断,其他与第二次一致。

格式检查

格式检查与表达式的解析实际上是一体的,仅仅添加一点判断即可。

表达式因子判断

在每一个表达式的两边判断是否有括号来检查sin里表达式因子是否合法,为此,在一开始读入字符串时,在两边加上括号后再传入getNode,如此处理后可以保证所有的表达式最外面都有一对括号,否则不合法。

其他格式判断

对于其他的格式,使用matcher.lookingAt(),保证每次匹配都是从第一个字符开始匹配,如果在匹配之后发现字符串不为空,说明该字符串没有成功匹配,即为错误格式。

优化

对于这部分我只采用了合并常数项的操作,原因是经常出现的0和1往往会大大增加长度,因此将加法关系中的所有常数项化为一项,将乘法关系中的常数也相乘在一起。与第二次不同,由于使用了多叉树,不用去想方设法地把不在一层的同类因子合并在一起,另外也不用担心因为多层嵌套导致的多余的括号无法轻易消除。

类的分析

度量分析

下表是对每一个方法所取的度量分析,除了表达式输出的两种关系的简化方法toItem()认知复杂度较高外,这是由于在化简时需要判断当前节点的类型,并进行相应的处理造成的,其他均在一个较低的水平上。

Method CogC ev(G) iv(G) v(G)
AddRelation.AddRelation() 0 1 1 1
AddRelation.AddRelation(ArrayList) 0 1 1 1
AddRelation.derivative() 1 1 2 2
AddRelation.toItem() 56 12 16 16
AddRelation.toString() 1 1 2 2
ConstFactor.ConstFactor(BigInteger) 0 1 1 1
ConstFactor.add(Factor) 0 1 1 1
ConstFactor.derivative() 0 1 1 1
ConstFactor.getNumber() 0 1 1 1
ConstFactor.setNumber(BigInteger) 0 1 1 1
ConstFactor.toItem() 0 1 1 1
ConstFactor.toString() 0 1 1 1
Main.main(String[]) 2 1 2 3
MultRelation.MultRelation() 0 1 1 1
MultRelation.MultRelation(ArrayList) 0 1 1 1
MultRelation.derivative() 7 1 4 4
MultRelation.toItem() 58 14 18 18
MultRelation.toString() 3 3 2 4
NodeFactory.getFactor(String) 20 11 11 14
NodeFactory.getItem(String) 6 3 5 7
NodeFactory.getNode(String) 7 4 5 8
PowerFactor.PowerFactor(BigInteger) 0 1 1 1
PowerFactor.add(Factor) 0 1 1 1
PowerFactor.derivative() 3 3 3 3
PowerFactor.equals(Object) 3 3 2 4
PowerFactor.getIndex() 0 1 1 1
PowerFactor.setIndex(BigInteger) 0 1 1 1
PowerFactor.toItem() 0 1 1 1
PowerFactor.toString() 1 2 1 2
RegexFunction.replaceBracket(String) 10 1 6 6
Relation.Relation() 0 1 1 1
Relation.Relation(ArrayList) 0 1 1 1
Relation.addNode(Node) 0 1 1 1
Relation.deleteNode(Node) 0 1 1 1
Relation.getNodeArrayList() 0 1 1 1
Relation.multNode(Node) 8 5 6 6
Relation.setNodeArrayList(ArrayList) 0 1 1 1
TrigFactor.TrigFactor(TrigType,BigInteger,Node) 0 1 1 1
TrigFactor.add(Factor) 0 1 1 1
TrigFactor.derivative() 9 2 5 5
TrigFactor.getIndex() 0 1 1 1
TrigFactor.getTrigType() 0 1 1 1
TrigFactor.getVar() 0 1 1 1
TrigFactor.setIndex(BigInteger) 0 1 1 1
TrigFactor.setTrigType(TrigType) 0 1 1 1
TrigFactor.setVar(Node) 0 1 1 1
TrigFactor.toItem() 7 4 2 5
TrigFactor.toString() 3 2 2 3

下表为对每一个类的度量分析,可见复杂度最高的仍是RegexFunction和NodeFactory,这是由于对表达式处理时的复杂性所导致的。其他类均在一个较为合理的水平上。

Class OCavg OCmax WMC
AddRelation 3.83 16 23
ConstFactor 1 1 8
Item 1.81 14 29
Main 2 2 2
MultRelation 4.83 18 29
NodeFactory 7.33 12 22
PowerFactor 1.5 3 15
RegexFunction 6 6 6
Relation 1.57 5 11
TrigFactor 1.83 5 22
类图分析

  • Main

出于解耦的设计,Main只负责读入字符串与输出结果。

  • NodeFactory

这个类是工厂类,用于根据输入的表达式字符串返回相对应的表达式。其具体操作与第二次基本相同。

  • Relation

在这次作业中由于将二叉树修改为了多叉树,因此将之前的subNodeA与subNodeB修改为了ArrayList,并修改了相应的对ArrayList操作的方法。

  • AddRelation

这个类是一个加法关系,其储存了一个ArrayList,用于保存其子节点,这些子节点可以是任何Node,求导操作也非常简单,返回一个新的加法关系,包含每一个节点的求导结果。

  • MultRelation

这个类是一个乘法关系,其储存了一个ArrayList,用于保存其子节点,这些子节点可以是任何Node,求导操作也非常简单,返回一个新的加法关系,包含每一个节点的求导结果与其他节点相乘的乘法关系节点。

  • xxxFactor

在第三次作业中,TrigFactor添加了一个var,如下图所示,var作为sin或cos中的变量,即sin(var),其余类型的factor没有改变。

优缺点分析
  • 优点
  1. 将二叉树修改为了多叉树,使程序的递归次数减少,提高了程序的运行效率。
  2. 去除了不必要的类Item,简化了优化操作。
  • 缺点
  1. 对于除了常数因子的其他因子没有进行同类项合并,导致优化效果不佳。

Bug分析

第一次作业

  1. 读入时忽略了表达式的加减号,如果第一个是减号,我的程序将忽略该减号,导致求导结果错误

    • bug出现位置:Regexfunc.getitem(String)
    • 方法度量分析:

    如下表所示,可以分析该bug出现在全项目中各种复杂都最高的类和方法中

    Method CogC ev(G) iv(G) v(G)
    Regexfunc.getitem(String) 33 1 16 16

第二次作业

  1. 在将转化后的Item输出时,没有考虑到toString方法可能出现输出xxxx*-sin(x)的情况,即输出格式错误

    • bug出现位置:Item.toString()

    • 方法度量分析:

    如下表所示,可以分析该bug出现在全项目中各种复杂都最高的类和方法中

    Method CogC ev(G) iv(G) v(G)
    Item.toString() 31 1 12 16

第三次作业

  1. 在进行优化操作toItem()时,如果最后优化到该节点返回空时(如对1-1进行优化),在toString中没有考虑到该节点为空的情况,而是直接选取了ArrayList的第一个元素输出,这就导致了越界。

    • bug出现位置:
    • 方法度量分析:AddRelation.toItem()、MultRelation.toItem()

    如下表所示,可以发现两个方法都有很高的复杂度。

    Method CogC ev(G) iv(G) v(G)
    AddRelation.toItem() 56 12 16 16
    MultRelation.toItem() 58 14 18 18

总结

这三次的bug都发生在方法复杂度很高的地方,而在复杂度低的方法中往往不容易出现bug,这提醒我们在写方法时一定要注意方法的简易性、可读性、解耦性,否则bug往往就出现在这些没有严格按照规则编写的方法中。

Hack策略

肉眼Hack法

实际上肉眼很难找到别人的bug,特别是当一份程序写的很乱,同时没有注释时。不过我们可以从下面几点开始:

  1. 从表达式解析入手,将其表达式的正则表达式与自己进行比较,发现其中的不同之处,再思考两种写法是否等价,也可以再正则表达式可视化中进行比较。
  2. 观察求导的逻辑,是否是自洽的。
  3. 观察输出方法,是否可能在输出时出现非法格式的情况,考虑极端条件。
  4. 观察递归次数,是否可能由于递归次数多而无法处理大量嵌套的数据。

数据自动生成器

为了减少肉眼debug的工程量,通过自己写的正则表达式,通过python逆向生成数据,对于后续的存在递归的形式,采用逆向递归向下的方法,继续采用正则表达式的逆向生成,生成数据。由于正则表达式是严格按照形式化表述写的,因此数据的覆盖性有一定的保证。相比之下,这种方法比肉眼Hack法有效多了,因为仅仅看代码,很有可能跟着别人的书写思路走,最后没有发现问题所在,而大量的自动构造数据,可以找出一些意想不到的bug。

重构总结

总的来说,这个单元作业经过了一次大重构和一次小重构,其中从一到二基本上改变了整个项目的框架,在从二到三仅仅改了一点点,即将二叉树修改为了多叉树。

从一到二

储存结构

将第一次作业中的数组储存修改为了二叉树表达式树储存。

类图

第一次作业没有继承关系,也没有将常数因子和幂因子分开,求导也是直接在表达式中进行;在第二次作业中,将表达式作为一个表达式树处理,因此表达式树中的所有节点都继承了Node类,同时将各个类型的因子分开处理,并将求导操作作为接口,分散至每一个类型分别实现,在求导操作时只需要调用该类的求导方法即可。

度量分析

第一次作业的圈复杂度平均值为2.78,而第二次作业为2.42,可见在重构之后,程序的圈复杂度降低了,更加符合面对对象的思想了。

从二到三

储存结构

将第二次作业中的二叉树储存修改为了多叉树表达式树储存。

类图

删去了Item类,同时将Relation中的属性修改为了ArrayList,此外类图基本一致,没有做太多的修改。

度量分析

第一次作业的圈复杂度平均值为2.42,而第二次作业为2.56,圈复杂度变高是因为将优化的操作主要放在了AddRelation和MultRelation中,这导致了该方法变得十分复杂,拉高了整体的圈复杂度。

心得体会

心态篇

在这单元作业中,我第二次作业在提交前5小时才开始进行优化操作,心中难免有点紧张,很多地方都没有细想,直接开始修改代码,最后结果便是没有优化成功,以后要摆好心态,在ddl之前也要先细想之后再开始实现代码。

能力篇

这次是我第一次写面对对象的较大的程序,慢慢从第一次作业的不太面对对象转变到了能够写出符合面对对象的程序。另外作业的迭代开发,也让我学习到了如何为下一次扩展打好基础,以免大规模的重构。

另外这也是我真正第一次将数据结构的知识运用在自己的程序中,对比之前的项目,都是很多无脑for循环加ifelse,这次慢慢将数据结构的思想实现在了程序中。

Guess you like

Origin blog.csdn.net/qq_45551930/article/details/115255632