OO第一单元作业总结
OO第一单元作业总结
一、基于度量分析程序结构
1.第一次作业
第一次作业相对比较简单,根据题目的意思,可以把每一项归纳为axb的形式,这样可以把式子化为最简再进行求导。对于优化来说,我利用HashMap对所有项进行合并同类项,使得表达式保持在一个相对较短的长度。
首先来看看类图。
第一次作业我尝试使用面向对象的思想来构造,Item表示项,Term用来处理项,Constant存储了许多正则表达式来处理表达式。这样的优点就是每个类都在处理自己应该做的事情,在debug的时候很容易锁定错误位置。但同样存在一个问题。一个像在求导(derivable)后,返回值不应该是String类型(或者说这样不好),而更应该是Item类型。这个问题我在第三次作业的时候才意识到。然后来看一下复杂度。


可以看出,Term类中的Term构造方法和主函数的复杂度较高,这是因为这一部分我对表达式的处理较为复杂导致的。这是这次作业的问题之一。
总的来说,第一次作业较为简单,无论是面向过程还是面向对象,复杂度都不会很高。
2.第二次作业
第二次作业难度一下子上升了很多。这次作业加入很多元素,比如"三角函数"和"表达式因子",让同学们摸不到头脑。很显然,这次作业无法再使用正则表达式进行整体的判断了,因为你不能通过正则表达式找出表达式因子。这时候,我们就需要一个因子一个因子的进行判断。我们先来看看类图。

Factor是因子类,被五种类型的因子继承,同时实现了Derivable接口。对于每种因子,都实现toString()、derivable()方法,用于之后的求导和输出。这样的优点是结构比较清晰,但是缺点也有很多,比如类之间相互依赖,很有可能会造成爆栈的情况。许多同学使用了表达式树的方法完成本题,但我没有使用这种方法,这也是我的方法效果不如其他同学的原因之一。所以,我认为我的框架很不好。下面是复杂度分析。



上图中可以发现我在设计中使用了大量的方法,是由于有许多未使用过的方法没有被使用过,究其原因,是因为懒得删了重构造成的不知道某个方法删除后会不会导致程序出现bug,所以在最后我没有选择删除。同样,也有很多方法的复杂度较高,这些方法也是我认为我写的不是很好的方法,因为它们使用了不少的if-else分支进行判断。在以后的作业中我需要注意这一点。
总而言之,第二次作业的难度直线上升,与我第一次作业使用的方法产生了矛盾,这也是我不得不选择重构的原因。也因此,我没有进行优化,因为留给优化的时间少之又少,索性不做这些有风险的事情了。
3.第三次作业
第三次作业相对于第二次作业,难度提升不大,只是多了WF的判断和三角函数的嵌套。我听说不少的同学只在第二次作业的基础上加了十几行代码就完成了。在开始第三次作业之前,我对第二次作业的代码进行了简单的重构,即将derivable的返回值改为Item而不是String,并对toString方法进行了修改,然后才开始的第三次作业。类图如下:
这次的结构与上次相似,只是加入了WF的判断和三角函数的嵌套,显得类图十分复杂,但是与上次基本相同。因为使用的架构与上次相似,所以优点和缺点还是与第二次作业相同。下面是复杂度分析。



第三次和第二次作业之间难度并没有很大,所以改动很少,不用说很多。
二、分析自己的程序BUG
1.第一次作业
第一次作业遇到的问题就是处理三个连续的符号的问题,不过这个问题只需要我预处理一下字符串即可解决。添加了这个处理,并未大幅影响复杂度。
2.第二次作业
由于架构的问题,我在第二次和第三次作业都出现了爆栈和超时的问题。主要现象就是当遇到嵌套的很多的表达式时,同一个表达式要进行很多次toString方法,导致超时。所以我选择在Expression类中添加了der(求导后结果)和str(转化为字符串结果)记忆结果,这样就可以保证同一个表达式只需要进行一次toString和derivable方法,从而减少了大量运行的时间,成功通过。以下是修复Bug后/修复Bug前的复杂度对比。

可以看到修复Bug后的复杂度提升了,因为我加入了一次if判断str和der是否已经被记录过。所以这样的方法只能是临时办法,代码的构架才是最主要问题。
3.第三次作业
本次作业和第二次作业一样,也是出现了当嵌套过深时造成的超时现象。修复方法类似第二次,由于本次Item类的存储压力较大,需要存储大量表达式,所以我在Item类中加入了记忆对象,降低了时间消耗。复杂度也同样上升了。只能说这样修复只是无奈之举,最核心的原因还是架构过于低效。
三、互测策略与他人BUG分析
1.互测策略
由于 懒 兴趣,我在第一次完成后准备进行python评测机的制作,而整个单元的互测环节我都是用评测机完成的。评测机的制作分为生成部分、编译运行部分和对比部分。其中编译运行部分只需要使用python打开cmd并对文件进行编译,通过重定向写入读出结果即可。
生成部分起初采用通过整个正则表达式随机生成完成,但由于随机性太强、样例强度不够高以及第二、三次作业对正则表达式的限制而被迫改变。第二版的评测机使用了dfs算法,按照因子生成表达式。其中生成不同因子、不同符号等等概率可以通过调整变量的值而改变,使用起来样例强度很高。
对比部分起初采用的方法是直接通过equals方法判断相同。sympy先将随机生成表达式求导并化简,在于程序输出结果进行对比得到结果。由于sympy的缺陷和程序输出的不规范性,对表达式和程序输出需要进行一定的预处理。但是由于三角函数的加入,直接判断相等会造成错误的判断,所以最后改为了区间中随机取点判等。最后可以正常使用。
2.他人BUG分析
在互测中,除了评测机检查,我也也会构造一些深度嵌套的样例进行测试。最后,发现同学的bug总共分为几类:
1.结果为0但输出为空
2.输出的格式错误,比如:---x、(1+x)*、sin(2*x)等
3.表达式的一部分没有被成功识别,这种情况常发生在表达式中有多个括号的情况
4.使用了Integer数据类型,导致超过Integer范围的数字无法识别
5.爆栈、运算时间过长
这些情况发生的原因主要是没有检测一些特殊情况或是核心逻辑出现了问题,也有可能是同学手滑写错了代码。
四、有关重构
可能是由于刚学习OO的原因,我的架构十分可惜,导致每周都要经历一次重构。重构确实是可以解决不少曾经解决不了的BUG,但同时也十分消耗时间,容易产生新的bug。当部分重构时,你可能不敢对以前的代码进行改动。系统非常稳定,所有代码不要随便动.jpg所以就会出现我现在的状况,有很多方法其实并没有意义,但是却不敢删除,导致代码量显得很多。
这也充分说明了,在写代码之前有一个好架构的优势。有了好架构,后面的代码写起来会十分舒服。所以,在写代码之前,一定要花充足的时间设计一个优秀的框架。
五、心得体会
通过本单元的学习,我对面相对象有了更深的理解。我认识到了一个好的架构对一个工程是多么重要,所以以后再动手之前需要先仔细想一想这样的架构可扩展性强不强、复杂度高不高等问题。
但是这次作业我感觉对于刚学习oo的我们不是很友好,尤其是第二次作业,作业的难度有了质的提升。对比看来,第一次和第二次作业之间的难度与第二次和第三次作业之间的难度差距过大,导致很多人在第二次作业失利后,也很难做出第三次作业(可能是心态的原因)。这样还造成了第一单元的作业工程量大部分都集中在第二周,很多人(包括我)在第二周花费的时间比第一周和第三周加起来消耗的时间还要多。所以希望助教组可以平衡一下第一次作业的难度,将代码工程量分配的均匀一些。

浙公网安备 33010602011771号