OO unit1 summary
Unit 1 summary
一、前言
三周左右的学习,OO第一单元顺利结束了,个人认为有必要写个blog来反思总结一下自己第一单元的学习情况,以便更好地进行后面的学习。
之前从来没有写blog的习惯,这是自己第一次写blog~(^-^)
二、程序结构分析
CogC:它将一段代码被阅读和理解时的复杂程度,估算成一个具体数字.
-
出现"break"中止了线性的代码阅读理解,如出现循环、条件、try-catch、switch-case、一串的and or操作符、递归,以及jump to label:代码因此更复杂
-
多层嵌套结构:代码因此更复杂
ev(G):基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。
-
基本复杂度高意味着非结构化程度高,难以模块化和维护。
-
消除了一个错误有时会引起其他的错误
iv(G):模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。
-
件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。
-
模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G):是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数
- 圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
①.h1
1.UML

2.复杂度分析及设计意图

MainClass
-
程序入口,控制程序的执行流程(由于功能统一,后续不再介绍)
-
由于只设计到函数调用,复杂性很低
Reader
-
负责将读入字符串,经过处理生成Poly类之后进行返回
-
read方法利用正则表达式对字符串进行信息的提取
-
removeBlank用于协助read进行字符串的信息提取
-
由于设计到正则表达式解析字符串,有各种匹配拆分操作,CogC较大
Poly
-
管理整个表达式,有一个HashMap作为属性,储存各个不同的Term类
-
addTerm,向Poly中添加新的Term,添加过程中会利用HashMap特点进行化简,用于构造Poly
-
show打印Poly信息,有优化处理
-
复杂度良好
Term
-
有coeff,degree两个属性,最底层的基本存储形式
-
mergeTerm用于项的合并
-
复杂度良好
3.总体规模
| Class | Attribute | Method | Lines |
|---|---|---|---|
| Reader | 0 | 2 | 67 |
| Poly | 1 | 3 | 89 |
| Term | 2 | 5 | 27 |
4.优缺点
优点:
-
基本完成题目要求
-
能把读取信息的代码块从Main中提取出来单独成类,初步具有工厂模式的意识
缺点:
-
整体架构扩展性很差
-
在输出的时候不知道使用toString,而是直接进行了println
②.h2
1.UML

2.复杂度分析及设计意图



Function
- 抽象类,各种函数都继承此抽象类,定义了公共的方法getDerivation,setSign用于项的符号处理
Adder
- 继承Function,两个对象相加,对getDerivation进行了重写,以二叉树的形式存储
Multer
- 继承Function,两个对象相乘,对getDerivation进行了重写,二叉树形式存储 与Adder相似
Constant
- 继承Function,存储常数
Pow
- 继承Function,存储幂函数
Sin
- 继承Function,存储正弦函数
Cos
- 继承Function,存储余弦函数与正弦函数类似
Nest
- 继承Function,存储嵌套类,里面有两个属性,分别是外层函数,内层函数,为之后的嵌套扩展做准备
Regex
- 由于采用正则解析字符串,涉及到各种模式串,统一用一个类进行管理
Factory
-
简单工厂模式,对于传入的字符串返回一个Function对象,在解析的时候进行类似于递归下降的正则解析方法,对于头部的对象不断进行包装,完成二叉树建立
-
同h1,由于采用正则匹配进行解析,导致复杂度一直较高
3.总体规模
| Class | Attribute | Method | Lines |
|---|---|---|---|
| Regex | 15 | 9 | 63 |
| Factory | 1 | 6 | 183 |
| Function | 1 | 2 | 17 |
| Multer | 2 | 4 | 48 |
| Adder | 2 | 4 | 40 |
| Constant | 1 | 3 | 22 |
| Pow | 4 | 3 | 32 |
| Sin | 3 | 3 | 30 |
| Cos | 4 | 3 | 31 |
4.优缺点
优点:
- 可以说这是我自认为最OO的一次了,基于二叉树设计数据结构,无论是建树过程还是getDerivation亦或是toString,都能很漂亮地通过递归完成,思路十分清晰,层次结构划分十分清楚,通过类的继承第一次体会到了真正意义上的OO
缺点:
-
由于采用了二叉树存储,在读入字符串形成对象的时候,需要对头部不断进行包装,导致的结果就是使以下这类本来十分简单的多项式被过度包装得十分复杂(层数增加非常快)
-
\[x*x*x*x*x*x*x \]
-
同时二叉树存储导致化简极其困难,只要不属于同一个父节点就没办法化简(或许有办法,但我太菜没想到~)对于这样很容易合并的多项式也没法合并
-
\[x+x**2-x**2 \]
③.h3
1.UML

2.复杂度分析及设计意图


h2到h3这里经过了一次重构,主要目的是解决之前化简困难的问题,本质上是地方没有什么变动,h2设计的架构已经具有了的扩展性,可以看出是耦合度最小的一次
- 思路就是将之前的二叉树变成多叉树,二元运算变成连加、连乘,个人选择了ArrayList和HahsMap相结合的方式进行存储
Nest
- 继承Function,主要实现嵌套函数,由于嵌套只涉及到内层和外层(如果有多层还是可以看成内层和外层),所以这里可以选择二叉树结构,依据嵌套求导公式写好getDerivation就行了
Factory
-
先扫一遍检查是否有不合规范的空白符,除空白符之后,就可以递归地进行正则解析
-
遇到括号的时候递归调用parseExp将结果入栈 之后进行组装的时候出栈 Factor扫描之后直接生成对象并组装成Term,Term出入栈组装成Exp
-
由于是通过形式化表述构造的模式串,所以不能匹配上则是格式错误,在parse的过程中随时抛出Exception处理WF
其他地方与h2完全相同
3.总体规模
| Class | Attribute | Method | Lines |
|---|---|---|---|
| Function | 1 | 3 | 17 |
| Expression | 1 | 4 | 66 |
| Term | 3 | 10 | 226 |
| Constant | 1 | 5 | 29 |
| Pow | 4 | 5 | 44 |
| Sin | 3 | 5 | 42 |
| Cos | 3 | 6 | 44 |
| Factory | 1 | 10 | 250 |
4.优缺点
优点
- 直接在h2基础上扩展,(
虽然h2到h3重构了一次)但是大体框架还是没变~~
缺点
-
Factory中没有采用正统的递归下降进行解析,而采用了地柜正则解析,导致为了正则的正确实现多了很多无谓的冗余操作,虽然道理简单,看似无懈可击,但实则操作困难,容易出bug。
-
为了能化简,我怎么感觉OO味淡了?感觉面向过程似乎更明显了一些?
三、个人bug分析
①h1
toString
在输出的时候产生部分错误,对于系数为0的项,自己断言已经从HashMap中remove了,在输出的时候没有给它带上符号
公测用例和互测用例中此Bug均有出现
原因:
-
mergeTerm的时候处理有问题
-
输出的时候并不知道递归调用toString,导致逻辑极其混乱,容易产生bug
-
没有进行覆盖性测试
从复杂性分析可以看出来 Poly里的show方法圈复杂度相较其他方法已经高出很多了
②h2
toString
-
由于二叉树存储结构化简十分困难,思维混乱,在特定的情况会产生优化错误,丢失括号
-
toString调用之后没有进行存储,导致调用次数过多而超时
原因:
-
二叉树存储结构本身存在缺陷
-
对于递归调用的时间成本不敏感
总的来说并不是因为类或是方法复杂性过高导致的bug,而是由于二叉树存储这种结构先天的缺陷主要还是我太菜
互测中也是以上两个bug被轮流hack……
③h3
checkWF
-
WF在方法中有一个没判出来,sin(+ 30)**
原因:
-
由于一来直接去了空白符,空白符没考虑完整导致后续模式串检查不能正确实现
出现Bug主要是方法上的缺陷导致的,写了一个假递归下降,理解错了……
由于互测不允许出现WF的数据,没被hack,逃过一劫
四、发现Bug的策略
-
1.将自己在写作业时遇到过的坑记下来,等到互测的时候用来手动hack
-
2.写了python自动机进行随机测试

随机生成数据,强度很低,我跑了2000轮才发现h2的bug……
如果能够针对性地构造覆盖性的数据应该会好一点,但总比人工比对,手动黑盒测试效率高就是了
不过战果还是有的,前两次h中都能hack到较多的人,h3中也hack到许多,不过由于规则原因,都没法用就是了
- 至于白盒测试,我仅仅在第一次中进行 后面发现看完代码真的太费时间了
并且还有许多很不好看的代码
由于本菜鸡不在高端局,黑盒测试效率远比白盒测试效率高
五、重构经历总结
一共经理了两次重构,分别是h1到h2,h2到h3
前面的部分已经列出了UML和复杂度分析~~就不再列啦ヾ(≧▽≦*)o
h1 -> h2
可以看出h1几乎没有什么扩展性,当时写h1的时候其实就已经做好了重构的准备了,h2中加入了层次划分,继承等OO的方法进行框架构造,可以说是思路最清晰的一次,OO味最浓的一次,可以采用了二叉树存储,导致化简很困难
h2 -> h3
由于h2扩展性较好,h3在架构层面几乎不需要更改,只是需要改一改数据结构,并对数据结构做一下方法在接口上的调整,使得能顺利化简,当然只是只最基本的化简,三角函数那部分我没有去考虑……
六、心得体会
-
从面向过程的思想转变为面向对象的思想对于我这种面相过程都不太行的人来说是较为困难的,所以其实有很多地方都是只是披着OO的皮……
-
但是不可否认的是,有些工作,不用面向对象的思路来完成是十分困难的,
比如第一单元作业 -
最好在做第一次作业的时候就要注意扩展性,否则后面又得花大量时间重构
-
选对方法很重要,
不要一头攒死,用了一个假递归下降头铁到底,实际上真正去请教大佬递归下降可能并不会花那么多时间研究正则,不断调试,虽然正则能力得到了极大提升 ( ̄︶ ̄)↗ -
Java能力得到了较大的提升
-
多读同学的优秀代码,学习优秀同学的做法
虽然正则头铁到底这种思路是h1某位同学那受到启发的 -
如果开始没有做好扩展性,只能重构,那么不要吝啬,适当重构不仅能提升code能力,同时还能让自己对于架构的理解更加清晰

浙公网安备 33010602011771号