我永远喜欢着OOP——第一次总结
一、三次作业总结分析
1. 第一次作业
1.1 作业分析
第一作业主要是给我们引入了一个对于非法输入处理的思想,包括第一次上机,都一直围绕着一个全新的主题,就是非法输入处理,而对于这次作业本身,其实难度并不是很大,甚至用纯C也不会有很大的工作量,但是引入了一个这样一个重要思想笔者觉得是学到很多的
1.2 程序架构
说起第一次作业,笔者其实是有为第二次作业做准备的(但是看起来准备的并不合格),在第一次作业中笔者还专门准备了一个虚基类来准备继承其他的项(因为怕操作失误删了),以方便在第二次作业如果加入其他的函数,就直接加上一个新的子类,然后上层逻辑用利用多态,根本不用改(事实证明是我想少了)。
直接上UML图吧(先让我去git clone 一下)
可以看到,看到每个类的方法很多,不过细看可以看出来很多其实这主要是方法模块化的结果。
首先是关于错误处理和输入规范化,笔者单独实现了一个Parser解析类来完成这个工作,利用正则分割和匹配每一项,在遇到非法输入即抛出异常,有顶层类捕获并输出WF
然后就是求导部分,因为只有加法,所以只需要表达式调用每个项的求导即可完成求导
最后是化简,也就是优化,在本章作业里,优化都是一个让我觉得很难受的地方,也不一定说每个优化有多么复杂的逻辑或是算法,但是更多的给我的感觉是,从整理到求导整个过程像是一个整体,而优化就像是没有关系的一件事情硬插在了里面。
不过现在静下心来思考,其实,优化不应该被放在任何一个因子、项、表达式类里面,其实,笔者觉得应该装饰者模式来处理也许会更好,每种优化作为一个装饰者,然后将项、表达式、因子传入,然后再得到化简后的结果,这样的组织首先不会导致化简逻辑和运算逻辑混为一谈,两件事情高度耦合,错误就很容易发生(笔者本人也是深受其害,以至于Bug修复的时候只需要注释掉simplify()
函数就可以修复绝大多数同质Bug),所以还是回到了高内聚低耦合的话题上,这件事情应该是设计阶段要仔细思考的一件事。
所以也可以看到,我的项和表达式里面充斥着大量的和项与表达式运算无关的方法,他们也严重违反了单一职责原则
Class | CBO | DIT | LCOM | NOC | RFC | WMC |
---|---|---|---|---|---|---|
Arithmetic | 3.0 | |||||
Parser | 1.0 | 1.0 | 1.0 | 0.0 | 21.0 | 13.0 |
Poly | 2.0 | 1.0 | 1.0 | 0.0 | 39.0 | 23.0 |
PowerItem | 2.0 | 1.0 | 4.0 | 0.0 | 26.0 | 29.0 |
Solution | 2.0 | 1.0 | 1.0 | 0.0 | 16.0 | 3.0 |
Total | 68.0 | |||||
Average | 1.75 | 1.0 | 1.75 | 0.0 | 21.0 | 17.0 |
从度量中也可以看到整体复杂度其实还是不高的
2. 第二次作业
2.1 作业分析
第二次相对于第一次,求导规则更加复杂,数据如何组织是一件比较麻烦的事情,由于当时笔者没有将常数作为单独一项来处理,所以导致在优化合并的时候,提取系数极其困难
2.2 程序架构
直接上类图,咱们看图说话
很庞大,但是其实就是因子继承虚基类,然后所有承在一起的因子构成一个所谓的Box,Box中用HashMap来维护三项,主体逻辑跟上一次完全相同,但是化简逻辑复杂了很多,从图中也可以看到有很多化简函数放在了里面,让人看不懂,这也是这三次作业的败笔所在,没有把化简功能分离出来,这样不仅不利于测试化简功能,而且程序的未知行为无法估测,这也是这样测试中出现Bug的一个十分重要的原因
下面给出程序的度量
Class | CBO | DIT | LCOM | NOC | RFC | WMC |
---|---|---|---|---|---|---|
Constant | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
ItemType | 4.0 | 1.0 | 2.0 | 2.0 | ||
Solution | 4.0 | 0.0 | 1.0 | 0.0 | 11.0 | 4.0 |
ItemFactory | 6.0 | 0.0 | 1.0 | 0.0 | 5.0 | 9.0 |
TirgoItem | 4.0 | 1.0 | 2.0 | 2.0 | 11.0 | 10.0 |
SinItem | 7.0 | 2.0 | 5.0 | 0.0 | 17.0 | 11.0 |
CosItem | 7.0 | 2.0 | 5.0 | 0.0 | 17.0 | 11.0 |
Item | 7.0 | 0.0 | 8.0 | 2.0 | 15.0 | 12.0 |
Parser | 4.0 | 0.0 | 1.0 | 0.0 | 10.0 | 13.0 |
Poly | 2.0 | 0.0 | 1.0 | 0.0 | 17.0 | 25.0 |
PowerItem | 6.0 | 1.0 | 8.0 | 0.0 | 21.0 | 29.0 |
ItemBox | 9.0 | 0.0 | 1.0 | 0.0 | 22.0 | 44.0 |
Total | 170.0 | |||||
Average | 5.083333333333333 | 0.5454545454545454 | 2.8333333333333335 | 0.36363636363636365 | 11.692307692307692 | 14.166666666666666 |
可以看到,由于Poly
和ItemBox
类中集合了过多本不该属于他们的方法,所以导致这两个类的复杂度很高,最终的Bug也是出现在这两个类的协作中
3. 第三次作业
3.1 作业分析
这次的作业看起来改动很大,其实仔细分析可以发现,只是新增了一个嵌套功能,也就是说,如果保证嵌套的内容没有问题,那么整个程序的逻辑和第二次作业基本是一致的,我也是按照这个思路来组织,并根据指导书给出的因子、项、表达式这样的分层结构来实现的设计
3.2 程序架构
基本就是按照指导书给出的三层结构来实现的设计,下面给出UML图
结构很清晰,容我再次痛斥一次我这种肮脏的优化设计,耦合度真的很高,无法测试,导致系统存在大量的不确定性,正确性无法保证的同时,还无法单独调试改错,实在丑陋
Class | CBO | DIT | LCOM | NOC | RFC | WMC |
---|---|---|---|---|---|---|
ExceptionUtil | 1.0 | 1.0 | 1.0 | 0.0 | 5.0 | 1.0 |
Main | 4.0 | 1.0 | 1.0 | 0.0 | 12.0 | 2.0 |
PowerFactor | 5.0 | 2.0 | 2.0 | 0.0 | 18.0 | 9.0 |
NumFactor | 7.0 | 2.0 | 2.0 | 0.0 | 17.0 | 9.0 |
Factor | 7.0 | 1.0 | 4.0 | 5.0 | 20.0 | 12.0 |
FactorFactory | 7.0 | 1.0 | 1.0 | 0.0 | 9.0 | 13.0 |
ExprFactor | 5.0 | 2.0 | 4.0 | 0.0 | 22.0 | 15.0 |
CosFactor | 6.0 | 2.0 | 1.0 | 0.0 | 36.0 | 19.0 |
SinFactor | 6.0 | 2.0 | 1.0 | 0.0 | 35.0 | 19.0 |
Term | 13.0 | 1.0 | 1.0 | 0.0 | 51.0 | 49.0 |
Expr | 3.0 | 1.0 | 3.0 | 0.0 | 61.0 | 63.0 |
Total | 211.0 | |||||
Average | 5.818181818181818 | 1.4545454545454546 | 1.9090909090909092 | 0.45454545454545453 | 26.0 | 19.181818181818183 |
可以看出,底层的模块的复杂度还较为良好,而上层的模块,也就是项和表达式,由于过多的化简逻辑掺杂其中,导致这两个类的复杂度很高,是十分糟糕的情况
二、关于Bug
1. 我的Bug
笔者在第二次和第三次都出现了Bug,而且情况很类似,都是一个几行代码就能修复但是却很致命的Bug,我觉得原因有下面几点:
- 思路不够清晰,或者说由于模块耦合过度导致思路混乱
- 数据组织方式有问题,数据的组织形式往往是在设计时针对求导来设计的,而往往忽略了优化部分,然而又没有采用新的组织,导致最终出现问题或者化简困难
- 本身算法水平较差,算法效率不够
所以我觉得,在设计一个程序的时候,首先,应该保证良好的模块间解耦,从而能够让思维集中而不用去一直全局考虑,其次,就是要合理组织数据,如果要完成的两件事情有各自的较优数据组织方式,那么重新组织数据也不失为一种方法
2. 别人的Bug
关于如何发现别人的Bug,我从两个角度说吧
出于行业内的代码审查和代码走查方式:
这种方式强调看代码逻辑,也就是白盒测试,在看代码的同时不断提出疑问,然后从这些疑问中寻找可能存在的bug,黑盒测试在这里只起辅助验证作用。
然而,在现有的游戏模式下,首先,你要面对的是六七份代码,每份的代码量都在千行左右,而且有的代码的可读性又比较差,毕竟没有统一的设计规范,这种方法基本不可行,毕竟这种方法在企业中的效率也就只是几个人两个半小时几百行代码的样子
所以,萌生了一种很没趣的方式——评测机玩法
我也叫它摇奖玩法,就是通过自己定义一个生成逻辑,然后模拟评测机去一直测测测,什么时候测到了就算捞到了,这个方法主要的效果是会给编程人员较大的压力和动力去完善自己的代码,对于评测人员,应该可以提高编写脚本的能力,可谓收获颇丰呢
三、关于设计模式
既然说到设计模式了,就简单提一下
对于本次作业,我觉得有下面这两种模式都是比较推荐的:
- 工厂模式,借用工厂模式将因子的生成逻辑(判断 + 生成)封装,代码看起来会很简洁,也可以看到我从第二次作业,类图里面就多了一个
FactorFactory
- 装饰者模式,这个主要用于解决化简优化问题,每个化简逻辑可以被定义为一个装饰者,然后将求导的结果一层层装饰,最终获得优化结果,这个模式是笔者前两天才想起的,没有用到作业中也是可惜了