OO第一次博客作业
学习目标
面向对象第一单元的三次作业,迭代式的完成了包含简单幂函数和简单正余弦函数的导函数及其组合的求解,学习目标主要有以下几点:
1、熟悉面向对象的程序设计思想
2、学会使用类来管理数据
3、依靠继承和接口实现层次化设计
第一次作业
第一次作业需要完成的任务为简单多项式导函数的求解,多项式仅包含幂函数和常数,这次作业中我并没有进行层次化的设计,而是在一个poly类中实现了构造方法,求导方法和toString()方法。
UML类图
基于度量来分析自己的程序结构
先给出三个参数的注解
ev(G),是用来衡量程序非结构化程度的。基本复杂度高意味着非结构化程度高,难以模块化和维护。
Iv(G),是用来衡量模块间调用关系的。模块设计复杂度高意味模块耦合度高,导致模块难于隔离和维护。
v(G),是用来衡量一个模块判定结构的复杂程度。圈复杂度大说明代码质量低且难于测试和维护。
从图中可以看出,在第一次作业中,除了新建poly类,将多项式相关的数据结构和方法放入了poly类而没有全部放置在main类外,整个程序依然使用的是结构化的设计思想,而且方法之间的耦合程度都很高,调试和修改的难度都很大,也基本不具备可拓展性,在第二次作业放出后不可避免地进行了重构。
bug分析
课下测试时我采用的是构造测试数据的方法,在强测中没有被测出bug,互测中发现了一个bug,是当系数为1时我将系数省略却输出了*。因为第一次的代码量较少,互测中我主要依靠阅读理解别人的代码来检查是否有bug,hack出一名同学对于表达式最前的符号处理发生了问题。
第二次作业
第二次作业在第一次的基础上增加了正余弦函数,于是这一次我新建了工厂类和term类,但是由于这一次的因子类型有限,所以我将所有的项都化为了\(ax^bsin(x)^ccos(x)^d\)的标准形式,建立了统一的求导和合并同类项的规则。
UML类图
基于度量来分析自己的程序结构
这一次程序的基本复杂度较第一次有所降低,但是因为将所有term都化为标准形式的代码量较大,createTerm()方法中也多次调用其他方法,导致模块设计复杂度较高,而且也不是很利于调试。第二次作业设计的结构也为第三次作业的拓展做了准备。
评测机相关
第二次作业因为代码量较大,完全依靠阅读已经无法在互测开放的时间内对所有程序进行完备的测试,所以在课下阶段搭建了评测机。但是在应用中发现,评测机构造的数据过于随机,基本测试不到程序最有可能出错的边界数据,所以最后发现的bug还是依靠课下构造的测试数据。
bug分析
构造的测试数据如下:
sin ( x ) ** 1 + cos ( x ) **1 + x ** 2
+++1
+-+001*x
0
x - x
-9*x
- + 9*x
- - -9 * x
0++0--0++01--01+-0001000-+0010+sin(x)-cos(x)++sin(x)**2--cos(x)**2++x**2--x**3+++1-+-1
+cos(x)+x*sin(x)+cos(x)**1*sin(x)**1
+-009*sin(x)**2-+cos(x)**2
sin(x)-x*cos(x)-x**2+x**0002-sin(x)**2--+00001*cos(x)**2+x-2*x---1*x**2
--x* sin ( x ) ** 2 + - -1 * x * cos ( x ) **2
---00001*sin(x)**000001--+001*cos(x)**-000001
我自己的代码在强测和互测中都没有发现bug,互测中发现的bug是在指数为50时有一位同学输出了WF,感觉前两次作业的复杂度不是很高,课下仔细测试就可以排除掉所有bug。
第三次作业
第三次作业的难度较前两次作业有很大的提升,主要是表达式也可以作为因子在项和三角函数中间进行嵌套,这一方面导致仅仅依靠正则表达式无法对输入进行有效的处理,另一方面也增加了对输出化简的难度。我在这一次定义了一个Term接口,所有实现这个接口的类都需要实现求导方法,在toString()方法内部对输出进行了一些优化。
正则表达式处理输入
我每读入一个表达式,就将表达式因子和三角函数嵌套的因子用[]括起来,然后通过正则表达式对输入是否合法进行判断。
public class Regular {
public static final String legalChar = "[ \\t]|[\\+-]|[0-9]|x|[sin]|[cos]|\\ (|\\)|\\*";
public static final String sp = "[ \\t]*";
public static final String symbol = "(\\+|-)";
public static final String integer = "(" + symbol + "?\\d+)";
public static final String exponent = "(\\*\\*" + sp + integer + ")";
public static final String polyFunc = "(\\[.*?\\])";
public static final String powFunc = "(x" + sp + exponent + "?)";
public static final String sinFunc = "(sin" + sp + polyFunc + sp + exponent + "?)";
public static final String cosFunc = "(cos" + sp + polyFunc + sp + exponent + "?)";
public static final String triFunc = "(" + sinFunc + "|" + cosFunc + ")";
public static final String function = polyFunc + "|" + powFunc + "|" + triFunc + "|" + integer;
public static final String term = symbol + "?" + sp + "(" + function + ")(" + sp + "\\*" + sp + "(" + function + "))*";
public static final String poly = "(" + symbol + "?" + sp + term + ")(" + sp + symbol
+ sp + term + ")*";
}
UML类图
基于度量来分析自己的程序结构
通过表中的数据可以发现,复杂度较大的区域还是集中在Factory工厂类当中,因为工厂类中的每一个方法都有可能会调用本方法或者工厂类中的其他方法,造成模块设计的复杂度和圈复杂度都很高。
bug分析
这次强测没有完全通过,有一个点运行超时了,互测也因为TLE被hack了五次,经过调试发现是toString()方法运行时间过长,也就是对输出的简化逻辑时间复杂度太高,在修复阶段把部分优化相关的代码全部注释掉之后,就通过了原本超时的测试点,而且速度提升大约十倍,到0.2秒左右。因为这一次分到了B房间,所以同屋的同学都有bug,互测hack的也比较多,主要的有以下几类错误:
1、运行超时
stdin:(x*(x*(x+(x*(x*(x*(x*(x*(x*(x*(x+cos((x**2))**2)))))))))))
2、将cos(0)和sin(0)的值都当成0
stdin:cos(cos((0)))*x
stdout:0
3、输出格式错误,sin中的表达式因子未加括号
stdin:sin(x+x)
stdout:2*cos(2*x)
4、表达式格式判断错误
stdin:- + 9*x
stdout:WRONG FORMAT!
心得体会
1、要把重点放在程序架构的设计上,不要过分追求性能分,舍本逐末。
2、要在前两次作业中主动思考可能的拓展方向,为自己的代码留下迭代的空间,否则可能会导致三次作业三次重构的现象发生。
3、要在课下进行充分的测试,自行构造尽量完备的测试数据,一方面可以保证自己程序的正确性,另一方面边界数据更有可能是hack他人的利器。