前言
这三次的作业对我个人来说难度较大,主要是因为我还对 Java 语言以及面向对象的编程思想不太熟悉。
在刚开始编写程序的过程中,我大多是采用了面向过程的想法,在后来的作业中才逐渐理解到面向对象编程的基本思想以及面向对象的好处。在这一单元的测试里,虽然第一次和第三次作业均无 bug,但第二次作业的强测中我因为疏忽大意,没有充分测试,导致程序有 2 个 bug 没有被及时发现,挂掉了很多点,这也是我在第一单元中感到十分遗憾的地方。
下面我将介绍我在第一单元中的设计以及心得体会。
一. 作业架构分析
1. 第一次作业
1.1 类图以及设计思路分析:
设计思路:读入字符串 -> 处理字符串 -> 递归下降分析表达式 -> 去除括号 -> 化简合并 -> 输出结果
在处理字符串这一步,我去掉了字符串中的空格、制表符,并将 ** 替换为 ^,方便后续处理。
处理字符串后,我采用递归下降的方式解析整个表达式,通过链表建立起相应的数据结构。
在去除括号这一步,其实目的就是去除表达式中的表达式因子,只要表达式中还存在表达式因子,就将其对应的项去除,并产生新的项。
在化简合并中,我先将所有项中的相同变量因子的次方进行合并,再调整项中因子的顺序,将常数因子放在项链表的前面,然后对常数因子进行相乘合并,最后再去除表达式中多余的部分,从而化简整个表达式,我将这个任务交给了 Simplify, MergeExpr 这两个类。
优点:
思路清晰,建立了比较清楚的结构,每一部分都有自己需要做的事情。
缺点:
我将化简合并的任务全都交给了两个类去实现,这样就会导致这两个类的复杂度过高,这样复杂的一个任务应该化整为零,分别交给对应的类去实现对应的方法,否则会导致代码过于臃肿,主要的码量都堆积在这两个类中。
1.2 类复杂度分析:
1.3 方法复杂度分析:
2. 第二次作业
2.1 类图以及扩展分析
第二次作业相比第一次难度有了很大的提升。
我考虑新建了自定义函数因子类和三角函数因子类,并进行了相应的优化。
由于加入了三角函数因子后,想要合并同类项或进行其他优化操作,都必须考虑如何判断两个三角函数因子内的表达式是否相同,为了解决这个问题,我设计了项之间的比较方法,即按照项 toString 后得到字符串的字典序大小进行比较,由此再对表达式中的项进行排序,经过排序后的表达式再进行 toString 后进行比较,即可判断两个表达式是否相同。
在第一次作业的基础上,额外实现的优化还有 sin^2 + cos^2 = 1。
除此之外,为了降低 Parse 类中 parseFactor() 方法的复杂度,我考虑为每一种 Factor 设计了相应的解析方法;我又将 Simplify 和 MergeExpr 进行了封装,并将这两个类的方法都设为了静态方法,将这两个类作为工具类,便于调用。
优点:
整体代码逻辑上比较清晰,各部分处理相互独立,在 debug 过程中可以设置多处输出,在哪里出了问题可以很方便地进行定位
缺点:
Simpilfy 和 MergeExpr 这两个类复杂度过高,延续了第一次作业的设计,由于考虑到重构的代价比较高,而且这两个类其实功能十分独立,类似于 Java 库中的 Math 工具类,用起来其实也比较方便,所以并没有进行重构
2.2 类复杂度分析
2.3 方法复杂度分析
3. 第三次作业
3.1 类图
在第三次作业中,新增了导数因子以及静态工具类 Derivation 用于对导数因子进行求导。
Derivation 的实现类似于 Parse, 采用递归下降的方法对求导因子内的表达式求导:
对表达式求导的过程是对表达式中的每个项求导,对项求导的过程可以拆分为对因子求导,再给出对因子求导的方法即可。
优点:
对第二次作业的原来的代码几乎没有没有改动,只是新添了两个类,所以第三周作业其实工作量很小,花费的时间比较短。从迭代的角度上来说,还是比较成功的。
缺点:
Derivation 类作为求导工具类,复杂度过高。应该为每种结构因子写出相应的求导方法,从而简化求导操作,减小代码复杂度。
3.2 类复杂度分析
3.3 方法复杂度分析
二. bug 分析
本单元的 bug 只出现在了第二次作业中
对第二次作业中的 bug 分析如下 :
1. bug 出现的原因:
在将自定义函数的形参替换为实参的过程中,我采用了将实参(其实就是因子)进行 toString 后替换的方法,但是对各种因子的 toString 方法并没有进行规范化,在输出符号问题上出现了 bug
2. bug 修复的方法:
我首先完善了因子的 toString 方法,之后又将所有的实参视为了表达式,将其作为表达式因子进行 toString 后,再对形参进行整体替换,这样保证了结果的正确性
三. 心得体会
- 最开始的架构至关重要,最初架构的可扩展性好不好直接决定了后续的工作量。
- 对每种方法进行编写时应该秉持一个原则:当想要对某个对象的内容进行修改时,应该严格限制修改的条件,并给出所有可能的情况下的正确操作。这样在后续对程序进行扩展维护的时候,可以减少很多的工作量,而且最重要的一点,不容易出现 bug。
- 对每种方法进行编写时应该秉持一个原则:当想要对某个对象的内容进行修改时,应该严格限制修改的条件,并给出所有可能的情况下的正确操作。这样在后续对程序进行扩展维护的时候,可以减少很多的工作量,而且最重要的一点,不容易出现 bug。
- 在测试程序的时候,要尽可能多地考虑特殊情况,尤其是测试各种优化操作,因为优化才是最容易出错的地方。同时注意设计优化时要严格限制优化条件,严格规范优化结果。