前言
本学期第一次接触面向对象的思想和编程。在第一单元的作业中,专注于求导,并试着用面向对象的方法来解决求导问题。
三次求导作业,第一次作业重点是多项式求导,死二次作业在多项式求导的基础上增加了三角函数求导,第三次作业增加了括号嵌套的层层求导。不同于以往的编程训练,面向对象的开发每次的作业更注重结构设计,以便往后的迭代开发,为自己减少推倒重构的风险,减少开发难度。
一 基于度量来分析自己的程序结构和缺陷
第一次作业分析
第一次作业主要有三个类组成,分别为Main,Derivative,Item类,UML结构图如下所示:
Main类主要用来键盘输入和屏幕输出操作,并调用Derivative类进行输入处理,Item用于处理Derivative传入的多项式的各项底数和指数。
在Derivative类中,难点在于表达式的分割,再次,我采用正则式来进行表达式分割。但是我用正则式的缺点在于产生了一个正则打天下的naive想法,结果就是很容易在处理较长的表达式时候极易爆栈,而且考虑情况也不很完全,一些边边角角的WRONG FORMAT!情况就无法很好地处理。在互测中也因此比较惨淡。
互测中,阅读其他同学的代码,分开匹配表达式是很好的一种办法,我所在房屋里我认为比较好的一些匹配做法是:1.尽可能让每一个项都标准化,即将‘++’‘--’‘+-’‘-+’全部换为单运算符,标准化的表达式简化正则式的匹配、、书写;2.匹配错误情况,匹配到了除了合法字符以外的字符就返回错误;判断正确就把合法的空格,水平制表符统统吃掉(replaceall),这样就可以带着简短的表达式进入下一阶段的判断。
下面是对方法复杂度的分析,第一次作业的实现功能较为简单,复杂度爆红即复杂度高的方法略少;
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Derivative.dealMyItem() | 1.0 | 4.0 | 4.0 |
Derivative.Derivative(String) | 1.0 | 7.0 | 7.0 |
Derivative.getMyItem() | 1.0 | 3.0 | 3.0 |
Derivative.getSize() | 1.0 | 1.0 | 1.0 |
Derivative.optimizeMyItem() | 5.0 | 5.0 | 5.0 |
Derivative.optimizeOutput(String) | 1.0 | 14.0` | 14.0 |
Derivative.print() | 1.0 | 7.0 | 7.0 |
Derivative.setMyItem(String) | 1.0 | 2.0 | 2.0 |
Item.compressItem() | 1.0 | 14.0 | 14.0 |
Item.getFactor() | 1.0 | 1.0 | 1.0 |
Item.getFactor_d() | 1.0 | 1.0 | 1.0 |
Item.getIndex() | 1.0 | 1.0 | 1.0 |
Item.getIndex_d() | 1.0 | 1.0 | 1.0 |
Item.getItem() | 1.0 | 1.0 | 1.0 |
Item.mergeMyItem(Item) | 1.0 | 1.0 | 1.0 |
Item.setFactor() | 1.0 | 3.0 | 3.0 |
Item.setFactor_d() | 1.0 | 1.0 | 1.0 |
Item.setIndex() | 1.0 | 2.0 | 3.0 |
Item.setIndex_d() | 1.0 | 1.0 | 1.0 |
Item.setItem(String) | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
Total | 25.0 | 72.0 | 73.0 |
Average | 1.1904761904761905 | 3.4285714285714284 | 3.4761904761904763 |
第二次作业分析
第二次作业收到之后,发现增加了三角函数求导和三角函数幂函数形式的求导,当时就估计很有可能在第三次作业中增加括号嵌套的求导,这就会使得正则式匹配不再好使,因此毅然决定推倒重写,在第二次作业中不再使用正则式进行匹配;先上UML结构图:
可以发现本次作业结构十分复杂,当时咨询了一位软院大佬以后,按照他的建议,严格按照参考书,将匹配分为因子(Factor)项(Item)表达式(Expression)三级数据结构,最底层的因子又下辖幂函数,三角函数,常数的匹配方法,(据该大佬说我们的作业有点淡淡的编译原理文法分析的气息),在此不再赘述。对应三级数据结构的就是对应三级数据结构的三级分析方法类,首先将几个三级分析类都要使用的方法扔进最顶层的分析方法类,三级分析类就直接继承到了该顶层类,这也是我第一次在作业中应用继承的方法。其余如IO部分等和第一次作业保持一致。
这一次作业给我的指导和启发就在于让我学到了这种数据结构类+分析方法类结合的编程方法,对于表达式的判断也就不再需要在最顶层判断,而是分散到各级的类当中处理,符合情况就返回匹配到的项,因子,否则就Null,这就使得表达式的匹配和分割可以比较愉快的进行下去。这样编程的整体构造方法就比第一次作业更加软工了一些。
这一次作业几乎是完全推倒重来,因此bug众多也是难以避免的,指导书中对于表达式的表述对于第一项可以带一个正负号,包括第一因子为常数且值为1则可以省略该常数因子或表示为证号开头的形式,这些特殊情况我的处理做的不太好,问题集中在ExpressionAnalyser即表达式分析类中,特判第一项和正常匹配后续项数的方式导致行数和bug众多,在互测中也因为这个类的书写问题被打爆,值得庆幸的就是问题都是出在该类方法,就尝试修复该类,结果修复环节一改就超60行,只能在bug修复中将首项和后续项判断方法合并进行,终于一次性扫光了所有的互测中的bug。
第二次作业的三级结构,就像摊大饼,很多方法就摊开了,因此复杂度尚可,当然,以后的书写中将会考虑使用Pakage将各级进行包装,否则目录中的众多类方法还真是令人吃不消。下面是方法复杂度的分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
SinXAnalyser.analyse(String,int) | 5.0 | 8.0 | 9.0 |
SinX.toString() | 2.0 | 1.0 | 2.0 |
SinX.sinxD(SinX) | 3.0 | 3.0 | 3.0 |
SinX.SinX(BigInteger) | 1.0 | 1.0 | 1.0 |
SinX.SinX() | 1.0 | 1.0 | 1.0 |
SinX.getIndex() | 1.0 | 1.0 | 1.0 |
SinX.combine(SinX) | 1.0 | 1.0 | 1.0 |
PowerFunctionAnalyser.analyse(String,int) | 4.0 | 4.0 | 4.0 |
PowerFunction.toString() | 2.0 | 1.0 | 2.0 |
PowerFunction.PowerFunction(BigInteger) | 1.0 | 1.0 | 1.0 |
PowerFunction.powerD(PowerFunction) | 2.0 | 2.0 | 2.0 |
PowerFunction.combine(PowerFunction) | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 4.0 | 4.0 |
ItemAnalyser.analyse(String,int) | 4.0 | 4.0 | 4.0 |
Item.toString() | 1.0 | 2.0 | 3.0 |
Item.numOfItem() | 1.0 | 1.0 | 1.0 |
Item.mult(Factor) | 10.0 | 10.0 | 10.0 |
Item.getFactor(int) | 1.0 | 1.0 | 1.0 |
IntegerAnalyser.analyse(String,int) | 4.0 | 4.0 | 14.0 |
FactorAnalyser.analyse(String,int) | 9.0 | 9.0 | 9.0 |
ExpressionAnalyser.analyse(String,int) | 9.0 | 6.0 | 12.0 |
Expression.printD() | 1.0 | 4.0 | 4.0 |
Expression.print() | 1.0 | 2.0 | 2.0 |
Expression.derivation() | 4.0 | 9.0 | 10.0 |
Expression.better() | 5.0 | 6.0 | 6.0 |
Expression.add(Item) | 1.0 | 1.0 | 1.0 |
CosXAnalyser.analyse(String,int) | 5.0 | 8.0 | 9.0 |
CosX.toString() | 2.0 | 1.0 | 2.0 |
CosX.cosxD(CosX) | 3.0 | 3.0 | 3.0 |
CosX.CosX(BigInteger) | 1.0 | 1.0 | 1.0 |
CosX.CosX() | 1.0 | 1.0 | 1.0 |
CosX.combine(CosX) | 1.0 | 1.0 | 1.0 |
ConstFactor.toString() | 1.0 | 1.0 | 1.0 |
ConstFactor.ConstFactor(BigInteger) | 1.0 | 1.0 | 1.0 |
ConstFactor.combine(ConstFactor) | 1.0 | 1.0 | 1.0 |
Analyser.skipSpace(String,int) | 1.0 | 2.0 | 3.0 |
Analyser.setIndex(int) | 1.0 | 1.0 | 1.0 |
Analyser.nextChar(String,int) | 1.0 | 2.0 | 2.0 |
Analyser.getIndex() | 1.0 | 1.0 | 1.0 |
Total | 96.0 | 112.0 | 136.0 |
Average | 2.4615384615384617 | 2.871794871794872 | 3.4871794871794872 |
第三次作业分析
第三次作业果然上了表达式的嵌套,有了第二次作业打下的结构基础和匹配基础储备,这就稍微容易,线上UML结构图:
本次作业有了嵌套,嵌套主要是三角函数类型的嵌套,和幂函数底数的嵌套,因此同样采用三级结构的同时,因此在处理中专门针对这两个特点,遇到三角函数前括号,前括号就切割字符串,采用递归但不回溯的办法继续匹配括号内的内容。本次作业的处理思想和实现最后看起来也没那么难以进行,但是在实际作业中仍然花了很多时间不断尝试,以至于几乎是压周二晚的线提交作业,很大程度可能是我一直以来对于递归的不熟悉和畏惧,C语言时如此,汇编语言时也如此,这次大量的调试中,看着递归层层运行和不断debug对于递归的处理也是熟悉了很多。
本次作业的缺陷是对于整个表达式的匹配识别中开了一些天窗,即使用if特判一些情况,并没有很好地将所有情况的判断分散到三级架构中进行,这在一二次作业中不涉及递归运行还良好,在本次作业中这些通过if特判开天窗判断的办法就带来了一些bug,这就使得我带着一些已知的bug进入了强测中。
以下是方法复杂度分析:
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
VarFactor.VarFactor(Base,BigInteger) | 1.0 | 1.0 | 1.0 |
VarFactor.VarFactor(Base) | 1.0 | 1.0 | 1.0 |
VarFactor.print() | 1.0 | 4.0 | 4.0 |
VarFactor.isIndexZero() | 1.0 | 1.0 | 1.0 |
VarFactor.derivation() | 1.0 | 1.0 | 1.0 |
SinXAnalyser.analyse(String,int) | 5.0 | 5.0 | 6.0 |
SinX.SinX(Expression) | 1.0 | 1.0 | 1.0 |
SinX.print() | 1.0 | 1.0 | 1.0 |
SinX.derivation() | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 6.0 | 6.0 |
ItemAnalyser.analyse(String,int) | 5.0 | 5.0 | 10.0 |
Item.print() | 1.0 | 5.0 | 5.0 |
Item.mult(Factor) | 1.0 | 4.0 | 4.0 |
Item.mult(BigInteger) | 1.0 | 1.0 | 1.0 |
Item.isConst() | 1.0 | 1.0 | 1.0 |
Item.derivation() | 2.0 | 4.0 | 5.0 |
IntegerAnalyser.analyse(String,int) | 4.0 | 4.0 | 14.0 |
FactorAnalyser.analyse(String,int) | 7.0 | 8.0 | 8.0 |
Factor.print() | 1.0 | 1.0 | 1.0 |
Factor.derivation() | 1.0 | 1.0 | 1.0 |
ExpressionAnalyser.analyse(String,int) | 8.0 | 8.0 | 15.0 |
Expression.print() | 1.0 | 4.0 | 4.0 |
Expression.isConst() | 2.0 | 2.0 | 2.0 |
Expression.derivation() | 1.0 | 2.0 | 2.0 |
Expression.add(Item) | 1.0 | 1.0 | 1.0 |
Expression.add(Expression) | 1.0 | 1.0 | 1.0 |
CosXAnalyser.analyse(String,int) | 5.0 | 5.0 | 6.0 |
CosX.print() | 1.0 | 1.0 | 1.0 |
CosX.derivation() | 1.0 | 1.0 | 1.0 |
CosX.CosX(Expression) | 1.0 | 1.0 | 1.0 |
ConstFactor.print() | 1.0 | 1.0 | 1.0 |
ConstFactor.getVal() | 1.0 | 1.0 | 1.0 |
ConstFactor.ConstFactor(BigInteger) | 1.0 | 1.0 | 1.0 |
BaseAnalyser.analyse(String,int) | 10.0 | 10.0 | 10.0 |
Base.print() | 1.0 | 1.0 | 1.0 |
Base.derivation() | 1.0 | 1.0 | 1.0 |
Analyser.skipSpace(String,int) | 1.0 | 2.0 | 3.0 |
Analyser.skipExp(String,int) | 2.0 | 3.0 | 7.0 |
Analyser.setIndex(int) | 1.0 | 1.0 | 1.0 |
Analyser.nextChar(String,int) | 1.0 | 2.0 | 2.0 |
Analyser.getIndex() | 1.0 | 1.0 | 1.0 |
Total | 81.0 | 106.0 | 136.0 |
Average | 1.975609756097561 | 2.5853658536585367 | 3.317073170731707 |
二、基于Metrics度量分析程序结构
下面附上基于CK标准度量分析程序结构:
第一次作业
第二次作业
第三次作业