一、前言
第一单元作业主要内容为表达式求导,其最终任务是一个支持简单幂函数和简单正余弦函数的导函数及其组合的求解问题。本文将从程序结构分析、bug分析、互测策略、对象创建模式等多个方面进行总结。
二、程序结构分析
第一次作业
总体架构:
Main.java
中进行输入输出处理,Polynomial
管理项Item
。
规模度量与风格探测:
主要问题在于derivate
方法复杂度过高。
类耦合度分析:
第二次作业
总体架构:
SineFunc
、Const
、CosineFunc
、PowerFunc
、XFunc
、Function
均实现Function
接口,其中定义了derivate
和toString
方法,所有基础函数因子由工厂模式Factory
生成,Item
类内部使用ArrayList
管理上述因子,Expression
类内部使用ArrayList
管理Item项,底层逻辑整体结构为二维ArrayList
,此外FormatTest
和InputProcessor
分别做输入格式判断与预处理,为交由工厂做准备。
规模度量与风格探测:
类耦合度分析:
第三次作业
总体架构:
底层逻辑沿用Task 2架构,不同的是将各类基础函数与加法乘法组合看做同一层,均实现Item
接口。
规模度量与风格探测:
可以看到在需求逐渐扩大的第三次作业下,bad smell
的出现次数远高于前两次作业,主要是一些复杂方法和复杂语句未进行更深一步的抽象与分离,复用性不高。
类耦合度分析:
三、程序bug分析
在第一、二次作业的中测、强测、互测中均未出现bug。
在第三次作业的强测中出现1个bug,互测中出现1个bug。
bug 1:
原因简述:
三角函数内部出现“ 符号 空白字符 无符号数 ”式的表达式时不能正确判断WRONG FORMAT。在原有架构中,预处理前使用正则表达式(见FormatParse.java
)仅处理部分Wrong Format情况,在预处理(见InputProcessor.java
)时去掉了全部空白字符,在工厂模式递归生成项(见Factory.java
)时判断是否存在三角函数内部表达式因子缺少外层括号的Wrong Format。这种架构使得上述样例能够通过两层Wrong Format的检验从而导致错误。
样例输入:
cos(- 1)
样例输出:
WRONG FORMAT!
修复方式:
将对于三角函数内部表达式因子缺少外层括号的Wrong Format提前至预处理前进行,在FormatParse.java
的testFormat
方法中新增一个参数isExpression
表示其是否必须为因子,在testFrmat
方法中增加相应的判断,与之对应的在replaceExpressionMulAndTest
方法中增加对应参数isExpression
和参数的生成条件,最后在MainClass.java
中调用replaceExpressionMulAndTest
方法时添加初始参数false
。
bug 2:
原因简述:
输出字符串以'('
开头以')'
结尾时会错误的去掉这两个字符,即使他们不是配对的一对括号。在原有架构中,会对最终的输出字符串做去掉头尾括号的特判操作(见MainClass.java
),在此过程中未对括号进行配对,使得不配对的括号会被去掉从而导致错误。
样例输入:
(sin(x)-x)*sin(1)
样例输出:
(cos(x)-1)*sin(1)
修复方式:
删除了对输出字符串头尾多余括号的特判操作。
针对所出现bug的分析与总结:
bug 1主要原因在于在整个设计、编写与测试过程中没有考虑到这一种特殊情况,属于结构上的漏洞,因此修复起来也较为麻烦。bug 2主要原因在于优化过程中对最后一步输出的特判条件出错,未使用在程序中本多次出现的括号匹配,属于细节上的漏判,修复起来较为容易。
四、互测Hack策略
在互测中,主要对程序进行覆盖性和特殊性测试。
在覆盖性测试部分,使用正则表达式自动化构造测试样例并使用sympy
进行自动化测试,对于出现问题的样例点进行保证能重现错误的化简,构建出其最简形式,以期找到其bug的出现原因。在这一步中,挑选出错误较多的几份程序作为特殊性测试的重点关注对象。(下图为一份自动生成的50个样例点数据)
在特殊性测试部分,使用对拍器对所有程序同时进行测试。首先使用自己设计与测试阶段所使用过的或自己出现过问题的样例进行测试,其次使用边界条件下的边界样例进行测试,在有条件的情况下阅读覆盖性测试中问题较多的程序并针对性构造样例,也会使用其他人提供的在其他Room中出现问题的样例进行测试。(下图为对拍器效果图)
从互测结束后Room内所有提交的互测样例可以看出,综合覆盖性和特殊性测试的方式,能较为完整的找出所在Room中其他人的大多数bug及其原因,且所花费时间与精力处于可以接受的范围,总体上能够取得较好的效果。
五、对象创建模式
在第一次作业中,仅使用工厂模式创建出简单幂函数对象,使用ArrayList
管理,在第二作业加入乘法组合后,仍然使用工厂模式创建简单三角函数与简单幂函数,使用二维ArrayList
管理因子和项,在这两次作业中均未意识到项的加法组合和乘法组合也是可以通过工厂模式实现的。在第三次作业中,受讨论区和第二次作业互测中看到的大佬代码启发,将基本函数与加法组合、乘法组合同级对待,使用工厂模式递归创建项,将所有项递归交由顶层项管理,取得了较为良好的效果。
本次作业是笔者第一次接触面向对象的思想,主要问题在于第一次作业时没有为将来的迭代开发留出空间,因此在第二次作业进行了完全的重构,在考虑第三次作业需要的嵌套规则后建立了架构,使得第三次作业能够直接使用第二次作业的底层逻辑,仅改动输入、工厂与优化部分,大大降低了第三次作业的难度。
通过这一个单元的学习与练习,初步认识了面向对象的基本思想,体会到面向对象架构所具有的面向过程所不具备的独特特质,同时充分认识到迭代开发中良好可拓展性和可读性的重要性。