301
社区成员
发帖
与我相关
我的任务
分享
第一次作业任务量较小,因此源代码只有410行,主要集中在Simplify、Parser、Poly类,对应输入化简类、字符串解析类、多项式运算输出类这几个主要处理阶段。
coe * x ^ powerX,有Mono乘、toString方法类设计的优点:Simplify的预处理使我在Parser解析字符串时考虑的情况大幅减少。我采用递归下降的方式对输入字符串进行解析,形成表达式树,层次划分明确。Expr再转为Poly,将加、乘、化简移入Poly类中完成,最后由Poly输出结果字符串。各类各司其职,耦合度较低,基本符合单一职责原理。
类设计的缺点:没有很好利用Factor来统一管理各因子,Expr中是toPoly()方法,但在Variable、Number中是toMono方法,各因子行为不统一。在Term中使用大量if(... instanceof ...),没有完全发挥出接口的优势,也为hw_2的局部重构埋下伏笔。
类复杂度:
第一次作业逻辑并不复杂,并且经过重构后我的类层次性划分较好,因此平均操作复杂度(OCavg)、最大操作复杂度(OCmax)、加权方法复杂度(WMC)均较低,反应出第一次作业代码判断逻辑较简单,可维护性和测试性高,代码质量较好。
部分方法复杂度:
截取了方法复杂度较高的几类方法,Simplify预处理加减号的方法中,我枚举了可能遇到的大量情况,因此认知复杂度(CogC)、设计复杂度(iv(G))、圈复杂度(v(G))均较高。Mono的toString方法复杂度同样较高,因为我在打印输出coe * x ^ power时为追求性能,枚举了大量情况。
经验感悟:大量的if-else或switch-case并不符合面向对象的设计思维,在之后的设计中要尽量规避。
第二次作业加入了自定义函数、指数函数因子、多层括号嵌套,因此代码量急剧增加。Poly和Mono类负责运算、化简、判断相等、toString等等,代码量较大。Parser也因为输入情况的不断增加,代码量也有显著增长。
exp(Factor) ^ power。有toPoly()方法coe * x ^ powerX改为coe * x ^ powerX * exp(expoPoly),expoPoly也为Poly类,是指数函数中的Factor转换而来的多项式。为合并同类项,增加equals()方法判断相等类设计的优点:
f(f(1,1), 1)这种函数嵌套情况的实参提取问题。且可扩展性高,若加入新的形参形式,同样可以用这种方式处理,在新迭代情景上的可延展性好。if(... instanceof ...),由Factor统一调用各方法。类设计的缺点:仍旧没有解决部分类的大量switch-case模式,如Mono的toString()、Parser的parseFactor()、Simplify的预处理方法等等,这些部分很容易出现bug。
类复杂度
此次的增量开发要求较为复杂,Poly类增加了许多方法,且很多方法相互调用,如combineSamePower(合并同类项)方法和equals方法的相互调用,导致Poly类的平均操作复杂度(OCavg)、加权方法复杂度(WMC)较高。Parser由于增添了解析指数函数因子和自定义函数的功能,平均操作复杂度(OCavg)也有上升。
部分方法复杂度
此次迭代中,Parser的parseFactor的方法新增了对指数函数因子的判断解析,同时在解析自定义函数时,需要调用AllFunc的newFunc()函数副本拷贝方法、Func的toExpr()实参代换形参方法,因此认知复杂度(CogC)、基本圈复杂度(ev(G))、设计复杂度(iv(G))、圈复杂度(v(G))都有显著上升。Poly的equals()方法涉及与Mono的equals()方法相互调用,基本圈复杂度(ev(G))、圈复杂度(v(G))也较高。
经验感悟:各类相互调用方法会大幅提升方法复杂度,导致各类、各方法相互依赖,给代码测试和维护带来巨大困难。
第三次迭代相对第二次而言增加的功能较简单,新增了求导因子,并允许自定义函数的嵌套使用。因此新增了Deri类,代码总体增量不多。
coe * x ^ powerX * exp(expoPoly)的Mono进行求导类设计的优点:在Poly层面进行求导,而非在Expr层面进行求导,Poly已经完成了函数展开、括号展开等,因此Poly的各项Mono比Expr的各项Term更简洁,也更容易求导。
类设计的缺点:Poly的derivation()方法和Mono的derivation()方法相互调用,会提升方法复杂度,使程序调试难度上升。且Poly又新增功能,使原本就复杂的Poly方法更多、更复杂,可能造成bug富集在Poly类中。
类复杂度
由于Poly承担了运算、化简、求导、输出等多种任务,Poly的复杂度较高。
部分方法复杂度
与hw_2的方法复杂度基本持平。Mono新增的derivation()求导方法,由于Mono需分类讨论coe * x ^ powerX、coe * exp(expoPoly)、coe * x ^ powerX * exp(expoPoly)等形式,基本圈复杂度(ev(G))较高
经验感悟:Poly作为主要处理类,应该进行一定的功能外调,而不是把多种功能都集中在Poly中。
很不幸,在hw_1完成过程中我就进行一次大的重构。重构前我试图在Expr层面就进行括号展开,但无法处理形如(1 + 2) * (3 + 4)或(x + 1) ^ 2等多个括号连乘的形式。重构后我引入了Mono类和Poly类,在这两类中进行运算、括号展开、合并同类项的优化和输出,奠定了本次作业的基本框架。
hw_1主要架构设计如下:
在hw_1较为完善优秀的架构上,hw_2的迭代思路较为明晰,只是对Factor进行了局部重构,使Factor能统一管理各因子,并调用各因子的具体方法。整体架构依旧延续hw_1。
hw_2新增架构如下:
if(... instanceof ...),由Factor统一调用各因子的具体方法。hw_3新增功能较少,因此本次只是在hw_2架构上新增功能,并未进行重构。
hw_3新增架构如下:
若新增三角函数因子,我会修改parseFactor()方法新增对三角函数因子的判断,新增三角函数类继承Factor因子,修改Mono属性以支持三角函数。同时,Mono和Poly类也要新增三角函数运算方法、化简方法、输出方法。
若新增求积分算子,同样先在parseFactor()方法中新增对积分因子的判断。新增积分类继承Factor接口。可以在Expr层面对表达式求积分,也可以在Poly层面对表达式求积分。
hw_1和hw_3的bug数量较少,主要集中在hw_2中。hw_2公测数据中,(((((((((((x^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8)^8这组数据因为括号的深层嵌套,我出现了CPU_TIME_LIMIT_EXCEED问题。这是因为在处理表达式次幂问题中,我采用了每次都暴力解析的方法,导致运行时间过长。修改后,我只解析表达式一次,然后根据次幂把表达式深拷贝数次,解决了该问题。
这个bug出现在Term的toPoly方法中,这个方法圈复杂度较高,因此我们在编写程序过程中,应该尽量降低圈复杂度,可以有效防止一些潜在bug。
我因为充分的本地测试而没有被hack,在本地测试中,我发现的主要bug都由深浅拷贝引起。由于表达式树的复杂嵌套,对表达式进行拷贝时未深度拷贝,各表达式因子相互关联,修改某一表达式引发所有关联表达式的改变,导致错误。
以后应注意深浅拷贝问题,确保不同对象没有任何关联。
我主要采用评测机和手捏数据结合的方式进行hack,在hw_2成功hack到别人。评测机提供随机数据,手动构造提供边界数据、特殊数据。如exp(exp(dx(x ^ 2 + 3))) * dx(exp((3 * x ^ 2)))、dx(f(x))等复杂形式的嵌套。
还有同学在预处理字符串中未分类详尽,可能会造成格式上的处理错误,因此编造格式上的特殊字符串也是思路之一。
除此之外,我还浏览了同房间同学的代码。不少同学的架构都与我自己的架构有很大差异,如有些同学没有引入Poly、Mono类,而是在Expr中执行大量操作。因此我针对代码中类复杂度、方法复杂度高的类和方法,编写了一部分数据来hack。
为提高程序性能,我主要通过合并同类项、优化toString()方法来缩短输出字符串。合并同类项设计Poly的判断相等,因此为Poly增加了equals()方法。Mono的toString()方法中我详细判断了系数为0,x幂次为1,expoPoly为因子、exp无需两层括号等情况,缩短了输出的字符串长度。
每处代码优化后我都会在本地进行充分测试,以保证优化后代码的正确性,再进行下一处的优化。同时对toString()这种大量分类讨论的方法,我通过写批注来保证代码可读性,通过封装麻烦的判断语句来保证代码简洁性,而不是一昧堆砌在if条件判断中。
OO作业任务量很大,迭代更是有可能出现意料不到的错误,虽然过程很痛苦,但完成后还是得到了很多经验。
首先,仔细阅读指导书后,先形成大致的架构设计,会对编写代码有很大帮助。不论是积极与同学讨论想法架构,还是参考往年博客的架构设计,形成一个好的架构和清晰的思路能让写程序事半功倍。
其次,要充分利用java语言的特性,如接口、父类等等,从原本c语言的面向过程编程思维转换到面向对象编程思维。
最后,一定要充分进行本地测试,评测机真的很重要!
hw_1是让我觉得最吃力的,当时我对类的划分和架构设计是很迷茫的。如果能在hw_1的指导书中加入架构设计的启发,或许能让我当时有些初步的思路。