hOmewOrk 1 简单多项式导函数
程序构架:
“一个面向过程的程序!”——发出C语言老师的声音
这一次作业表面上用了面向对象,实则是一只披着OO的皮的面向过程编程。
在输入后,先实例化一个多项式类对象,再进行检查(调用check方法),接着用加减号分割整个多项式成为一个个项装入ArrayList中(调用create方法),然后计算每个项的导数放入一个新的ArrayList中(调用cal方法),最后用zhengli方法一把输出。
不准笑我的中文命名:(
DuoXiangShi类是整个工程的核心,各种方法上文也提及了。Xiang类只有存放和取出系数和指数两种功能,实际效果等同于C语言的结构体。Dealer这个类说来尴尬,是DuoXiangShi类的create方法写完之后超过了checkStyle规定的长度,于是偷懒找了个类扔进去部分代码(很不好的设计风格,在后两次作业中改正了)。由此导致DuoXiangShi类和Dealer类耦合极其严重。
代码行数大约350行,是我写过的第二大规模的工程(一周后就不是了)。
我出现的Bug:
公测中出现错误的数据:
--12345678*x^1+-87654321*x^1++19283746*x^1--47382846*x^1 +- x^ 0 --x^ +0 + 123*x^999- …… (错误点在 --x^ +0)
互测中错误样例典型:
1.空输入
2.形似x 1 和 x x^2的输入
错误原因:未能识别出 形似 ++ 1 和 由空格连接的各个因子的表达式为非法。
追溯原因:在DuoXiangShi的check方法中缺少对于这两种格式的排错的正则。
怎么找别人的bug?
(胡乱找)
1.使用f( )坑人 找输入处理的漏洞
2.对空格的使用进行检测 eg 2 * x ^ 2
3.500个+x快乐stackoverflow
hOmewOrk 2 包含简单幂函数和简单正余弦函数的导函数
程序构架
这次作业的代码构架几乎是第一次作业的复刻。所以没啥好多说(复读)。细节上的变动在于对Xiang的成员变为了4个(一个系数和三个幂次)。在DuoXiangShi中增加了处理三角函数的相应功能。所以耦合度高的问题依然存在。更重要的是,这仍然是一个披着OO外皮的面向过程程序。
我出现的bug:
这次在本地经过了很完全的测试,自以为很稳健,结果强测挂了3个点。。。。
但是修复时候通过输出中间结果(没有化简的导数),很快确定bug位置处于化简和输出的方法中。用不到十分钟就发现3个测试点都是一个bug,是在DuoXiangShi类的output方法中的if语句少了一个else语句,导致一个if进入后依然会进入后面几个不应该进入的if块(类似case语句没有break),所以输出总是多一个0。
在互测中,我当了一会狼人,27hack-0hacked.(很奇怪为什么互测连强测能找出来的bug都找不出来)。
怎么当狼人怎么找别人的bug?
主要是用WF作为测试样例,进行本地测试。我人工造了将近100个数据,在本地对7个人手动测试。用了两个小时不到。
典型的测试样例有:
1.缺项 eg sin(x)+ , sin(x)*
2.缺符号 eg x 12 , xsin(x)cos(x)
3.用大数放大误差 eg 999999999999*sin(x)^4*x-999999999999*x*cos(x)^4-999999999999*x*sin(x)^2+x*cos(x)^2*999999999999
4.输入莫名字符(比如中文) eg sin(x)*x 发想
hOmewOrk 3 包含简单幂函数和简单正余弦函数的导函数
代码构架:
(IDEA的UML真是神器)
这次作业改过自新,采取了真的面向对象(至少是我这样的弱鸡理解的面向对象)的编程思想。算法采用的是递归下降(最大的优点在于可以甩锅)。
对于输入输出单独建立InOut类,在其中建立一个DuoXiangShi对象后就把输入扔给它,让它计算就OK了。
对于DuoXiangShi,要进行表达式是否合法的检测,然后根据加减号把多项式切成一个个单项式,建立DanXiangShi对象然后喂给它就行啦~
对于DanXiangShi,依然要进行合法性检测,然后乘号把单项式切成一个个因子,建立YinZi对象然后丢给它。
对于YinZi对象,就是要区分各种因子的类型(有Sin,Cos,MiYinZi,ComlexSin,ComlexCos,Comlex共六种),然后建立各自的因子对象就行啦。
前三种因子分别是不嵌套的sin和cos以及幂函数,就直接求导,给出Output就行啦。
后三种因子就需要递归调用DuoXiangShi,然后利用嵌套函数求导公式求出导数。
整个过程是根据递归的思路做的,尽量在类和类之间不直接调用而是采用消息传递的机制。两个类之间都只有构造器和output方法需要交换一次字符串,所以耦合度不高。每个模块各自对应一个极其清晰的功能,所以内聚度也不错。
这次作业代码总量接近1000行,是我接触计算机以来写过的最大规模的工程。说实话,我真的难以相信自己可以管理这么大规模的工程。而且开发实际上只用了3天(半天想算法,一天半写代码,一天debug)。整个工程虽然庞大,但是组织结构清晰,所以bug定位非常容易。
我出现的bug:
这次强测终于没出bug(虽然所有性能分都是0)。
互测中出现的5个错误是一个同质bug:比如 对于sin (sin ( 48 ) ^ +2 ),我的程序会报WF
原因是对于sin/cos中我设置了一个开关:当sin/cos后的括号中是表达式因子,而表达式因子中没有x时会报WF。这个设计显然是多余的,直接在开关的if语句中&flase就改掉了。
怎么找别人的bug?
在没法WF的情况下用空格和TAB来找bug成为了王道。
例如
sin ( ( ( ( ( x ) ) ) ) ) ^ +20 - - +3*2
sin ( x ) * cos ( x ) ^ +11
+ - ( sin(x) * cos (x ) )
这些检测样例的强度都不高,又由于在A组,我把第二次作业所有的测试样例(将近百个)都测了一遍,只找了5个bug。
Applying Creational Pattern
重构第二次作业代码构架
为输入输出单独构造类,多项式类接收Input类的消息,并将最终输出传递给Output类。多项式类拥有的方法应有check方法(检查输入是否合法),并重写split方法(将每个项传给单项式类并存储求得的导函数),重写toString方法(用来优化并输出给Output)。单项式类的功能是接受多项式传来的一个项并识别之,创建sin,cos或幂函数对象,完成项的导数的计算。具体来说,应该重写split方法和toString方法。sin,cos和幂函数类的功能是对接收的因子求导。
第一次作业的代码重构为以上构架的真子集(去掉sin类和cos类即可)。
第三次作业的代码在我看来不需要大规模重构,但仍然需要在功能划分上做出一些改进。
1.多项式类的构造器方法过长(constructor的长度是不受checkstyle约束的),超过了200行。而且在类中定义了几十个私有变量(大多是与正则相关)。这样不符合开发习惯的设计是为了在多项式类中完成对于表达式检测的绝大部分工作。
(如此死亡复杂度的多项式类)(手动狗头)
虽然互测和强测证明我这一段代码写的没有逻辑上的漏洞,但是我还是希望可以将这一段检测分为多个检测的方法,例如空格检测、运算符个数检测、括号匹配检测等。把要用的正则放到方法中。这样的代码风格更好,而不是为了应付checkstyle。
2.在各个具体的因子类中,我都用了try-catch语句(如下图),作为一种提升鲁棒性的做法。这也导致有时候虽然正确地报出wf,我作为作者并不知道具体的错误处理过程。解决的方法是在各个具体因子类中也加入check方法,并在cal方法中调用check方法(提高内聚度)。
void cal()
{
try {
DuoXiangShi duoXiangShi = new DuoXiangShi(inside);
duoXiangShi.cal();
daoShu = duoXiangShi.output();
}
catch (Exception e)
{
System.out.println("WRONG FORMAT!");
System.exit(0);
}
}
最后有一点点心得:我真的感觉到了人的能力是逼出来的,尤其是我这样并不善于coding的同学。OO这门课真的非常善于逼迫人去做自己曾经不敢想的事情——但是做到了之后,感觉其实也没那么难。
如果你看到了这里,那就很感谢你啦~
(顺便推荐下IDEA的MetricReloaded插件超好用【匿】