OO第一单元总结
这一单元的主要任务是求导,难点一是在输入的解析,特别是第三次作业引入嵌套后;另一个难点就是如何层次化地表示整个表达式,既方便构建,又方便后续的求导与化简。
程序结构分析
主要使用idea的插件MetricsReloaded完成。首先介绍一些度量的概念与含义,这部分选取自一些博客的总结。
一些度量的含义
方法复杂度
- ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在$[1,v(G)]$之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
- iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在$[1,v(G)]$之间,值越大联系越紧密。
- v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
类复杂度
- OCavg:平均循环复杂度
- WMC:总循环复杂度
第一次作业
我只列出了超过一定阈值的度量信息,即可能存在复杂度过高的情况



第一次作业我本来是想尝试建立层次关系,以便后续扩展。因此所有可求导的类继承了求导接口,然后又按常规划分表达式层次的方式,用表达式、项、因子三层进行划分。这导致我第一次作业整体代码量很大,但到第三次作业的时候其实并没有派上太大用场,除求导接口外其他大部分都重构了。我的教训就是千万不要为了OO而OO,建立一些实际上用不着的层次关系,架构的设计还是要根据实际的需要。
复杂度分析中可以看到我的表达式类和多项式类的toString方法复杂度较高,这是因为我把优化输出的部分放在了toString部分,导致方法内有大量的条件判断,如判断指数是否为0为1等。如果要优化架构,可能会考虑彻底把优化输出的部分独立出来,或者把条件判断用私有的方法简化一下,可能会使单个方法的复杂度降低。另一个复杂度较高的方法是工厂中获取项的方法,这个是因为第一次的格式太简单,我有点偷懒,想用类似递归下降的方式去解析,结果发现一个方法内用条件判断和循环就搞定了,但是也导致这个方法比较复杂。类复杂度可以看出也是由于前面提到的这几个方法导致的。
第二次作业



第二次作业基本延续了第一次的架构,同样地分层没有派上大用场,我还是把幂函数和三角函数以及系数用一个统一的类来表示,因为这样虽然不利于后续拓展,但对于化简很简单。由于架构没变化,只是加了三角函数,复杂度较高的方法和第一次没有太大区别。
第三次作业



第三次作业大幅度重构了架构,基本上采用了指导书上的方式,但是也偷了一点懒,因为实际上只有三角函数用到了嵌套关系,我并没有额外增加一个表示嵌套的类,而是在三角函数类内部进行表示。
复杂度较高的方法只有递归下降解析三角函数时的方法,还是上面的原因,因为三角函数本身格式比较复杂,用到了大量的条件判断,条件可能也比较复杂,优化方式就是把一些复杂的条件判断独立为私有方法。
Bug分析
三次作业的强测和互测中我都没有被找到bug,前两次作业是因为本身难度不高,第三次是我放弃了优化输出的部分,尽量保持程序的简洁性,主要精力放在了保证正确性上。
寻找bug的策略
- 覆盖性测试,第一次作业不少同学在某些项的输出时存在一些类似“笔误”的错误,后面的作业由于复杂度提升也有类似的低级错误,这些可以在写代码阶段通过单元测试等手段得到解决。
- 压力测试,主要是第一次作业,用各种长表达式检测同一组内的正则表达式是否存在性能问题。
- 边界测试,手工构造的一些边界点。
设计模式的应用
虽然在之前还看了一些设计模式相关的书,但非常遗憾这三次作业基本上没用到具体的设计模式。第一次作业虽然从名字上用了工厂,但只是用了一个静态方法形式的简单工厂,后两次作业解析输入用的是递归下降,创建对象的具体参数可以通过递归下降的过程方便地得到,似乎也没有用工厂模式的必要。至于其他的设计模式,可能是我理解不够,没有想到如何在这次作业中使用,希望在之后的作业中找到更多应用设计模式的机会吧。

浙公网安备 33010602011771号