面向对象设计与构造:表达式求导单元作业总结
第一部分:基于度量的程序结构分析
横向对比
Metrics
- homework_one
- homework_two
-
homework_three
关键指标
- ev(G):程序非结构化程度,数值高低影响模块化和维护
- iv(G): 模块设计复杂度,数值高低影响模块耦合度
- v(G): 模块判定结构复杂度
三次作业对比:
- 第一次作业并未采用java模块化思维设计程序,采用面向过程思想编写程序
- 第二次作业程序结构化程度改进明显,模块设计复杂度有所降低,模块间的耦合性有所降低,能够在第一次作业基础上做一些维护和拓展
- 第三次作业模块耦合度明显改进,模块组织更合理。但程序的结构化程度反而降低说明程序的设计思路不佳,其中checkAndAdd是一方法实现两功能,扩展性能较差,架构仍缺乏合理性。
类图分析
- homework_one
-
homework_two
-
homework_three
三次作业对比:
- 第一次作业基于面向过程思想,初步实践java语法
- 第二次作业在第一次作业的基础上开始有了模块化实现的改进,从实现的类方法初步可见面向对象的封装性
- 第三次作业难度上升明显,程序引入了求导接口,初步实现了各种对象的递归嵌套。但每一个类的方法仍不太具规律性,每个类结构设计仍不够合理,有的类甚至超过三百行,高内聚低耦合的设计原则体现较少,从架构方面可以看出程序的结构化程度仍然较低
自我评价
- 优点:类的归属划分与方法的实现逻辑相对清晰
- 缺点:程序缺乏扩展性和可维护性,当新需求出现时仍需重构大量代码。高内聚低耦合的设计原则体现较少,并且没有运用继承和抽象类等,导致程序结构稍有分散,缺乏整体性
第二部分:BUG分析
公测用例全部通过
互测被找到BUG
-
homework_one
**样例 **:
x^--2
正确输出:WRONG FORMAT!
我的输出:2*x
特征:幂函数的指数出现连续两个符号
错误定位:
protected String opSimplify(String s) {
String str = s.trim();
Pattern p = Pattern.compile("[+-]\s*+[+-]((?=\d)|(?=\s*+x))"); //独占模式
Matcher m = p.matcher(str);
错误原因: opSimplify方法是对表达式中两连续符号的化简,如将表达式中的 “- -” 替换为 “+”,但代码中的正则表达式忽略了幂函数指数部分出现两连续符号的问题,因此修改为:
Pattern p = Pattern.compile("(?<!\^)\s*+[+-]\s*+[+-]((?=\d)|(?=\s*+x))"); //独占模式
添加前置约束,滤掉幂函数指数出现两连续符号的情况。
BUG与设计结构的关联性
-
长正则处理字符串
-
先进行输入表达式符号化简 的合理性
-
面向过程的架构思路
- homework_two和homework_three并无互测被找到的BUG
从分类树角度分析程序设计问题
- 第一次作业仅包含幂函数因子和常数因子,以分类树的角度可分为:表达式→项→因子(幂函数,常数),我的程序设计以面向过程架构,采用:读入表达式→检查非法字符→简化运算符→正则匹配项→项求导结果append至结果字符串→输出字符串ans,没有考虑对表达式和项的抽象,导致了大正则的萌萌哒行为,也是被hack的关键原因
- 第二次作业新加入了sin(x)和cos(x)因子,以分类树的角度可分为:表达式→项→因子(幂函数,常数,sin(x),cos(x)),我的程序有了层次的划分:poly→term,但采用三元组进行的求导运算,因此第三次作业大量代码无法复用
- 第三次作业加入了嵌套因子,不能简单地以树形结构划分,因此可采用接口或继承等方式,抽象出加减运算类(表达式类),乘除运算类(项类),常数类,幂函数类,三角函数类。因为嵌套因子的存在,我的程序单独为三角函数类设立Factor和Exp两个相关量,采用接口的方式对各类规定实现求导接口,重写了toString方法,每一个对象的只保存该对象的原表达式。我的程序实现了输出的正确性,但在编码过程中遇到了许多困难,例如在输出结果的增添()处理,导致输出表达式混乱,没有能够及时优化
第三部分:Hack策略
静态Hack
检查代码
-
分析代码逻辑,找出显式或隐藏BUG
-
用自己在构思过程的输入设计与调试阶段出现的BUG测试
成功案例
-
homework_one,Lancer代码原型:
protected void printExp() { ... if (i == 0 && temp.charAt(0) == '+') { temp = temp.substring(1); } System.out.print(temp); }
该玩家在输出表达式时未考虑求导结果为0的情况,贸然对输出字符串进行编辑,抛出异常
-
homework_two,Saber代码BUG:
样例:2* -x^1 * sin(x)
在构思过程中考虑到出现非首因子带有符号的情况
-
动态Hack
-
随机数据生成 脚本对拍 eval
.cpp
freopen("in.txt","w",stdout); srand(time(0)); int opt = rand()%5; if(opt == 0){ putchar("x"); ... } else if(opt == 1)... else if...
我的随机生成数据的一般写法,homework1和homework2易接近完备性测试(根据处理流程生成数据)
.bat
cd C:UsersRedDesktopmul_test14src //编译 javac -d ./ ./*.java -encoding UTF-8 ... cd C:UsersRedDesktopmul_test12src java Enter < in.txt > temp.txt //运行temp.txt中的样例 python cal.py < temp.txt > out2.txt //结果返回到out2.txt fc out1.txt out2.txt //对比两文件差异 if errorlevel == 1 ( copy hack.txt + in.txt //找到后记录 goto A )
.py
exp = input() s = str(eval(exp)) //计算 if len(s) > 8 : print (s[0:8]) //控制精度 else : print (s)
测试比较
Hack成功率:
-
homework_one 静态 > 动态
-
homework_two 动态 > 静态
-
homework_three (静态 ≈ 动态) << 10%
第四部分:Applying Creational Pattern
架构
- 类图构建
- 封装合理性
- 类的抽象
- 递归的深度挑战
尝试工厂模式
if (str.equals("addsub")) {
Factor a = new AddsubComb(); //加减类
return a;
} else if (str.equals("mul")) {
Factor a = new MulComb(); //乘除类
return a;
} else if ...
重构
homework_two的poly类到homework_three的AddSubComb类
- 更新输入检查,提取项的方式
- 求导模式的复用: 表达式的导数 = ∑ 项的导数
- 重写toString方法