Resumen de la primera unidad orientada a objetos

Resumen de la primera unidad orientada a objetos

Análisis de diseño

primera asignación

Arquitectura general

La clase Main es responsable de leer y generar, pasando la cadena leída a la clase de método Regexfunc.La clase Regexfunc se usa especialmente para el procesamiento de expresiones regulares y devuelve la expresión Expression después de analizar la cadena; hay un Polyitem en la Expresión El hashmap se utiliza para guardar cada elemento de la expresión, y cada Polyitem contiene un Factor, que es el factor del elemento.

análisis de clase

Análisis métrico

La siguiente tabla es el análisis métrico tomado para cada método, a excepción de los dos métodos de derivación y análisis de expresiones regulares, que tienen una mayor complejidad cognitiva, los demás se encuentran en un nivel inferior.

Método CogC ev(G) IV(G) v(g)
Expresión.Expresión() 0 1 1 1
Expresión.addPolyitem(Polyitem) 2 1 2 2
Expresión.derivada() 4 1 3 3
Expresión.toString() 23 2 12 13
Factor.Factor() 0 1 1 1
Factor.Factor(EnteroGrande,EnteroGrande) 0 1 1 1
Factor.getCoeficiente() 0 1 1 1
Factor.getIndex() 0 1 1 1
Factor.much(Factor) 0 1 1 1
Factor.setCoeficiente(EnteroGrande) 0 1 1 1
Factor.setIndex(BigInteger) 0 1 1 1
Principal.principal(Cadena[]) 3 1 2 3
Polielemento.Polyelemento() 0 1 1 1
Polielemento.Polyitem(EnteroGrande,EnteroGrande) 0 1 1 1
Polielemento.addFactor(Factor) 0 1 1 1
Polielemento.getFactor() 0 1 1 1
Polielemento.setFactor(Factor) 0 1 1 1
Regexfunc.getitem(String) 33 1 dieciséis dieciséis

La siguiente tabla muestra el análisis de medición para cada clase, se puede ver que Expression y Regexfunc son los más complejos, esto es lo mismo que el análisis anterior, debido a la complejidad del método de derivación y el análisis de expresiones regulares.

Clase OCmed OCmáx WMC
Expresión 4.75 13 19
Factor 1 1 7
Principal 3 3 3
poliita 1 1 5
Regexfunc 12 12 12
análisis de diagrama de clases

  • Principal

Debido al diseño de desacoplamiento, Main solo es responsable de leer cadenas y generar resultados.

  • Regexfunc

Esta clase es una clase de método que maneja expresiones regulares, con un solo método para analizar una cadena y devolver la expresión.

A continuación se muestra cómo escribir una expresión regular. De acuerdo con la expresión formal dada en el título, escriba de abajo hacia arriba, es decir, la expresión regular de arriba puede "llamar" a la expresión regular de abajo:

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

La expresión regular resultante se ve así:

  • Expresión

Esta clase se basa en el tema, y ​​la expresión se obtiene agregando una serie de elementos, por lo que solo hay un atributo en ella, es decir, un hashmap utilizado para almacenar cada elemento de la expresión. Además, dado que todos los elementos se pueden obtener en esta clase, el método de derivación se escribe en esta clase.

  • poliita

Esta clase es la clase de elemento, que contiene los factores del elemento. Por lo tanto, su atributo tiene solo una clase Factor, correspondiente a su factor. Su método es solo un método muy simple addFactor para agregar Factor.

  • Factor

Esta es una clase de factor, que almacena los atributos correspondientes a un factor: coeficiente coeficiente, índice índice. En esta clase hay un método mul adicional para multiplicar dos Factores para obtener el Factor resultante.

Análisis de ventajas y desventajas.
  • ventaja
  1. Cada parte se desacopla y cada diseño de función que debe completarse se separa en diferentes clases, lo que favorece la expansión posterior.
  2. Debido al diseño de desacoplamiento, al depurar, solo necesita observar qué parte funcional tiene un problema, y ​​puede ubicar rápidamente las partes relevantes del proyecto, y la depuración es rápida.
  • defecto
  1. Factor y Polyitem son en realidad equivalentes, es decir, Polyitem no es realmente necesario, y solo hay un Factor en Polyitem, lo que trae algunos problemas a la derivación de la expresión: primero debe obtener Polyitem y luego Factor, lo que afecta la eficiencia operativa del proyecto. .
  2. El análisis y la escritura de expresiones regulares son confusos y lleva mucho tiempo depurarlos.

segunda tarea

Arquitectura general

La clase principal es responsable de leer y generar, pasando las cadenas de lectura a NodeFactory para obtener expresiones, donde Nodefactory es una clase de método especialmente utilizada para el análisis de expresiones, y luego NodeFactory creará varios nodos Node para formar un árbol de expresión y devolver el nodo raíz. . Otras clases heredan directa o indirectamente de Nodo.Nodo conduce a dos tipos de nodos: Relación y Factor, que representan la relación y el factor en el árbol de expresión respectivamente. La relación se divide además en la relación aditiva AddRelation y la relación multiplicativa MultRelation, y el factor se divide además en el factor triangular TrigFactor, el factor de potencia PowerFactor y el factor constante ConstFactor.

análisis de expresiones

Dado que la expresión tiene una relación anidada, y la expresión regular solo es equivalente a una máquina de estados finitos, no puede completar la coincidencia de expresiones con un número indefinido de veces de anidamiento, por lo que se usa el autómata push-down, es decir, agregar una pila para cooperar con la fórmula de la expresión regular para completar el análisis de la expresión.

Aquí hay un diagrama de flujo de mi expresión leída en:

getNode (entrada de cadena)

Primero pase la expresión como parámetro al método getNode, este método devuelve un nuevo nodo como expresión. El primer paso de este método es cambiar los paréntesis más externos en la expresión a llaves Aquí utilizo una pila simplificada para esta operación, de modo que los factores de expresión de la siguiente capa se puedan expresar a través de expresiones regulares Después de reconocer la fórmula, cada el elemento se separa y se pasa al siguiente método getItem después de la coincidencia de bucle.

Por ejemplo: x+(x**2+1+sin(x))*x**2después de que se lea la cadena de caracteres, se colocará después de repalceBracket x+{x**2+1+sin(x)}*x**2, de modo que el factor de expresión se pueda identificar proporcionando una expresión regular al hacer coincidir. Además, dado que los paréntesis de sen y cos también estarán sujetos a la misma operación, he cambiado la coincidencia de sen y cos a sen{x} o sen(x).

getItem(entrada de cadena)

Luego de ser procesado por getNode, este método se encarga de devolver el nodo correspondiente al ítem. La primera es realizar una mayor segmentación, identificar y segmentar los factores en el ítem a través de expresiones regulares, y luego pasarlo al método getFactor para obtener el nodo correspondiente al ítem y devolverlo.

getFactor(entrada de cadena)

Este método devuelve el nodo correspondiente al factor a través del patrón de fábrica. El primero es juzgar si el factor es un factor de expresión, si es un factor de expresión, pasar la cadena del factor a getNode para obtener el nodo de la expresión, en otros casos, devolver el tipo de factor correspondiente.

análisis de clase

Análisis métrico

La siguiente tabla es el análisis de medición de cada método, a excepción del método toString de salida por la expresión, que tiene una complejidad cognitiva alta, los demás están en un nivel bajo.

Método CogC ev(G) IV(G) v(g)
AddRelation.AddRelation(Nodo,Nodo) 0 1 1 1
AddRelation.derivative() 0 1 1 1
AgregarRelación.normalizar() 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
Artículo.Artículo() 0 1 1 1
Elemento.Elemento(EnteroGrande,EnteroGrande,EnteroGrande,EnteroGrande) 0 1 1 1
Artículo.addItem(Artículo) 0 1 1 1
Artículo.derivado() 0 1 1 1
Elemento.getCoef() 0 1 1 1
Artículo.getCosIndex() 0 1 1 1
Artículo.getPowerIndex() 0 1 1 1
Artículo.getSinIndex() 0 1 1 1
Artículo.multiItem(Artículo) 0 1 1 1
Artículo.normalizar() 0 1 1 1
Elemento.setCoef(BigInteger) 0 1 1 1
Item.setCosIndex(BigInteger) 0 1 1 1
Elemento.setPowerIndex(BigInteger) 0 1 1 1
Elemento.setSinIndex(BigInteger) 0 1 1 1
Elemento.aElemento() 0 1 1 1
Elemento.toString() 31 1 12 dieciséis
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,这次慢慢将数据结构的思想实现在了程序中。

Supongo que te gusta

Origin blog.csdn.net/qq_45551930/article/details/115255632
Recomendado
Clasificación