OO第一单元总结 多项式求导
一、基于度量的程序结构分析
1.第一次作业
1.1 UML类图
1.2 代码行数
1.3 代码复杂度
1.4 设计分析
第一次作业和面向对象思想配合的并不是很好。我将整个程序分成三个类:主函数Main、构造正则表达式分析输入的DealRegex类和求导化简输出的Compute类。其中DealRegex存储了构造正则表达式的变量,并读取输入,识别每一项,用Hashmap存储指数和系数。Compute类则有求导方法和输出方法。
总的来说,这次作业中我并没有理解面向对象的真正含义,只是运用面向过程的思维方法,将整个程序拆成几个函数并放到不同的文件中,也没有专门建立管理数据的类,无扩展性,在第二次实验中需要进行重构。并且几个方法在对输入输出进行分析的时候用了过多的条件判断,使得复杂度高。
虽然本次实验输入的数据都是合法的,我在正则表达式的构建中已经加入了对空格的判断,这为后续的迭代提供了帮助。利用“小正则”方式来匹配字符串,不容易出现问题。
2.第二次作业
2.1 UML类图
2.2 代码长度
2.3 代码复杂度
2.4 设计分析
在第二次实验中,随着三角函数加入,我初步运用类来管理数据,但存在以下缺点和不足:
- Main方法太长,把许多操作暴露出来,缺少封装性。
- 利用了共享对象这一特性来传递参数。实质上,这样子操作有很大的缺陷,容易改变容器中的对象性质。
- 创建了PowerFuc、CircuFuc、Term、Expression等类来管理数据,但是这些类都是单独创建的,并没有使用接口或继承等方法来增加它们的关联性。
- 求导的处理:三角函数求导后会出现两项的相乘,而我直接在类中创建属性来存放求导后得到的参数,并没有创建新的对象来存放他们。
- 化简方面,只是简单的考虑了合并同类项,并没有考虑三角函数的特殊化简情况。
应用在第三次实验中的修改方法:
- 缩减main函数长度,单独建立读取输入的类和输出的类。
- 增加接口,共同实现求导和转换字符的方法。
- 求导后创建新的对象来存储。
3. 第三次作业
3.1 UML类图
3.2 代码长度
3.3 代码复杂度
3.4 设计分析
这一次作业首次运用接口和多态的思想来处理。事实上,由于递归的存在,这样处理也是必须的。创建各个类来存储的数据三角函数、幂函数、常数,并创建加法类、乘法类来实现计算。加法类、乘法类内均由一个列表来管理项。
- 嵌套解决方法:首先进行预处理,将最外层的括号()都替换成[],避免对正则表达式的判断造成影响。随后在分析中取得表达式因子字符串、三角函数括号内的字符串,递归调用分析函数。
- 简单化简思想:合并简单的同类项例如常数项。若加法项中有加法项或只有一项的乘法项可进行化简,乘法类似。
- 由于输入分析和化简部分大多集中在Add、DealRegex、Mult三个类中,导致其复杂度增加。
二、程序BUG分析
-
第一次作业在强测和互测中均未发现Bug。
-
第二次作业在强测和互测中均未发现Bug。
-
第三次作业在强测中发现一个BUG,互测中发现两个同质BUG。
Bug集中在WF判断和表达式化简上。WF判断中,我在方法中途将字符串内空格替换为空,导致嵌套判断有误。在表达式化简中,我对最终输出的字符串判断,若它以括号开始,以括号结束,就可把括号去掉,却忽略了输出形如
(cos(x)+cos(x))*sin(1)
的情况。总的来说,自己的BUG都比较的细节,采用阅读代码的方法不容易发现,需要测试数据的帮助。
三、分析BUG策略
-
构造比较全面的测试样例
在第一次实验中由于输入仍不复杂,可以非常全面地进行测试。在后两次测试中,构造全面的样例相对比较困难,我也容易受到思维的局限性。
-
自动评测机
利用自动评测机,可以自动生成测试数据并进行比对。当然,由于测试样例比较随机,可能无法覆盖到特殊的点。总的来说,搭建评测机还是可以极大地帮助我们在测试中寻找BUG。以下是讨论区中参考的文章:
https://course.buaaoo.top/assignment/149/discussion/410 搭建自动评测机致思路
https://course.buaaoo.top/assignment/149/discussion/433 基于Linux命令行自动化测试
-
对拍器
在互测过程中进行批量操作,避免打开八个IDEA项目并逐一输入对照。
https://course.buaaoo.top/assignment/151/discussion/493 bash批量操作的一些基本方法
四、应用对象创建模式来重构
for (String str : facList) {
Matcher m1 = circu.matcher(str);
Matcher m2 = power.matcher(str);
Matcher m3 = dint.matcher(str);
Matcher m4 = expfac.matcher(str);
if (m1.find()) { //找到三角函数
BigInteger tem;
if (m1.group(3) != null) {
tem = new BigInteger(m1.group(4));
} else {
tem = BigInteger.valueOf(1); //tem如果为0? =1
}
if (!tem.equals(BigInteger.ZERO)) {
if (m1.group().startsWith("sin")) {
SinFuc sin = new SinFuc(tem, m1.group(2), null); //创建实例
sin.setFactor(); //处理字符串
derlist.add(sin);
} else {
CosFuc cos = new CosFuc(tem, m1.group(2), null);
cos.setFactor();
derlist.add(cos);
}
}
} else if (m4.find()) { //找到()表达式因子
derlist.add(analyse(m4.group(1))); //字符串
} else if (m2.find()) { //找到幂函数
if (m2.group(2) != null) {
powerIndex = powerIndex.add(new BigInteger(m2.group(2)));
} else {
powerIndex = powerIndex.add(new BigInteger("1"));
}
} else if (m3.find()) { //找到整数
coef = coef.multiply(new BigInteger(m3.group()));
}
以上是我第三次作业中的部分代码。该代码所属的方法其实完成了两项操作:一是从表达式字符串中提取每一项的字符串加入列表,二是根据字符串分析得到具体对象。事实上,完全可以新建一个简单工厂来实现,根据输入来自动建立相应的因子。
五、总结与心得体会
在三次作业的一步步迭代中我对面向对象与面向过程两种编程方法有了更加深刻的认识。同时,我也掌握了利用“小正则”分析输入、实现接口和多态、重写、对象比较等知识点。在一次次不厌其烦的重构中我也明白了代码拥有良好拓展性的重要意义。
在互测方面,自己对于找别人BUG并没有花费大量时间,因此成绩也一般。寻找BUG,进行全方面的测试是很重要的一个环节,而课程组也旨在锻炼我们这方面的能力,这方面我还有很大的不足与进步空间。在阅读他人代码的过程中,我切切实实意识到,思路清晰、格式清楚的代码会给阅读者带来很大的方便,而这也是我要注意的。
总的来说,在输出结果的正确性上而言,我认为本次作业完成的还不错。但是在代码的架构、可拓展性,以及互测等环节中,我认为自己还可以做的更加好。