OO第一单元总结

OO第一单元总结

第一次作业

(1)基于度量来分析自己的程序结构

类图如下:

OO第一单元总结

​ 第一次实验中,程序包含三个类:MainClass、Expression、Term,一个求导接口DerivateExpression。由于表达式中仅含有常数和幂函数,每一项转化成a*x**b的形式用Term类储存。故没有为每种因子定义类,虽然某种程度上减少了代码量,但也导致了我homework_2难以扩充程序,只好重构。

代码规格如下:

OO第一单元总结

Term类:

OO第一单元总结

​ 包含两个变量,常数和幂函数的指数。当幂函数的指数为0时表示常数项。包含求导方法(derivate)和生成字符串(toString)。

Expression类:

OO第一单元总结

包含一个容器存放Term对象,其中standard方法是去掉字符串中的多余空格和冗余+-号。

类分析如下:

OO第一单元总结

​ 由上可以看出,主要代码集中在Expression类和Term类。其中Expression类OCavg(the Average Cyclomatic Complexity),即平均循环复杂度过高。主要原因是实现添加Term的方法中,会先循环Expression中所有Term对象,若存在幂函数相同,则系数相加;否则新建Term对象添加到Expression中。究其原因是没有很好将不同的功能分配到相应的类中。

方法复杂度分析:

OO第一单元总结

注:

  • ev(G)基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护**。

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

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

​ 由上图数据不难发现,Expression的构造器存在复杂度过高的问题,已经在上文讨论过了,是由于添加Term类。此外,Term的toString方法复杂度极高,究其原因,是没有合理的创建常数和幂函数类,导致生成String时需要对所有情况进行判断。这些操作都集中在一个方法中,导致方法复杂度过高。

(2)分析自己程序的bug

第一作业中的bug:

​ 没有考虑到表达式开头可能出三个[+|-]的情况。

修改bug:

​ 由于只在表达式开始才可能出现三个连续的[+|-],只需在standard方法中,去除空格后,特判开头的[+|-]。

(3)分析自己发现别人程序bug所采用的策略

​ 第一次实验,尝试随便写了一些简单的数据,并没有hack掉别人

(4)重构经历总结

​ 并没有

(5) 心得体会

​ 第一次实验,和之前学习过的课程很不一样。从阅读“有点长”的指导书,到一点点的写出第一次作业。虽然过程充满了艰辛(一周前才开始学java),但当通过中测的时候,还是很有成就感的。

第二次作业

(1)基于度量来分析自己的程序结构

类图如下:

OO第一单元总结

​ 第二次作业绝对是我三次作业中,结构最混乱的一次了(虽然第三次miss),第二次的作业中不仅添加了三角函数,而且添加了括号嵌套表达式。最初的想法是,分成四个类:Constant,Power,Sin,Cos。每个类继承Derivate接口。但在实际编写代码时发现,在项化简后,对于没有括号的部分求导结果是固定的三项相加。而对于括号部分,可以采取递归的方式求导。

​ 此外,由于第一次作业结构不清晰,第二次作业不得不重构。但是!第二次的结构也不是很清晰,导致了第三次作业的重构。

代码规格如下:

OO第一单元总结

​ 整体上看,代码量相较第一次提升了一倍。

​ 很容易发现,Term类的代码量约占了整体的一半,显然是类的创建不完善,大量的操作都集中在Term类中。这在第三次作业中得到了改善。

类分析如下:

OO第一单元总结

​ 相较第一次作业而言,Expression依旧存在问题,但是最严重的问题出现在Term类中。OCavg和WMC过高,原因在于,虽然定义了不同的因子类,但是在求导时,并没有使用因子类 的求导方法,而是在Term中实现了求导。导致了Term的内聚过高。

方法分析:

OO第一单元总结

大部分方法复杂度较低,上图只列出复杂度较高的三个方法,

  • Term.toString()需要考虑各种情况,还需要针对()嵌套表达式,故复杂度较高。笔者暂时能想到的解决方法是,完善各个Factor因子的toString函数,将功能合理划分,降低方法复杂度
  • Expression.delBracket()方法实现了检测并删除多余的嵌套(),如(((x)))。由于需要判断的情况比较多,暂时没有解决方法
  • Term.derivate()上文中笔者曾提到过,由于没有将求导方法合理分配到Factor的子类,故Term的求导方法复杂度极高,容易出现错误(强侧中的一个bug就是在求导方法中)。同时也不易进行代码的修改和添加,导致了homework_3的重构

​ 由上述分析不难发现,本次作业较第一次而言,有一定程度上的进步,虽然可以正确的划分对象,但是对于对象的方法不能良好掌握。

(2)分析自己程序的bug

第二作业中的bug:

  1. TLE:toString嵌套太深且多次调用。

    修改bug:在方法的最前面用一个String变量保存toString结果,避免了多次调用造成的TLE

  2. 求导错误:对()连乘进行递归求导时,采用(FIrstExp).derivate*()...()+(FirstExp)*(()...()).derivate。对后面剩余()的求导没有add到求导结果中(疏忽了)。

    修改bug:将完整的求导结果添加到结果中

(3)分析自己发现别人程序bug所采用的策略

​ 尝试使用了几个边界数据,还是没有hack到别人。很惭愧

(4)重构经历总结

​ 初次接触面向对象编程,对于程序的结构不了解,写出的程序没有良好的可扩展性。在以后的编程中,应该注意程序的结构,并且降低类之间的耦合。提高程序的可扩展性

(5) 心得体会

​ 第二次实验由于重构,花费的时间远长于第一次实验。但是因为第一次实验的经验,知道了哪里不足,重构时重点注意。重构代码使我发现了很很多需要改善的地方,对自己的能力有很大的提升。

第三次作业

​ 第三次作业是我心中写的最好的一次。(虽然miss了)有了前两次作业的经验,对类和类中方法的创建更加的得心应手。第三次作业中增加了三角函数嵌套和检验表达式正确。三角函数的嵌套求导较容易实现,我的大部分时间都花费在对表达式正确性的判断,因为时间过长,导致了miss。对表达式正确性判断,我才用了类似递归下降的方法,表达式分成了三个层次:Expression、Term、Factor,在每个层次仅检查对应的形式,如Expression仅检测是否为空串,若为空串则抛出异常,不针对项Term内部检测,使得对表达式的检测思路变得清晰了,也不容易出现bug。

(1)基于度量来分析自己的程序结构

类图如下:

OO第一单元总结

​ 所有的因子都继承Factor,实现Derivate接口。注意:Expression即表达式也是Factor的一个子类,这是为了适配项中的出现()表达式和三角函数嵌套表达式的情况。我认为是较前两次作业的较大进步,在实际的coding中,有确实方便了很多,在Term类中声明Factor的容量,可以统一管理包含表达式在内的因子。

类之间的关系如下:

OO第一单元总结

​ 看起来似乎还是挺整洁的,关系大致为:

​ Expression -> Term -> Factor (->Expression 三角函数嵌套)

代码规格如下:

OO第一单元总结

​ 首先,恭喜自己代码量突破1000行!随着实验的迭代,代码量从最初的200+到了现在1000+,(虽然有代码冗余的嫌疑)。相较前两次实验,各个类的行数都比较合适,最多的Term类行数稍微多一下, 有些方法应当更合理的分配。比如对字符串中因子的查找和删除,可以通过新建一个类,用静态方法实现。

类分析如下:

OO第一单元总结

​ 和上述分析结果一样, Term类复杂度过高, 应该将Term中的与Term内部变量无关的方法单独创建类,写成静态方法。Expression同理

方法分析:

OO第一单元总结

大部分方法复杂度较低,上图只列出复杂度较高的三个方法,

  • Term.type:这个方法是实现检测String类型变量的因子类型的方法,笔者认为这个方法内部判断过多,应当适当将对每种因子检测单独作为一个方法,以减少方法内部复杂度。同时,包括这个方法的Term.finFactor Factor.delBracket对等方法可与单独创建一个类,修改为静态方法。
  • Term.Term:构造函数复杂度过高,构造函数内部的主题是一个switch函数,对不同类型的变量使用不同的构造函数。笔者的想法是将每种因子添加单独创建一个private方法,减少构造方法的复杂度。

可以看出第三次作业的复杂度提升很大,各个方法之间耦合度也很高,这是第二单元的笔者需要提高的方面。

第三作业中的bug:

  1. 在判断是否存在项前‘-’的错误:笔者使用一个方法判断项前是否存在‘-’,但是,这个方法出现了逻辑错误!!!导致--这种情况会返回WF,这也正是修改这个bug造成了笔者超过了ddl。。。

OO第一单元总结

上图是修改后的结果,笔者最初忘记了--之间可能存在空格,导致了逻辑错误。

(3)分析自己发现别人程序bug所采用的策略

​ miss了第三次,没什么想法了。不过有一种想法 sin(sin((x**0)))对于三角函数内部函数求导为0的情况

(4)重构经历总结

​ 第二次作业的结构过于糟糕,第三次作业直接重构。但是在coding的过程中能感受到自己在有意对比前两次作业,来改进自己的程序。

​ 经验总结:

1. 动手实操前应该想好程序的结构,可以不考虑细节,但是一定要明确每个类的变量和主要方法
2. 对于有些和类中关系不大的方法,可以创建一个类,将这些方法改为静态变量,可以有效减少类中方法的数量,避免类里面“乱糟糟”的
3. 降低类之间、方法之间的耦合,只有这样,下次的作业才可能在之前的基础上迭代。耦合高的结果只有一个:修改程序时牵一发而动全身。

(5) 心得体会

​ 虽然第三次作业miss,但是对我来说意义非凡,自我感觉真正意义上的窥探到面向对象编程的思想,同时自己的代码量也有c语言的几百行第一次突破了1000行(过程曲折坎坷)。值得一提的是,没有重视工厂模式,在日后的编程会注意这方面的尝试

上一篇:C#根据对象属性获取属性的字符串


下一篇:Typescript-07-if 条件语句