OO第一单元总结
设计分析
第一次作业:
由于第一次作业仅要求处理简单的幂函数多项式,所以我采用以项为基本单位的方式读入表达式,根据基本形式a*x**b利用正则读入每一项,再进行计算。
特别地,由于本次没有WF,可以进行一些预处理,将空格去除,考虑到每一项之间最多只有三个符号,可以穷举将项之间若干符号化简为一个符号。
关于输出,这次作业比较简单,很难出错,所以考虑去尽力化简,主要集中在以下几个方面:
-
合并同类项
-
指数为1、系数为1不输出,系数为-1只保留负号
-
将正项提到最前先输出
-
删除0项
-
用x*x替换x**2
度量分析
method | CogC | LOC |
---|---|---|
Expression.derivation(ArrayList) | 16.0 | 42.0 |
Expression.Expression(String) | 8.0 | 23.0 |
Expression.getTermList() | 0.0 | 3.0 |
Main.main(String[]) | 0.0 | 17.0 |
Term.getCoe() | 0.0 | 3.0 |
Term.getExp() | 0.0 | 3.0 |
Term.setCoe(BigDecimal) | 0.0 | 3.0 |
Term.Term(BigDecimal,BigDecimal) | 0.0 | 4.0 |
TermCal.cal(String) | 27.0 | 61.0 |
class | OCavg | OCmax | WMC |
---|---|---|---|
Expression | 5.666666666666667 | 11.0 | 17.0 |
Main | 1.0 | 1.0 | 1.0 |
Term | 1.0 | 1.0 | 4.0 |
TermCal | 10.0 | 10.0 | 10.0 |
可见,复杂度主要集中于derivation求导方法、Expression获得表达式方法以及TermCal获得项方法。
对于求导方法,由于有化简操作,进行了很多特判,导致复杂度过高;对于获得表达式方法,由于没有使用HashMap而是ArrayList,合并同类项每次都需要遍历列表,导致复杂度过高;对于获得项方法,由于是利用正则匹配了一段项的字符串,需要对字符串进行分析,利用了很多if(甚至三重if)来辨别以及特判,导致复杂度过高。
本次作业UML类图
本次架构较为简单,共四个类:
-
Main类:负责读入数据和输出
-
Expression类:负责处理表达式,通过正则表达式匹配每一项传入TermCal,得到项后合并同类项,处理完表达式子后进行求导。
-
Term类:负责获得项的信息。
-
TermCal类:解析Expression传来的字符串,得到该项的信息。(其实这个该作为Term的一个方法)
优缺点分析
-
优点
-
层次简单,正确性高
-
化简效果较好
-
-
缺点
-
部分方法签到了过多的if语句,增加复杂度的同时降低了代码的可读性
-
可拓展性太低
-
第二次作业
本次作业加入了三角函数以及能满足括号嵌套的表达式因子,显然,第一次作业的架构无法满足这次的作业,所以果断采取重构,根据评论区大佬们的推荐,采用了递归下降的方法,逐步递归,得到一个多叉树,求导也是类似思想,从底层因子开始求导顺着树往回得到整个项的导数,最后再将个项结果相加,得到表达式的求导结果。同样地,由于本次作业没有WF,采取了与第一次相同的预处理,使得整体流程更为简单。
度量分析
method | CogC | LOC |
---|---|---|
Constant.derivation() | 0.0 | 3.0 |
Constant.print() | 0.0 | 3.0 |
Constant.setCoe(BigInteger) | 0.0 | 4.0 |
Constant.setName(String) | 0.0 | 4.0 |
Constant.toString() | 0.0 | 3.0 |
Expression.derivation() | 17.0 | 27.0 |
Expression.print() | 1.0 | 5.0 |
Expression.setSign(char) | 0.0 | 3.0 |
Expression.setTermList(ArrayList) | 0.0 | 3.0 |
Expression.toString() | 1.0 | 8.0 |
Expson.derivation() | 0.0 | 3.0 |
Expson.print() | 0.0 | 3.0 |
Expson.setExpression(Expression) | 0.0 | 4.0 |
Expson.setName(String) | 0.0 | 4.0 |
Expson.toString() | 0.0 | 3.0 |
Factor.derivation() | 1.0 | 26.0 |
Factor.getCoe() | 0.0 | 3.0 |
Factor.getExp() | 0.0 | 3.0 |
Factor.getExpression() | 0.0 | 3.0 |
Factor.getName() | 0.0 | 3.0 |
Factor.getType() | 0.0 | 3.0 |
Factor.print() | 0.0 | 2.0 |
Factor.setCoe(BigInteger) | 0.0 | 3.0 |
Factor.setExp(BigInteger) | 0.0 | 3.0 |
Factor.setExpression(Expression) | 0.0 | 3.0 |
Factor.setName(String) | 0.0 | 3.0 |
Factor.setType(int) | 0.0 | 3.0 |
Factor.toString() | 1.0 | 26.0 |
Handle.getChar() | 1.0 | 7.0 |
Handle.mateExp() | 4.0 | 22.0 |
Handle.mateFactor() | 7.0 | 17.0 |
Handle.mateTerm(char) | 4.0 | 50.0 |
Handle.newConstant() | 5.0 | 25.0 |
Handle.newExpression(ArrayList) | 0.0 | 3.0 |
Handle.newExpson(Expression) | 0.0 | 8.0 |
Handle.newTerm(ArrayList) | 0.0 | 3.0 |
Handle.newVariableTri() | 12.0 | 39.0 |
Handle.newVariableX() | 10.0 | 33.0 |
Handle.setStr(String) | 0.0 | 3.0 |
Main.main(String[]) | 0.0 | 25.0 |
Term.addFactor(Factor) | 0.0 | 3.0 |
Term.derivation() | 25.0 | 32.0 |
Term.getSign() | 0.0 | 3.0 |
Term.print() | 1.0 | 6.0 |
Term.setFactors(ArrayList) | 0.0 | 3.0 |
Term.setSign(char) | 0.0 | 3.0 |
Term.toString() | 4.0 | 11.0 |
VariableTri.derivation() | 0.0 | 3.0 |
VariableTri.derivationTri(int) | 12.0 | 29.0 |
VariableTri.print() | 0.0 | 3.0 |
VariableTri.setCoe(BigInteger) | 0.0 | 4.0 |
VariableTri.setExp(BigInteger) | 0.0 | 4.0 |
VariableTri.setType(int) | 0.0 | 4.0 |
VariableTri.toString() | 34.0 | 53.0 |
VariableX.derivation() | 13.0 | 28.0 |
VariableX.print() | 0.0 | 3.0 |
VariableX.setCoe(BigInteger) | 0.0 | 4.0 |
VariableX.setExp(BigInteger) | 0.0 | 4.0 |
VariableX.setName(String) | 0.0 | 4.0 |
VariableX.toString() | 13.0 | 26.0 |
class | OCavg | OCmax | WMC |
---|---|---|---|
Constant | 1.0 | 1.0 | 5.0 |
Expression | 2.8 | 8.0 | 14.0 |
Expson | 1.0 | 1.0 | 5.0 |
Factor | 1.7692307692307692 | 6.0 | 23.0 |
Handle | 3.6363636363636362 | 8.0 | 40.0 |
Main | 1.0 | 1.0 | 1.0 |
Term | 2.5714285714285716 | 9.0 | 18.0 |
VariableTri | 5.0 | 20.0 | 35.0 |
VariableX | 4.0 | 10.0 | 24.0 |
可见,本次作业复杂性主要集中于Handle解析表达式字符串、求导操作、toString返回函数原型以及匹配三种因子操作中。对于解析表达式字符串,由于利用递归下降,涉及多层递归,使得复杂性过高;对于三种因子,各自所需保存的属性不同,且求导方式不同,所以考虑将他们都继承Factor类,分别重写求导和toString方法,利用简单工厂模式去获取各因子的信息。由于求导以及toString涉及到化简,用到了很多if特判,导致整体复杂性过高。
本次作业UML类图
本次作业由9个类组成:
-
Main类:负责读入和输出数据
-
Handle类:负责利用递归下降解析输入的表达式字符串
-
Expression类:表达式类,用ArrayList存储若干项,
-
Term类:项类,用ArrayList存储若干因子
-
Factor类:因子的父类
-
Constant、VariableTri、VariableX、Expson类:分别为常数因子、三角函数因子、幂函数因子以及表达式因子,继承Factor类
优缺点分析
-
优点
-
逻辑较为清晰
-
利用递归下降生成的多叉树结构,可拓展性强
-
-
缺点
-
对于工厂模式理解不够深,提取因子的过程中用到了工厂模式,但是还是需要利用switch去辨别每个因子的类别。
-
并没有完成对于求导结果的深合并及化简
-
没有完全体现面向对象高内聚低耦合的特点,在处理输入字符串时耦合性过高。
-
第三次作业
本次作业加入了三角函数内的表达式嵌套以及WF的判断,基于第二次作业递归下降的思想,本次修改的地方并不多,只需要再匹配三角函数时匹配到他左括号的时候多匹配一个因子即可;至于WF的判断,我首先开了个数组,里面包含了合法表达式中可能出现的字符,若输入中有不属于该数组的字符则抛出异常,否则进入递归下降,在每一层中按照指导书给的格式进行判断,若不符合则抛出异常,主要在于吃掉正确位置的空白字符以及匹配好右括号(判断全局变量是否越界or表达式未读取完提前结束)。
度量分析
Method | CogC | LOC |
---|---|---|
Constant.derivation() | 0.0 | 3.0 |
Constant.print() | 0.0 | 3.0 |
Constant.setCoe(BigInteger) | 0.0 | 4.0 |
Constant.setName(String) | 0.0 | 4.0 |
Constant.toString() | 0.0 | 3.0 |
Expression.derivation() | 17.0 | 27.0 |
Expression.print() | 1.0 | 5.0 |
Expression.setSign(char) | 0.0 | 3.0 |
Expression.setTermList(ArrayList) | 0.0 | 3.0 |
Expression.toString() | 1.0 | 8.0 |
Expson.derivation() | 0.0 | 3.0 |
Expson.print() | 0.0 | 3.0 |
Expson.setExpression(Expression) | 0.0 | 4.0 |
Expson.setName(String) | 0.0 | 4.0 |
Expson.toString() | 0.0 | 3.0 |
Factor.derivation() | 1.0 | 26.0 |
Factor.getCoe() | 0.0 | 3.0 |
Factor.getExp() | 0.0 | 3.0 |
Factor.getExpression() | 0.0 | 3.0 |
Factor.getFactor() | 0.0 | 1.0 |
Factor.getName() | 0.0 | 3.0 |
Factor.getType() | 0.0 | 3.0 |
Factor.print() | 0.0 | 2.0 |
Factor.setCoe(BigInteger) | 0.0 | 3.0 |
Factor.setExp(BigInteger) | 0.0 | 3.0 |
Factor.setExpression(Expression) | 0.0 | 3.0 |
Factor.setFactor(Factor) | 0.0 | 1.0 |
Factor.setName(String) | 0.0 | 3.0 |
Factor.setType(int) | 0.0 | 3.0 |
Factor.toString() | 1.0 | 26.0 |
Handle.clean() | 2.0 | 7.0 |
Handle.getChar() | 2.0 | 9.0 |
Handle.initialize() | 4.0 | 14.0 |
Handle.mateExp() | 3.0 | 25.0 |
Handle.mateExpson() | 8.0 | 28.0 |
Handle.mateFactor() | 8.0 | 17.0 |
Handle.mateTerm(char) | 4.0 | 55.0 |
Handle.newConstant() | 6.0 | 28.0 |
Handle.newExpson(Expression) | 0.0 | 8.0 |
Handle.newVariableTri() | 24.0 | 60.0 |
Handle.newVariableX() | 15.0 | 42.0 |
Handle.setStr(String) | 0.0 | 3.0 |
Main.main(String[]) | 4.0 | 37.0 |
Term.addFactor(Factor) | 0.0 | 3.0 |
Term.derivation() | 25.0 | 32.0 |
Term.getSign() | 0.0 | 3.0 |
Term.print() | 1.0 | 6.0 |
Term.setFactors(ArrayList) | 0.0 | 3.0 |
Term.setSign(char) | 0.0 | 3.0 |
Term.toString() | 4.0 | 11.0 |
VariableTri.derivation() | 0.0 | 3.0 |
VariableTri.derivationTri(int) | 14.0 | 31.0 |
VariableTri.getFactor() | 0.0 | 4.0 |
VariableTri.print() | 0.0 | 3.0 |
VariableTri.setCoe(BigInteger) | 0.0 | 4.0 |
VariableTri.setExp(BigInteger) | 0.0 | 4.0 |
VariableTri.setFactor(Factor) | 0.0 | 4.0 |
VariableTri.setType(int) | 0.0 | 4.0 |
VariableTri.toString() | 34.0 | 55.0 |
VariableX.derivation() | 13.0 | 28.0 |
VariableX.print() | 0.0 | 3.0 |
VariableX.setCoe(BigInteger) | 0.0 | 4.0 |
VariableX.setExp(BigInteger) | 0.0 | 4.0 |
VariableX.setName(String) | 0.0 | 4.0 |
VariableX.toString() | 13.0 | 26.0 |
Total | 205.0 | 741.0 |
class | OCavg | OCmax | WMC |
---|---|---|---|
Constant | 1.0 | 1.0 | 5.0 |
Expression | 2.8 | 8.0 | 14.0 |
Expson | 1.0 | 1.0 | 5.0 |
Factor | 1.6666666666666667 | 6.0 | 25.0 |
Handle | 4.666666666666667 | 12.0 | 56.0 |
Main | 3.0 | 3.0 | 3.0 |
Term | 2.5714285714285716 | 9.0 | 18.0 |
VariableTri | 4.111111111111111 | 20.0 | 37.0 |
VariableX | 4.0 | 10.0 | 24.0 |
可见,由于整体架构与第二次作业几乎没有区别,所以与第二次作业相同,复杂度仍主要体现在Handle解析表达式字符串、求导操作、toString返回函数原型以及匹配三种因子操作中,尤其时Handle类复杂度提升了很多,就是因为多了一些if去判断WF的问题。
本次作业UML类图
不难发现,第三次作业的UML类图与第二次的一模一样,让我深刻感受到了面向对象编程方式再增量开发时的便捷性,但是对于WF的判断就有点不OO了,没有另开一个类进行判断而是直接在handle里头用一堆if去判断。
优缺点分析
-
优点
-
逻辑较为清晰
-
深刻体会到了面向对象方式的可拓展性
-
-
缺点
-
第二次的问题仍然保留
-
为了保证正确性(懒)并没有继续化简输出
-
BUG分析
第一次作业
由于有预处理,所以解析表达式很难出错,强测互测都没有找到bug。
第二次作业
强测T了2个,互测也是给hack了相同的点,主要问题是化简的过程中需要对于求导结果和toString结果反复利用,导致程序不断递归就T了,提前定义一个变量把二者结果保存即可。
第三次作业
互测被hack了一个点,三角函数括号内因子为常数因子且为负数的时候读取出错,读入的常数因子符号始终为正数,究其原因,是读入三角函数的左括号后,本来是想要清理空白字符的,用错函数把正负号也吃掉了,导致例如sin(-11)*x的求导结果为sin(11),将这个位置运用的函数更改即可。
Hack策略
主要采取覆盖性测试,当发现有错误样例的时候逐渐缩短输入表达式,直至找到真正有bug的用例。
其次是阅读他人的代码,查看是否能够读懂并找到一些边界情况使其无法满足或令其超时。
第三次作业互测时由于我默认同屋的人都已de好第二次强测的bug,所以主要只测试了三角函数内加各种因子的样例,忽略了WF判断错误使得正确输入输出WF的情况,导致同屋一些bug没有hack到。
重构经历总结
主要是在第一次到第二次作业进行了重构,完全是推倒重做,第一次作业只需要进行一些预处理并通过正则处理字符串即可,但是第二次作业引入三角函数以及表达式因子,存在括号嵌套等内容,表达式获取以及求导的方法都发生了极大的变化,显然第一次那样简单的架构无法满足,所以吸取了其他同学的经验利用了递归下降的结构,从前文的类图可以明显地发现第二次的结构比第一次复杂很多,但逻辑更加清晰了。
通过这次重构,我体会到了预先设想好架构的重要性,由于第一次作业的要求很弱,通过预处理很简单的能够完成,但是对于后续作业时无法再进行这样操作的,所以提前设想好后续会有什么需求,在前期搭好相应的架构,在后续增加需求的时候就不会为推倒重做而恼火了。
心得体会
通过pre和三次作业,渐渐理解了面向对象的编程方法,OO的思想逐渐根深蒂固,并且掌握了一些git相关的操作。
研讨课听了同学们关于自动评测机、化简思路的分享,并且在互测期间拜读了部分化简做得很好的同学的代码,获益良多。
还有个我认为挺重要的一点是课程对于代码风格的限定,让我深刻感受到代码风格统一的重要性,特别是在互测期间看他人代码的时候,格式正确的情况下阅读确实会轻松一些。