OO第一单元总结

第一单元总结

第一单元架构图

  • UML类图

第一次作业分析

第一次作业要求完成最简单的表达式展开,但是开始的时候还是没有什么头绪。在仔细阅读了training给出的参考代码后,我开始试图从面向对象的角度来重新思考问题。其实我认为设计的架构可以由形式化表达自然而然的生成,即表达式 → 项 → 因子的思路来进行解析即可,其中因子又分为普通的变量(这里我们常量视为指数为0的变量)。所以基本的解析思路就有了,即按照这个思路进行递归式的解析即可。这种解析的思路或者说架构,在后续的第二第三单元里都十分好用,基本就是按照这个思路展开,设计好对应的expr,term,factor类型即可自然地完成解析。但是一次作业仍然比较困难,我认为最大的难点就在于对java语言的不熟悉,在很多细节的处理上非常纠结,不知道怎么才能写出更为简洁清晰的代码。

  • 一些具体的实现思路

在第一单元的设计中,架构的实现还是较为简单,即按照形式化表述来进行设计。我认为真正的难点在于细节上的实现问题,这里来分享一些当时采用的解决方法。

  1. 表达式展开问题 : 由形式化定义我们很容易知道因子是由变量因子(常量因子也属于变量因子)和表达式因子组成的,那么一个关键的问题就在于如何将这两者共性找出来,通过简洁的代码即可完成表达式展开。这里我采用了factor作为一种接口,变量因子和表达式因子都来实现这个接口。那么乘法应该怎么实现呢?我在variable类和expr类中定义了一个varSet(在后续的第二次和第三次作业中改为units)。这个hashset的设计思路就在于想把表达式因子和变量因子的不同结合起来,即表达式因子是由多个变量因子组成,即它的varSet有多个var,而variable即可视为其varSet只有一个var,然后利用接口中实现getVarset方法,即可将两者的乘法利用factor作为应用对象简洁的完成乘法。
  2. 合并同类项:这里合并同类项的思路较为简单,只要指数相同,系数相加即可,利用一个 HashMap<Integer, Variable> 即可完成。
  3. 结果输出:通过重写toString即可完成,这里我们采用的是数据与表现分离的方式,好处在于很容易修改输出结果,在保留了具体的数据后也方便我们进行优化。

总结

虽然在第一次作业中思路较为清晰,理解了一定的递归下降的原理。但是在一些细节的实现上还是面对了很大的困难,而且由于刚刚接触很多地方的写法更接近与c语言,一些细节的实现过于复杂。特别是对于hashmap和hashset以及共享对象的危险性意识不够,这在第二次作业时造成了难以发现的严重的bug,在后续会专门说到。

度量分析

第二次作业分析

其实有了第一次作业的经验,第二次作业并不难,只要为新增加的函数实现建一个类以及实现factor接口即可(三角函数,自定义函数,和求和函数都可以视为是一个因子),下面简述一下具体实现。

  • 三角函数类:我们很容易分析到,三角函数类需要一个type指明它是sin还是cos,其次它内部包含了一个因子(在后续的第三次作业中这个因子的范围更广,所以也可以看出factor接口实现的重要性,这给我们增量开发带来了极大的遍历)。那么如何表示这个因子呢,在第二次作业的时候我在三角函数内部定义了一个factor类型的变量,后来我发现这种这种方法极其不便利,大大地增加了代码的耦合度,即三角函数内又包含另一个类的对象,也正是这种设计导致我在第二次作业的时候出现了共享对象的问题,即多个三角函数对象的factor是同一个factor,这带来极为隐蔽的bug,不是一种好的设计。所以在第三次作业中,我用String类型来表示三角函数类内部的因子,这种尽可能多利用基本类型,也可以说是不变类型的思路给我的第三次作业带来了很大的方便。
  • 自定义函数类:这里的具体思路就是用两个arraylist来存贮形参和实参,因为arraylist默认按输入顺序来储存,自然的形成了对应关系。同时如果多次调用自定义函数,我们只需要将实参的arraylist清空,再重新加入即可。而实参的代入我们采取的是字符串替换,同时要主要再实现字符串替换的时候手动套一层括号以保证符合形式化表达。最后按照正常的表达式解析即可,并返回一个expr类对象。
  • 求和函数:求和函数思路类似自定义函数,利用循环不断替换来实现代入即可。

总结

第二次作业由于第一次作业的架构扩展性较好,只需要将新增的变量实现factor接口,以及重写对应的方法即可。但是出现了对象共享的问题,这给我第二次作业带来了很大的困难,在快截止的时候才de出bug。

度量分析

第三次作业分析

第三次作业由于我们第二次的架构较为容易扩展,所以难度就更小了。这里简单的说一下都做了哪些小改动即可完成第三次作业。

  • 首先是三角函数内部可以嵌套。在第二次作业的时候我们已经提到,我们把三角函数内部看作是由一个因子组成,在第二次作业是这个因子只能是变量,而在第三次作业时他可能性非常多看似很难解析,但其实我们只需要把这个因子当成表达式解析即可。即我们只要在识别到sin或者cos后,对其内部调用parseExpr方法,去解析一个表达式,从而自然而然的完成嵌套解析。
  • 函数嵌套问题。函数嵌套问题也很好解决,无非就是每个参数就是一个表达式因子,我们对每个参数调用parseExpr方法即可自然地完成嵌套解析。

总结

在第三次作业的时候深刻的体会到了一个良好的可扩展的架构的重要性,同时感概于面向对象设计给代码拓展带来的极大的便利。比如在对应新加的类型我们只需要给他实现一个factor接口,就很自然的融入到了之前的解析中。

度量分析

代码bug分析

在自己使用自动化测试的时候主要出现的bug就是由于对象共享导致的bug。这个bug会导致两种情况。第一种情况就是一个对象会在未知的情况下修改,因为他被多个对象共享。另一种情况是在使用hashset和hashmap时,会出现自己以为时两个不同的对象,但实际上时不同的引用对象引用同一个对象,这就导致无法加入到hashset中,从而硬气bug。

hack策略

hack的主要策略时争对与0有关的各种情况,以及sin(-1)**2,和int的一些边界情况。同时阅读他人代码,从逻辑混乱处尝试找到bug

自动化检测思路

  • 生成数据
    这次实验的数据构造思路较为简单,我们只需要依据形式化表达,递归地生成数据即可,即通过编写下面三个函数即可完成,其中depth是用于控制第三次作业中函数嵌套的深度。
    def genExpr(depth)
    def genTerm(depth)
    def genFactor(depth)
    为了随机生成数据用到了python中的随机数以及exrex。我们只需要构造好正则表达式,利用exrex.getone即可自动化生成基本的数据,最后再利用随机数将其排列组合即可。
  • 正确性判断
    这里主要利用了同学的代码进行对拍,再利用sympy库完成正确性检测即可。

心得体会

在OO第一次作业的时候面对了很大的挑战,对java语言的不熟悉,无法理解面向对象编程的思维,以及问题的思考难度等等都带来很大的困难。但在过程中积极地和同学交流,和助教学长沟通,以及阅读同学发布的贴子,逐渐有了一定的思路。在代码实现的过操中,仍然面对了很多问题,出现了很多莫名其妙的bug,通过不断的调试,最终都通过了测试。

这次作业最大的收获一个是对java语言的逐渐掌握,对面向对象编程的思维理解加深,以及在确保正确性而编写自动化测试代码时都收益良多。

posted @ 2022-03-26 15:46  马曦迪  阅读(51)  评论(1编辑  收藏  举报