BUAA_OO第一单元总结

七仔^_^ 学生 2024-03-21 21:06:57

OO第一单元总结

基于度量对程序结构的分析

  • UML类图

img

设计遵从递归下降的思想,Expr->Term->Factor->...,构建出了表达式树Expr后传入静态方法TransPoly,将表达式树转化成多项式Polynomial。Mono代表单项式,是多项式的一部分。

自我评价:
优点:类的建构比较清晰地体现了递归下降的思想,有比较强的可迭代性和内聚性,后续若要新增可以直接修改Mono类和新建新的因子。
缺点:部分类的复杂度较高,递归调用较多,代码量较冗余。

  • 类的规模与总代码规模

img

这张图显示了每个类中的代码行数和代码规模
总代码行数:1250
源代码行数:1018

  • 类复杂度分析

img

可以发现我的Definer、Lexer、Polynomial类的复杂度较高,这是因为Definer是处理自定义函数的,涉及到了递归调用,Lexer是解析输入字符的,内部有多重if-else,Polynomial是多项式类,内部与Mono类的方法有很多互相递归调用,导致复杂度偏高。

OCavg(Average opearation complexity):平均操作复杂度
OCmax(Maximum operation complexity):最大操作复杂度
WMC(Weighted method complexity):加权方法复杂度

  • 方法复杂度分析

img

​CogC:圈复杂度,表示程序中的独立路径数目。
ev(G):本质复杂度,表示程序中必须要有的控制流程数目。即,如果减少程序中任何一条路径,则程序将不完整。
​Iv(G):内在复杂度,程序本质上的复杂度。表示程序在没有框架、库、编译器等支持时的复杂度。
v(G):程序体积,表示程序中的独立语句数目。

可以发现复杂度较高的方法有:

  • Lexer.Lexer 这个方法采用了多个if-else来判断输入字符的类型,因此结构比较较复杂
  • Mono.isSingle 这个方法用于判断单项式输出是否需要加括号(在exp内部的话),所以有许多if-else存在,导致复杂度较高
  • Mono.toString... 这类方法主要是为了分多种情况讨论toString的重写方法,里面不可避免地有许多的if-else。
  • Polynomial.toString 该方法涉及到递归调用Mono中的toString方法,导致复杂度较高

综上所述,复杂度较高的方法一般都是if-else层数较多或者设计较深的递归调用或者两个方法之间的相互调用。

架构设计体验

三次作业简述:

  1. 读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的单变量表达式,输出恒等变形展开所有括号后的表达式。

    • 第一次作业关键在于递归下降和确定基本项的思路。递归下降负责分析表达式中的结构,基本项思路负责去括号和化简。

      递归下降理解:将一个表达式逐渐向低级形式分解,根据加减分出项,根据乘法分出因子,因子又下分为表达式因子,常数项因子,变量因子。

    • 由于笔者在第一次作业时没有完全理解基本项的思路,于是没有单独建一个Mono(基本项)类。而是在Poly(多项式)类中用HashMap<Integer, Biginteger>来存储每个基本项,其中Integer代表x的幂次,BigInteger代表系数。并且在Poly类中提供了多项式加减乘的多种合并去括号化简的方法。

  2. 读入一系列自定义函数的定义以及一个包含幂函数、指数函数、自定义函数调用的表达式,输出恒等变形展开所有括号后的表达式。
    第二次作业新增了嵌套括号,自定义函数和exp指数函数。

    • 嵌套括号: 只需要递归下降时把幂次加入到因子中就可以解决嵌套括号。第一次作业我采用预处理解决,此时难以继续使用。故我将幂次加入到Expr,多个Factor类中,在最后的多项式化简过程再调用Poly类中的加减乘法转化成多项式。
    • 自定义函数:我参考了往届学长Hyggge的思路,新建了Definer类用于解析自定义函数,并新建了FuncFactor封装函数因子
    • exp指数函数:这部分只需要改变基本项的组成。基本项为(BigInteger coe, BigInteger index, Poly poly) 分别代表系数,x的指数,exp的指数(用多项式表示)
  3. 读入一系列自定义函数的定义以及一个包含幂函数、指数函数、自定义函数调用、求导算子的表达式,输出恒等变形展开所有括号后的表达式。

    • 本次作业相对于前两次修改较少。我新增了求导因子(DerFactor),并且在Poly类中新增求导(derive)方法。

迭代场景

新添加三角函数因子sin,cos。
只需要新建一个三角函数因子,然后重写Mono的内部架构,改写Poly类中的加减乘乘方的方法即可。

程序bug分析

在第二次作业中,我遇到了很多bug,发现很多都来自于深克隆和自定义函数转字符串处理的细节。

深浅克隆问题

第二次作业,我对深浅克隆的认识太浅,以至于写的clone方法我以为是深克隆,但本质上却只是“部分深克隆”。比如:

M m = new M(this. , this. , this. );
return m;

这个就是典型的不稳定的深克隆,如果this.的内容都是Integer类或者String,那么这个m就是一个地址与原来地址完全不同的对象。如果this.的内容是自己定义的一个类的实例化(如this.mono),那么这个m和原来的对象的mono还是一个地址,仅仅实现了Integer和String基本类的深克隆,而自定义的类不会深克隆,故我称之为“部分深克隆”。

new方法只能深克隆Integer和String等基本类,而对自定义类没能力深克隆

所以要完全新建一个自定义类的实例,然后再用new方法。

自定义函数转字符串处理问题

我的自定义函数转字符串方法依托于每个因子重写的toString方法。所以toString的写法非常关键。而我忽略了表达式因子的括号,导致后续解析中报了许多问题。

向后移位的细节

第三次代码的问题在于解析求导因子的时候没有及时让词解析器lexer向后移一位。

分析他人bug的策略

本人分析他人bug采用的是同学开源的评测机,通过分析出错误的数据,从而判断该同学的错误。

优化

我没有过多地考虑优化的问题,仅仅考虑了以下三点:

  1. 在Poly中的toString方法将符号为正的数放到表达式开头输出
  2. 通过多层if判断省略1*,()^0,exp(0),这个判断语句比较复杂,需要细心编写。最后将"-1*"全部变成"-"。
  3. 在Mono类中新增方法isSingle,用于判断exp中的因子是否需要括号,从而减少了不必要的括号输出。

我的优化基本保持了原有架构不变,仅仅是改变了输出逻辑和输出后的字符串处理,所以不影响简洁性和正确性。但是由于笔者第一单元进行的比较艰难,对于优化的思考比较粗浅,算是一个遗憾吧。

心得体会

在OO的第一单元,我深刻体会到了OO课程的强度,难忘第二次作业连写几天代码最后快到ddl发现代码的结构性问题,最后连夜重构还是以中测失败告终。 但是OO确实磨练了我的心智和时间规划能力,也让我清楚认识到敲键盘之前需要用手好好推演一下代码的架构是否可行。
第一单元的作业让我认识到:

  1. 高内聚低耦合的代码写法的重要性,让方法的复用和扩展性更强。
  2. 注意测试的全面性,不要忽视手搓数据的重要性。
  3. 提前开始构思编写,遇到重构时回旋余地会大一些。
  4. 多多关注讨论区,讨论区有很多人的思路很有启发性和深度。
  5. 考虑设计不可变变量,从源头上避免深浅克隆问题。

未来方向

也许可以给予一些视频层面上对于递归下降的讲解,可能会对初学者更为友好。
除此之外我觉得OO课程组的第一单元制作的十分优秀,节奏较难但可以跟得上。

...全文
56 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧