前言
OO的第一单元作业,是以表达式求导为主题的Java编程作业,其中涉及到不少面向对象的知识。本单元是整个课程的基础,但是由于个人种种原因,没有很好的完成作业,因此本文既是一篇总结,也是一份反思。
一、基于度量的对自己程序结构的分析
首先贴出本次代码的UML类图及方法复杂度、类复杂度等。由于三次作业的代码结构主要都是以前一次为基础进行迭代,故以下以第三次作业为例进行展示。
UML类图:
方法复杂度:
类复杂度: 输入预处理类(单独列出):
在构思本次作业时,我主要采用了层次化构造的思路。自底而上,分别是因子、项、表达式。因子对应的Factor类,拥有底数、指数等属性,用以描述因子的组成;上一层的项,对应Term类,本质上是一个因子的列表,利用了项是“由乘法运算符连接若干任意因子组成”的这一性质;而最顶层的,则是完整的表达式,对应Expression类,其本质是项的列表,利用了表达式是“由加法和减法运算符连接若干项组成”的这一性质。
同时,为了实现求导操作,创建了DifferentiationFactory类,可以对项及因子进行求导操作。其中对项的求导,实质上是通过分解并提取项中的因子,并进行递归调用等操作实现的。除此之外,我还创建了一个针对输入的预处理类InputPreprocessor,用于将输入的字符串转化为能够实现求导等操作的项对象、因子对象。
这样的结构较为简单,思路较易缕清。但因此也带来了功能的耦合,由于没有对不同的函数分别建立类对象,因此在每次要进行求导时都要先判断其类型,并作相应的转换,不难很好地体现面向对象的思想。同时正是因为贪图结构上的简单,也给我带来了不少bug。
二、程序bug分析
以下按bug产生的原因进行总结。
原因1:未考虑边界条件。这种对于极限的考量是我长期以来所欠缺的,测试时仅仅关注最为普通的测试样例,对一些特殊的情况并未加以考虑。在原先的编码思路中,如果某个项的求导为零,那么从最终表达式能够得到化简的角度出发,我将这样的项略去。但是我并未考虑到如果最终整个表达式求导的结果就是0的情况,此时应该输出一个0;而此前的设计则仅会输出一个空串。
原因2:对指导书的理解偏差。对于项之前的符号,按照最初的理解,最多仅能有两个;但在未通过测试并参考讨论区同学的意见后,我发现项之前其实最多可以带三个符号。因此我调整了输入预处理类中对应的匹配方法,对这种情况进行适应。
原因3:对递归函数理解不清。在对项进行求导的过程中,最初采用的返回值类型是因子类型,用以直接加入到最后的结果项中,并且设置了一个全局变量,最终以该全局变量的值为结果;但实际上在函数的递归调用过程中,其对全局变量进行了多次修改,产生了意想不到的结果,因而产生了错误。因此我将该函数的返回值类型改为项类,并删去了全局变量,从而得到正确的求导结果。
三、发现他人bug的策略
我主要是对初次提交未考虑到的边界情况,构造常数、空串、多前缀符号项等样例用以测试,发现也有部分同学对边界考虑不周,导致了类似的bug。在阅读同学代码的过程中,我发现了与总结课上老师指出的一样的问题。有的同学代码逻辑比较混乱,用边界数据进行测试果然出了问题;有的同学层次分得较为清晰,在多个关键点辅以相应的注释,这样的代码则较易读懂,因此较难产生bug。这也启发我要捋清代码逻辑,先构思好结构后再动手编码,可以有效减少bug的产生。
四、对象创建模式
本次作业中,我虽然并未使用接口、继承等使代码更具结构化的方式,但也尝试使用工厂模式进行求导。主要的思路是将求导的逻辑封装在一个类中,调用者只需传入某个项,就能自动实现相应的求导并返回求导后的结果。在之后的作业中我要更加重视对象间的逻辑结构关系,合理利用Java提供的接口、继承等特性,力图写出风格优美、功能完备的代码。
五、对比和心得体会
翻阅优秀代码,不禁惊叹于同学们精巧的思路,以及对Java语言提供的接口容器的灵活应用。在我的代码中,主要还是采用了ArrayList这样的简单列表结构,但实际上还可以采用Map等结构,实现用更简单清晰的逻辑实现预定功能。因此我也要拓展自己的视野,多看看网上的技术博客,以及多关注讨论区同学的经验分享,力图在向他人学习、自我学习的过程中逐步提升自我。
在OO的第一单元,我发现了自己存在的诸多不足;而且由于当前的特殊时期,我的学习方式也发生了极大的转变。但面对第二单元的挑战,我将迎难而上,不破不还。