一、程序结构分析
第一次作业:对幂函数表达式进行求导(保证输入合法)
结构:
复杂度分析:
在第一次的作业中,我还没有逃脱面向过程的思维模式,基本上还是一Main到底的代码风格。
可以看到我一共只用了两个类,一个类Nape表示项,它记录了一个项的符号,系数,指数,并实现了求导功能。另一个类则是主类,实现了输入,把项进行分离,再进行求导,合并同类项,再输出的功能。
在下面的复杂度分析中可以看到,主类的循环复杂度非常高,这非常不利于代码的扩展与维护。我在这里想到的是,可以将输入,输出也形成一个类,合并同类项在项中作为方法实现,便可以大大降低主类的负担,并使得代码的可维护性与扩展性得到提高。
第二次作业:因子增加了sin(x),cos(x)(输入不保证合法)
结构:
复杂度分析:
第二次作业中,我依然没能更深刻的理解面向对象,只是简单的把几个过程对象化。
首先,是Nape项这个对象,这个对象用四元组描述了一个项,并实现了比较,求导的方法。
然后是输入类,实现了将输入的项分离出来,判断是否是WRONG FORMAT,以及合并同类项的功能。
再然后是简化类,实现的是三角化简的功能,本次作业中我的化简思路仅仅停留在了将a*s^2+b*c^2这类式子的化简上。
再然后是输出类,实现了将求导后的各个项再次组成合法的表达式并且输出的任务。
最后是主类,主类的功能仅仅是将这些过程的顺序展示的更清楚而已。
在复杂度分析中可以看到,我的输入,输出,简化类的循环复杂度都标红,说明设计的非常不好,这为我在第三次作业的重构做了铺垫。
第三次作业:增加了表达式因子,三角函数括号内允许出现所有因子(输入不保证合法)
结构:
复杂度分析:
第三次作业中采用了化整为零的思想以及表达式树的结构。将因子分为了幂函数,常数函数,正弦函数,余弦函数进行分别处理,将符号分为加,减,乘,也进行了单独处理,并使得这些都继承求导接口,方便求导。
Node类,表示表达式树上的节点。
Function类是函数类,父类是节点类,子类包括PowFunc(幂函数),NumFunc(常数类),SinFunc(正弦函数类),CosFunc(余弦函数类)。每个子类都具体实现了求导的功能。
Options类是符号类,父类是节点类,子类包括Plus(加法),Minus(减法),Mul(乘法)。每个子类都具体实现了求导功能。
Input类是输入类,描述是一个过程,实现了如何判断表达式是否为WF,将表达式分离出所有的项,如何将项分离出因子,并实现将表达式变成表达式树的功能。
Output类是输出类,描述的是一个过程,实现了如何将表达式树转换成合法表达式的功能。
主类则是描述各个过程直接顺序的类。
二、程序中的bug
第一次作业:对幂函数表达式进行求导(保证输入合法)
第一次作业并没有出现错误。但是性能分依然没有达到满分。主要原因是疏忽了要将系数为正的项进行优先输出。
第二次作业:因子增加了sin(x),cos(x)(输入不保证合法)
第二次作业出现了两个问题,首先是WF的判断中,对非法字符的判断出现了纰漏。
其次,做了一个负优化。我在将x**2替换成x*x的过程中太过简单粗暴,使得类似于x**24这样的项在输出时变成了x*x4这样的非法表达式。
第三次作业:增加了表达式因子,三角函数括号内允许出现所有因子(输入不保证合法)
第三次作业的问题主要出在两个大的方面:
第一是WF的判断,我的WF在Input中完成使得耦合度非常的高,不仅逻辑混乱而且难以修改与扩展。吸取了这次的教训,我在修改时将WF进行了独立的判断,并成功解决掉了这一大问题。导致这个大问题的主要是两个问题:
1.正则表达式错误。2.在修改checkstyle的时候,将参数搞混了。
第二是输出的符号的问题。导致这个大问题的原因在于空白符没有处理好,导致"[+-][ \\t]*"与[+-]在判断相等时出现错误。
三、分析bug策略
第一次作业:对幂函数表达式进行求导(保证输入合法)
第一次互测过程中,我使用的是随机生成的大数据进行hack的策略。
在这一次的测试中效果并不好,因为这一次难度并不大,大家对于基本功能的实现都比较完善了,出现错误的地方在于边缘数据。因此引发的思考是测试应该由随机数据与边缘数据组成。
第二次作业:因子增加了sin(x),cos(x)(输入不保证合法)
第二次作业的互测,依然使用的是随机生成的大数据。由于程序的难度上升,以及优化过程很容易产生奇怪的bug,导致本次互测成功hack到了人。
第三次作业:增加了表达式因子,三角函数括号内允许出现所有因子(输入不保证合法)
第三次作业的互测,使用的是结构非常复杂,但实际非常简单的数据比如-x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x-(x))))))))))))))。
四、应用对象创建模式来重构
我主要对第三次作业进行了重构。
结构:
新增了Bds,Xiang,Yinzi,WF类与Zhengze接口,分别表示表达式类,项类,因子类,判断格式类与正则表达式接口。这样做是为了减少Input类的负担,减少耦合度。Input将只要承担输入的功能,表达式分离出项,表达式建树的功能由Bds类实现,项分离出因子,项建树的功能由项类实现,因子建树的功能由因子类实现,WF的判断由WF类独立实现,正则表达式接口则提供这些类共需要的正则表达式。这样在优化时,我们只需要关注项与项之间的优化,因子与因子之间的优化即可。在扩展性方面,无论是增加符号还是函数都非常的方便。
五、对比和心得体会
在进行了三次面向对象作业的完成,以及学习其他同学,进行重构的过程中。我渐渐从面向过程的思维模式转换到了面向对象的思维过程。在我看来面向对象的思维模式解决问题,即是把问题中的对象抽离出来单独考虑,我们不需要去考虑整个问题,而是单独去思考某一功能,这一功能与其他功能的依赖性是非常低的,这样才方便修改与扩展。同时在构建完对象之后,我们的工作即是在问题的背景下,将这些对象的功能有机的结合起来,从而达到解决问题的目的。
除此之外,在bug的修复过程中我也有很多体会。首先是数据,数据的生成可以通过借由技术手段生成随机数据,同时我们自己也应该思考边缘数据的情况。其次是问题的出处,由于面向对象程序高度模块化,使得在检查问题的时候可以在可能出问题的模块中一一检查,因此只要在设计框架的时候设计的足够好,修改bug也能做到事半功倍的效果。