OO第一单元总结

一、程序剖析

第一次作业

第一次作业较为简单,任务为对简单多项式进行求导,该多项式仅包括常量和幂函数及其组成的表达式。
UML如下:
OO第一单元总结
度量分析如下:
OO第一单元总结
本次作业剖析:
由于是第一次作业,而且功能较为简单,因此并没有组织得很好,也不是很体现面向对象的思想。基本设想是分了三类:主类、项类和工具类。读入时先判断首项是否以正负号开头,若不是则添上一个正号,这样就统一了表达式中每一项的形式。然后直接使用项的正则表达式去匹配,输出时因为0、1等特殊指数和系数的存在进行了一些特判。做第一次作业时完全没有考虑过扩展性等问题,只考虑了如何完成本次任务,因此事实上本次作业虽然代码写得还算比较有条理,但扩展性差。
Bug:
本次作业比较顺利,写完后唯一的BUG就是在输出为0时会输出空字符串,不过该Bug我自己测的时候就已经发现。公测、强测并未发现Bug,互测阶段也未被HACK。

第二次作业

第二次作业在第一次的基础上加入了三角函数,并且三角函数也可以带有幂次,另外会出现除因空白字符位置的其他原因造成的WRONG FORMAT!输出。
UML如下:
OO第一单元总结
度量分析如下:
OO第一单元总结
本次作业剖析:
第二次作业稍微复杂一些,但是考虑到每一项仍然可以用 coeff*x**power1*sin(x)**power2*cos(x)**power3的形式来统一表示(同时也不想重构),因此我并未增加新的类,而是直接给原Poly类增加了成员变量。为了继续使用HashMap来存储Poly,我又新添加了一个类用来存储x、sin(x)和cos(x)的指数并将该类作为key。同时读入方式也发生变化(读入时先去空白字符),基本原则是分离出每一项后去匹配因子,大致基于以下规则:

  • 对首项的处理与第一次作业相同
  • 对于表达式,从第一个字符开始遍历
  • 忽略开头的符号,从碰到的第一个非+-字符开始统计,如果某次的+-不是紧跟着*出现,则表明该+-号前为一项,并从表达式切出

此后用正则表达式匹配因子即可。重复该过程直到需要解析的表达式为空或者某次循环没有发生切项的动作。如果匹配失败,表明格式错误。
另外此次作业的优化除合并同类项外我还进行了cos(x)**2+sin(x)**21-sin(x)**21-cos(x)**2型的合并(包括需要提取公因式的),其他的合并由于都存在变长的风险,而且继续增加代码量可能会使Bug变多,因此我并未实现。
这次的缺点也很明显了,由于沿用了第一次的成果,几乎毫无扩展性可言,而且类里结构很混乱,同时Term和Tools类的分工有些暧昧。
Bug:
本次出现了较多的Bug:

  1. 由于替换空白字符我使用了\s,导致垂直制表符也被替换而不能判断WF而错误。
  2. 平方项合并时的if-else嵌套逻辑有误,导致一些式子合并时走进错误的路线而出错。
  3. 由于我判断表达式项分离已完成采用的是循环完成后i==str.length()的形式(str指处理的表达式),而我由于每次都会切掉表达式的一部分,因此str.length()是实时变化的,而i的变化相对滞后,因此如果某次切掉后的剩余表达式长度恰好等于上一次切的位置,就会出问题。

其中公测顺利通过,而强测测出了1,互测测出了2和3。

第三次作业

第三次作业加入了表达式因子和嵌套规则,更加复杂了。
UML如下:
OO第一单元总结
度量分析如下:
OO第一单元总结
OO第一单元总结
本次作业剖析:
前面两次作业的缺点我已经说过了,所以第三次作业到手直接开始重构。
不过第二次作业的基本思想被继承了下来:先分离出每一项,再匹配因子。
我首先为每种因子都编写了一个类(表达式因子除外),由于表达式因子的存在,构造器采用传入字符串的方式,并由其自行解析。这种写法极大地方便了对嵌套的处理。由于最终的表达式是由若干项相加而成的,而项中又可以包含表达式,因此这两个类存在相互调用的情况(事实上sin和cos中的自变量也是表达式类的)。为每一个类都编写求导方法(实现求导接口),这样其嵌套求导就比较方便。最后的输出也是编写了各自类的输出。
这样切项的时候如果遇到括号,就只用关注最外一层的括号(大体方法与第二次作业类似),同时匹配因子时也只关注最外层的括号,匹配完成后即可传给各自的类进行参数提取、合法性判断等一系列操作。
由于害怕出Bug以及TLE还有情况过于复杂,本次基本未优化。
总的来说,这次作业虽然最难,却是我写得思路最清晰、面向对象性最强的一次作业。
Bug:
本次除了因为手抖匹配时少打一个空白项而被公测卡了5次之外,没有发现其他Bug。强测全通过,互测也未被HACK。
当然我自己在下面测试时发现括号无脑嵌套太深(类似((((((((x))))))))这种)时会TLE,于是专门对这种形式的嵌套进行了去括号处理。

二、测试相关

由于本人过菜,并不会搭评测机,因此所有的测试和HACK全靠人肉。自我测试时首先会随机打数据,没问题后会回想自己在写的时候有哪些觉得乱的地方,再去针对性地进行构造数据测试。偶尔也会用人脑把程序跑一遍来寻找潜在的Bug。
HACK别人时会挑选一两位幸运玩家来这么干(当然主要是对我觉得难懂的地方进行爆破),对于其他玩家就只采用测试自己程序的数据以及一些人肉随机数据。
这三次作业,第一次时并未HACK到别人;第二次则是利用有人会合并同类项之后才判断指数是否超范围HACK成功;第三次则是利用递归层数太深而TLE进行爆破,同时无意中发现别人的程序某些情况下会异常,于是爆破+1。

三、总结、反思与体会

第二次作业我的优化写得相当混乱,改进空间还很大,而第三次作业虽然我一直认为很复杂,但我后来想了想,理论上进行一些简单的优化还是可以做到的。
OO第一单元的学习结束了,虽然开始时我的程序基本上还是面向过程的,但是从第三次作业来看(同时对比看到的一些优秀代码),我确信我经过这几次作业我已经对面向对象有了一个基本的认识,并具有了按照面向对象思想编程的能力。以现在的眼光去审视我的第一次作业,简直惨不忍睹。相信有了这一回的教训,我在之后的作业中也能逐渐得心应手,综合考虑扩展性等因素,而不是次次重构。

上一篇:eclipse从外面复制内容粘贴时保留原格式


下一篇:关于CSS3的常见bug以及hack