2021_BUAAOO_第一单元总结

第一单元总结

一、程序结构

第一次作业

思路

第一次作业要求实现简单多项式求导,相对来说比较简单。根据数学上的知识,多项式可以分解为若干个单项式的和,所以我自然地就建立了两层结构,即:表达式(代表多项式)和项(代表单项式),类图如下:
image
Item类主要存储每一个项的系数和指数,而Expression则用一个ArrayList存储项,多个项表示相加。当从Main得到表达式的时候,先传入Expression分割为一个个项的字符串,然后传入Item,解析为一个个项。求导的时候只需要对每个项单独求导并对Expression类中ArrayList的相应位置进行替换即可。各个类之间的关系是线性的,比较简单。

度量分析

类复杂度分析

image

方法复杂度分析

image

ev(G)是基本复杂度,表示程序的非结构化程度,也就是模块化的程度;iv(G)是模块设计复杂度,衡量模块之间的耦合程度;v(G)是圈复杂度,衡量模块判定结构的复杂度。知道了基本参数的意义之后可以看出,我的第一次作业的方法的结构化程度较好,大部分方法的耦合度和判定结构的复杂度比较低,但Expression和Item的构造方法以及toString方法的耦合度和判定结构的复杂度都在一个比较高的水平。

第二次作业

思路

由于这次加入了三角函数因子和表达式因子,于是我在上一次项这个层面的基础上又向下抽象出了一层:因子。因子包括幂函数因子、三角函数因子和表达式因子,而这些具体的因子共有的属性是指数,共有的方法是求导,于是我创建了一个Factor类作为父类,PowerFactor(指数因子)、SinFacotr(正弦因子)、CosFactor(余弦因子)、ExpressionFacotr(表达式因子)都继承自Facotr类。考虑到表达式因子的特殊性以及第三次作业可能遇到的嵌套求导问题,我给每个因子都增加了一个Expression属性(就是最顶层的那个表达式类)用来表示它们内部嵌套的表达式,这样在构建顶层表达式的时候就会一层一层往下递归,直到遇到最简单的因子(常数因子和幂函数因子)为止。在Item(项)的层面,考虑到效率的问题,我选择了用HashMap来存储各个因子,于是我就构造出了相应的FactorKey类来作为HashMap的键。而在Expression(表达式)的层面,我同样采用了HashMap来存储各个项,于是也就有了相应的ItemKey。采用这种递归的结构带来的显而易见的好处是能够很好地逐层解析带有多层括号的表达式,采用HashMap则使得合并同类项变得比较容易,但是这也带来了结构复杂度急剧升高的缺点,程序的可扩展性不强。
image

度量分析

类复杂度分析

image

方法复杂度分析

image

由于方法过多,此处仅截取复杂度较高的方法。从度量数据可以看出,相较于第一次作业,模块化的程度保持稳定,但方法之间的耦合度以及方法判定结构的复杂度有了极大的上升,表明我为了完成需求降低了不少代码质量。

第三次作业

思路

第三次作业加入了三角函数因子内部的嵌套以及对表达式合法性的检查。由于我在第二次作业时对第三次可能的需求做了较好的预测,因此我的程序主体改动并不大,只是稍微修改了一下三角函数因子内部的求导方式,其它地方基本没有改动。但是为了满足解析表达式因子的要求,我在Cut类中新增了几个方法用于辅助解析。由于Cut类里面的方法比较面向过程,因此给程序的复杂方面带来了一些负面影响。同时为了满足表达式合法性的检查,我新创建了一个Check类,利用递归下降分析法判断表达式因子的合法性,如果合法才会传给我的Expression类进行解析。

image

度量分析

类复杂度分析

image

方法复杂度分析

image

由于方法过多,此处只截取复杂度较高的方法。相较于第二次作业,方法的模块化程度下降了,方法之间的耦合度进一步上升,方法判定结构的复杂度也有一定程度的上升。

二、bug分析

第一次作业

由于结构较为简单,基本没有出现bug。

第二次作业

出现的bug主要是忽略了对于Null的考虑。具体说来就是我把最底层的因子--幂函数因子的表达式属性设为了Null,但在后续操作过程中没有对Null做出特殊处理而把它当做了一个普通的属性来调用,从而产生了不少的NullException。为了解决这个bug,我加入了不少if判定是不是Null,虽然解决了bug,但进一步增加了程序的复杂性,也让程序更加不优雅了。

第三次作业

主要bug都是关于格式方面的。

一个是在Check类中,由于对递归下降分析法的理解不够好,导致在判定表达式正确性的时候出现问题。例如x ** 2,由于x和**中间有一个空格,我的Check就会把x当做一个单独的因子而结束幂函数层面的递归,导致后续的bug。解决方法是修改递归下降的过程,让其能够正确地解析。

另一个bug是输出表达式的格式。由于我在三角函数因子中用Expression来表示其内部的嵌套,但是指导书要求在三角函数内部嵌套的是因子,因此我输出的结果中三角函数因子的括号内部可能缺少一个括号,从而导致错误。修改的方式是在Cut类中加入了isNormalFacotr方法,对表达式进行判定,判定其是否符合因子的标准,然后做出相应修改。但是由于isNormalFacotr的逻辑不够完善,依旧存在问题,因此在互测中仍被测出了bug。

三、互测策略

由于直接去读别人的代码实在是太累了,于是我采取了利用评测机进行黑盒测试的方法找出其他人的bug。我搭建评测机的方法是:根据指导书上的表达式生成式,逐层生成因子、项、表达式,并在每一步中都保持比较高的随机性。这样的话,当生成的测试样例足够多的时候,就会对几乎所有的情况都完成覆盖。利用python的subprocess模块,将每次的随机生成的表达式作为输入,将求表达式导数的JAVA程序的输出结果与python的sympy模块的求导结果进行比较,从而判定JAVA程序运行结果是否正确。利用这种方法搭建的评测机,我在第一次作业和第二次作业中分别找出了一位同学的bug,在第三次作业我也找出了一位同学的bug,但是由于对互测阶段测试数据格式的理解存在偏差,导致我的hack数据x**000无法被提交通过,因此第三次一无所获。从互测后公布的结果来看,我的评测机能够找出70%左右的bug,这主要是因为我的评测机主要是为了检验我的程序的正确性而设计的,因此对于形式验证可以解决的bug没有考虑(大数越界bug)。同时我的评测机忽略了对于JAVA程序输出结果格式的检查,这也导致了我在第三次互测中被hack了一次。

四、重构经历总结

我的重构操作发生在第二次作业,目的并不是为了是代码更加简洁优雅,而是为了能够解析多层括号嵌套的表达式

image

image
从代码量上来看,我第一次作业的代码在240行左右,而我在第二次作业重构了这一部分代码之后代码量上升到了接近500行,而当作业而完成之后更是接近1000行。从类图上看(在第一部分已展示),第二次我的代码结构复杂程度和耦合程度相较于第一次有了极大的提升。因此我认为我的这次重构只是一个半成品--只追求了正确性而忽略了代码结构和复杂度的管理。

五、心得体会

经过了这一单元的学习,我开始了由面向过程的思维到面向对象的思维的转变,不过这种转变还不够彻底,当遇到一些问题的时候我还是会第一时间想到应该怎么用面向过程的方法去处理。但让我感到高兴的是,至少从总体上来看,我的架构是一个面向对象的架构,只是在一些局部的问题处理上可能会显得面向过程。

这一单元我最大的感受就是架构的重要性。只有设计出一个好的架构,才能较好地控制程序的规模和复杂程度。好的架构也会让我们在实现一些功能的时候显得更加容易、更加优雅。我在第二次作业的时候试图使用归一化处理,但是只实现了一半,因为表达式因子与其它的因子显得格格不入。现在想来,如果我在架构设计上能够给予表达式因子一些特殊的地位,那我就没有必要多出很多的if判断语句了,这也会让我的递归实现地更加清晰,降低debug时的难度。

综上所述,我今后的主要目标是在实现功能的前提下优化架构的合理性,降低开发、调试和维护的难度。

posted @ 2021-03-27 10:45  康bao  阅读(83)  评论(0编辑  收藏  举报