OO第一单元总结
一、分析程序结构
第一次作业
在做第一次作业时,我还没有很好地理解面向对象的思想,存在以下的结构性问题:
1) 只设置了单项Item一个类。
2) 存储各个单项的集合ArrayList直接在Main函数中声明。
3) 读入多项式的过程直接在Main函数中用面向过程的方式完成。
4) 合并、求导、输出都直接作为Main函数处理ArrayList的方法.
当然,这些存在的问题在第二次作业中都得到了一定的解决。
第一次作业的时候我没能很好地掌握正则表达式的写法,用超长正则匹配、外形混乱、乱套括号,后来使用group分割时看不清分组、每次改动都非常麻烦。
if (m.group(23) != null) { exponent = new BigInteger(m.group(26)); }
(group有这——么——多——)
因为首项与普通项的格式有区别,我采用了分开判断的方式:先判断首项,若成立就循环判断之后的字符串。后来我才学习到了一个小技巧:可以先判断整串字符的首字符,若非‘+’或‘-’就直接在最前面拼接一个‘+’。这样就可以使用普通项格式对整个字符串进行循环判断了。
使用循环逐项判断的方式可以避免爆栈。
处理输出时,一定要考虑好各种情况,比如对x^-1的求导结果即-x^-2。在这里我写了大量(很不优雅的)if-else判断,而且将输出系数和输出指数的过程分别放在两个函数中处理,不知道还能不能简化。
最后还要注意特殊情况的处理,譬如输入纯常数时应输出0,输入计算结果为0的式子如x-x时要正确处理,这些都是我在互测时发现的可能出现的问题。
第二次作业
在对面向对象的思想有了一定理解的基础上,划分了多项式(Poly)、项(Item)、因子(Factor)三个类,因为只有常数因子、幂因子、sin因子、cos因子这几种简单的因子,对他们的求导处理也很类似,所以在这里我并没有将他们分为不同的类,而是在Factor类里通过标签来区分,常数因子直接归为单项的因子。很显然这样的架构扩展性不强,在第三次作业里就不能再使用了。
这次作业中我偷懒使用了整项大正则匹配的方式,先判断格式是否正确。
如上图所示,每识别读入一个Poly就在其中循环识别Item,每识别读入一个Item就在其中循环识别Factor,这样逐层构造,求导和输出时也是这样从底层向上层逐层处理的。
第三次作业
这一次,我继续使用了多项式(Poly)-项(Item)-因子(Factor)的结构,其中因子为父类Factor和子类Constant、Power、Sin、Cos、PolyFac。
用一个InputHandler类单独处理读入,用递归的方式。求导的过程与第二次作业类似,各层级相互调用实现递归求导,最终递归输出。
虽然结构思想上看起来与第二次作业类似,但因为第二次作业有的地方的处理实在是写得太烂(比如将常数因子全部转为单项的系数,等于对每个因子求导后都要把它的系数归于所在项的系数,不知道一开始是怎么想的),我几乎完全重构了代码。关于优化,只做了最简单的合并。
二、分析自己程序的Bug
第一次作业
在对各项系数和指数分割时使用了大量的if-else判断,if的条件没能实现全覆盖,遗漏了m*x^0这种指数为0的情况,因此在强测时错了三个点。
第二次作业:
理解指导书错误导致的WF问题。没有处理sin(x)和cos(x)括号中可以存在空白符的情况,将有空白符的情况直接判断为WF。
手滑导致的WF问题。忘记在正则表达式的开头加上空白符判定,如果表达式以空白符开头则会直接判断为WF。
单项求导部分的代码存在错误。对形如x^4*34*sin(x)*cos(x)的式子求导时没有问题,但是对形如x*sin(x)*x^3*sin(x)^-2*cos(x)的式子求导时就会出错。在修复bug时我采用了一种简单粗暴的方式,在读入式子后,先对各单项中的同类因子进行合并,再求导。例如对上式我们就可直接合并为x^4*sin(x)^-1*cos(x),虽然不能从根本上解决求导函数中的错误,但是可以暂时解决出错的问题。
三、互测阶段的策略
我的策略比较简单粗暴。
一是通过读代码发现一些非常简单的表面错误(读代码也是很好的学习过程)。
二是按照“自己曾考虑过的”可能出错的特殊情况去测试,而这样做在第一次作业时就出了问题。在互测时,一个人用一个测试点刀了房间里的四个人,其中也包括我,但是我一共也就被找到了这一个bug。直到互测结束时我也没能找出自己的bug,当然也就找不到别人的这个bug。最后在讨论课上助教讲解时才提到了m*x^0这个测试点,也就是我的bug。虽然说一整个Room加一整个宿舍的人都没想到这种特殊情况也是罕例,但从这也能看出这种寻找bug的方式有多么大的局限性。简而言之,其实我只是在用自己的代码思路作为“标准参照”寻找别人的bug,这显然是很有问题的。
四、重构思路
其实我认为我的代码存在的最大问题还是不够“面向对象”,在一些问题上常常偷懒使用“面向过程”的思路直接解决。另外,我还是没有很好地理解“接口”的思想,不能将其灵活地运用在代码中。