OO-第一单元总结

1.设计迭代

面向对象课程第一单元的核心问题是化简表达式,即对给定输入的表达式进行解析并运算,化简消去不必要的括号。本单元共分三次作业,依此可简略地概括为仅包含多项式的表达式化简,增添有三角函数(sin,cos)、自定义函数、求和函数的表达式化简,以及增添可嵌套函数的表达式化简。

1.第一次作业

1.1 架构

hw1

第一次作业的架构可以分为解析,运算和输出三个部分。

解析字符串的部分采用的是递归下降方法,由ParserLexer两个类完成这项工作。Lexer负责将输入的字符串划分为若干token的线性排列,Parser则通过内部方法“递归式”的调用将线性的字符串转变为层次化的结构(Expr,Term,Factor)。这一部分的设计主要参考了训练栏目的代码,并顺便在Lexer内部实现了一个字符串预处理方法用于去除空白字符和把**替换为单字符的^以便在后边识别token时偷个懒

尽管对于表达式元素的识别是划分层次的,但Expr,TermFactor都共同的使用Poly类来存储数据,这样只需要在实现Poly类之间的加、减、乘、乘方方法即可。对于数据的存储,因为第一次作业仅仅涉及多项式,基本上所有项可由其指数与系数唯一确定(0是例外,会造成多个<指数,系数>均对应0),故使用以指数为键值的HashMap存储一个多项式。

我的运算过程是与解析递归过程一起工作的。对于Factor,返回一个ExprExponent,其Poly的内容已经设置好;对于Term,不断地将新解析的Factor与当前的Term相乘;对于Expr,不断将新的Term与当前Expr相加减。这样利用解析过程本身的树状结构完成由下自上的计算过程,并能保证当上层元素需要计算时下层的元素已经计算完毕。

计算完毕的Expr已经完成了理论上的化简,但是为了获得性能分还需要对输出进行优化,这一部分实现于Poly类的toString()方法。

1.2 度量
class OCavg OCmax WMC
expr.Poly 3.33 10.0 30.0
Parser 4.5 9.0 18.0
Lexer 1.8 4.0 9.0
expr.Expr 2.33 5.0 7.0
expr.Exponent 1.0 1.0 5.0
expr.Term 1.33 2.0 4.0
MainClass 1.0 1.0 1.0
Total 74.0
Average 2.47 4.57 10.57
method CogC ev(G) iv(G) v(G)
Parser.parseFactor() 20.0 4.0 11.0 11.0
expr.Poly.toString() 18.0 3.0 9.0 10.0
expr.Poly.add(Poly) 13.0 1.0 5.0 5.0
expr.Poly.mul(Poly) 10.0 1.0 6.0 6.0
Parser.parseExpr() 7.0 1.0 6.0 6.0
expr.Expr.addTerm(Term, String) 7.0 1.0 5.0 5.0
Parser.parseTerm() 4.0 1.0 5.0 5.0
Lexer.consumeToken() 3.0 2.0 3.0 4.0
Lexer.getNumber() 2.0 1.0 3.0 3.0
expr.Poly.pow(int) 2.0 1.0 3.0 3.0
expr.Term.addFactor(Factor) 2.0 1.0 2.0 2.0
expr.Poly.negate() 1.0 1.0 2.0 2.0
Lexer.Lexer(String) 0.0 1.0 1.0 1.0
Lexer.getCurToken() 0.0 1.0 1.0 1.0
Lexer.preProcess() 0.0 1.0 1.0 1.0
MainClass.main(String[]) 0.0 1.0 1.0 1.0
Parser.Parser(Lexer) 0.0 1.0 1.0 1.0
expr.Exponent.Exponent() 0.0 1.0 1.0 1.0
expr.Exponent.Exponent(BigInteger) 0.0 1.0 1.0 1.0
expr.Exponent.Exponent(BigInteger, BigInteger) 0.0 1.0 1.0 1.0
expr.Exponent.Exponent(BigInteger, int) 0.0 1.0 1.0 1.0
expr.Exponent.getPoly() 0.0 1.0 1.0 1.0
expr.Expr.getPoly() 0.0 1.0 1.0 1.0
expr.Expr.negate() 0.0 1.0 1.0 1.0
expr.Poly.Poly() 0.0 1.0 1.0 1.0
expr.Poly.addUnit(BigInteger, BigInteger) 0.0 1.0 1.0 1.0
expr.Poly.getPoly() 0.0 1.0 1.0 1.0
expr.Poly.sub(Poly) 0.0 1.0 1.0 1.0
expr.Term.getPoly() 0.0 1.0 1.0 1.0
expr.Term.negate() 0.0 1.0 1.0 1.0
Total 89.0 36.0 78.0 80.0
Average 2.97 1.2 2.6 2.67

可以发现最复杂的方法是parseFactor(),其中存在重复代码没有被抽象成单独的方法。此外就是用于处理输出化简的toString()方法。外加上Poly类还需要实现有一定复杂程度的各种运算方法,导致其平均圈复杂度也比较高。

1.3 bug分析

第一次作业中的bug被测出来的共两个,一个是在乘方运算的时候没有复制当前的Poly类状态,导致在乘方循环调用乘法的过程中被乘数也发生了改变;另一个就是在遇到0*x**b这种出现在运算后的项时没有进行判断,导致存储的多项式中“0”项的表示不唯一,同时在输出化简方法中有默认表达式中不存在这类项,导致最终输出格式出错。

2.第二次作业

2.1 架构

hw2

第二次作业主要是增加了两种三角函数、自定义函数和求和函数。本次迭代的主体架构没有大的变化,不过由于在设计第二次作业时没有构思出一个比较好的方法实现解析自定义函数和求和函数,我最终还是选择了预处理字符串的方法实现这两个功能,因此实际上变成了预处理、解析、计算和输出四个部分。

预处理部分主要是实现了一个能够分析函数定义并且实现替换方法的CustomFunc类,以及在Lexer中的一些私有替换方法。

解析部分则只增添了对于三角函数的解析,不过当时在回看第一次作业的代码的时候感觉有些丑陋,因此把对乘方和常数的解析提取成了单独的方法以避免代码复制。

数据存储延续前次作业的思路,只是在存在三角函数的情况下一个项改为由变量x的指数和若干三角函数之积唯一确定,因此构造Index类作为多项式HashMap的键值。而对于不同类型的三角函数,其实真正重要的信息是它们内部的因子和指数,也可以使用HashMap来存储<因子,指数>对,因此只使用同一个类来存储即可,只要在其内部额外标注三角函数类型。而计算部分由于高度依赖HashMap结构,因此对于键值均重写了equals()hashCode()方法,并对Index间的运算做了单独的实现。

输出化简由于Poly,Index的二级结构,分别在两者自己的toString()方法中实现,在实现过程中重新进行了一定可读性调整。

2.2 度量
class OCavg OCmax WMC
expr.TriType 4.0 4.0 4.0
Parser 3.17 4.0 19.0
expr.Poly 3.0 9.0 33.0
expr.Index 2.88 9.0 23.0
Lexer 2.62 5.0 21.0
expr.Expr 2.2 5.0 11.0
MainClass 2.0 2.0 2.0
expr.CustomFunc 1.71 3.0 12.0
expr.Tri 1.4 3.0 7.0
expr.Term 1.33 2.0 4.0
expr.ExpoFunc 1.0 1.0 4.0
expr.TriFunc 1.0 1.0 2.0
Total 142.0
Average 2.33 4.0 11.83
method CogC ev(G) iv(G) v(G)
expr.Poly.toString() 15.0 3.0 8.0 9.0
expr.Index.toString() 14.0 2.0 9.0 9.0
expr.Poly.add(Poly) 13.0 1.0 5.0 5.0
expr.Poly.mul(Poly) 10.0 1.0 6.0 6.0
Lexer.replaceSum(StringBuilder) 7.0 1.0 4.0 4.0
Parser.parseExpr() 7.0 1.0 6.0 6.0
expr.Expr.addTerm(Term, String) 7.0 1.0 5.0 5.0
Lexer.getMatchBracket(int, StringBuilder) 6.0 2.0 4.0 6.0
Parser.parseFactor() 5.0 4.0 4.0 4.0
Parser.parseConst() 4.0 1.0 5.0 5.0
Parser.parsePower() 4.0 1.0 3.0 3.0
Parser.parseTerm() 4.0 1.0 5.0 5.0
expr.CustomFunc.CustomFunc(String) 4.0 3.0 3.0 3.0
expr.Index.equals(Object) 4.0 3.0 3.0 5.0
expr.Index.isConstant() 4.0 4.0 2.0 4.0
expr.Index.mul(Index) 4.0 1.0 3.0 3.0
expr.Tri.equals(Object) 4.0 3.0 3.0 5.0
Lexer.consumeToken() 3.0 2.0 3.0 4.0
expr.Expr.equals(Object) 3.0 3.0 2.0 4.0
expr.Poly.equals(Object) 3.0 3.0 2.0 4.0
Lexer.getNumber() 2.0 1.0 3.0 3.0
expr.Poly.pow(BigInteger) 2.0 1.0 3.0 3.0
expr.Term.addFactor(Factor) 2.0 1.0 2.0 2.0
Lexer.preProcess() 1.0 1.0 2.0 2.0
Lexer.replaceFunc(String, StringBuilder) 1.0 1.0 2.0 2.0
MainClass.main(String[]) 1.0 1.0 2.0 2.0
expr.CustomFunc.createProtoExpr(String) 1.0 1.0 2.0 2.0
expr.CustomFunc.replaceAll(StringBuilder, String, String) 1.0 1.0 2.0 2.0
expr.CustomFunc.substitution(List) 1.0 1.0 2.0 2.0
expr.Poly.negate() 1.0 1.0 2.0 2.0
expr.TriType.getType(String) 1.0 3.0 1.0 3.0
Lexer.Lexer(String, Map) 0.0 1.0 1.0 1.0
Lexer.getCurToken() 0.0 1.0 1.0 1.0
Parser.Parser(Lexer) 0.0 1.0 1.0 1.0
expr.CustomFunc.CustomFunc(String, String) 0.0 1.0 1.0 1.0
expr.CustomFunc.getName() 0.0 1.0 1.0 1.0
expr.CustomFunc.substitutionSum(BigInteger) 0.0 1.0 1.0 1.0
expr.ExpoFunc.ExpoFunc(BigInteger) 0.0 1.0 1.0 1.0
expr.ExpoFunc.ExpoFunc(BigInteger, BigInteger) 0.0 1.0 1.0 1.0
expr.ExpoFunc.ExpoFunc(BigInteger, int) 0.0 1.0 1.0 1.0
expr.ExpoFunc.getPoly() 0.0 1.0 1.0 1.0
expr.Expr.getPoly() 0.0 1.0 1.0 1.0
expr.Expr.hashCode() 0.0 1.0 1.0 1.0
expr.Expr.negate() 0.0 1.0 1.0 1.0
expr.Index.Index(BigInteger, Map) 0.0 1.0 1.0 1.0
expr.Index.createExponent(BigInteger) 0.0 1.0 1.0 1.0
expr.Index.createTri(Factor, TriType, BigInteger) 0.0 1.0 1.0 1.0
expr.Index.hashCode() 0.0 1.0 1.0 1.0
expr.Poly.Poly() 0.0 1.0 1.0 1.0
expr.Poly.addUnit(BigInteger, Index) 0.0 1.0 1.0 1.0
expr.Poly.getPolyData() 0.0 1.0 1.0 1.0
expr.Poly.hashCode() 0.0 1.0 1.0 1.0
expr.Poly.sub(Poly) 0.0 1.0 1.0 1.0
expr.Term.getPoly() 0.0 1.0 1.0 1.0
expr.Term.negate() 0.0 1.0 1.0 1.0
expr.Tri.Tri(TriType, Factor) 0.0 1.0 1.0 1.0
expr.Tri.getExpr() 0.0 1.0 1.0 1.0
expr.Tri.getType() 0.0 1.0 1.0 1.0
expr.Tri.hashCode() 0.0 1.0 1.0 1.0
expr.TriFunc.TriFunc(Factor, TriType, BigInteger) 0.0 1.0 1.0 1.0
expr.TriFunc.getPoly() 0.0 1.0 1.0 1.0
Total 139.0 84.0 138.0 154.0
Average 2.28 1.38 2.26 2.52

可以发现基本上依然是parseFactor()方法和toString()比较复杂,但是通过提取公共函数和分级处理,其易理解性有了一定的提高。此外,此时可以发现Poly类内集中了过多的放大,如果将运算方法单独抽离成几个独立的类或许会更好。

2.3 bug分析

在实现IndextoString()方法时把"x"后的乘号与乘方写在了一起,导致如果指数为1不显示乘方时"x"后的乘号也会消失。此外还有一个优化上的问题,常数的Index不统一导致无法在计算过程中合并起来,在随后的第三次作业中发现了这个问题并进行了修复。

3.第三次作业

2.1 架构

hw3

第三次作业新增的三角函数和自定义函数内部可以为所有因子,且支持一定程度的嵌套,以及任意层级的括号。三角函数内部可为所有因子、括号嵌套等原有架构已经实现(三角函数的输出需要额外考虑不是幂函数因子与三角函数因子情况下的括号问题)。因此本次作业架构上与前次没有很大区别,主要修改在于预处理过程,即自定义函数分析和替换,包括按照最外层逗号拆分实参变量,递归替换作为实参的函数等。在修改问题的过程中有重构了代码,把替换自定义函数和求和函数的方法进行了合并,以便于进行递归替换。

此外,在测试过程中还发现常数0对应表示不统一的问题仍然存在,会造成在三角函数内部的、运算结果为0的表达式不能被正确的识别,影响sin(0),cos(0)的优化,因此改进了各个实现Factor的类的构造方法,完全的统一常数的Index

2.2 度量
class OCavg OCmax WMC
expr.TriType 4.0 4.0 4.0
Lexer 3.44 7.0 31.0
Parser 3.17 4.0 19.0
expr.TriFunc 3.0 5.0 6.0
expr.Poly 2.67 9.0 48.0
expr.Index 2.6 10.0 26.0
MainClass 2.0 2.0 2.0
expr.Expr 2.0 5.0 12.0
expr.CustomFunc 1.71 3.0 12.0
expr.Tri 1.4 3.0 7.0
expr.Term 1.33 2.0 4.0
expr.ExpoFunc 1.2 2.0 6.0
expr.SignalNumber 1.0 1.0 1.0
Total 178.0
Average 2.41 4.38 13.69
method CogC ev(G) iv(G) v(G)
expr.Index.toString() 18.0 2.0 10.0 10.0
Lexer.replaceFunc(String) 17.0 1.0 7.0 7.0
expr.Poly.toString() 15.0 3.0 8.0 9.0
expr.Poly.add(Poly) 13.0 1.0 5.0 5.0
expr.Poly.mul(Poly) 11.0 1.0 7.0 7.0
expr.Poly.isExprFactor() 10.0 6.0 4.0 6.0
Parser.parseExpr() 7.0 1.0 6.0 6.0
expr.Expr.addTerm(Term, String) 7.0 1.0 5.0 5.0
Lexer.getMatchBracket(int, String) 6.0 2.0 4.0 6.0
Lexer.splitVars(String) 6.0 1.0 3.0 6.0
expr.TriFunc.TriFunc(Factor, TriType, BigInteger) 6.0 2.0 5.0 5.0
Parser.parseFactor() 5.0 4.0 4.0 4.0
Parser.parseConst() 4.0 1.0 5.0 5.0
Parser.parsePower() 4.0 1.0 3.0 3.0
Parser.parseTerm() 4.0 1.0 5.0 5.0
expr.CustomFunc.CustomFunc(String) 4.0 3.0 3.0 3.0
expr.Index.equals(Object) 4.0 3.0 3.0 5.0
expr.Index.isConstant() 4.0 4.0 2.0 4.0
expr.Index.mul(Index) 4.0 1.0 3.0 3.0
expr.Tri.equals(Object) 4.0 3.0 3.0 5.0
Lexer.consumeToken() 3.0 2.0 3.0 4.0
Lexer.getFuncHeadFrom(int, String) 3.0 3.0 2.0 3.0
expr.Expr.equals(Object) 3.0 3.0 2.0 4.0
expr.Poly.equals(Object) 3.0 3.0 2.0 4.0
Lexer.getNumber() 2.0 1.0 3.0 3.0
expr.ExpoFunc.ExpoFunc(BigInteger) 2.0 1.0 2.0 2.0
expr.Poly.isZero() 2.0 3.0 1.0 3.0
expr.Poly.pow(BigInteger) 2.0 1.0 3.0 3.0
expr.Term.addFactor(Factor) 2.0 1.0 2.0 2.0
MainClass.main(String[]) 1.0 1.0 2.0 2.0
expr.CustomFunc.createProtoExpr(String) 1.0 1.0 2.0 2.0
expr.CustomFunc.replaceAll(StringBuilder, String, String) 1.0 1.0 2.0 2.0
expr.CustomFunc.substitution(List) 1.0 1.0 2.0 2.0
expr.Poly.negate() 1.0 1.0 2.0 2.0
expr.TriType.getType(String) 1.0 3.0 1.0 3.0
Lexer.Lexer(String, Map) 0.0 1.0 1.0 1.0
Lexer.getCurToken() 0.0 1.0 1.0 1.0
Lexer.preProcess() 0.0 1.0 1.0 1.0
Parser.Parser(Lexer) 0.0 1.0 1.0 1.0
expr.CustomFunc.CustomFunc(String, String) 0.0 1.0 1.0 1.0
expr.CustomFunc.getName() 0.0 1.0 1.0 1.0
expr.CustomFunc.substitutionSum(BigInteger) 0.0 1.0 1.0 1.0
expr.ExpoFunc.ExpoFunc() 0.0 1.0 1.0 1.0
expr.ExpoFunc.ExpoFunc(BigInteger, BigInteger) 0.0 1.0 1.0 1.0
expr.ExpoFunc.ExpoFunc(BigInteger, int) 0.0 1.0 1.0 1.0
expr.ExpoFunc.getPoly() 0.0 1.0 1.0 1.0
expr.Expr.getPoly() 0.0 1.0 1.0 1.0
expr.Expr.hashCode() 0.0 1.0 1.0 1.0
expr.Expr.negate() 0.0 1.0 1.0 1.0
expr.Expr.toString() 0.0 1.0 1.0 1.0
expr.Index.Index(BigInteger, Map) 0.0 1.0 1.0 1.0
expr.Index.createExponent(BigInteger) 0.0 1.0 1.0 1.0
expr.Index.createTri(Factor, TriType, BigInteger) 0.0 1.0 1.0 1.0
expr.Index.hasExpo() 0.0 1.0 1.0 1.0
expr.Index.hashCode() 0.0 1.0 1.0 1.0
expr.Index.triSize() 0.0 1.0 1.0 1.0
expr.Poly.Poly() 0.0 1.0 1.0 1.0
expr.Poly.Poly(Map) 0.0 1.0 1.0 1.0
expr.Poly.Poly(Poly) 0.0 1.0 1.0 1.0
expr.Poly.addUnit(BigInteger, Index) 0.0 1.0 1.0 1.0
expr.Poly.addUnit(int, int) 0.0 1.0 1.0 1.0
expr.Poly.getPolyData() 0.0 1.0 1.0 1.0
expr.Poly.getSize() 0.0 1.0 1.0 1.0
expr.Poly.hashCode() 0.0 1.0 1.0 1.0
expr.Poly.setPolyData(Map) 0.0 1.0 1.0 1.0
expr.Poly.sub(Poly) 0.0 1.0 1.0 1.0
expr.SignalNumber.getPoly() 0.0 1.0 1.0 1.0
expr.Term.getPoly() 0.0 1.0 1.0 1.0
expr.Term.negate() 0.0 1.0 1.0 1.0
expr.Tri.Tri(TriType, Factor) 0.0 1.0 1.0 1.0
expr.Tri.getExpr() 0.0 1.0 1.0 1.0
expr.Tri.getType() 0.0 1.0 1.0 1.0
expr.Tri.hashCode() 0.0 1.0 1.0 1.0
expr.TriFunc.getPoly() 0.0 1.0 1.0 1.0
Total 181.0 107.0 165.0 189.0
Average 2.45 1.45 2.23 2.55

伴随着表达式形式的复杂程度加深,兼具输出和化简的toString()方法变得更加臃肿且不易理解,函数替换方法也过于复杂。Poly类为了更好的处理常数计算,导致运算方法也变得复杂起来,导致表达式类的复杂性更加凸显。如果将输入字符串预处理、输出表达式化简、表达式类运算等方法提取出来应当可以降低代码的复杂性,提高可维护性。

2.3 bug分析

bug主要集中在0常数的处理上。在这一单元的最后,我逐渐发现了当前这种将各个层次的类统一使用Poly存储的弊端,那就是一旦增添新的单项式形式,整个Poly类需要拓展,发生很大的变化,容易产生bug。此外,在当前的数据存储结构下,0常数无法单一表示,只能在运算方法中修修补补,某种程度上增大了它们的复杂度也更容易出错。

2.bug与测试策略

2.1 bug统一分析

总结在迭代过程中遇到的bug,可以发现基本上都出现在比较复杂的toString()方法、运算方法和函数替换预处理方法中。而且这些方法所在的类功能都相对比较庞杂,不容易进行测试和debug。

2.2 测试策略

一方面尝试自己写了一个基于文法的测试数据生成器进行黑盒测试,然而在一些细节的限制上我的程序处理的不好,有时不能保证测试数据的完备性。因此在测试过程中也结合了人工构造测试数据的方法,选择一些极端情况数据,如超大数据,负数,求和函数起始大于结束等等。

3.体会

这一单元完成的这些任务可以说是我对于面向对象编程的第一次实践,感觉真的是非常耗费精力。特别是第一、第二次作业,架构的设计让我绞尽脑汁,甚至第二次更为辛苦。毕竟第一次作业是白纸上作画,有了大概的思路就开始写代码,尽管主观上想要把握着OO的思想去构建代码,但是第二次迭代的时候还是觉得之前的代码有很多地方写得太过随意、拓展性不高,以至于我最终放弃了在解析过程中解析自定义函数和求和函数转而去在预处理过程中使用字符串替换这种取巧的方法解决问题。在写这篇博客,我才发现

通过第一单元的学习和实践,我感觉到面向对象编程最重要的一点就是“设计”。拓展性强、简洁清晰的设计能够使迭代开发过程变得轻松,能够减少bug出现的可能并便于代码测试。当然,良好的设计也不是一蹴而就的,只有一边丰富理论知识,一边经历着充满重构和苦难的实践,才能更好的把握这种设计思维。

posted @ 2022-03-26 02:40  scrail  阅读(30)  评论(0编辑  收藏  举报