菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
245
0

BUAA_OO_第一单元总结

原创
05/13 14:22
阅读数 71541

OO第一单元总结

一、程序结构分析

第一次作业设计:

设计思路:

第一次作业的表达式的基本形式较为简单,只有很简单的求导模式,对每一个项,不论因子有多少,总可以写成a*x**b形式,而其求导的结果是a*b*x**(b-1),是一个相对固定的结果,因此不必再建立乘法求导法则或是链式求导法则,而是直接按照公式根据项的系数和指数,直接输出求导后项的结果,再利用输出的方法来输出符合格式要求的表达式。

UML类图:

第一次作业主要包含三个类,即polylist类(多项式)、poly类(项)和factor类(因子),对项类设置系数coef和指数index分别代表常数项和幂函数因子,然后在多项式类中用HashMap来存储各个项,并进行求导操作。结构成线性,简单清晰。

基于度量的结构分析:

1、方法复杂度分析

可以看出,方法的控制分支个数都比较均匀,且不是很多,并且大多数的方法的认知复杂度和圈复杂度都比较小,代码的可维护性和可测性都比较好,代码比较清晰。但是Polylist.out()这个方法的认知复杂度和圈复杂度都很高,代码不仅难以阅读,自己的本地测试也很难测试全面,一旦出现bug会很难解决,代码可维护性差。

导致这一问题的原因是我为了实现输出结果的优化,在输出的过程中使用了大量的条件语句,使得代码变得复杂混乱。

2、类复杂度分析

Polylist的平均方法复杂度和加权方法复杂度都比较高,原因在于它将求导、优化和输出几大核心功能于一身,功能过于复杂,因此代码臃肿,复杂度高。

3、代码整体规模分析

代码量不是很大,但是分布不够均匀,polylist类由于承担了太多的核心功能而冗长,需要进一步剥离功能,程序结构才更匀称。

第二次作业设计:

设计思路:

第二次作业引入了三角函数因子和表达式因子,这时候,之前的系数-指数二元组形式的求导方式只能用在不含表达式因子的项中,而含有表达式因子的项则需要递归调用表达式求导法进行解决。

这次作业我还改变了一些结构上的布置,对于输入的处理从第一次作业的分割项和因子,改变为了利用递归下降分析法来实现对于输入的处理,这种改变的好处是可以在处理输入数据的同时实现对输入格式的检查,有很好的可拓展性,有助于第三次作业的开展。

递归下降的基本模式:

此外,随着因子的增多,我利用了工厂模式,来统一生产各种不同的因子。

优化部分:

关于优化,优化的进行分为两个部分,一部分在输出的时候对系数和指数进行特殊的讨论,对于系数为0、1或-1的一些项,以及指数为1或0的一些项进行特殊的讨论。

比如,对1*x直接改为x。

·比如-1*x在它处于项的开头时直接改为-x。

·比如x**1直接改为x。

·比如x**0直接改为1。

同时,在求完导后存储导函数数据的时候,直接将每一项的不同因子的常数部分和幂函数因子部分进行合并。

另一部分是对于输出的结果(字符串)进行匹配、替换,比如将‘1替换为*。

UML类图:

这张图基本上展现了各个类之间的相互关系,我的各个类和代码的相互关系比较简单清晰,通过递归下降法读入表达式信息,再通过递归调用下一层级的类的求导方法最终完成表达式的求导,最终输出,结构简单清晰。但是为了提高运算、输出效率,我还是对一些简单的项的情况进行了剥离的单独处理,使得结构的清晰度有些许下降。

基于度量的结构分析:

1、方法复杂度分析

将各方法按照复杂度由小到大的顺序排列,节选最后一部分如上。

可见,方法的控制分支个数都比较均匀,且不是很多,绝大部分的方法都是比较简单的,认知复杂度和圈复杂度都比较小,代码的可维护性和可测性都比较好,代码比较清晰。但是Expression.factor(),Poly.out(),Polylist.out()这三个方法的复杂度却很高,代码的可读性、可维护性等都有较大的问题。其原因在于,在这几个方法中,我都是将几个方面的功能符合在一个方法中,比如Expression.factor()这个方法需要判断并读入所有可能类型的因子。而其余两个输出的方法则需要在负责输出字符串格式的同时兼顾优化的功能。没能将各功能细化到各个方法中是我的不足。

2、类复杂度分析

除了上面提到过的Expression、Polylist和Poly三个类,另外两个类MainClass和FactorFactory也出现了操作复杂度较高的问题,这是由于在Mainclass中,我试图通过在Mainclass中对最终的输出语句进行进一步的优化而使用过了大量条件语句。而在FactorFactory中,我则是通过多个条件语句来生成不同种类的因子。同样使用了大量集中的判断,最终导致了较高的操作复杂度。

3、代码整体规模分析

可以看到,大多数类的代码规模都比较均匀,只有Polylist、Poly、Expression由于集中承担了多个核心任务而导致代码比较臃肿,应该进一步剥离功能,建立新的类来完成相应的功能,类的封装还是存在问题。

第三次作业设计:

设计思路:

第三次作业在第二次作业的基础上并没有太大的改动,只是彻底抛弃了固定结果的格式化求导,而是对项和因子都定义了各自的求导方式,通过对求导方法的调用实现求导。

本次作业的难点在于三角函数的求导,它的求导需要调用其他因子的求导,还需要利用链式法则和乘法法则来进行。另一个难点是对于输入的格式检查,需要利用递归下降的处理法来进行,为了简化递归下降处理法承担的功能的复杂程度,我将一部分的格式检查任务分配给了一些新建的方法,利用正则表达式来进行判断,并初步简化输入,以方便后续的检查和信息存储。

关于优化,这次的优化在输出的过程中进行。当输出每一项的时候,如果含有带0的系数则直接将该项省略。此外,第二次作业中对特殊指数、系数项的讨论仍然存在,仍然对其进行优化。对于一个项的不同的因子,在求导后存储的时候就直接将常数和幂函数进行合并,三角函数因子和表达式因子由于判断因子是否相等相对困难,故没有进行合并。

UML类图:

通过本试图可以看到,随着三角函数中可以嵌套其他因子这个机制的引入,因子类之间的关系变得复杂,彼此不再处于相对平等的关系,多项式因子和三角函数因子地位较为特殊,可能出现嵌套等形式。与此同时,整个工程的结构也更加复杂。但是,总体而言,我的程序的结构非常简单清晰:通过递归下降方法读入表达式,然后递归调用各层级的存储结构的求导方法进行递归求导,逻辑清晰、结构简单。上面提到的嵌套形式是源于题目的要求,也并非设计上的漏洞。

基于度量的结构分析:

1、方法复杂度分析

通过上图,我们可以看到方法的控制分支个数都比较均匀,且不是很多。但是一些复杂的方法,包括Afactor.derivation(),Bfactor.derivation(),Poly.out(),Poly.dif(),Expression.factor(),它们的认知复杂度和圈复杂度都比较高,以最突出的Expression.factor()为例,它为了成功读入各种类型的因子并判断格式是否错误使用了大量的条件语句,不仅使得代码不够清晰,还大大加深了认知复杂度和圈复杂度,使得代码的维护和在本地的测试都难以进行,程序维护成本增高,可测性降低。

2、类复杂度分析

根据上图可知,多个类的操作复杂度和加权复杂度都出现了过于复杂的问题。

FactorFactory操作复杂度高的原因在于它通过多个case语句创建了所有可能出现的类。

Expression等的加权复杂度高原因在于含有复杂度较高的方法,如Expression.factor(),从而拉高了加权复杂度。
3、代码整体规模分析

包括Poly、Expression、Adactor、Bfactor在内的类由于集中承担了多个核心任务并且含有复杂度很高的方法而导致代码比较臃肿,应该进一步剥离功能,建立新的类来完成相应的功能,类和方法的封装还是存在问题。

DMS(依赖结构矩阵)分析:

·第一次作业:

第一次作业的依赖关系如图所示,是一条斜向下的直线,说明第一次作业的各个类之间的依赖关系是自顶而下的线性调用。这正是第一次作业的按层次分类封装导致的结果。

·第二次作业

第二次类的数量和类之间的耦合程度明显增高。最为突出的是Expression类,它对几乎所有类都有依赖关系,原因在于在递归下降分析中,我在分析出读入的信息后,直接在Expression分析分过程中直接实例化了各种需要用到的类,才导致Expression类耦合度很高。在实例化的过程中,虽然定义了抽象数据类型factor,但是在利用工厂模式将它们制造出后,紧接着又进行了强制类型转换,导致factor的存在失去了意义。应当在需要用到某个类的特有的属性的时候再进行强制类型转化,而不是用工厂模式创建后马上再强制类型转换。

与此同时,Poly类的那一列出现了上三角部分的耦合数据,出现这个现象的原因在于表达式因子的存在导致了Poly对原本上层的Polylist的依赖,最终导致了这种现象的产生。

而Poly对Indexs的严重依赖在于Poly的产生、存储、求导、输出几大方法都用到了Indexs,最终导致耦合度很大。

·第三次作业

第三次的DMS图和第二次的DMS图相比,其变化在于整体耦合度的增加和上三角部分耦合度出现的数量的增加。

变化最大的Afactor和Bfactor在第三次作业中耦合度陡增,在于三角函数中可以嵌套所有种类的因子,因此在创建、求导、输出的过程中对其他因子有着大量的依赖,才导致了这种结果。

与此同时,三角函数中的嵌套因子的存在使得它在求导的结果变成了Poly,所以导致了它们对上层的Poly的调用,导致上三角出现了新的耦合度方格。

二、bug分析

在进行第二次作业的时候,由于改变了架构,花费了大量时间在递归下降法的使用上,而当时对它的使用还不够熟悉,同时又想兼顾正确性和优化,因此,在不太熟悉的情况下,将0项直接省略,导致了表达式输出中可能出现缺少某个因子的形式。
比如3*0+x 会错误输出为 3*+x,导致错误输出。(3+0) 会变成 (3+)导致错误。
1与-1也会出现类似的缺失问题。
这些bug都出现在.out()方法、和main()方法内的优化输出的部分。
同时,这种优化并没能将含0项整个省略,而只是省略了0本身,并不能提高多少性能,这一点我在第三次作业的项的输出中,通过对0因子的判断来决定是否需要输出这一项,达到了对含0项省略的效果。

上面两个图中,第一个图是各方法圈复杂度的分布,第二个图是各方法代码行数的分布。可以看到,出现bug的.out()类和main类所占的圈复杂度比例和代码行数比例相对于未出现bug的方法而言都比较高,这也导致了这几个方法的可测性不好,在本地测试时没能成功找到bug;而在出现bug后,对这几个方法的修复也因为方法复杂度较高而耗费了我大量的精力。

三、发现别人bug的策略

在互测的阶段,我发现别人的bug主要通过两种手段:

·利用自动测评机,自己构造符合输入要求的测试数据,通过大量的对比测试来找到含有bug的代码。

优点:省时省力,一次搭建,次次可用。每次只需要改变一下测试数据的构造方式,即可沿用测评机继续测试其他作业的程序。

缺点:缺乏针对性。能否发现bug完全依赖于生成数据的函数的强度和覆盖能力,但是直接根据正则表达式很难生成高强度的测试数据,而且数据有盲目性,无法针对性地找到bug,heck的可能都是同质bug。

·在利用自动测评机发现bug后,针对性地研读出问题的程序的代码,有针对性地分析其bug所在。在帮助其修复这个bug后,继续测试,再去寻找新的不同类型的bug,防止反复heck同质bug。

优点:可以针对性地构造数据,精准heck代码中存在的不同种类的bug,同时可以在阅读的过程中学习好的方法,以及警戒一些错误的、不好的做法,增长自己的积累。

缺点:需要消耗大量的时间和精力,而且不能够保证一定可以发现bug,很可能无所收获。

四、重构经历总结

在第二次作业的时候进行了重构。重构的原因主要取决于一下的方面:

·因子种类变多,引入工厂模式可以优化结构。

·输入的表达式变得复杂,原来的拆分方式难以条理清晰地解决问题。

·为了第三次作业的格式检查做铺垫,递归下降法不仅可以用于处理输入,还可以处理个格式检查。而且个时间差的实现总归要使用递归下降或者类似的方法进行。

根据UML类图的对比,我们可以看到,经过这次重构,程序的结构层次由原来的线性结构,变成了现在的更加复杂的结构,同时,由于工厂模式和factor接口的引入,使得程序的DIT增加,程序的抽象程序结构更加复杂。

五、心得体会

OO第一单元作业虽然已经结束,但是它作为帮助学习者成长的养料,其作用还远远没有结束。

首先,我的第一单元的代码存在以下的问题:

·度量上:

一些方法过于冗长,甚至超过了checkstyle的限制程度,导致后两次的作业的checkstyle并未拿到满分。

同时,这些本身复杂度很高的方法也会导致其所在的类有着较高的加权复杂度,使得所在类代码的可维护性和可测性都变差。

·一些类和方法之间的耦合度较高,难以修改。

比如我的求导法则,并没有将它抽象成一个操作,用一个统一的进行管理,而是根据不同的因子、项、表达式等类来分别书写,方法和类耦合度高,难以修改。

·对于优化的考虑过于简单,一些更有效的操作如项之间的合并操作,三角函数的化简操作并未能实现,只实现了一些最近基本的优化。

·OO的利器——封装、继承、多态的运用还不够熟练,对类的封装,类之间的继承,多态的运用都还不够精准,也不够熟练。很多操作的实现都是通过

通过第一单元的训练,我的许多能力也得到了提升:

·我对面向对象的思想有了更加直接更加深刻的认识。

·我学会了自己搭建测评机,自己构造测试数据来实现自动化测试。

·我在互测中锻炼了自己阅读代码的能力,同时也学习到了很多好的设计思路和技巧。

每次作业的发布、完成与修改,总结,都伴随着迎难而上的磨砺和逐渐进步的欣喜。会在看到互测屋中其他同学优化得很好的代码时感叹自己代码好烂,但是又会在通过所有强测点也并没有被人heck时流露出一丝欣喜。而在最薄弱的优化上,我也在每次作业中缓慢进步,正所谓“进一寸有一寸的欢喜”,我一定不会树立畏难思想,而是主动迎难而上,在一次次作业的打磨中不断进步,收获欢喜。

THE END\0

发表评论

0/200
245 点赞
0 评论
收藏