OO第一单元总结

OO第一单元总结

第一次作业

架构设计思路

​ 第一次作业我一开始打算用接识别正则表达式的方法,但在写的过程中发现这种方法需要对各种情况分类讨论,很容易讨论不全。之后我参考了训练中的代码,决定采用递归下降的思路。按照文档中对表达式,项和因子的定义,我构造了Expr, Term, Factor三个类,其中Factor类表示除表达式因子外的所有因子,Term类和Expr的定义则符合形式化表述。因此,ParserFactor()函数可能返回Expr或Factor的实例,需要定义ExprAndFactor接口;ParserTerm()函数可能返回Term或Expr的实例,需要定义ExprAndTerm接口。

​ 针对题目中拆括号的要求,我在ParserTerm的时候做统一处理。拆括号的情况可以分为两类,一是表达式因子的幂,一是表达式因子相乘。我先处理表达式因子的幂,将其转换为表达式因子相乘。具体方法为ParserFactor函数中如果遇到Expr类,则将Expr的属性Power设置成指数,返回到ParserTerm函数后,在addFactor时进行循环,加入指数个表达式因子。拆除表达式因子相乘的括号则遍历Term类中的ArrayList, 遇到Expr类时调用自定义的表达式乘法函数。因此,ParserTerm函数的返回值不含括号。

​ 在写作业的时候我主要纠结在如何处理正负号的问题上。按照形式化表述,正负号共分为三类,分别是整数前的正负号,项前面的正负号,和表达式中每一项前面的正负号。我选择将除整数的正负号外的其它符号都归入Term中处理,这样可以保证Expr类中的ArrayList全都是相加的关系。

​ 具体的类图如下:

image

复杂度分析

属性个数 方法个数 代码总规模
main 0 2 31
lexer 3 4 44
parser 0 4 106
Expr 1 7 108
Term 4 9 92
Factor 2 4 24
Complexity metrics
Method CogC ev(G) iv(G) v(G)
Lexer.Lexer(String) 0 1 1 1
Lexer.getNumber() 2 1 3 3
Lexer.next() 7 2 4 6
Lexer.peek() 0 1 1 1
MainClass.handleSign(String) 0 1 1 1
MainClass.main(String[]) 0 1 1 1
Parser.Parser(Lexer) 0 1 1 1
Parser.parserExpr() 5 1 5 5
Parser.parserFactor() 17 3 8 8
Parser.parserTerm(String) 4 1 4 4
module.Expr.addTerm(ExprAndTerm) 2 1 2 2
module.Expr.getPower() 0 1 1 1
module.Expr.getTerms() 0 1 1 1
module.Expr.mergeTerms() 10 1 6 6
module.Expr.setPower(int) 0 1 1 1
module.Expr.setTerms(ArrayList) 0 1 1 1
module.Expr.toString() 35 3 13 14
module.Factor.getConstant() 0 1 1 1
module.Factor.getPower() 0 1 1 1
module.Factor.setConstant(BigInteger) 0 1 1 1
module.Factor.setPower(int) 0 1 1 1
module.Term.Term(BigInteger, int) 0 1 1 1
module.Term.addFactors(ExprAndFactor) 4 1 3 3
module.Term.getCoefficient() 0 1 1 1
module.Term.getTermPower() 0 1 1 1
module.Term.setExprPositive(boolean) 0 1 1 1
module.Term.setTermPositive(boolean) 0 1 1 1
module.Term.simplify1() 8 1 5 5
module.Term.simplify2() 3 1 3 3
module.Term.termsMulti(ArrayList, ArrayList) 3 1 3 3
Class OCavg OCmax WMC
Lexer 2.5 6 10
MainClass 1 1 2
Parser 4.25 8 17
module.Expr 3.71 14 26
module.Factor 1 1 4
module.Term 2.11 5 19
Project v(G)avg v(G)tot
project 2.67 80

​ 在复杂度方面ParserFactor()函数和toString()函数复杂度较高,控制分支数和行数都很多,应该把它们拆分成多个函数。ParserFactor()函数中对数字、幂函数、表达式因子的解析可以单独写函数;toString()函数不应该只在Expr类中使用,而应该递归调用Term和Factor的toString。此外,Parser类没有面向对象而是面向过程,导致复杂度很高。

bug分析

这次作业没有在强测和互测中出现bug,也没有找到别人的bug

第二次作业

架构设计思路

​ 第二次作业在第一次的基础上增加了三角函数因子,自定义函数因子和求和函数因子,其中自定义函数因子和求和函数因子经过化简后不会出现在最终的表达式中。因此最终的表达式中的项可以写成

\[a*x^b*sin(x^{c_1})^{d_1}*...*sin(x^{c_n})^{d_n}*cos(x^{e_1})^{f_1}*...*cos(x^{e_n})^{f_n} \]

由于这次作业中三角函数里面只可能是常数因子和幂函数,所以在Term类的属性中,我使用四个HashMap表示三角函数,如下所示:

private HashMap<Integer, Integer> sinPower = new HashMap<>(); //sin里面是幂函数 key为里面的幂
private HashMap<Integer, Integer> cosPower = new HashMap<>(); //cos里面是幂函数 key为里面的幂
private HashMap<BigInteger, Integer> constantSin = new HashMap<>(); //sin里面是常数
private HashMap<BigInteger, Integer> constantCos = new HashMap<>(); //cos里面是常数

这种表示方法在这次作业里方便计算,但不具有扩展性,无法进行迭代开发。

​ 对于自定义函数和求和函数的处理,我开始写了一个直接在MainClass里用正则表达式替换的版本。但这导致MainClass非常臃肿,处理自定义函数和求和函数的方法各有50行左右,而且这违背了递归下降的思路,同时不利于后续的迭代开发。因此我又写了一个递归下降的版本,定义了自定义函数和求和函数的类,在ParserFactor函数下进行调用。但这个版本也存在很多问题,主要都是由于浅拷贝导致的。还有我在解析sum(i,s,e,t)处理t的时候是正常解析完t,如果i还小于等于e,则将lexer的pos设置回到t之前,继续解析一遍t. 我将i是否大于e的循环判断放在parserExpr里面,但这会导致正常解析表达式时会出错。最终我还是提交了第一个正则表达式的版本。

image

复杂度分析

属性个数 方法个数 代码总规模
main 2 4 154
lexer 3 4 47
parser 0 8 147
Expr 2 6 64
Term 8 19 203
Factor 3 6 24
TrigFactor 3 6 31
Calculate 0 5 92
Complexity metrics
Method CogC ev(G) iv(G) v(G)
Lexer.Lexer(String) 0 1 1 1
Lexer.getNumber() 2 1 3 3
Lexer.next() 8 2 4 6
Lexer.peek() 0 1 1 1
MainClass.handleSign(String) 0 1 1 1
MainClass.main(String[]) 3 1 3 3
MainClass.substitudeDef(String) 16 1 5 8
MainClass.substitudeSum(String) 16 1 6 8
Parser.Parser(Lexer) 0 1 1 1
Parser.constantFactor() 4 1 3 3
Parser.exprFactor() 1 1 2 2
Parser.parserExpr() 5 1 5 5
Parser.parserFactor() 5 5 5 5
Parser.parserTerm(String) 4 1 4 4
Parser.powerFactor() 4 1 3 3
Parser.trigonFactor(String) 5 1 4 4
module.Calculate.mergeTerms(ArrayList) 10 1 5 5
module.Calculate.mul1(HashMap<Integer, Integer>, HashMap<Integer, Integer>) 4 1 3 3
module.Calculate.mul2(HashMap<BigInteger, Integer>, HashMap<BigInteger, Integer>) 4 1 3 3
module.Calculate.simplify2(Term) 3 1 3 3
module.Calculate.termsMulti(ArrayList, ArrayList) 3 1 3 3
module.Expr.addTerm(ExprAndTerm) 2 1 2 2
module.Expr.getPower() 0 1 1 1
module.Expr.getTerms() 0 1 1 1
module.Expr.setPower(int) 0 1 1 1
module.Expr.setTerms(ArrayList) 0 1 1 1
module.Expr.toString() 17 3 7 8
module.Factor.getConstant() 0 1 1 1
module.Factor.getPower() 0 1 1 1
module.Factor.setConstant(BigInteger) 0 1 1 1
module.Factor.setPower(int) 0 1 1 1
module.Term.Term(BigInteger, int) 0 1 1 1
module.Term.addFactors(ExprAndFactor) 7 1 5 5
module.Term.compareCoe(Term) 1 1 5 5
module.Term.getCoefficient() 0 1 1 1
module.Term.getConstantCos() 0 1 1 1
module.Term.getConstantSin() 0 1 1 1
module.Term.getCosPower() 0 1 1 1
module.Term.getFactors() 0 1 1 1
module.Term.getSinPower() 0 1 1 1
module.Term.getTermPower() 0 1 1 1
module.Term.setCoefficient(BigInteger) 0 1 1 1
module.Term.setConstantCos(HashMap<BigInteger, Integer>) 0 1 1 1
module.Term.setConstantSin(HashMap<BigInteger, Integer>) 0 1 1 1
module.Term.setCosPower(HashMap<Integer, Integer>) 0 1 1 1
module.Term.setExprPositive(boolean) 0 1 1 1
module.Term.setSinPower(HashMap<Integer, Integer>) 0 1 1 1
module.Term.setTermPositive(boolean) 0 1 1 1
module.Term.simplify1() 67 10 16 20
module.Term.xsincosToString() 20 2 14 14
module.TrigFactor.TrigFactor(String) 0 1 1 1
module.TrigFactor.getInsidePower() 0 1 1 1
module.TrigFactor.getOutsidePower() 0 1 1 1
module.TrigFactor.isSin() 0 1 1 1
module.TrigFactor.setInsidePower(int) 0 1 1 1
module.TrigFactor.setOutsidePower(int) 0 1 1 1
Class OCavg OCmax WMC
Lexer 2.5 6 10
MainClass 5 8 20
Parser 3.25 5 26
module.Calculate 3.4 5 17
module.Expr 2.33 8 14
module.Factor 1 1 4
module.Term 2.89 20 55
module.TrigFactor 1 1 6
Project v(G)avg v(G)tot
project 2.82 158

​ 这次复杂度较高的是MainClass,Calculate和Parser类,MainClass复杂度高是因为自定义函数和求和函数直接替换的函数冗长,且没有面向对象。Calculate里主要是各种计算方法,内聚较差。此外还有Term类里simplify1函数控制分支数太多,行数也太多,应该将其拆分为多个函数。同时Term类的属性过多,导致有很多getter和setter方法,方法数过多。解决方法可以再定义一个化简后项的类,将化简前后的类分开,分担Term的职能。

bug分析

​ 这次作业我在化简sin(-a)的偶数次方时,直接把符号提出来,导致最后符号出了问题。这主要是因为化简的函数太臃肿,分支数太多,到最后忘了三角函数还有指数。此外,课后测试的时候也没有做充分的测试。

第三次作业

架构设计思路

​ 由于我上次作业直接用正则表达式进行替换,所以这次作业我进行了重构,加入DefExpr类,其中包含parserDef和parserSum的函数。同时改变三角函数的存储方式,用HashMap<Expr,Integer>表示。

​ 在写parserDef函数时,我开始将读入的表达式中f(形参,...)中的形参直接进行解析得到Expr的实例后存入HashMap<char,Expr>中(char为函数定义中的xyz)。然后new Parser解析函数定义式,在parserFactor中如果遇到xyz就返回HashMap<char,Expr>中对应的expr,但这样是对expr的浅拷贝,致使后面计算过程中出现问题,最后我将Expr转化为String,再解析一次才解决问题,但这样会导致多余的计算。

image

复杂度分析

属性个数 方法个数 代码总规模
main 0 2 42
lexer 3 4 47
parser 2 9 168
Expr 3 10 91
Term 6 18 230
Factor 2 6 42
TrigFactor 2 8 51
Calculate 0 4 69
DefExpr 3 4 110
Complexity metrics
Method CogC ev(G) iv(G) v(G)
DefExpr.addDef(String) 1 1 2 2
DefExpr.getLefts() 0 1 1 1
DefExpr.parserDef(Lexer) 1 1 2 2
DefExpr.parserSum(Lexer) 16 1 10 12
Lexer.Lexer(String) 0 1 1 1
Lexer.getNumber() 2 1 3 3
Lexer.next() 8 2 4 6
Lexer.peek() 0 1 1 1
MainClass.handleSign(String) 0 1 1 1
MainClass.main(String[]) 1 1 2 2
Parser.Parser(Lexer, DefExpr) 0 1 1 1
Parser.constantFactor() 4 1 3 3
Parser.defFactor() 1 1 2 2
Parser.exprFactor() 1 1 2 2
Parser.parserExpr() 5 1 5 5
Parser.parserFactor() 8 8 8 8
Parser.parserTerm(String) 4 1 4 4
Parser.powerFactor() 4 1 3 3
Parser.trigonFactor(String) 2 1 2 2
module.Calculate.mergeTerms(ArrayList) 10 1 5 5
module.Calculate.mul(HashMap<Expr, Integer>, HashMap<Expr, Integer>) 4 1 3 3
module.Calculate.simplify2(Term) 3 1 3 3
module.Calculate.termsMulti(ArrayList, ArrayList) 3 1 3 3
module.Expr.addTerm(ExprAndTerm) 2 1 2 2
module.Expr.equals(Object) 3 3 2 4
module.Expr.getBasics() 0 1 1 1
module.Expr.getPower() 0 1 1 1
module.Expr.getTerms() 0 1 1 1
module.Expr.hashCode() 0 1 1 1
module.Expr.setBasics(HashSet) 0 1 1 1
module.Expr.setPower(int) 0 1 1 1
module.Expr.setTerms(ArrayList) 0 1 1 1
module.Expr.toString() 17 3 7 8
module.Factor.equals(Object) 4 3 3 5
module.Factor.getConstant() 0 1 1 1
module.Factor.getPower() 0 1 1 1
module.Factor.hashCode() 0 1 1 1
module.Factor.setConstant(BigInteger) 0 1 1 1
module.Factor.setPower(int) 0 1 1 1
module.Term.Term(BigInteger, int) 0 1 1 1
module.Term.addFactors(ExprAndFactor) 7 1 5 5
module.Term.compareCoe(Term) 1 1 3 3
module.Term.equals(Object) 4 3 5 7
module.Term.getCoefficient() 0 1 1 1
module.Term.getCosFactors() 0 1 1 1
module.Term.getFactors() 0 1 1 1
module.Term.getSinFactors() 0 1 1 1
module.Term.getXpower() 0 1 1 1
module.Term.hashCode() 0 1 1 1
module.Term.isSingle(Expr) 23 11 11 29
module.Term.setCoefficient(BigInteger) 0 1 1 1
module.Term.setCosFactors(HashMap<Expr, Integer>) 0 1 1 1
module.Term.setExprPositive(boolean) 0 1 1 1
module.Term.setSinFactors(HashMap<Expr, Integer>) 0 1 1 1
module.Term.setTermPositive(boolean) 0 1 1 1
module.Term.simplify1() 35 6 10 15
module.Term.xsincosToString() 22 2 14 14
module.TrigFactor.TrigFactor(String) 0 1 1 1
module.TrigFactor.equals(Object) 4 3 4 6
module.TrigFactor.getInside() 0 1 1 1
module.TrigFactor.getTriPower() 0 1 1 1
module.TrigFactor.hashCode() 0 1 1 1
module.TrigFactor.isSin() 0 1 1 1
module.TrigFactor.setInside(Expr) 0 1 1 1
module.TrigFactor.setTriPower(int) 0 1 1 1
Class OCavg OCmax WMC
DefExpr 4.25 12 17
Lexer 2.5 6 10
MainClass 1.5 2 3
Parser 3.22 8 29
module.Calculate 3.5 5 14
module.Expr 2 8 20
module.Factor 1.33 3 8
module.Term 3.22 14 58
module.TrigFactor 1.25 3 10
Project v(G)avg v(G)tot
project 3.14 204

​ 和上次作业相比,这次作业对自定义函数和求和函数新定义了DefExpr类,并且采用递归下降的方法进行解析,使得MainClass复杂度降低了很多,但由于自定义函数和求和函数都在DefExpr类中,导致该类内聚度较差,应该再定义一个Sum类。Term类中isSingle, toString, simplify1函数控制分支数都太多,而且Term类的函数和属性都太多,导致Term类圈复杂度较高。

bug分析

​ 这次作业在强测和互测中我都因为没有考虑到sum(i,s,e,t)中s大于e的情况而被扣分,主要是因为我对上次作业进行了重构,没有仔细看文档,忘了分类讨论。以后在迭代开发的过程中,不仅应该关注当前新增的条件,也应该把以前的要求再仔细看一遍。

​ 在互测中我用这次和上次作业中我出现的问题找到了两个其他人的bug.

心得体会

​ 在这一单元的作业中,我主要学习到了递归下降的思想,体验迭代开发的过程。尤其在第一次作业尝试过正则表达式方法后,我发现递归下降可以将繁杂的任务进行分解,逐一解决。这使代码的架构更加有层次,思路更清晰。迭代开发则需要各类高内聚低耦合,函数功能简单明确。我的主要问题是我依然不知道如何从面向对象的角度思考问题,写出来的代码很多都是面向过程。对于代码的复杂度我在写作业的过程中并没有考虑到,写博客的时候才意识到有些函数需要拆分,还有些类的复杂度太高、内聚性差。以后写作业时我会多关注复杂度的问题,而不仅仅在写博客的时候才去反思。此外,这一单元我做的测试太少,导致有些bug自己没有测出来。未来我会多做测试,尝试使用自动测评。

posted @ 2022-03-26 15:33  rmfl  阅读(30)  评论(0编辑  收藏  举报