面向对象编程总结——表达式求导

这次的面向对象编程作业的单元主要分成三个部分,三次作业程序的复杂度逐步加深,下面将分三个部分对这个单元的编程实践进行总结。为了文章的标述方便,统一如下标述:1.x*sin(x)即只用乘号连接的式子是Term(项),项内不可分的为因子(factor)

第一次作业

实验的要求:实现只有幂函数的表达式的求导。每一项至多只有 n*x^k类型的项,所以情况相对比较简单。整体程序的类图如下:

整个程序除了实现了一个入口类Main,而主要完成功能的就只有两个函数,一个是start,用来读入并且保存表达式,另一个是derivative,用来求导并且直接输出。在开始写的时候并没有仔细考虑后续的拓展的问题。没有仔细对功能进行分类。导致方法的复杂度比较高,可读性也很差。

采用IDEA中的插件对程序的复杂度和依赖关系进行分析发现:

 

圈复杂度(Cyclomatic complexity)

是一种代码复杂度的衡量标准,在1976年由Thomas J. McCabe, Sr. 提出。主要说明了程序的分支数量,数值越大,说明程序的分支越多,程序的质量越低。因为分支多的话会导致测试需要覆盖到的地方越多。

Essential Complexity (ev(G))基本复杂度:
基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
Module Design Complexity (iv(G))模块设计复杂度:
模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。(以上节选自 百度百科
通过这个分析工具可以很明显看出第一次的代码非常垃圾。。相互耦合程度高,而且逻辑不精练。

Bug修复

 在强测中发现了一处出现了bug,主要原因是匹配每个表达式项的时候忘记了开头空格的情况。

第二次作业

要求在第一次的基础上增加了对三角函数(sin(x),cos(x))的支持,同时要支持连乘。这次的作业相对比第一次来说复杂了一些,我设计了如下的类结构来完成这个问题。

其中Term对象中有一个四位的数组,保存这个项的系数以及各个项的指数。为了方便查找,实现了HashKey的类用来在Expression中实现对Term的索引。主要的运算工作有Expression和Term完成,而Inputprocess则是用于分析输入是否合法并且将分析结果输入到expression保存起来。求导操作和输出实现了分离。

对程序进行复杂度分析:

Term中的print方法复杂度非常高。。因为为了尽可能缩短表达式的复杂程度,需要处理的特殊情况比较多(有因子是0,系数是+1或-1,x的指数是1,是0等等)这里处理的不太好,应该再这个函数再将逻辑简化一些,再将其中的公共的功能部分提取出来。

InputProcess.start()是输入处理的部分,Expression.simple()是实现表达式化简的部分,都是逻辑相对比较复杂的部分,所以设计复杂度比较高。这里的主要问题是没有将子问题划分到位,导致写着写着就发现程序要的情况爆炸增长,以后应该注意在动手写程序的时候尽可能地将需要的部分划分到比较小的部分。

类间依赖矩阵:

主要问题是Expression和term之间的循环依赖问题。主要的来源是:为了在求导和存入的时候方便,对Expression的插入和延长可以使用term对象,同时由于每个项的求导是存在多项的加和情况,所以求导结果使用了expression来保存。这样就导致了两个类之间存在了循环依赖的问题。目前没有想到很好的方式去规避。

Bug修复:

在中测和强测中都没有出现bug,在自和同学的自测数据中也没有找到bug,此处略去。

程序的问题:

对于factor的可拓展性非常差(因为在term中并没有具体实现factor的对象而是取巧使用了一个数组保存值),导致第三次需要支持嵌套的时候程序只能重写。在输入处理中的InputProcess中逻辑分析的不清晰。

第三次作业

在第二次作业的基础之上实现了嵌套的功能。即需要支持类似sin(cos(x))的式子。

主要思路是采用编译器的结构,分为词法分析和语法分析两个部分。语法分析将表达式分解成表达式树。求导就是对表达式树进行一次遍历,对每个节点进行一次求导操作,并且在上层节点进行一次合并的操作。

因为factor有x,sin(?),cos(?),(?),数字等几种形式,所以实现了一个factor的结构,来统一处理不同的factor。

类图:

类图突然变得非常复杂主要原因是因为为了支持更加复杂的表达式,导致的嵌套,出现了factor-term-expression-Expresionfactor-factor这样的依赖循环。如果简单点来开是长下面这样。

类的功能说明:

Expresson:保存一个表达式,内部维护一个term 的列表

Term:保存一个项,内部维护一个factor的arraylist

Factor:是Sin ,Cos,Num,ExpressionFactor的对外接口

Kit:程序总需要使用的工具,错误处理和输出结果保存

Main:入口类

Sym:词法分析器,返回一个tocken的列表

Tocken:保存词法分析结果。

程序复杂度分析

只有少数的方法复杂度比较高:都是对输入进行处理的函数,情况比较多。在前期调试的时候Sym.sym(String),Expression.setup(Sym)是bug经常出现的地方。

类间的依赖分析同样发现了项因子表达式之间的相互依赖关系,这同样是类图复杂度爆炸的主要原因。这个思路是受之前的编译器大作业的影响。

存在的问题:

1.因子部分可以完善一下继承的关系:

ExpressionFactor-->trigonometric-->sin

               -->cos 

num

power

因为sin中是一个ExpressionFactor,sin和cos有很多相似的功能可以提炼出一个父类。

2.合并做的不好,可以使用递归的方式去比较两个表达式的值是不是相同。这样就不会有x+x求导得1+1的结果出现。

bug修复

在强测中测和自我测试中没有发现bug。

但是在前期测试中发现了一个规律,一定要将功能进行更好的分类,减少冗余和复杂条件,因为这样的代码越多bug出现的概率就越大。






猜你喜欢

转载自www.cnblogs.com/chenjinyu/p/10593943.html