第一次作业
第一次作业内容是对一个简单的表达式求导,因子只有幂函数和带符号整数,由于比较简单,我简单想了一下决定用map来存输入,就开始动手写了
程序结构
UML图
度量分析
名词说明:
-
OCavg:类平均圈复杂度, 继承类不计入
-
WMC:类总圈复杂度
-
ev(G):非抽象方法的基本复杂度, 基本复杂度是一个图论度量理论, 用来度量一个方法的控制流结构有多差, 基本复杂度的范围是 1 到 v(G)
-
iv(G):设计复杂度, 度量方法控制流与其他方法之间的耦合程度, 设计复杂度的范围是 1 到 v(G)
-
v(G):圈复杂度, 度量每个中不同路径执行的数量
可见我的Poly类耦合度较高,可能因为Poly类与Term类间的紧密程度比较强,应该再新建一个Parse类,将一些poly类方法放到其中,从而能够稀释耦合度
公测
优化了正项提前,合并同类项,指数和系数为+-1时的情况。所有点AC,但是性能分没有满,因为我没有优化项的结果为0时的情况,导致最终表达式中有+0出现。究其错误的本质原因在于我没有做足充分的测试,我只是手动测试了几个特殊样例,例如一个常数求完导后是0,但是没有测试像x+12这种样例,导致我输出了1+0。
互测
我寻找bug的策略是找++,+-,-+,--符号组合,以及项系数为+-1,指数为+-1的优化情况。我没能查出bug,感觉我们房应该是个大佬房,大家都尝试了很多次hack,但没有找出来一个错误。
第二次作业
第二次作业引入了三角函数因子和新的运算:因子之间的乘积,这次作业由于我在第一次作业中得架构过于简单,导致我需要重构。但是我第二次作业又偷懒了,我一想底只能是sin(x),cos(x),x,我就想当然地采用了三元组形式来存一个Term,然后用Map
程序结构
UML图
度量分析
高复杂度方法与类
依然可见我的表达式类的复杂度太高,可能是因为本应属于其它类做的事情让表达式类做了所导致的。解决办法可以是多创建几个类来分摊Expression类里的方法
强测
强测我错了三个点,原因都是因为一个bug,没有处理好一个项最前面的符号问题,导致了该是负号的地方成了正号。例如-x里的x读入不进来,我采取的措施是把符号转化为-1*,这样就很好的解决了问题
之所以会出现这样的原因还是我自己测试测得太少了。自己没有时间和能力来造一个自动评测机,以后还需要在这方面多努力,多向别人请教。
性能方面我优化了sin(x)**2+cos(x)**2=1,但是没有考虑sin(x)**4 - cos(x)**4 = sin(x)**2 - cos(x)**2,导致性能部分还是差了一些。
如果能早点看见yjz的帖子恐怕可以优化部分做得更好,但是我看见的时候已经来不及改了
互测
与强测的同一个bug导致我互测被刀的很惨,被刀了八刀。这也是我没有做好充分测试的后果。我此次找bug的策略是定向爆破,例如针对WF,指数不能超50以及空白符的处理。针对符号问题,例如+--1这种连着三个符号能不能处理正确。针对连乘,例如x*sin(x)*cos(x)*sin(x)一直乘好多项,测试其能不能处理好这种连乘。我也终于找到了别人的bug,例如x**49*x**49报WF的这种bug。
第三次作业
第三次作业引入了表达式因子,即表达式加上括号以后就会变成表达式因子。这使嵌套成为了可能。由于前两次作业的架构非常不合理,导致我第三次作业非常难受。我是在周二晚上看到了作业,一直到周五上午,我都苦苦思考究竟该如何保存我读入的东西,又该如何处理嵌套。我不停地看讨论区,不停地看课件和指导书,可我就是没有思路,也没有方法,期间我异常焦虑,甚至已经无法思考。这就是前两次作业我不认真架构的后果。直到周五中午,我询问了很多人,由于我菜到不会建树,于是我只好采取了一种递归处理的办法,每次将最外层括号改变成@和#,然后交给Factory处理,返回某个特定类,让所有的类都执行diff和toString方法,一层层递归。这样下我匆忙写好了程序,原本我的程序是可以简化sin((x+x))这种求完导以后内部可以化简成cos((2*x))这种。即可以简化内部嵌套的表达式。但是利用中测测出来的bug我一直都找不出来,无奈我只好放弃了对内部嵌套的表达式的优化,才最终过了中测。我认为我没有找到bug的原因是第一时间太紧,第二是架构不合理,Expression类和Term类过于庞杂,第三是我没有认真思考。
程序结构
UML图
度量分析
仓促之下写得代码果然是复杂度非常高,没有体现高内聚低耦合的特点,大家可以拿我当一个反例。Expression和Term类过于冗长,应该尽量使其方法分到不同的类中
强测
强测我曾担心我会出现很多错误,但结果是并没有。
互测
我在互测的找bug方法是手动hack,第一个是找深层嵌套,像(((((((x)))))))可能会由于递归而TLE。像把x**2变成x*x时不加括号可能会造成WF,如sin(x*x)。还有就是表达式因子不能有幂,例如(x+x)**2这种是WF。我之所以会想到测这些测试点是因为我在测自己程序的bug中发现了我有可能会出现这些问题。因此这也证明了互测中被hack的少的人往往hack别人hack的很多,因为他们再写程序的时候就已经注意到会有很多的bug,他们都记在脑子里了。
互测我有两个bug都被房间里的大佬们找出来了,一个是在判断输入是否是WF时处理正则表达式的时候忘加blank空白符,还有一个bug时简化输出时replaceall语句中的正则首项的^忘写了。这些错误导致我被hack了12次。强测没出错的我互测仍然出现了两个bug,说明了我在细节上考虑得不周到,如果说强测是整体性测试,那么互测就是细节和再细节的测试。
心得体会
- 提前留好接口,提高程序扩展性
- 创建对象时要时刻想着用设计模式,无论是工厂模式还是其他设计模式等等
- 合理地边测试边写代码,写完一段代码以后对其功能做一个测试
- 写代码的时候注意要写注释,起有意义的变量名,使用驼峰命名法
- 应用层次化的设计方法,使得类之间更加实现高内聚低耦合
对象创建模式
在第三次作业中,为了使得对象的创建更加便捷,我建立了一个工厂来根据参数生成不同的对象。我认为工厂模式的本质在于解耦合,即解离了创建对象与被创建对象得关系,将这一切封装在一个工厂中。
这样做的好处在于,它的可扩展性非常好,增加新的类只需要改变工厂;高内聚低耦合;合理利用了继承与多态,工厂返回的一定是个顶层的类或者接口,方便调用者管理和利用。
这样的工厂最大的意义在于实现了自动化的对象生成功能,用户无需了解具体过程是怎样,只需要把输入交给工厂,返回的就是我想要的对象。实现这样的工厂的基础是继承和多态。
总结
经历了这三次作业,我深刻体会到了OO的恐怖,也给了我很多的教训。
- 进行每次作业的时候都要为以后着想,提前留好接口,要充分考虑程序的可扩展性
- 要尽量早地找到思路,无论是通过问人还是看讨论区的方式,不能拖到ddl
- 多学习别人的代码,多看大佬们的代码,从中学习
- 写代码一定要写注释,起名要有意义,驼峰命名法
- 正则表达式多的时候一定要建一个Regex类,存这些正则,通过get方法来调用
- 要学会建一个Exception类,用try-catch来捕捉异常
- 当输入输出很复杂时要有输入输出类,同时还要有主类
- 学有余力要学习造一个评测机,来实现自动测试
- 再学有余力的话要想办法化简
写在最后
通过第一单元OO的学习,我对面向对象的理解更加深刻了。尽管自己的面向对象能力还是很弱,但我相信通过努力,我一定能慢慢追上来的。同时,我一定要做到多阅读大佬们的代码从中学习,多参与讨论区的讨论,在实践的过程中慢慢提升自我。感谢课程组和老师们的辛勤付出,感谢共同讨论、分享的同学,我非常期待OO的下几个单元。