面向对象第一单元总结
前言
本次面向对象程序设计与构造第一单元的三次作业,通过迭代开发实现了更多功能的表达式解析。通过这三次作业,我对递归下降、深克隆、接口等等有了更深刻的认识。
程序结构分析
第一次作业
类图
*略去了get[Parameter]和set[Parameter]的方法,下同
对类的解释说明
类(或接口)名 | 解释说明 | 实现接口 | 属性个数 | 方法个数 |
---|---|---|---|---|
Main | 程序入口,进行输入输出,并调用其他类 | 2 | ||
Parser | 分析输入字符串并转为Expr 类型 | 1 | 7 | |
Lexer | 分析输入字符串的结构,例如括号、数字、符号等 | 4 | 8 | |
接口Bottom | 幂函数的底数 | |||
接口Factor | 构成项的因子 | |||
Expr | 表达式类,其中 terms 属性用于存储构成表达式的若干项 | Bottom | 1 | 5 |
Term | 项类,其中 factors 属性用于存储构成项的若干因子,isNega 属性用于表示正负 | 2 | 12 | |
Var | 变量因子类,其中 variableNum 属性用于存储变量的类别,该属性为0,1,2时分别对应变量为x,y,z;variables 属性用于存储支持的变量名,通过 variables[variableNum] 可获得此变量的名称 | Factor Bottom | 2 | 3 |
Num | 常数因子类,其中 num 属性用于存储常数的值 | Factor | 1 | 3 |
Power | 常数+指数或表达式+指数,其中 bottom 属性用于存储底数,expo 属性用于存储指数 | Factor | 2 | 7 |
SimplifiedExpo | 用于存储表达式化简后每一项各变量的指数,其中 expoX 、expoY 、expoZ 属性分别表示 x 、y 、z 的指数 | 3 | 7 | |
Simplified | 用于存储表达式化简后每一项,其中 count 属性表示项的系数,SimplifiedExpo 属性同上述,不做赘述 | 2 | 4 |
复杂度
*基于 MatricsReloaded
插件实现,其中 CogC
、ev(G)
、iv(G)
、v(G)
分别代表1. 认知复杂度、2. 基本复杂度,用来衡量程序非结构化程度、3. 模块设计复杂度,用来衡量模块判定结构,即模块和其他模块的调用关系、4. 圈复杂度,用来衡量一个模块判定结构的复杂程度。(下同)
(部分引用自IDEA圈复杂度插件(MetricsReload)下载与使用_metricsreloaded_Dkangel的博客-CSDN博客)
Module | v(G)_avg |
---|---|
oo_hw1 | 2.81 |
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
expr.Expr.addTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.extend() | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.multAndSimplify() | 9.0 | 1.0 | 7.0 | 7.0 |
expr.Num.getNum() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.Num(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.getBottom() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.isConst() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.Power(Bottom) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.Power(Bottom, Integer) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.Power(Bottom, Integer, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.getCount() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.getSimplifiedExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified(SimplifiedExpo, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.SimplifiedExpo.allZero() | 1.0 | 1.0 | 1.0 | 3.0 |
expr.SimplifiedExpo.compareTo(SimplifiedExpo) | 17.0 | 8.0 | 1.0 | 8.0 |
expr.SimplifiedExpo.equals(Object) | 3.0 | 2.0 | 4.0 | 4.0 |
expr.SimplifiedExpo.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.SimplifiedExpo.SimplifiedExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.SimplifiedExpo.SimplifiedExpo(int, int, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.SimplifiedExpo.toStr(boolean) | 17.0 | 1.0 | 10.0 | 13.0 |
expr.Term.addFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.extend() | 26.0 | 3.0 | 11.0 | 11.0 |
expr.Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.getNega() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.inMult() | 12.0 | 1.0 | 6.0 | 12.0 |
expr.Term.mult(ArrayList, ArrayList) | 9.0 | 1.0 | 5.0 | 5.0 |
expr.Term.multExt(ArrayList) | 4.0 | 2.0 | 3.0 | 3.0 |
expr.Term.setIsMinus(Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.setIsNega(Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(Boolean, Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getNum() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getVariable() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.Var(char) | 1.0 | 1.0 | 1.0 | 4.0 |
Lexer.ahead() | 4.0 | 2.0 | 4.0 | 5.0 |
Lexer.aheadNumber() | 1.0 | 2.0 | 1.0 | 2.0 |
Lexer.back() | 1.0 | 2.0 | 1.0 | 2.0 |
Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
Lexer.getToken() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.isAlgo(char) | 1.0 | 1.0 | 1.0 | 5.0 |
Lexer.isVar(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
Main.toStr(HashMap) | 21.0 | 1.0 | 9.0 | 9.0 |
Parser.parseExpr() | 7.0 | 1.0 | 5.0 | 6.0 |
Parser.parseFactor() | 4.0 | 3.0 | 5.0 | 5.0 |
Parser.parseFactorConst(Lexer) | 3.0 | 1.0 | 4.0 | 4.0 |
Parser.parseFactorExprExpo(Lexer) | 8.0 | 3.0 | 4.0 | 4.0 |
Parser.parseFactorVarExpo(Lexer) | 8.0 | 3.0 | 4.0 | 4.0 |
Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseTerm(Boolean) | 3.0 | 1.0 | 4.0 | 4.0 |
Average | 2.81 | 1.34 | 2.28 | 2.81 |
方法复杂度与优缺点分析
根据上述量化数据可以得出,expr.SimplifiedExpo.compareTo(SimplifiedExpo)
、expr.SimplifiedExpo.toStr(boolean)
、expr.Term.extend()
、expr.Term.inMult()
、Main.toStr(HashMap)
五个方法复杂度较高。
expr.SimplifiedExpo.compareTo(SimplifiedExpo)
方法复杂度较高的原因在于过多的嵌套了 if-else,需要优化if-else的嵌套层数。
expr.SimplifiedExpo.toStr(boolean)
方法复杂度较高的原因在于过多的嵌套了if-else,且拆分了过多的 StringBuilder.append()
使得代码易读性下降。
expr.Term.extend()
方法复杂度高的原因在于并未对Factor
接口构造共有的 expr.Factor.extend()
方法,导致 expr.Term.extend()
方法需要使用多个if-else判断 factors
属性中各个Factor
对象的类型,明显增加了复杂度、显著降低了可读性。
expr.Term.inMult()
方法复杂度高的原因在于并未对Factor
接口构造共有的 expr.Factor.mult(Factor)
方法,导致 expr.Term.inMult()
方法需要使用多个if-else判断 factors
属性中各个Factor
对象的类型。同时输出类Simplified
的属性安排太过分散,一些实现 Factor
接口的类缺少对一些重要方法的封装,明显增加了复杂度、显著降低了可读性。
Main.toStr(HashMap)
方法复杂度高的原因在于缺少对一些重要的属性、输出方法的封装(如变量的指数),使得代码冗长。
Bug分析:
本次作业的Bug在于Lexer
中忽略了数字的长度。
本次作业我并没有将指数符号(即**)替换为单个字符(如^)。而由于本次作业中单个字符的符号较多,故我的Lexer
被设计为对字符仅支持单个读入,导致我需要通过两次Lexer
读入*来判断是否读入指数符号。故我需要在 Lexer
中设计回溯函数 Lexer.back()
,以防止*…(如z*3)而非**的情况。我在设计 Lexer.back()
方法时,忽略了数字的长度,无脑将 pos--
,使得计算出现问题(例如x*10
)。
修复Bug前后代码复杂度无明显差异。
第二次作业
类图
*由于此次作业Bug修复时经历了重构,故展示重构后的类图,重构前见 Bug分析
部分
对类的解释说明
类(或接口)名 | 解释说明 | 实现接口 | 属性个数 | 方法个数 |
---|---|---|---|---|
Main | 程序入口,进行输入输出,并调用其他类 | 2 | ||
Parser | 分析输入字符串并转为Expr 类型 | 2 | 11 | |
Lexer | 分析输入字符串的结构,例如括号、数字、符号等 | 3 | 11 | |
接口Factor | 构成项的因子 | 3 | ||
Express | 表达式类,其中的 terms 属性表示构成表达式的若干项 | 1 | 9 | |
Term | 项类,其中的 factors 属性用于表示构成项的若干因子 | 1 | 13 | |
Expr | 表达式因子类,其中的 express 属性表示表达式因子的底数;expo 属性表示表达式因子的指数 | Factor | 2 | 7 |
Var | 变量因子,其中的 varType 属性表示变量的类别,该属性的值为1,2,3分别对应变量的名称为x,y,z;expo 属性表示幂函数的指数 | Factor | 2 | 9 |
Const | 常数因子,其中的 value 属性表示常数的值 | Factor | 1 | 6 |
Trigonometric | 三角函数因子,其中的 functionType 属性表示三角函数的类型,该属性的值为1,2分别对应函数的名称为sin,cos;expo 属性表示三角函数因子的指数;parameter 属性表示三角函数括号内的因子 | Factor | 3 | 7 |
Extended-Trigonometric | 将三角函数括号内因子展开后的三角函数因子,其中的 functionType 、expo 属性含义同Trigonometric 类;express 属性表示三角函数括号内原先因子展开后的表达式;simplified 属性用于记录 express 属性是否被化简 | Factor | 4 | 8 |
Function | 自定义函数类,其中的 functionType 属性表示自定义函数的类型,该属性的值为1,2,3分别对应自定义函数的名称为x,y,z;varList 属性表示函数定义时的形参列表;express 属性表示函数的表达式 | 3 | 5 | |
FunctionList | 自定义函数的集合类,用于存储定义了的若干自定义函数。其中的 functions 属性的Key 表示自定义函数类型(即自定义函数的 functionType ) | 1 | 5 | |
Simplified | 用于存储表达式化简后每一项,其中的 num 属性表示项的系数;expoX 、expoY 、expoZ 属性分别表示 x 、y 、z 的指数;sinMap 、cosMap 的Key 表示三角函数括号内的表达式,Value 表示三角函数因子的次数。 | 6 | 6 |
复杂度
Module | v(G)_avg |
---|---|
oo_hw2 | 2.42 |
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
expr.Const.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.Const(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.multiply(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.pow(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.Expr(Express, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.getExtendedExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.multiExpo(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.toExpress() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.addTerm(Term) | 2.0 | 3.0 | 3.0 | 3.0 |
expr.Express.call(ArrayList, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.clone() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.Express() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Express.extend() | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Express.multi(Express) | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Express.multi(Factor) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.setNega(boolean) | 4.0 | 2.0 | 3.0 | 3.0 |
expr.Express.simplify() | 13.0 | 1.0 | 9.0 | 9.0 |
expr.ExtendedTrigonometric.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric. ExtendedTrigonometric(int, Express, boolean, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric. ExtendedTrigonometric(int, Express, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.getExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.getFunctionType() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.add(Simplified) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.isSameKind(Simplified) | 12.0 | 10.0 | 5.0 | 12.0 |
expr.Simplified.isZero() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified(BigInteger, int, int, int, HashMap, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.toString() | 17.0 | 1.0 | 12.0 | 12.0 |
expr.Term.addFactor(Factor) | 2.0 | 3.0 | 3.0 | 3.0 |
expr.Term.call(ArrayList, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Term.clone() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Term.extend() | 14.0 | 1.0 | 10.0 | 10.0 |
expr.Term.multi(Factor) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Term.multi(Term) | 2.0 | 1.0 | 3.0 | 3.0 |
expr.Term.putTrigonometric(ExtendedTrigonometric, HashMap, HashMap) | 15.0 | 1.0 | 5.0 | 7.0 |
expr.Term.simplify() | 11.0 | 6.0 | 7.0 | 10.0 |
expr.Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(ArrayList, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(boolean, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.toNega() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.multiExpo(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.toExtendedTrigonometric() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.Trigonometric(int, Factor, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.Trigonometric(String, Factor, int) | 1.0 | 1.0 | 1.0 | 3.0 |
expr.Var.beCalled(ArrayList) | 3.0 | 3.0 | 2.0 | 3.0 |
expr.Var.call(ArrayList, ArrayList) | 6.0 | 5.0 | 6.0 | 6.0 |
expr.Var.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getVarType() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.multiExpo(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.Var(int, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.Var(String, int) | 1.0 | 1.0 | 1.0 | 4.0 |
Function.call(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.Function(int, ArrayList, Express) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.Function(String, ArrayList, Express) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.getFunctionType() | 0.0 | 1.0 | 1.0 | 1.0 |
Function.nameToType(String) | 1.0 | 1.0 | 1.0 | 4.0 |
FunctionList.addFunction(int, Function) | 2.0 | 3.0 | 3.0 | 3.0 |
FunctionList.call(int, ArrayList) | 1.0 | 2.0 | 2.0 | 2.0 |
FunctionList.call(String, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
FunctionList.FunctionList() | 0.0 | 1.0 | 1.0 | 1.0 |
FunctionList.nameToType(String) | 1.0 | 1.0 | 1.0 | 4.0 |
Lexer.ahead() | 8.0 | 2.0 | 7.0 | 10.0 |
Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
Lexer.getToken() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.isAlgo(char) | 1.0 | 1.0 | 1.0 | 6.0 |
Lexer.isFunc(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Lexer.isTrigonometric(char) | 1.0 | 1.0 | 1.0 | 2.0 |
Lexer.isVar(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.tokenIsFunc() | 1.0 | 1.0 | 3.0 | 3.0 |
Lexer.tokenIsTrigonometric() | 1.0 | 1.0 | 2.0 | 2.0 |
Lexer.tokenIsVar() | 1.0 | 1.0 | 3.0 | 3.0 |
Main.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Main.stringSimplify(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseExpress() | 4.0 | 1.0 | 5.0 | 5.0 |
Parser.parseFactor() | 5.0 | 5.0 | 5.0 | 5.0 |
Parser.parseFactorConst() | 3.0 | 1.0 | 4.0 | 4.0 |
Parser.parseFactorExpr() | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.parseFactorFunc() | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parseFactorTrigonometric() | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.parseFactorVar() | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.parseFunc() | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseFuncVarList() | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.Parser(Lexer, FunctionList) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseTerm(Boolean) | 1.0 | 1.0 | 2.0 | 2.0 |
Average | 1.65 | 1.33 | 2.05 | 2.42 |
方法复杂度与优缺点分析
根据上述量化数据可以得出,expr.Express.simplify()
、expr.Simplified.isSameKind(Simplified)
、expr.Simplified.toString()
、expr.Term.extend()
、expr.Term.putTrigonometric(ExtendedTrigonometric, HashMap, HashMap)
、expr.Term.simplify()
六个方法复杂度较高。
expr.Express.simplify()
方法复杂度较高的原因是缺少对Simplified
类的 equals()
、hashCode()
方法的重写,导致过多的使用循环和if-else语句以判断同类相,显著降低了代码的可读性和可维护性。
expr.Simplified.isSameKind(Simplified)
方法复杂度较高的原因是缺少对Express
类的 equals()
、hashCode()
方法的重写,导致过多的使用循环和if-else语句以判断相等。
expr.Simplified.toString()
方法复杂度较高的原因是过度拆分 StringBuilder.append()
,同时未将已封装好的类(如变量因子)和其 toString()
方法利用起来,使得代码冗长且过多的利用if-else语句。
expr.Term.extend()
方法复杂度较高的原因类似于第一次作业,在于并未对Factor
接口构造共有的 expr.Factor.extend()
方法,导致 expr.Term.extend()
方法需要使用多个if-else判断 factors
属性中各个Factor
的类型,明显增加了复杂度、显著降低了可读性。
expr.Term.putTrigonometric(ExtendedTrigonometric, HashMap, HashMap)
方法复杂度较高的原因是 ExtendedTrigonometric
类缺少对一些使用频率较高的方法进行封装,使得此方法过多的使用switch-case和if-else语句,降低了代码可读性和可维护性。
expr.Term.simplify()
方法复杂度较高的原因是缺少对Factor
接口构造共有的 expr.Factor.mult(Factor)
方法,导致 expr.Term.simplify()
方法需要使用多个if-else判断 factors
属性中各个Factor
的类型,导致可读性显著下降,复杂度显著上升。
优点在于,在接口Factor
中定义了 Factor.call()
方法,并在所有实现接口的类中实现了这个方法,使得函数调用代码的行数下降,增加了不同类的内聚,降低了耦合。
Bug分析
重构前代码的类图
对类的解释说明
与第一次作业实现相同的类和属性不过多赘述,由于增加了带指数的表达式因子,所以Expr
类实现了Factor
接口。
Func
类的属性与第二次作业重构后的Function
类类似,故不过多赘述。
新增的FuncCall
类实现了Factor
接口,作为一种表达式因子,factorParaList
属性用于存储调用时函数的参数。
出现的问题
由于我在解析需要处理的字符串时,将读入的自定义函数调用读为FuncCall
类存入Expr
类作为解析结果,但由于自定义函数的参数嵌套调用,导致我FuncCall
类将调用参数代入函数并转换为Expr
类后,嵌套了过多的Expr
,且我的 expr.Expr.extend()
方法没有妥善处理多层Expr
嵌套的展开,导致结果出错。
对问题的解决方案
在自定义函数调用读入时就将表达式的参数带入,并将最终的带入结果存入最终的表达式类,并在表达式类的展开方法里妥善处理多层表达式类嵌套的展开。
复杂度对比
重构后的代码复杂度已在上述 方法复杂度
部分中展现,故下文展示重构前的代码复杂度。
Module | v(G)_avg |
---|---|
oo_hw2_bef | 2.80 |
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
expr.SimplifiedExpo.toStr2(StringBuilder, boolean, boolean, boolean, boolean) | 22.0 | 1.0 | 9.0 | 16.0 |
expr.Term.simThenToString() | 20.0 | 1.0 | 9.0 | 9.0 |
expr.SimplifiedExpo.toStr(boolean) | 17.0 | 1.0 | 10.0 | 13.0 |
expr.Term.extend() | 16.0 | 1.0 | 9.0 | 9.0 |
expr.SimplifiedExpo.addPower(Power) | 14.0 | 1.0 | 5.0 | 10.0 |
expr.Term.getTerms(ArrayList, ArrayList) | 13.0 | 3.0 | 6.0 | 6.0 |
expr.Expr.multi(Expr) | 9.0 | 1.0 | 5.0 | 5.0 |
expr.Expr.simplify() | 9.0 | 1.0 | 7.0 | 7.0 |
expr.Power.triExtend() | 9.0 | 4.0 | 5.0 | 5.0 |
expr.Term.mult(ArrayList, ArrayList) | 9.0 | 1.0 | 5.0 | 5.0 |
Lexer.ahead() | 8.0 | 2.0 | 7.0 | 10.0 |
expr.Term.triExtend() | 8.0 | 1.0 | 7.0 | 7.0 |
expr.SimplifiedExpo.addTri(Trigonometric) | 7.0 | 1.0 | 3.0 | 5.0 |
expr.Term.multIn() | 7.0 | 1.0 | 7.0 | 7.0 |
expr.Var.change(ArrayList, ArrayList) | 6.0 | 3.0 | 4.0 | 4.0 |
Parser.parseFactor(HashMap) | 5.0 | 5.0 | 5.0 | 5.0 |
expr.SimplifiedExpo.toTerm() | 5.0 | 1.0 | 6.0 | 6.0 |
Parser.parseExpr(HashMap) | 4.0 | 1.0 | 5.0 | 5.0 |
Parser.parseFactorExprExpo(Lexer, HashMap) | 4.0 | 2.0 | 3.0 | 3.0 |
Parser.parseFactorTrigonometricExpo(Lexer, String, HashMap) | 4.0 | 2.0 | 3.0 | 3.0 |
Parser.parseFactorVarExpo(Lexer) | 4.0 | 2.0 | 3.0 | 3.0 |
expr.Func.Func(String, ArrayList) | 4.0 | 1.0 | 3.0 | 4.0 |
expr.FuncCall.FuncCall(String, HashMap) | 4.0 | 1.0 | 3.0 | 4.0 |
expr.Simplified.toString() | 4.0 | 2.0 | 3.0 | 3.0 |
expr.Term.multExt(ArrayList) | 4.0 | 2.0 | 3.0 | 3.0 |
Parser.parseFactorConst(Lexer) | 3.0 | 1.0 | 4.0 | 4.0 |
expr.Expr.extend() | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Expr.simplifyExpr() | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Expr.triExtend() | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Power.change(ArrayList, ArrayList) | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Trigonometric.Trigonometric(String) | 3.0 | 1.0 | 2.0 | 3.0 |
Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
Lexer.getTokenIsFunc() | 1.0 | 1.0 | 3.0 | 3.0 |
Lexer.getTokenIsTrigonometric() | 1.0 | 1.0 | 2.0 | 2.0 |
Lexer.getTokenIsVar() | 1.0 | 1.0 | 3.0 | 3.0 |
Lexer.isAlgo(char) | 1.0 | 1.0 | 1.0 | 6.0 |
Lexer.isFunc(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Lexer.isTrigonometric(char) | 1.0 | 1.0 | 1.0 | 2.0 |
Lexer.isVar(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Main.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parseFactorFunc(Lexer, String, HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parseFuncVarList(Lexer) | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parseTerm(Boolean, HashMap) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Expr.change(ArrayList, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Expr.simThenToString() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.FuncCall.triExtend() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.SimplifiedExpo.addVar(Var) | 1.0 | 1.0 | 1.0 | 4.0 |
expr.SimplifiedExpo.noOutput() | 1.0 | 1.0 | 5.0 | 5.0 |
expr.Term.change(ArrayList, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Trigonometric.funcType() | 1.0 | 1.0 | 1.0 | 3.0 |
expr.Var.Var(char) | 1.0 | 1.0 | 1.0 | 4.0 |
expr.Var.toString() | 1.0 | 1.0 | 1.0 | 4.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.getToken() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.isComma(char) | 0.0 | 1.0 | 1.0 | 1.0 |
Main.stringRepUnimpo(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.Parser(Lexer) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseFunc(Lexer, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.Expr() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.Expr(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.addTerm(Term) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Func.getExprOfFunc() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Func.getFuncType() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Func.getVarList() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Func.setExprOfFunc(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.FuncCall.addFactorPara(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.FuncCall.change(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.Num(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.change(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.getNum() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Num.triExtend() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.Power(Bottom, Integer) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.getBottom() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Power.toString() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified(SimplifiedExpo, BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.getCount() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.getSimplifiedExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.SimplifiedExpo.SimplifiedExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(Boolean, Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.addFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.getNega() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.setIsNega(Boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.Trigonometric(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.change(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.extend() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.getExtendExpr() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.getFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.getTrigonometricType() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.setExtendExpr(Expr) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.setFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.simplify() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.triExtend() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.equals(Object) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getNum() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getVariable() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.hashCode() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.triExtend() | 0.0 | 1.0 | 1.0 | 1.0 |
Average | 2.49 | 1.17 | 2.37 | 2.80 |
可以看出,重构后的代码复杂度有显著降低。
第三次作业
类图
对类的解释说明
类(或接口)名 | 解释说明 | 实现接口 | 属性个数 | 方法个数 |
---|---|---|---|---|
Main | 程序入口,进行输入输出,并调用其他类 | 2 | ||
Parser | 分析输入字符串并转为Expr 类型 | 2 | 12 | |
Lexer | 分析输入字符串的结构,例如括号、数字、符号等 | 3 | 13 | |
Derivative | 求导的方法类,其中的 var 属性表示对何变量求导,express 属性表示待求导的表达式 | 2 | 2 | |
接口Factor | 构成项的因子 | 4 | ||
Express | 表达式类,其中的 terms 属性表示构成表达式的若干项 | 1 | 10 | |
Term | 项类,其中的 factors 属性用于表示构成项的若干因子 | 1 | 14 | |
Expr | 表达式因子类,其中的 express 属性表示表达式因子的底数;expo 属性表示表达式因子的指数 | Factor | 2 | 8 |
Var | 变量因子,其中的 varType 属性表示变量的类别,该属性的值为1,2,3分别对应变量的名称为x,y,z;expo 属性表示幂函数的指数 | Factor | 2 | 10 |
Const | 常数因子,其中的 value 属性表示常数的值 | Factor | 1 | 7 |
Trigonometric | 三角函数因子,其中的 functionType 属性表示三角函数的类型,该属性的值为1,2分别对应函数的名称为sin,cos;expo 属性表示三角函数因子的指数;parameter 属性表示三角函数括号内的因子 | Factor | 3 | 8 |
Extended-Trigonometric | 将三角函数括号内因子展开后的三角函数因子,其中的 functionType 、expo 属性含义同Trigonometric 类;express 属性表示三角函数括号内原先因子展开后的表达式;simplified 属性用于记录 express 属性是否被化简 | Factor | 4 | 9 |
Function | 自定义函数类,其中的 functionType 属性表示自定义函数的类型,该属性的值为1,2,3分别对应自定义函数的名称为x,y,z;varList 属性表示函数定义时的形参列表;express 属性表示函数的表达式 | 3 | 5 | |
FunctionList | 自定义函数的集合类,用于存储定义了的若干自定义函数。其中的 functions 属性的Key 表示自定义函数类型(即自定义函数的 functionType ) | 1 | 5 | |
Simplified | 用于存储表达式化简后每一项,其中的 num 属性表示项的系数;expoX 、expoY 、expoZ 属性分别表示 x 、y 、z 的指数;sinMap 、cosMap 的Key 表示三角函数括号内的表达式,Value 表示三角函数因子的次数。 | 6 | 10 |
复杂度
Module | v(G)_avg |
---|---|
oo_hw3 | 2.63 |
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Derivative.Derivative(Var, Express) | 0.0 | 1.0 | 1.0 | 1.0 |
Derivative.result() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.Const(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.doDerivative(Var) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.multiply(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.pow(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Const.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.doDerivative(Var) | 4.0 | 1.0 | 3.0 | 3.0 |
expr.Expr.Expr(Express, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.getExtendedExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.multiExpo(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Expr.toExpress() | 2.0 | 1.0 | 3.0 | 3.0 |
expr.Express.addTerm(Term) | 2.0 | 3.0 | 3.0 | 3.0 |
expr.Express.call(ArrayList, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.clone() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.doDerivative(Var) | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Express.Express() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Express.extend() | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Express.multi(Express) | 3.0 | 1.0 | 3.0 | 3.0 |
expr.Express.multi(Factor) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Express.setNega(boolean) | 4.0 | 2.0 | 3.0 | 3.0 |
expr.Express.simplify() | 13.0 | 1.0 | 9.0 | 9.0 |
expr.ExtendedTrigonometric.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.doDerivative(Var) | 6.0 | 1.0 | 4.0 | 4.0 |
expr.ExtendedTrigonometric. ExtendedTrigonometric(int, Express, boolean, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric. ExtendedTrigonometric(int, Express, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.getExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.getFunctionType() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.ExtendedTrigonometric.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.add(Simplified) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.emptyOutput() | 1.0 | 1.0 | 5.0 | 5.0 |
expr.Simplified.isSameKind(Simplified) | 12.0 | 10.0 | 5.0 | 12.0 |
expr.Simplified.isZero() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.Simplified(BigInteger, int, int, int, HashMap, HashMap) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Simplified.simplifiedPrintX(StringBuilder, boolean) | 5.0 | 1.0 | 4.0 | 4.0 |
expr.Simplified.simplifiedPrintY(StringBuilder, boolean, boolean) | 6.0 | 1.0 | 4.0 | 5.0 |
expr.Simplified.simplifiedPrintZ(StringBuilder, boolean, boolean, boolean) | 6.0 | 1.0 | 4.0 | 6.0 |
expr.Simplified.toString() | 16.0 | 1.0 | 10.0 | 19.0 |
expr.Term.addFactor(Factor) | 2.0 | 3.0 | 3.0 | 3.0 |
expr.Term.call(ArrayList, ArrayList) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Term.clone() | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Term.doDerivative(Var) | 12.0 | 4.0 | 7.0 | 8.0 |
expr.Term.extend() | 14.0 | 1.0 | 10.0 | 10.0 |
expr.Term.multi(Factor) | 1.0 | 1.0 | 2.0 | 2.0 |
expr.Term.multi(Term) | 2.0 | 1.0 | 3.0 | 3.0 |
expr.Term.putTrigonometric(ExtendedTrigonometric, HashMap, HashMap) | 15.0 | 1.0 | 5.0 | 7.0 |
expr.Term.simplify() | 11.0 | 6.0 | 7.0 | 10.0 |
expr.Term.Term() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(ArrayList, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.Term(boolean, boolean) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Term.toNega() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.call(ArrayList, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.doDerivative(Var) | 6.0 | 1.0 | 4.0 | 4.0 |
expr.Trigonometric.multiExpo(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.toExtendedTrigonometric() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.Trigonometric(int, Factor, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Trigonometric.Trigonometric(String, Factor, int) | 1.0 | 1.0 | 1.0 | 3.0 |
expr.Var.beCalled(ArrayList) | 3.0 | 3.0 | 2.0 | 3.0 |
expr.Var.call(ArrayList, ArrayList) | 6.0 | 5.0 | 6.0 | 6.0 |
expr.Var.clone() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.doDerivative(Var) | 8.0 | 1.0 | 4.0 | 4.0 |
expr.Var.getExpo() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.getVarType() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.multiExpo(int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.toExpress() | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.Var(int, int) | 0.0 | 1.0 | 1.0 | 1.0 |
expr.Var.Var(String, int) | 1.0 | 1.0 | 1.0 | 4.0 |
Function.call(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.Function(int, ArrayList, Express) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.Function(String, ArrayList, Express) | 0.0 | 1.0 | 1.0 | 1.0 |
Function.getFunctionType() | 0.0 | 1.0 | 1.0 | 1.0 |
Function.nameToType(String) | 1.0 | 1.0 | 1.0 | 4.0 |
FunctionList.addFunction(int, Function) | 2.0 | 3.0 | 3.0 | 3.0 |
FunctionList.call(int, ArrayList) | 1.0 | 2.0 | 2.0 | 2.0 |
FunctionList.call(String, ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
FunctionList.FunctionList() | 0.0 | 1.0 | 1.0 | 1.0 |
FunctionList.nameToType(String) | 1.0 | 1.0 | 1.0 | 4.0 |
Lexer.ahead() | 8.0 | 2.0 | 8.0 | 11.0 |
Lexer.getNumber() | 2.0 | 1.0 | 3.0 | 3.0 |
Lexer.getToken() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.isAlgo(char) | 1.0 | 1.0 | 1.0 | 6.0 |
Lexer.isDerivative(char) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.isFunc(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Lexer.isTrigonometric(char) | 1.0 | 1.0 | 1.0 | 2.0 |
Lexer.isVar(char) | 1.0 | 1.0 | 1.0 | 3.0 |
Lexer.Lexer(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.tokenIsDerivative() | 0.0 | 1.0 | 1.0 | 1.0 |
Lexer.tokenIsFunc() | 1.0 | 1.0 | 3.0 | 3.0 |
Lexer.tokenIsTrigonometric() | 1.0 | 1.0 | 2.0 | 2.0 |
Lexer.tokenIsVar() | 1.0 | 1.0 | 3.0 | 3.0 |
Main.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
Main.stringSimplify(String) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseExpress() | 4.0 | 1.0 | 5.0 | 5.0 |
Parser.parseFactor() | 6.0 | 6.0 | 6.0 | 6.0 |
Parser.parseFactorConst() | 3.0 | 1.0 | 4.0 | 4.0 |
Parser.parseFactorDerivative() | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseFactorExpr() | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.parseFactorFunc() | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.parseFactorTrigonometric() | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.parseFactorVar() | 3.0 | 1.0 | 3.0 | 3.0 |
Parser.parseFunc() | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseFuncVarList() | 1.0 | 1.0 | 2.0 | 2.0 |
Parser.Parser(Lexer, FunctionList) | 0.0 | 1.0 | 1.0 | 1.0 |
Parser.parseTerm(Boolean) | 1.0 | 1.0 | 2.0 | 2.0 |
Average | 1.92 | 1.32 | 2.19 | 2.63 |
方法复杂度与优缺点分析
根据上述量化数据可以得出,expr.Term.doDerivative(Var)
方法复杂度较高。(略去了第二次作业复杂度已较高、第三次作业复杂度仍较高的方法)
expr.Term.doDerivative(Var)
方法复杂度较高的原因是,对求导的乘法法则封装不够彻底,构造了较为复杂的for语句和if-else语句,降低了代码的可读性和可维护性。
这份代码的优点在于,通过Factor
接口的 Factor.doDerivative(Var)
方法, 增加了类的内聚性,降低了耦合性,使得求导的复杂度降低,特别是对Term
类的求导,避免了因判断Factor
类型所需if-else语句导致的极高复杂度。
Bug分析
本次出现2个Bug。
第一个Bug
第一个是输出格式错误。之所以第二次作业没有发现这个错误,是因为第三次作业我优化了项的输出方式,当系数为1且后续有元素输出时,不输出系数1(或-1)。这就导致我在输出后续项时需要判断前面是否已经输出了这个项的一部分因子,以确定StringBuilder中是否需要加入乘号(‘*’)。我使用 printNum
、 printX
、printY
、printZ
、printSin
这五个boolean
类的变量来记录是否已输出对应的项,但是我在输出 cos(...)
时,漏于判断 printZ
,导致在输出 z
后再输出 cos(...)
会出现缺少乘号的情况。解决方法时补上漏于判断的变量,出现问题的代码和对应被Hack数据如下。
代码
// 错误:
for (Express express : cosMap.keySet()) {
if (printNum || printX || printY // || printZ
|| printSin || printCos) {
sb.append("*");
}
printCos = true;
sb.append("cos((");
sb.append(express.simplify());
sb.append("))");
if (cosMap.get(express) != 1) {
sb.append("**");
sb.append(cosMap.get(express));
}
}
// 正确:
for (Express express : cosMap.keySet()) {
if (printNum || printX || printY || printZ
|| printSin || printCos) {
sb.append("*");
}
printCos = true;
sb.append("cos((");
sb.append(express.simplify());
sb.append("))");
if (cosMap.get(express) != 1) {
sb.append("**");
sb.append(cosMap.get(express));
}
}
被Hack输入和输出对比
% 输入:
0
-+(-+z**+2*(--cos(dx(59085))**0))
% 期待输出:
+z**2 (* 即 "+z**2*cos((0))**0" *)
% 我的输出:
+z**2cos((0))**0
复杂度对比:
CogC | ev(G) | iv(G) | v(G) | |
---|---|---|---|---|
Debug前 | 16 | 1 | 10 | 18 |
Debug后 | 16 | 1 | 10 | 19 |
从数据中可以看出,Debug后复杂度仅有小幅增大,这是因为新增了一处对 printZ
的判断。
第二个Bug
第二个是表达式因子展开为表达式错误,我无脑将输出(Express
类)默认值设置为 Expr.express
,然后用一个 i
∈[ 2
, expo
)的循环将 Expr.express
与输出相乘,以达到乘方的目的。但这样使得 expo = 0
时,输出为 Expr.express
而非 1
(或其他等价形式),转换错误。解决方法是特判 expo = 0
,输出仅有一个项且项中仅有一个常数因子 1
的表达式(Express
类)。出现问题的代码和对应被Hack数据如下。
代码
// 错误:
public Express toExpress() {
Express bottom = express.extend();
Express result = bottom;
for (int i = 1; i < expo; ++i) {
result = result.multi(bottom);
}
return result;
}
// 正确:
public Express toExpress() {
Express bottom = express.extend();
if (expo == 0) {
Express ret = new Express();
Term term = new Term();
term.addFactor(new Const(BigInteger.ONE));
ret.addTerm(term);
return ret;
}
Express result = bottom;
for (int i = 1; i < expo; ++i) {
result = result.multi(bottom);
}
return result;
}
被Hack输入和输出对比
0
cos((1+2)**0)
输出对比:
% 期待输出:
+cos((1))
% 我的输出:
+cos((3))
复杂度对比:
CogC | ev(G) | iv(G) | v(G) | |
---|---|---|---|---|
Debug前 | 1 | 1 | 2 | 2 |
Debug后 | 2 | 2 | 3 | 3 |
从数据中可以看出,Debug后复杂度增大,这是由于特判 expo = 0
的if语句显著增加了复杂度。
架构设计体验
重构体验见 第二次作业
,主要将自定义函数调用进行了优化,并优化了多层表达式嵌套时的表达式扩展和化简。
第二次作业到第三次作业的迭代,主要是增加了函数的交叉定义(限制了在自定义函数表达式的定义中只能调用已定义的自定义函数)和求导算子。
函数的交叉定义我得益于 FunctionList
类,每当读入一个自定义函数的定义,就将 FunctionList
更新,并将 FunctionList
放入 Parser
类中,使得 Parser
得以使用最新的自定义函数列表,也在交叉定义时得以调用已定义的函数。
求导算子我新增了一个求导的方法类,通过 var
、express
参数和各个表达式、项、因子类的求导方法,在保证高内聚低耦合的前提下,构造了求导类(Derivative
类)里的求导方法。一旦读入求导算子,我就构造一个求导类的对象,调用求导方法得到Expr
因子的对象,放入最终的表达式类中,使得构成表达式类的项和因子的种类尽量少。
分析别人程序Bug时的策略
我的Hack策略按时间两点,看代码前和看代码后。
看代码前的Hack主要在于增加表达式的规模,即cost。还在于构造0和其他数的运算,0次方等边界值。
看代码后的Hack主要在于结合被测程序的代码设计结构,分析代码中可能出现的问题,例如空指针,浅克隆等,针对性Hack。
心得体会
第一单元的这三次作业,让我对递归下降有了更深刻的理解。上学期的oopre课程我选了课,但是最后一次作业我懒于使用递归下降,只是使用正则匹配,导致我在接触到第一单元第一次作业时感觉难度较大,同时第一次作业于oopre最后一次作业较大的难度差距也让我一时间感觉难以下手。但是好在资料较多,网络上、课程组公众号上都有很多有关递归下降等的资料便于我去学习,我也在学习中不断加深了对递归下降的理解,这也让我明白偷懒终究是不行的,学习是要不停止的。
同时我明白了,接口并不只为了让函数的输入、输出参数更为简洁而设计,更要为函数的代码更为简洁而设计。一味的设计没有任何方法的接口意义不大,只会让将其作为参数的函数使用过多的if-else和类型转换进行特判。我应该将实现接口的若干类的共同的方法总结出来,写进接口中,使得调用这个方法的其他类函数更为简洁、明晰。
在互测过程中,我也明白了增强代码阅读能力、增强代码分析能力、增强可能错误的发现能力的重要性。增强这些能力,不仅让我们能更好地发现他人的Bug,在互测中加分,也在自己编写代码的时候帮我们检查、避免更多可能的问题。
在后续单元的程序编写中,我要严格吸取这三次作业的教训,增强代码可读性和可维护性,争取高内聚低耦合,避免漏写、少写参数的问题,增加自我测试样例的多样性,重视代码编写完成后的检查、测试,加入一些用于增强调试时函数调用栈的可读性的代码,使得调试时不至于因为调用过多层函数而一头雾水。