OO第一单元总结
三次作业分析:
-
第一次作业:
作业概述:
第一次作业就是对于简单多项式的求导,按照规定,多项式只由带符号整数和幂函数组成。
完成思路:
由于第一次作业的多项式组成比较简单,所以基本上对于程序没怎么进行架构,基本上是按照面向过程的原则去写的,将表达式从命令行输入,进行初步的空格,幂方处理,接着根据加减号将多项式分为一个一个的项,存放到一个HashMap中,接着对于容器中每一个元素进行分别求导再连接起来输出。
程序类图:
![]()
可以发现,第一次作业一共就四个类,而且这四个类之间没有任何关系,事实上,这一次的作业是使用面向过程的思想来写的,这四个类实际上就相当于四个函数,并且写的思路并不是很清晰。
程序结构分析:
属性:
-
String polynomial 从外界输入的字符串
函数:
-
InputHandler
构造函数,将外界传入的字符串构造成为自己的属性
-
handling
字符串处理函数,函数将传入字符串的空格去除,多个+-号进行合并,返回处理好的字符串。
Poly类
属性:
-
String polynomial
多项式字符串
-
HashMap<BigInterger, BigInterger> terms
存储一个个项的HashMap
函数:
-
Poly
构造函数,将多项式传入,通过一系列的方法将Poly类的属性构造出来
-
getPolynomial
外界得到多项式的方法
-
termFind
根据加减号,利用split函数将多项式分为一个一个的项的方法
-
Derivation
根据存储入HashMap中的数据进行求导的方法
-
Output
输出最终求导结果的方法
Term类
属性:
-
BigInterger parameter
一个项中的常数项
-
BigInterger index
幂指数
方法:
-
Term
构造方法,采用正则表达式匹配,将每一个项中的常数和幂指数分离出来,构造成一个项对象的属性
-
getParameter
-
getIndex
-
Main类
主函数入口
度量分析:
![]()
不难发现,Poly类中的Output函数拥有着极高的复杂度,这是因为本人第一次作业仍然是按照面向过程的思想来写的,所以这个函数内使用了很多if-else分支来进行特殊情况的判断,一旦又想到了新的情况,就继续向上加上if语句特判,这是由于写之前并没有先考虑完全。同时这一个Output方法行数远远地超过了60行,导致checkstyle扣了20分,而且由于架构地比较垃圾,也不太好改,这也恰恰证实了架构的重要性。
Bug分析:
第一次作业由于课下没有花多长时间debug,基本上依靠评测机,另外还试了几个简单的例子,此外就再也没有管它,这样不完全不详细的评测最终就导致了我的强测错掉了一个点,进入互测后,这一个bug也被他人发现进行了hack。这是一个十分愚蠢的bug,我在简化输出的时候,如果一项求出来是0,我直接输出时进行了continue操作,而不是输出0,这就导致了如果我的每一项求导求出来都是0的话,我最终是不会有任何输出结果的,这就是bug的产生原因,修复bug的方法就是增加一个flag标志,假如出现了求导结果非0的项,则令flag为1,如果到最后flag仍为0的话,则输出0,否则按照原样输出。
-
-
第二次作业:
作业概述:
第二次作业相较于第一次作业,难度有了较大的提升,相较于第一次作业仅仅有包含x的幂方的项进行求导,这次作业在加入了sin(x)和cos(x)这样的三角函数项,多项式因子的嵌套求导,同时还加入了乘法法则、链式法则这样的求导法则,在对于问题的分析上不可能单单仍然像第一次作业那样简单。
完成思路:
由于第二次作业不同于第一次作业有了各种各样不同的因子,多项式中项的组成也变得更加难以分析,甚至还出现了项中可以有有多项式嵌套的情况,在进行反复地思考后,不得不放弃第一次作业那样正则表达式强行匹配的方法,进行了重构。
这次的重构采用了递归下降的思路,从最上方的多项式,下降到将多项式分为一个个由加减号分割而成的项,再通过正则表达式匹配将项下降为不同的因子,带符号整数因子,三角函数因子,幂函数因子,多项式因子,接着,多项式因子继续进行递归下降,通过调用各种因子的求导方法,项的求导方法,最终得到整个多项式的求导结果。
程序类图:
![]()
第二次作业经历了重构,采用了递归下降的思路来完成代码的编写,并且采用了父类继承的方法,引入一个因子类父类,它拥有toString和derive方法,通过子类对于方法的重写来完成复杂的多项式的求导过程。
程序结构分析:
-
StringHandler类:
相当于一个处理输入字符串的Factor类,将输入的字符串进行格式化处理,本质上是一个函数,不可以被称之为一个类
-
Poly类:
多项式类,同时也是Element因子类的子类,由于本次作业中增加了嵌套的要求,多项式本身也作为一种因子,可以重写父类的derive和toString方法。
-
Term类:
项类,根据加减号将多项式分为一个一个的项进行构造,拥有一个存放项内各种因子的容器,拥有自己的求导方法,通过调用各个因子类的求导和toString方法来完成。
-
Element类:
因子类,是各种因子的父类,拥有toString和derive方法
-
Triangle类:
三角函数类,因子类的子类,重写父类Element类的toString和derive方法
-
Exponent类:
幂函数类,因子类的子类,重写父类的toString和derive方法
-
Const类:
常数类,因子类的子类,重写父类的toString和derive方法
-
Main类:
主函数入口
-
度量分析:

可以看见,相较于第一次作业进行了重构之后,在每一个求导时调用每一个因子的求导方法进行组合嵌套,比第一次直接对每一项进行求导复杂度低了不少,同时也没有了超过60行的函数,导致checkstyle扣分的情况,唯二复杂的是Term类中的构造方法和Poly类中的Termfind方法,一个是要将每一项中的各种因子存到Element容器中,而另一个是要将一个表达式中的各项都分出来,这两个部分的实现并没有那么容易,所以复杂度稍微高了一些。
Bug分析:
这次的作业,由于时间并不是十分的充裕,所以并没有对于结果的长度进行优化,没有进行优化带来的好处就是无论是强测还是互测都没有被发现bug,同时带来的问题就是极其糟糕的性能分,如果有机会的话,一定花一些时间在性能上进行改进。
-
第三次作业:
作业概述:
第三次作业在第二次作业的基础上增加了三角函数项内的嵌套,即sin和cos函数内可以出现各种因子项,而不是想第二次作业那样只能够出现x,除此之外,第三次作业还新增了对输入表达式形式的判断,如果输入的是不符合形式化表述的表达式,则输出Wrong Format!这个新增的功能个人认为是本次作业的难点。
完成思路:
由于第二次作业即采用了递归下降的写法,第三次作业的总体架构不需要做出很大的修改,只需要对于三角函数类进行修改,让三角函数支持嵌套,完成对应的求导方法即可。除此之外,为了对于错误的输入形式输出特定的字符串,新增一个异常类,在StringHandler模块对于表达式进行分析,并利用try—catch语句来捕获这种异常得到需要的输出。
程序类图:
![]()
第三次作业总体架构和第二次没有什么区别,只是在StringHandler这个类中多加入了许多方法,这是由于需要对于输入多项式的形式进行判断,这必须要的对多项式进行形式化处理之前就完成,只是这些方法还是又一些冗杂,还是可以合并和简化一下的。
程序结构分析:
-
StringHandler类:
相当于一个处理输入字符串的Factor类,将输入的字符串进行格式化处理,本质上是一个函数,不可以被称之为一个类
-
WrongFormatException类:
格式错误异常类
-
Poly类:
多项式类,同时也是Element因子类的子类,由于本次作业中增加了嵌套的要求,多项式本身也作为一种因子,可以重写父类的derive和toString方法。
-
Term类:
项类,根据加减号将多项式分为一个一个的项进行构造,拥有一个存放项内各种因子的容器,拥有自己的求导方法,通过调用各个因子类的求导和toString方法来完成。
-
Element类:
因子类,是各种因子的父类,拥有toString和derive方法
-
Triangle类:
三角函数类,因子类的子类,重写父类Element类的toString和derive方法
-
Exponent类:
幂函数类,因子类的子类,重写父类的toString和derive方法
-
Const类:
常数类,因子类的子类,重写父类的toString和derive方法
-
Main类:
主函数入口
度量分析:
![]()
-
可以发现,其中Trigangle三角函数类的构造方法复杂度奇高,这是由于在带三次作业中新增了对于三角函数嵌套的要求,这就导致我构造三角函数对象时 对于三角函数内部的内容仍然需要使用正则表达式匹配,这样增加了很多复杂度,事实上,可以把正则表达式匹配这一部分用一个函数封装起来,因为这一相同的方法是用了不止一次。
除此以外,还有很多判断格式是否正确的函数也拥有比较高的复杂度,仍然有待优化。
Bug分析:
这次的作业仍然出现了bug,bug的原因在于判断输入形式错误的时候,未将三角函数内的带符号整数符号与数字之间有空格纳入考虑,解决的方法特判即可。
互测方法:
要说这门面向对象课程最为具有特色的地方莫过于它作业的互测环节,大家匿名的在一个房间内互相测试他人的程序,发现自己bug的同时提升自己的debug能力,还能够学习到别人代码的与众不同的思路。实为一个很好的环节。
个人在三次作业中均进入了互测的环节,并在互测中采用了手动构造样例的方法,我构造样例的主要思路大体分为以下几点:
-
构造一些边界样例,很多人程序复杂的样例都能够通过,但是对于一些边界的简单的样例,很有可能就因为没有考虑完全,出现异常或者无法输出正确的结果
-
构造一些拥有大整数的样例,有些人可能没有用BigInterger
-
构造复杂的多层嵌套,如果没有使用递归下降的思路,复杂的嵌套可能会出现问题
-
构造样例中含有连续的多个符号,在第一次作业中,在互测的环节中,就发现了有人在多项式中两项之间如果出现了三个符号则会出现程序报错的情况。
-
阅读他人的代码后,发现他人代码的具有问题的部分来进行针对性地构造样例
重构经历:
这一个单元的作业我一共进行了一次重构,即第一次作业到第二次作业之间,第一次作业由于还是比较简单,另外还是没有能够完全理解面向对象的编程方法,还是本能地使用了面向过程的编程方法,这就导致了第一次的作业到第二次的作业的过渡上,第一次作业的完成内容基本上完全没有可以参考的价值,于是在第二次作业的时候,我从零开始进行了重构,先将总体的类的关系图确定好,理解好递归下降的完成思路,再进行代码的编写,很快就将总体的框架搭构好了,再经过一段时间的细节处理就完成了,由于第二次作业的好的架构,第三次作业也就变得十分的轻松,只要在两个类上进行一定的修改就可以轻松完成这次的作业。这次的重构经历让我深深地体会到了好的架构的重要性,好的架构思路是十分清晰的,并且具有很强的可拓展性。希望之后的作业从第一次开始就可以有优秀的架构,毕竟重构这件事还是挺累人的。
心得体会:
-
一定要先在脑海中先有了大致的思路和架构后再动手写代码,这样写起来是很快的,不要一点思路都没有就在那边瞎写,这样后面重构起来是十分麻烦的,基本上要推倒重来
-
要注意程序的可拓展性,因为每一个单元的作业基本上都是增量式开发,假如程序的可拓展性不好的话,就像我第一次作业那样完全没有可拓展性,那就必须要进行重构
-
测试自己的代码很重要,一定要对自己的代码进行完备而全面的检查,不要等着到互测的时候别人来给你找bug,或者是单单依赖评测机来给你找bug
-
相互学习的重要性,每两周一次的研讨课上,会有很多同学来交流分享他们完成作业的思路,给出一些好的方法,这样的分享对于我们思路的拓展是十分有益的,同时也会对我们作业的完成起到许多方法上的指引作用






浙公网安备 33010602011771号