OO_第一单元总结
第一单元总结
1. 基于度量的程序结构分析
1.1 第一次作业
第一次作业相比于后两次在结构上更为简单,因为只考虑到了常数、幂函数、单层括号等简单形式。但是就我个人而言,我认为第一次作业是对我挑战最大的一次,因为刚开始并不知道如何下手,即使做了训练项目依旧没什么想法,直到周四的实验课上,我受到实验题目递归的启发,才真正开始着手实验,写出了较为完整的作业。
1.1.1 度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模
-
类的属性个数、方法个数、类总代码规模
类的名称 类的属性个数 方法个数 类代码规模 Main 3 4 130 Parser 3 3 99 Add 0 1 16 Mul 0 1 16 Sub 0 1 17 -
方法个数、规模、控制分支数目
方法名 方法规模 控制分支数目 Main.main 20 0 Main.preOpe 17 0 Main.replaceExpr 21 0 Main.toStr 55 0 Parser.findMul 14 0 Parser.findAddOrSub 22 0 Parser.operation 49 0 Add.getResult 11 0 Sub.getResult 12 0 Mul.getResult 11 0
1.1.2 计算经典的OO度量,分析类的内聚和相互间的耦合情况
分析: 从下图可以看出Parser
类的findAddOrSub
方法的总复杂度过高,operation
方法的基本复杂度过高,Main
类的toStr
方法的基本复杂度、模块设计复杂度、圈复杂度都过高。模块设计复杂度判定的是模块和其他模块的调用关系,说明Main
类的toStr
模块的耦合度很高。
1.1.3 画出自己作业的类图并自我点评优点和缺点
- 画出自己作业的类图
-
自我点评优点和缺点
优点:
- 运用递归处理
- 把对表达式的处理集中于Parser类中,对于因子的处理单独建类分析
缺点:
- 部分方法的复杂度过高,耦合性很高,可以考虑把其中一些处理单独建立一个方法
- 因子的三个类很相似,但是没有进行抽象。
1.2 第二次作业
第二次作业在第一次作业的基础上增加了自定义函数、求和函数和三角函数,从定义方法的数量上就可以看出其难度明显提高,我主要是对第一次的方法进行了重构,延续了第一次的递归方法。
1.2.1 度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模
-
类的属性个数、方法个数、类总代码规模
类的名称 类的属性个数 方法个数 类代码规模 Add.java 0 1 27 Const.java 1 2 21 Cos.java 3 2 36 DefFactor.java 7 6 183 Easier.java 0 6 189 EveFactor.java 2 3 19 Main.java 8 3 97 Mul.java 0 1 46 MyMap.java 4 7 51 MyParser.java 7 4 160 Sin.java 3 2 37 SinCosToStr.java 0 1 40 Sub.java 0 1 34 Sum.java 2 4 59 -
方法个数、规模、控制分支数目
方法名 | 方法规模 | 控制分支数目 | 方法名 | 方法规模 | 控制分支数目 |
---|---|---|---|---|---|
DefFactor.addFactor | 4 | 0 | DefFactor.macherSinCos | 17 | 4 |
EveFactor.EveFactor | 5 | 0 | Sum.Sum | 14 | 4 |
EveFactor.getExpr | 3 | 0 | Main.replaceExpr | 20 | 7 |
EveFactor.getVar | 3 | 0 | expr.Add.getResult | 19 | 8 |
Sum.toMap | 3 | 0 | Easier.ope | 21 | 9 |
factor.Const.Const | 8 | 0 | expr.Sub.getResult | 25 | 9 |
factor.Const.toMap | 3 | 0 | Easier.numNeg | 24 | 10 |
factor.Cos.toMap | 3 | 0 | Easier.numOne | 23 | 10 |
factor.MyMap.MyMap | 6 | 0 | MyParser.term | 42 | 11 |
factor.MyMap.getCos | 3 | 0 | factor.SinCosToStr.toStr | 33 | 12 |
factor.MyMap.getNum | 3 | 0 | MyParser.operation | 31 | 15 |
factor.MyMap.getSin | 3 | 0 | expr.Mul.getResult | 37 | 19 |
factor.MyMap.getXPow | 3 | 0 | DefFactor.findMul | 25 | 20 |
factor.MyMap.setNum | 3 | 0 | MyParser.findMul | 25 | 20 |
factor.Sin.toMap | 3 | 0 | Easier.toSinCosStr | 41 | 24 |
Easier.toEasier | 11 | 1 | DefFactor.findAddOrSub | 33 | 29 |
Main.preOpe | 21 | 1 | MyParser.findAddOrSub | 33 | 29 |
Sum.exprOpe | 10 | 1 | DefFactor.operation | 58 | 31 |
Sum.expriOpe | 14 | 1 | Easier.toPrint | 55 | 34 |
DefFactor.defExprOpe | 16 | 3 | |||
Main.main | 28 | 3 | |||
factor.Cos.Cos | 18 | 3 | |||
factor.MyMap.equals | 11 | 3 | |||
factor.Sin.Sin | 19 | 3 |
1.2.2 计算经典的OO度量,分析类的内聚和相互间的耦合情况
分析: DefFactor
类的operation
,Easier
的toSinCosStr
、toPrint
、ope
,factor
的SinCosToStr
,MyParser
的operation
、term
,expr
包中Add
的getResult
与Sub
的getResult
基本复杂度高,Easier
的toSinCosStr
、toPrint
,DefFactor
类的operation
,MyParser
的term
模块设计复杂度高,
Easier
的toSinCosStr
、toPrint
,DefFactor
的findAddOrSub
,MyParser
的findAddOrSub
的圈复杂度高。
模块设计复杂度越高,耦合性越大。
Easier
类的toSinCosStr
方法会历遍装载sin
和cos
的容器,并且需要调用toSting
方法将其转换为字符串,故耦合度很高。toPrint
方法同理,只不过历遍的是因子的容器。MyParser
的term
方法是进行表达式递归处理的方法。在处理过程中,需要根据不同的情况调用不同类型的因子的类进行递归处理,故耦合度很高。DefFactor
的findAddOrSub
同理,只不过处理的是自定义因子。
1.2.3 画出自己作业的类图并自我点评优点和缺点
- 画出自己作业的类图
-
自我点评优点和缺点
优点:
- 将自定义表达式、三角函数、求和函数、常数设为因子,结构清晰。
- 运用递归处理,逻辑清晰,不易出错。
- 适用自己设计的
MyMap
类存储计算后的表达式因子,可以应对表达式因子形式多样的需求。
缺点:
- 没有把所有因子抽象出来,项类的处理更为复杂。
- 对于表达式运算方法也没有进行抽象。
- 只进行了
sin(0),cos(0)
的优化,优化程不够。
3.1 第三次作业
第三次作业相比于第二次作业只是多了多重嵌套,我还是采用了递归的方法,但是相比于第二次,第三次作业方法抽象程度更高,逻辑更加清晰了。
1.3.1 度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模
- 类的属性个数、方法个数、类总代码规模
类的名称 | 类的属性个数 | 方法个数 | 类代码规模 |
---|---|---|---|
Easier.java | 0 | 9 | 264 |
MyParser.java | 8 | 5 | 189 |
MyMap.java | 4 | 15 | 112 |
Sum.java | 2 | 4 | 65 |
Main.java | 1 | 2 | 60 |
Mul.java | 0 | 1 | 49 |
SplitTerm.java | 0 | 1 | 41 |
Cos.java | 3 | 2 | 43 |
Sin.java | 2 | 2 | 41 |
DefFactor.java | 2 | 2 | 38 |
Power.java | 2 | 2 | 38 |
Sub.java | 0 | 1 | 32 |
Add.java | 0 | 1 | 29 |
Const.java | 1 | 2 | 21 |
EveFactor.java | 2 | 3 | 21 |
-
方法个数、规模、控制分支数目
各方法的规模均小于60行。
方法名 | 控制分支数目 | 方法名 | 控制分支数目 |
---|---|---|---|
expr.Add.getResult | 8.0 | factor.Power.Power | 7.0 |
expr.Mul.getResult | 19.0 | factor.Power.toMap | 0.0 |
expr.Sub.getResult | 8.0 | factor.Sin.Sin | 4.0 |
factor.Const.Const | 0.0 | factor.Sin.toMap | 0.0 |
factor.Const.toMap | 0.0 | factor.Sum.exprOpe | 1.0 |
factor.Cos.Cos | 4.0 | factor.Sum.Sum | 4.0 |
factor.Cos.toMap | 0.0 | factor.Sum.toMap | 0.0 |
factor.DefFactor.addFactor | 0.0 | Main.main | 3.0 |
factor.DefFactor.defExprOpe | 3.0 | Main.preOpe | 1.0 |
factor.EveFactor.EveFactor | 0.0 | ope.Easier.easierSinCos | 9.0 |
factor.EveFactor.getExpr | 0.0 | ope.Easier.easierSinCosOk | 5.0 |
factor.EveFactor.getVar | 0.0 | ope.Easier.isTwo | 18.0 |
factor.MyMap.clone | 0.0 | ope.Easier.numNeg | 10.0 |
factor.MyMap.cosIsEmpty | 3.0 | ope.Easier.numOne | 10.0 |
factor.MyMap.equals | 3.0 | ope.Easier.ope | 9.0 |
factor.MyMap.getCos | 0.0 | ope.Easier.toEasier | 1.0 |
factor.MyMap.getNum | 0.0 | ope.Easier.toPrint | 34.0 |
factor.MyMap.getSin | 0.0 | ope.Easier.toSinCosStr | 24.0 |
factor.MyMap.getXPow | 0.0 | ope.MyParser.findAddOrSub | 21.0 |
factor.MyMap.MyMap | 0.0 | ope.MyParser.findMul | 15.0 |
factor.MyMap.setCos | 0.0 | ope.MyParser.matchParen | 11.0 |
factor.MyMap.setNum | 0.0 | ope.MyParser.operation | 17.0 |
factor.MyMap.setSin | 0.0 | ope.MyParser.term | 13.0 |
factor.MyMap.sinIsEmpty | 3.0 | ope.SplitTerm.splitTerm | 17.0 |
factor.MyMap.toEasyCos | 3.0 | ||
factor.MyMap.toEasySin | 3.0 |
1.3.2 计算经典的OO度量,分析类的内聚和相互间的耦合情况
分析: 从下图可以看出,Easier
类的toPrint
方法、toSinCosStr
方法的基本复杂度、模块设计复杂度、圈复杂度达到预警程度。
-
toPrint
方法会历遍装载表达式的容器,会调用很多factor
包中各种因子的方法,在判断过程和输出过程中也会调用StringBuilder
类的方法,故耦合度很高。 -
toSinCosStr
方法会历遍Sin
、Cos
容器,且会调用很多Sin
、Cos
类的方法和StringBuilder
类的各种方法,故耦合度也很高。 -
DefFactor
类中的operation
方法是进行表达式递归的方法,在这个方法中会调用各种表达式因子的方法,也会调用Add
、Sub
、Mul
等运算方法,故耦合度很高。
1.3.3 画出自己作业的类图并自我点评优点和缺点
- 画出自己作业的类图
-
自我点评优缺点
优点:
- 适用自己设计的
MyMap
类存储计算后的表达式因子,可以应对表达式因子形式多样的需求。 - 设计更加抽象,为
Add
、Sub
、Mul
类设置了Ope
接口,为Const
、Sin
、Cos
、Sum
、Power
类设置了Factor
接口,对因子的处理更加简单 - 在表达式处理上使用了改进的递归处理,在递归开始时先判断两端是否有括号,去括号后对表达式进行一步步分类处理。相比于第二次处理,把所有项都看作一个更抽象的因子,使得在需要处理子表达式时,不用在因子类中单独处理,而是统一放在
operation
方法中。 - 表达式合并时,会先比对两个表达式的各种系数,如果相等便进行合并,使每个表达式中只存储不能合并的项,每个项中存储不能合并的因子。
- 将不同功能的类用放在不同软件包,功能逻辑清晰。
缺点:
- 在化简部分,只化简了
sin(0)、cos(0)、
以及sin()^2+cos()^2
的形式,且在面对多样的表达式时,化简功能不能很好处理。 - 有部分打印字符功能的方法功能比较相近,但是没有进行合并,显得方法冗余。
- 适用自己设计的
2. 分析自己程序的bug
2.1 分析未通过的公测用例和被互测发现的bug,对比分析出现了bug的方法和未出现bug的方法在代码行和圈复杂度上的差异
2.1.1 第一次公测互测
第一次公测中我没有被发现bug,但是在互测的时候被发现了一个bug,这个bug主要是由于对replace的理解不清晰,因为replace会替换掉所有匹配的字符串,而我代码中的本意是替换第一个字符串,导致在同时出现多个相同匹配时全被换掉了,出现了问题。
问题所在类和方法:Main
类的replaceExpr
方法
分析:Main
类的replaceExpr
方法在代码行和圈复杂度上相比于其他方法并不算高,甚至处于较低水平,我认为出现这个bug的原因不在方法的复杂度上,更多的是对replace
的理解不当。
2.1.2 第二次公测互测
第二次互测中我并没有被发现bug,但是在强测中出现了一个问题,问题的原因是对“深克隆”“浅克隆”的理解不够,当时写代码的时候其实有意去避开这个问题,并且去搜索了有关深克隆的知识,但是我在克隆时其实还是出现了一些问题,后来我通过在MyMap
中写了clone
的方法进行深克隆,解决了这个问题。
问题所在类和方法:Sub
类的getResult
方法
分析:Sub
类的getResult
方法在代码行和圈复杂度上相比于其他方法其实属于较低水平,出现这个问题的原因主要是对深浅克隆的理解不当。
2.1.3 第三次公测互测
第三次作业我自认为是自己架构最好的一次,也没有做什么测试就通过了中测,但是可能是因为这种盲目自信,加上对格式理解的不清晰,成了错误最惨重的一次。虽然归根结底只有一个问题,就是出现这种(((())))
括号时,去括号需要循环匹配,但是在做题时,我对题意理解不够,以为这种是错误格式,于是没有考虑到这种情况。
问题所在类和方法:MyParser
类的operation
方法
分析:MyParser
类的operation
方法圈复杂度处于中等水平,代码行也只有34行,相比于其他方法是正常范围内。出现bug的原因是对题目理解不够,测试不够全面。
2.1 对Bug的总结
我认为我出现的bug主要问题不在方法的复杂度和代码耦合度上,其实主要还是自己对于题目的思考不够全面,测试覆盖性不够。其实每次的作业,我都在完全通过中测后就不再进行测试了,看题目也只是觉得点到为止,经常自认为xx格式应该不对,不会出现,但是其实是自己的理解问题。我认为在接下来的单元测试中,我应该更深刻地去理解题目,而不是停留于表面。
3. 分析自己发现别人程序bug所采用的策略
在互测中,我主要通过边界数据(比如0
、sin(0)**0
),以及一些在化简方面可能出现问题的数据进行测试。边界数据,是我认为最有效能够测出他人bug的方法(曾经一个数据hack了3个同学)。
在第一次互测时,我观察了房间中其他同学代码的架构,其中有一位同学在输出的地方使用"1*"->""
的方法进行化简,我针对这个bug给出了一个带有11*x
的数据,成功hack了他。
4. 架构设计体验
4.1 架构逐步成型过程
三次作业我都沿用了递归的架构,在前两次作业中,我采用的方法是先进行预处理(去除多余空格,化简+-、-+、--、++
等连续的符号,运用正则表达式进行乘方展开),再进行递归处理表达式,因为预处理已经解决了乘方问题,我只需在递归中处理乘法和加法即可,直到递归至因子返回。
显然我第一二次使用正则表达式乘方展开的策略很容易出错,但是好在还没有发现问题,但是遇到第三次可能多层嵌套时,正则表达式的匹配就显得力不从心。
在第三次作业中,我没有在预处理中乘方展开,而是将乘方也看成因子,在乘方类中进行对其表达式进行循环处理,对表达式处理部分,我依旧采用了一二次的架构,如果表达式有最外层括号,先去括号,再进行递归的加减乘分析。同时也将各种运算进行抽象,将各种因子进行抽象,建立了他们的接口,使得对他们的处理更加得简洁。
5. 心得体会
经过本单元,我认为我的能力提高了很多。在本单元第一次作业中,我刚刚开始完全无从下手,对java语法的不熟悉,加上没有什么思路,使得我整个人都对这门课产生了恐惧。我还尝试使用预解析进行处理寻找思路,但是并没有用。经过第一次实验课后,我从实验课的代码递归中找到了思路,当天就从开始的一行未写到写出完整作业(可以见得一个好的思路多么重要)。一直到后来的第二次、第三次作业我的总体思路一直是递归处理只是不断在其上进行了补充。到第三单元的时候,我感觉自己写出的作业已经比较完善了。相比于三周前的自己,我认为我的能力得到了一定的提升。
但是在测试情况上,我认为我还有很大的提升空间。首先就是认真阅读题目!认真阅读题目!认真阅读题目!我总是对题目的要求盲目地加上自己的想法,抑或是没有看清,这个问题对我bug的产生有着不可磨灭的”贡献“;其次就是认真理解助教的提示,忽视助教的提示的结果总是会在之后的某一刻体现出来;最后,多做测试,不要因为过了中测就觉得自己的代码没有问题,显然在强测和互测中会被发现的。
这一个单元,有恐惧也有希望和欣喜,我也将继续努力,弥补自己的不足!冲呀!!!