【2019年BUAA面向对象课程】第一单元总结

2 篇文章 0 订阅
1 篇文章 0 订阅

写在前面

这一单元总共包括三次作业,从第一次作业的幂函数求导开始,逐渐扩展功能,到第二次作业的包括幂函数和三角函数的求导,再到最后允许幂函数和三角函数相互嵌套的表达式求导。详细作业描述请移步:第一次作业第二次作业第三次作业

这个单元给我最深的体会是:在设计的时候不仅要考虑设计是否巧妙,还得考虑程序的可扩展性。在这几次作业中,我逐渐明白了以前计算机组成课老师经常说的“机制与实现分离”的道理。这样做可以让我的程序在扩展功能的时候,只需要修改具体的实现,而无需改变以前设计好的机制。

1. 第一次作业

1.1 我的设计

第一次作业比较简单,只需要处理简单的幂函数的求导,所以没有使用继承和接口的机制。我设计了三个类:Launcher、InputParser和Polynomial。其中Launcher是启动类,包含了main函数,里面包括了程序的几个基本操作:读入、求导和输出;InputParser是处理输入的类,由于我采用的是递归下降的方法,在处理输入的字符串的同时构建多项式类;而Polynomial则是需要构建的多项式类,里面用ArrayList存表达式中的各项。

UML类图如下:
在这里插入图片描述
使用IDEA插件Calculate Metrics对方法的分析结果如下:(各参数的意义请参考文末的附录)
在这里插入图片描述
可见问题最大的方法是负责输出各项的printTerm,其模块耦合度和独立路径条数都比较高,不易维护。

1.2 bug分析

这次作业是我这一单元中唯一一次被强测和互测检测出bug的作业(qwq,在最简单的一次作业中挂了)。出问题的原因在于,为了减小输出的长度,我省略了每一项前的系数-1中的1,而忘记去掉后面的*号了。例如,对于项 -1*x,我省略了其中的1,却没有去掉后面的*,结果输出了-*x

这次作业给我的教训很大,后来我总结出错的原因在于我没有完整地测试代码每一种可能的分支。在之后的两次作业中,我做了以下改进:

  1. 自己写评测机,随机生成简单或复杂的、合法或不合法的数据,保证测试量;
  2. 在借鉴极限编程的思想,在设计之前先列出许多样例,考虑各种可能出错的情况,并在设计与实现的过程中始终提醒自己;
  3. 在关键代码部分实现所有分支全覆盖测试。

功夫不负有心人,在后两次作业的强测和互测中,我再也没有被找出bug(qwq)。

1.3 找别人的bug

由于这次作业比较简单,除了像我这种zz会输出错误格式的结果外,一般不会出现运算错误。所以我测试的重点还是放在检测别人的程序对输入数据的format是否判断正确上。

我主要根据以下几个方式构造wrong format样例:

  1. 在奇怪的地方加空格、tab和\f等空白字符;
  2. 在奇怪的地方加+-*等运算符;
  3. 在奇怪的地方去掉某些字符。

事实证明,这种方法很有效果,最后?了17次(逃。

2. 第二次作业

2.1 我的设计

相比于第一次作业,第二次作业加入了sin(x)cos(x)的三角函数因子。由于预感到下一次作业会允许sincos的括号中会允许嵌套表达式,所以我重新设计了代码的架构,方便下一次作业进行扩展。

我沿用了上一次的递归下降的思想,在读入输入字符串、判断合法性的同时构建表达式。与上一次作业最大的不同是,我把Polynomial类改成了Expression - Term - Factor三层结构。其中Expression类为表达式;Term类为项,在表达式以+号-号连接;Factor类为因子,包括x的幂次和三角函数,在项中以*号连接。

UML类图如下:
在这里插入图片描述

为了方便在Term中对各种项统一处理,我把Factor定义为一个接口,Trigon(三角函数抽象类)和PowerX(x的幂次类)都实现了这一接口。

然后为统一处理三角函数类,我用了继承的思想,让Sin和Cos类都继承Trigon这一抽象的三角函数类。

在思考如何方便下次扩展的过程中,我逐渐体会到了机制与策略分离的妙处所在。在策略部分,我调用求导、化简等比较抽象的步骤,而具体的机制部分则通过多态调用不同的方法。这样,在下一次作业中,我只需修改具体方法的实现,而无需改动策略部分的代码。

使用IDEA插件Calculate Metrics对方法的分析结果如下:(各参数的意义请参考文末的附录)(由于这次方法比较多,所以只展示有问题的部分)
在这里插入图片描述
在这里插入图片描述
可见此次作业架构设计的最大的问题在与代码的结构化程度不高,导致代码难以维护。这也是我要努力的方向。

2.2 bug分析

由于上次作业的教训刻骨铭心,所以这次我采用人工+自动的测试方式,通过两个方面测试程序的鲁棒性:

  1. 在设计与编程之前就构造大量比较坑的样例,并在编码过程中时刻考虑可能出错的情况,并将想到的坑样例记录下来,留着以后测试自己(和别人)的代码;
  2. 借鉴大佬的评测机代码,写了一个我自己的评测机。利用Xeger,以一定的概率生成正确或者错误的输入样例,并用sympy的equals函数与同学对拍,判断正确与否。(事实证明,sympy的equals函数不仅慢,而且不太靠谱?,在下一次作业中我改进了这个问题)

2.3 找别人的bug

由于这次作业我没有被找出bug,本着人不犯我,我不犯人的原则,我没有很努力地找别人的bug,只测试了我在编程的过程中想到的一些坑样例,?了2次就溜了。

3. 第三次作业

3.1 我的设计

这次作业在第二次作业的基础上,允许在sincos的括号中嵌套表达式。由于上一次作业时我已经考虑过了扩展的问题,所以在这一次作业中,我只加了一个ExpressionFactor和ConstFactor的类,他们都实现了Factor的接口,分别表示包含表达式和常数的项,用于嵌套在sincos的括号中。

UML类图如下:
在这里插入图片描述
求导的机制与上一次作业大体一样,只是在乘法求导的基础上引入了链式法则的求导。这里就体现了策略与机制分离的好处了:我只需要修改输出和化简的具体实现,对于策略部分就无需过多的改动了。

使用IDEA插件Calculate Metrics对方法的分析结果如下:(各参数的意义请参考文末的附录)(由于这次方法比较多,所以只展示有问题的部分)

在这里插入图片描述
在这里插入图片描述
可见代码的复杂度增高以后,模块之间的耦合度也上升了2333。这说明我还没有熟练地掌握面向对象的思想,在之后的作业中我要尽量地朝着高内聚,低耦合的目标前进。

3.2 化简方法

对于化简方法我专门写了一篇博客介绍,请移步我的另一篇博客

3.3 bug分析

这次作业我沿用了上次作业的经验,采用人工检测+自动检测两种方法验证程序的正确性:

  1. 同第二次作业,在设计与编程之前就构造大量比较坑的样例,并在编码过程中时刻考虑可能出错的情况,并将想到的坑样例记录下来,留着以后测试自己(和别人)的代码;
  2. 重新写了评测机。这次主要有以下两点改进:
    2.1没有采用上次利用sympy自带的equals判断,而是借鉴了指导书上的方法,在[-10, 10]区间上均匀选取1000个数据点,代入得到的表达式计算结果,并与同学程序的答案进行对比。若出现不同,则用sympy的diff函数计算输入的导数,并代入之前对比时答案不同的数据点,判断是谁的答案错误。
    2.2加入了判断输出结果格式错误的功能。这次我将评测机中所有程序输出的结果重新输入我的程序判断格式。这样既起到了判断输出格式的作用,也是对我的程序对wrong format判断的检测。

3.4 找别人的bug

这次虽然我没有被找出bug,但我没有像上次一样佛系,而是决定主动出击?。和找自己的bug一样,我采用了人工检测+自动检测两种方法找别人的bug:

  1. 把组内同学的代码下载下来之后,先用评测机挂一晚上(逃;
  2. 然后再构造特殊样例检测(由于之前有一次rm指令用错了,把编程时积累的样例误删了,就只能临时构造样例了qwq)。

事实证明,自己构造的测试样例比评测机更加靠谱。(可能是我的测试样例的生成器写的太人畜无害了吧)

然而在听了老师分析这次作业之后,我才知道有用树状结构管理测试样例的方法,争取下次用上。

写在后面

很感谢老师和助教们这三次精心设计的作业,让我在实践之中从入门JAVA到写出上千行的JAVA代码,也体会到了面向对象的神奇之处。

感觉每次作业都能有新的体会,很期待之后的作业。

附录:代码分析各参数的解释

ev(G)基本复杂度是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。

Iv(G)模块设计复杂度是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。

v(G)是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值