BUAA OO Unit1 总结

文章讲述了作者在BUAA-OOUnit1单元的学习中,逐步提升面向对象编程能力,通过三次作业的递归下降解析算法实现表达式处理,分析自身和他人代码的Bug,以及对优化和正确性的深刻理解。
摘要由CSDN通过智能技术生成

BUAA-OO Unit1单元总结

前言

在历经了第一单元三次作业的磨难之后,笔者从一个OO和Java的小白晋升为有一点经验的小白了。回想起自己第一周开始做作业时由于缺乏Java的基础知识而一连几天毫无进展,最后连续鏖战将近二十个小时才勉强完成任务的狼狈的样子,但是也不是很完美的样子,感觉到这学期要受很大的折磨了!与我想的差不多,三次作业下来,皮都掉了一层,不过有了一定的面向对象的能力。

第一次作业

  • 作业内容
    读入一个包含加、减、乘、乘方以及括号(其中括号的深度至多为 1 层)的单变量表达式,输出恒等变形展开所有括号后的表达式。

  • 作业思路

    • 对于第一次作业的架构笔者使用课程组所给的trainning-advance的架构,也就是递归下降方式。该架构的主要思想是对输入的表达式抽象化、层次化,将其抽象为表达式、项、因子三个层次,表达式由项组成,项则由因子组成,在处理时通过递归的方式逐层深入处理,处理到因子层次时,根据所遇因子的不同种类选择不同的解析方法,解析后返回因子,本次递归结束。

    • 这种架构相对于字符串解析方式具有几个显著的优势。首先,它具有更高的容错性和可读性。我们可以清晰地看到每一层的行为,当程序出现错误时,能够更容易地定位到具体的错误层级和方法。与此同时,这种架构很少出现细节性bug,因为许多细节都被程序自动处理了,比如符号的顺序等。其次,代码的可扩展性很强。在新增加需求时,我们通常只需通过增加一些Factor因子种类即可解决。甚至对于像嵌套括号这样的问题,也无需额外的考虑,因为架构本身已经将这种情况包含在内。

    • Method metrics
      在这里插入图片描述
      在这里插入图片描述

    • Class Metrics
      在这里插入图片描述

      从Method metrics 分析中,可以看到在Expr中,simplify方法的CogC,ev(G),iv(G)等都特别的大,因为这个部分涉及合并化简,打印结果。笔者并没有将这些方法提炼出来,没有做到一个方法实现一个目的。
      Parser.parseTerm()Term.addFactor()中,因为涉及将所有的括号展开以及将次方项写成连乘项,所以方法臃肿,步骤重复较多。

    • 优点

      • 把基本框架的思路确定清楚了,而且建立了比较清晰的层次结构,这个整体的思路在后面两次作业中都没有变化过,避免了大规模的重构。
    • 缺点

      • 存在一个方法冗杂,处理的情况多。
      • 在类的设计过程中,如果一个复杂的任务需要由多个类完成,最好的方式是把任务分配给每一个涉及到的类,让每一个类都调用自己的方法,最后用一个类整合所有的方法,而不是使用一个类的一个方法独立完成。
    • Bug分析

      • 由于自己在考虑对问题进行字符串层次处理时候,考虑括号和加减号存在条件未分析清楚,存在这方面的bug。在分析时,一步一步来,不要着急。

第二次作业

  • 作业内容
    在第一次作业基础上,本次迭代作业增加了以下几点:
    本次作业支持嵌套多层括号。
    本次作业新增指数函数因子,指数函数括号内部包含任意因子。
    本次作业新增自定义函数因子,但自定义函数的函数表达式中不会调用其他自定义函数。

  • 作业思路

    • 在对第一次作业进行扩展时,引入了对嵌套括号的支持以及新的表达式因子:自定义函数和指数函数,显著增加了计算器的功能和复杂性。正如之前提及的,递归下降解析器的架构因其出色的可扩展性而在此次迭代中得到了充分的体现。对于嵌套括号的支持,第一次作业中的架构已经为此奠定了良好的基础。

    • 对于自定义函数和指数函数这两种新引入的因子,本质上的处理方法是增加两个实现了Factor接口的新类。这不仅显得优雅而且简化了扩展过程。

    • 然而,将这些概念付诸实践远比描述它们更为复杂。在处理自定义函数时,引入了一个名为SelfDefine的类,负责解析和处理自定义函数的定义式。当解析器遇到自定义函数时,它会立即对该函数处理后的表达式进行解析。这实际上采用了一套独立的表达式解析方案,即将传入的参数作为因子进行返回,处理完成后再将整个表达式作为一个表达式因子返回。而对于指数函数的处理,则相对简单一些:仅需将指数函数内的表达式解析,并在返回时附加上指数函数的名称。

    • SelfDefine中,实现了xyz的顺序替换,以保证在出现(y,z,x)等不良顺序的时候造成的麻烦。将自定义函数等号右边的xyz分别用不会出现的字符替换,这样增加了自定义函数在用Expr替换的便捷性。

    • 这样的迭代增加了实现的复杂度,但同时也极大地提升了计算器的功能性和灵活性,充分展示了递归下降解析器架构的强大扩展能力。
      在这里插入图片描述

      在类间关系这张图中,增加了 Exp 类,用于储存指数函数,增加了 SelfDefine 类,自定义函数中,储存函数表达式,函数替换后的Expr。
      可以看到,与第一次作业的大体框架没有发生很大的改变,体现了AST树和递归下降的可扩展性良好。

    • Method metrics
      在这里插入图片描述

    在这里插入图片描述

    • Class Metrics
      在这里插入图片描述

    • 在Metrics分析中,可以看到Term.addFactor()中进行多项式展开和次方展开,这种方法复杂度较高。、

    • SelfDefine.ChangeSelfDefine()发生字符串替换的时候出现了较高的复杂度。替换的时候是通过遍历字符串,使用String.substring()进行切割替换,还存在是否替换的特判。

    • 在Class Metrics分析中,发现存在一个类中OCavg较大,非抽象方法的复杂度较大,存在每个类中没有专司其职的情况。

    • 优点

      • 保持代码的可扩展性,实现了迭代的方便。
    • 缺点

      • 没有做到各司其职,存在方法实现目的过多。
      • 没有做到必要的化简,例如合并同类项。
      • 代码计算很大的次方时,时间复杂度超过了时间限定范围,这是方法实现的弊端。
    • Bug分析

      • 出现了括号嵌套时,高次方时间爆了,代码实现的弊端,改变起来比较困难。
      • 方法实现复杂,当解析的式子复杂时,会出现TLE的情况。
      • 在化简中,以是否含有Exp分类,变为有指数和没指数两类,然后在合并时,存在-+,+-这种情况,不符合自己定义的基本项,当处理后1-+exp(x)会出现bug。

第三次作业

  • 作业内容
    在第一次作业基础上,本次迭代作业增加了以下几点:
    本次作业支持求导操作,新增求导算子 。根据第三部分形式化表述,求导因子可以出现在很多位置,包括函数调用实参,本次作业函数表达式中支持调用其他“已定义的”函数。本周实验会着重指导求导将如何层次化实现。

  • 作业思路

    • 在第二次作业基础上,增加了求导和函数定义时候的调用。

    • 对于求导,可以将之看作一个Factor类,接口DeExpr(),在这个DeExpr()中,存在简单求导,链式求导和乘法法则。

    • 简单求导
      在这里插入图片描述

      常数,求导后变为0。
      x的次方通过展开为连乘,求导后变为1。
      指数函数求导不变。

    • 链式求导
      在这里插入图片描述

      链式求导,只存在指数函数中,通过对指数求导得到DeExp的字符串,然后与原字符串合并。

    • 乘法法则
      在这里插入图片描述

    处理方法:设置两个Arraylist,一个存储求导后的因子,一个存储原式。通过两层循环,当i!=j时,交叉相乘,相等时跳过,进而实现了乘法法则。

    • 类间关系在这里插入图片描述

      新增加DeExpDeExpr类,本可以增加DeNumberDeVar,但是由于求导结果简单,就省略。将新增的两类用Factor作为接口。

    • Method metrics

    • 在这里插入图片描述

    • 在这里插入图片描述

    • Class Metrics
      在这里插入图片描述

    新增的两类没有出现较高的复杂度,还是第二次作业的复杂度。
    在这里插入图片描述

    • 优点

      • 可扩展性良好,没有改变大体框架。
    • 缺点

      • 代码时间复杂度高,会出现爆TLE的情况。
    • Bug分析

      • 没有保证解析的相对基本项,出现了一部分异常抛出。
      • 代码复杂,爆了TLE,这个改不了。
  • 总结:

    • 三次作业层层递进,迭代逐渐复杂。在HW1仅仅进行字符串层面的处理,到HW2无法实现,进而重构成递归处理,由于将次方改写成连乘的形式,时间复杂度高,代码底层逻辑无法改变,造成频频爆了TLE。
    • 没有进行太大的优化,对于同类型合并,没有进行判断,造成输出过长。

分析自己程序的BUG

  • HW1字符串处理,进而造成分析情况复杂,可控性太低。造成bu出现了好多。考虑不清楚。
  • HW2中乘方变连乘,计算复杂度增加,爆了TLE。还有次方在化简,要采用BigInteger存储,否则会爆数据范围。出现存储不符合自己定义的基本项,出现了计算过程中的-+,+-情况,造成Parser解析错误。
  • 第一单元做下来Bug数目比较多,有以下感悟:
  • 不要迷信评测机,评测机主要是靠随机生成测试数据,很难随机出一些边界条件,一些边界条件还是需要靠自己手动生成。
  • 优化有风险,代码需谨慎,一定要保证在正确的基础上进行优化。

分析别人的BUG

使用评测机来跑别人的代码,没有进行分析代码,找代码错误。

分析自己进行的优化

  • Factor为连乘时,注意是否有0这个系数,避免存在很大的连乘,其中一项为0的情况。
  • 多项式中同类型合并,采用一般的数学方法进行化简。
  • 对于系数为1,-1和指数为0,1这类,通过输出时,特判进行化简。
  • 减少括号的使用,例如可以通过判断是否为Expr,进而使用括号和去除括号。

心得体会

这个单元标志着我的面向对象编程之旅的开始,与先前的导论课程相比,它展现了面向对象编程的独特魅力。虽然之前的课程已经让我觉得代码量庞大,编程过程充满挑战,但这个单元的体验让我意识到自己之前的学习还只是触及皮毛。经过这一个月的学习,我有了很多反思和感悟。
从知识层面上,我发现自己在编写面向对象作业时,对面向对象的设计理念的掌握有了显著提高。与上个学期相比,我的代码更加遵循面向对象的原则,类之间的耦合度降低,我也开始意识到工具类的重要性,这些进步都离不开理论课的学习。
在完成作业的过程中,我也学到了一些宝贵的教训,特别是关于测试的重要性。我意识到自己之前没有给予测试足够的重视,有时候缺乏集中精神,导致相似的功能中一个有bug而另一个没有。这教会了我在编码时要更加集中注意力,避免简单的复制粘贴。此外,我还体会到了规划的重要性。在第一次作业开始前,我花了很长时间思考架构,这样的准备让后续的工作变得更加顺畅,尤其是在第三次作业时,一切似乎都自然而然地落到了正确的位置上。
还有一次经历给了我深刻的启示。在第三次作业的互测阶段,我本想利用准备好的数据去测试别人的作业,却发现自己没有参与互测,这对我来说是一个巨大的打击。这次失败让我认识到,在完成作业时,正确性永远是最重要的。在评分标准中,优化和正确性是一种平衡,但优化虽难且收益有限。因此,我学会了在追求完美的过程中要懂得取舍,这不仅仅适用于编程,在生活中也同样重要。
随着面向对象编程的第一个单元的结束,我希望自己在未来的学习中能够避免重复之前的错误,让我的编程之旅更加顺畅。

在评分标准中,优化和正确性是一种平衡,但优化虽难且收益有限。因此,我学会了在追求完美的过程中要懂得取舍,这不仅仅适用于编程,在生活中也同样重要。
随着面向对象编程的第一个单元的结束,我希望自己在未来的学习中能够避免重复之前的错误,让我的编程之旅更加顺畅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值