BUAA_2022_OO_第一单元总结
摘要
本博客主要介绍本人针对面向对象第一单元作业(表达式化简)的架构以及一些心得体会。因本人在第二次作业时的架构已满足第三次作业要求且后续没有明显变动,故后两次作业合并分析。
1. 第一次作业
1.1 作业简介
第一次作业只针对含单层括号且只包括幂函数(常数可以看作0次幂)的表达式进行化简,最终得到的结果为多个幂函数相加。
1.2 架构设计
1.2.1 类图展示
1.2.2 类设计分析
paraser类:
paraser类集成了词法分析和递归下降功能,解析得到一棵表达式树。
exp类:
exp类作为树节点时其子节点均为term,表示exp由多个term相加组成。
term类:
exp类作为树节点时其子节点均为factor,表示term由多个factor相乘组成。
factor类:
factor类为一抽象父类,其子类包括var,const,expfactor。
const类:
代表常数类,使用一BigInteger类型属性存储对应的常数值。
var类:
代表幂函数,使用一BigInteger类型属性存储对应的幂次,使用String类型属性存储对应的符号(本次作业中均为x)。
expfactor类:
代表表达式因子,使用一BigInteger类型属性存储对应的幂次,使用exp类属性存储其对应的表达式。
1.3 程序结构分析
1.3.1 各类代码量
1.3.2 方法复杂度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Const.Const(BigInteger) | 0 | 1 | 1 | 1 |
Const.getCons() | 0 | 1 | 1 | 1 |
Const.getType() | 0 | 1 | 1 | 1 |
Const.testOutPut() | 0 | 1 | 1 | 1 |
Exp.addTerm(Term) | 0 | 1 | 1 | 1 |
Exp.calculate() | 1 | 1 | 2 | 2 |
Exp.mergeAdd(HashMap<BigInteger, BigInteger>, HashMap<BigInteger, BigInteger>) | 8 | 2 | 4 | 4 |
Exp.testOutPut() | 2 | 1 | 2 | 3 |
Exp.toString() | 22 | 4 | 10 | 12 |
ExpFactor.calculate() | 5 | 3 | 4 | 4 |
ExpFactor.getPow() | 0 | 1 | 1 | 1 |
ExpFactor.getType() | 0 | 1 | 1 | 1 |
ExpFactor.mergeMult(HashMap<BigInteger, BigInteger>, HashMap<BigInteger, BigInteger>) | 12 | 2 | 5 | 5 |
ExpFactor.setExp(Exp) | 0 | 1 | 1 | 1 |
ExpFactor.setPow(BigInteger) | 0 | 1 | 1 | 1 |
ExpFactor.testOutPut() | 0 | 1 | 1 | 1 |
Factor.getType() | 0 | 1 | 1 | 1 |
Factor.testOutPut() | 0 | 1 | 1 | 1 |
Main.main(String[]) | 0 | 1 | 1 | 1 |
Paraser.Paraser(String) | 0 | 1 | 1 | 1 |
Paraser.blank() | 3 | 1 | 3 | 4 |
Paraser.cons() | 0 | 1 | 1 | 1 |
Paraser.exp() | 4 | 1 | 5 | 5 |
Paraser.expFactor() | 2 | 1 | 4 | 4 |
Paraser.factor() | 5 | 1 | 5 | 5 |
Paraser.getChar(int) | 2 | 2 | 2 | 2 |
Paraser.isAddOrSub(char) | 1 | 1 | 1 | 2 |
Paraser.isNum(char) | 1 | 1 | 1 | 2 |
Paraser.num() | 5 | 1 | 5 | 6 |
Paraser.pow() | 2 | 1 | 3 | 3 |
Paraser.term() | 4 | 1 | 5 | 5 |
Paraser.var() | 2 | 1 | 4 | 4 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.calculate() | 8 | 1 | 7 | 7 |
Term.getSign() | 0 | 1 | 1 | 1 |
Term.mergeMult(HashMap<BigInteger, BigInteger>, HashMap<BigInteger, BigInteger>) | 12 | 2 | 5 | 5 |
Term.setSign(char) | 4 | 1 | 2 | 4 |
Term.testOutPut() | 1 | 1 | 2 | 2 |
Var.getPow() | 0 | 1 | 1 | 1 |
Var.getType() | 0 | 1 | 1 | 1 |
Var.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Var.testOutPut() | 1 | 2 | 1 | 2 |
总的来看,除了tostring函数因为做了较多的特殊判断认知复杂度较高,其他方法均较为合理。
2. 第二、三次作业
2.1 作业简介
第二、三次作业在第一次作业的基础上增加了三角函数、sum函数、自定义函数三项。
2.2 架构设计
2.2.1 类图展示
2.2.2 类设计分析
由第一次作业程序迭代而来,故只分析新增类。
Sin类:
factor的子类,Factor元素存储其因子,pow存储其幂次。
Cos类:
factor的子类,Factor元素存储其因子,pow存储其幂次。
Calresult类:
为便于因式合并,将除系数以外的项的组成(Sin因子,Cos因子,幂函数)抽象为一个类,复写其equals,hashCode方法,便于后续因式合并。使用HashMap存储sin因子、cos因子,使用BigInteger存储幂函数指数。
Calculate类:
发现加法乘法函数在许多类中都需要使用,故抽象至一计算类中。
Func类:
对应自定义函数,使用Exp存储自定义函数的表达式,ArrayList<String>存储函数形参。
FuncFactor类:
对应自定义函数调用,使用Func存储对应的自定义函数,ArrayList<Factor>存储函数调用时的实参。
Sum类:
对应sum函数,两BigInteger属性begin与end对应求和起始与终止值。Factor对应需要求和的因子。
2.3 程序结构分析
2.3.1各类代码量
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
CalResult.CalResult() | 0 | 1 | 1 | 1 |
CalResult.CalResult(HashMap<HashMap<CalResult, BigInteger>, BigInteger>, HashMap<HashMap<CalResult, BigInteger>, BigInteger>, BigInteger) | 0 | 1 | 1 | 1 |
CalResult.addCos(HashMap<CalResult, BigInteger>, BigInteger) | 0 | 1 | 1 | 1 |
CalResult.addSin(HashMap<CalResult, BigInteger>, BigInteger) | 0 | 1 | 1 | 1 |
CalResult.equals(Object) | 3 | 3 | 3 | 5 |
CalResult.getCoss() | 0 | 1 | 1 | 1 |
CalResult.getPow() | 0 | 1 | 1 | 1 |
CalResult.getSins() | 0 | 1 | 1 | 1 |
CalResult.handleTri(StringBuilder, HashMap<CalResult, BigInteger>) | 11 | 1 | 7 | 7 |
CalResult.hashCode() | 0 | 1 | 1 | 1 |
CalResult.isExpr(HashMap<CalResult, BigInteger>) | 16 | 8 | 4 | 8 |
CalResult.mult(CalResult) | 0 | 1 | 1 | 1 |
CalResult.multTri(HashMap<HashMap<CalResult, BigInteger>, BigInteger>, HashMap<HashMap<CalResult, BigInteger>, BigInteger>) | 4 | 1 | 3 | 3 |
CalResult.setPow(BigInteger) | 0 | 1 | 1 | 1 |
CalResult.toString(boolean) | 14 | 2 | 11 | 11 |
Calculate.mergeAdd(HashMap<CalResult, BigInteger>, HashMap<CalResult, BigInteger>) | 7 | 1 | 4 | 4 |
Calculate.mergeMult(boolean, HashMap<CalResult, BigInteger>, HashMap<CalResult, BigInteger>) | 18 | 3 | 6 | 7 |
Const.Const(BigInteger) | 0 | 1 | 1 | 1 |
Const.calculate(HashMap<String, Factor>) | 0 | 1 | 1 | 1 |
Const.getCons() | 0 | 1 | 1 | 1 |
Const.getType() | 0 | 1 | 1 | 1 |
Const.testOutPut() | 0 | 1 | 1 | 1 |
Cos.Cos(Factor) | 0 | 1 | 1 | 1 |
Cos.calculate(HashMap<String, Factor>) | 14 | 7 | 11 | 12 |
Cos.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Cos.testOutPut() | 1 | 1 | 1 | 2 |
Cos.toString() | 1 | 1 | 1 | 2 |
Exp.addTerm(Term) | 0 | 1 | 1 | 1 |
Exp.calculate(HashMap<String, Factor>) | 1 | 1 | 2 | 2 |
Exp.testOutPut() | 2 | 1 | 2 | 3 |
Exp.toString(boolean) | 43 | 6 | 13 | 15 |
ExpFactor.calculate(HashMap<String, Factor>) | 5 | 3 | 4 | 4 |
ExpFactor.getPow() | 0 | 1 | 1 | 1 |
ExpFactor.getType() | 0 | 1 | 1 | 1 |
ExpFactor.setExp(Exp) | 0 | 1 | 1 | 1 |
ExpFactor.setPow(BigInteger) | 0 | 1 | 1 | 1 |
ExpFactor.testOutPut() | 1 | 1 | 1 | 2 |
Factor.calculate(HashMap<String, Factor>) | 0 | 1 | 1 | 1 |
Factor.getType() | 0 | 1 | 1 | 1 |
Factor.testOutPut() | 0 | 1 | 1 | 1 |
Func.Func(String) | 0 | 1 | 1 | 1 |
Func.Func(String, Exp, ArrayList<String>) | 0 | 1 | 1 | 1 |
Func.addFuncFParam(String) | 0 | 1 | 1 | 1 |
Func.getExp() | 0 | 1 | 1 | 1 |
Func.getFuncFParams() | 0 | 1 | 1 | 1 |
Func.getFuncName() | 0 | 1 | 1 | 1 |
Func.setExp(Exp) | 0 | 1 | 1 | 1 |
Func.toString() | 1 | 1 | 2 | 2 |
FuncFactor.FuncFactor(Func) | 0 | 1 | 1 | 1 |
FuncFactor.addFuncRParams(Factor) | 0 | 1 | 1 | 1 |
FuncFactor.calculate(HashMap<String, Factor>) | 2 | 1 | 3 | 3 |
FuncFactor.testOutPut() | 1 | 1 | 2 | 2 |
Main.main(String[]) | 2 | 1 | 3 | 3 |
Paraser.Paraser(String) | 0 | 1 | 1 | 1 |
Paraser.addFunc(String, Func) | 0 | 1 | 1 | 1 |
Paraser.blank() | 3 | 1 | 3 | 4 |
Paraser.cons() | 0 | 1 | 1 | 1 |
Paraser.exp() | 4 | 1 | 5 | 5 |
Paraser.expFactor() | 2 | 1 | 4 | 4 |
Paraser.factor() | 8 | 1 | 8 | 8 |
Paraser.func() | 1 | 1 | 2 | 2 |
Paraser.funcUse() | 2 | 1 | 3 | 3 |
Paraser.getChar(int) | 2 | 2 | 2 | 2 |
Paraser.isAddOrSub(char) | 1 | 1 | 1 | 2 |
Paraser.isFunc(char) | 1 | 1 | 1 | 3 |
Paraser.isNum(char) | 1 | 1 | 1 | 2 |
Paraser.isSum(int) | 1 | 1 | 2 | 2 |
Paraser.isTri(int) | 2 | 1 | 3 | 3 |
Paraser.isVar(char) | 1 | 1 | 1 | 4 |
Paraser.num() | 5 | 1 | 5 | 6 |
Paraser.pow() | 2 | 1 | 3 | 3 |
Paraser.sum() | 0 | 1 | 1 | 1 |
Paraser.term() | 4 | 1 | 5 | 5 |
Paraser.tri() | 8 | 2 | 8 | 8 |
Paraser.var() | 2 | 1 | 4 | 4 |
Sin.Sin(Factor) | 0 | 1 | 1 | 1 |
Sin.calculate(HashMap<String, Factor>) | 16 | 7 | 13 | 14 |
Sin.getFactor() | 0 | 1 | 1 | 1 |
Sin.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Sin.testOutPut() | 1 | 1 | 1 | 2 |
Sin.toString() | 1 | 1 | 1 | 2 |
Sum.Sum(Const, Const, Factor) | 0 | 1 | 1 | 1 |
Sum.calculate(HashMap<String, Factor>) | 3 | 2 | 4 | 4 |
Sum.getType() | 0 | 1 | 1 | 1 |
Sum.testOutPut() | 0 | 1 | 1 | 1 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.calculate(HashMap<String, Factor>) | 5 | 1 | 5 | 5 |
Term.getSign() | 0 | 1 | 1 | 1 |
Term.mergeMult(HashMap<CalResult, BigInteger>, HashMap<CalResult, BigInteger>) | 17 | 2 | 6 | 6 |
Term.setSign(char) | 4 | 1 | 2 | 4 |
Term.testOutPut() | 1 | 1 | 2 | 2 |
Var.calculate(HashMap<String, Factor>) | 6 | 3 | 5 | 5 |
Var.getPow() | 0 | 1 | 1 | 1 |
Var.getType() | 0 | 1 | 1 | 1 |
Var.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Var.setSymbol(String) | 0 | 1 | 1 | 1 |
Var.testOutPut() | 1 | 2 | 1 | 2 |
Var.toString() | 1 | 2 | 1 | 2 |
乘法、加法因为要兼顾合并同类项,认知复杂度较高;输出函数要进行一定的化简,分支语句较多,认知复杂度较高,其他方法复杂度均较低。
3. Bug测试
本人在三次作业的强测、互测中均没有出现bug,并在三次互测中均发现了其他同学的bug,采用的测试方式如下
3.1 自动化测试
数据点的生成同样采用了递归下降的方法,可以看作对解析表达式求逆,即递归向下生成表达式。产生的数据使用subProcess.Popen函数重定向输入到jar包中,将两个jar包的输出使用eval函数对拍。但该方法存在缺点,即不能代入sin(x)**2 = 1 - cos(x)**2等复杂变换做正确性判断,可以考虑改成撒点代入数值的方式。同时没有对输出的格式的正确性做判断,可以将输出结果输入官方包做判断。
3.2 边界数据构造
通过阅读文法规则和数据限制,构造一些边界数据,如数值大小超越int值;针对零次幂、二次幂等优化的测试数据。受时间限制,并没有阅读代码然后构造针对数据,更多的是先使用一些数据测试其他同学的程序做了哪些工作,然后针对测试。
4. 架构迭代
为便于后续程序迭代,避免重构,在第一次作业设计时就预想了后续可能会出现其他种类因子等可能的情况,尽量只根据文法书写程序而不是利用一些题面给的限制条件(如幂次不超过8)。第二次作业注意到第一次作业的合并同类项方式不能继续使用,而合并同类项本质即为将系数以外的特征抽象出来,故重新设计了计算函数,在此基础上完成了新的合并同类项,同时完成了增量开发,此时的程序已满足三次作业的要求。
5. 心得体会
本单元作业对我个人而言并不算难,但作为一名重修生,学习下来依然有很多心得体会。
因为同时体验过去年和今年个题目,我认为这次第一单元题目的更改还是较为成功的。上一届的第一单元第一次作业我认为有过强的正则表达式引导倾向,导致许多同学第二单元重构;而本次作业在一开始就对使用递归下降做了一个好的引导,也能为学弟学妹们将来编译原理的学习做一定的铺垫(个人认为本单元的任务类似于编译原理pcode任务),我认为对作业题目的修改还是成功的。不过从另一种程度上讲第一次作业难度增加对假期没有完成pre且java开发不熟练的同学更不友好了,我本人就遇到了在第一次作业显得较为挣扎的学妹,最后也是压线才通过了中测,因此我依然会在这篇博客中重申我的观点,如果你不是奆佬,请提前认真完成pre的学习!