OO--第一单元总结

杨荣津-21375077 学生 2024-03-23 01:46:13

一、基于度量来分析自己的程序结构

  • 1. 代码量

    总行数源代码行数属性个数方法个数
    Definer.java876224
    DeriveFactor.java241603
    Exp.java191413
    ExpFactor.java625224
    Expr.java514216
    ExprFactor.java534114
    Factor.java13803
    FunFactor.java452924
    Lexer.java938226
    Main.java512501
    Mono.java211513
    MyOutputer.java585002
    NumFactor.java342714
    Parse.java266220111
    Poly.java304250111
    PowerFactor.java453624
    PreProcesser.java615301
    Simplify.java302701
    Term.java746126
    Token.java262133
    Unit.java423036
    总计145911612590
  • 2. 方法分析

    MethodCogCev(G)iv(G)v(G)
    Main.main(String[])1122
    baseclass.Exp.Exp(Poly)0111
    baseclass.Exp.getPoly()0111
    baseclass.Exp.toString()0111
    baseclass.Mono.Mono(BigInteger)0111
    baseclass.Mono.getExp()0111
    baseclass.Mono.toString()0111
    baseclass.Poly.addPoly(Poly)15177
    baseclass.Poly.addUnit(Unit)58131516
    baseclass.Poly.getPolys()0111
    baseclass.Poly.getPolysScale()3123
    baseclass.Poly.isEqual(Poly)4481819
    baseclass.Poly.isZero()0111
    baseclass.Poly.miss(Unit)1122
    baseclass.Poly.mulPoly(Poly)13177
    baseclass.Poly.powPoly(BigInteger)7166
    baseclass.Poly.toString()4234
    baseclass.Poly.vers()3133
    baseclass.Token.Token(Type, String)0111
    baseclass.Token.getStr()0111
    baseclass.Token.getType()0111
    baseclass.Unit.Unit(BigInteger, Mono, Exp)0111
    baseclass.Unit.getExp()0111
    baseclass.Unit.getPoly()0111
    baseclass.Unit.getXi()0111
    baseclass.Unit.setXi(BigInteger)0111
    baseclass.Unit.toString()0111
    entity.DeriveFactor.DeriveFactor(ArrayList)0111
    entity.DeriveFactor.toPoly()0111
    entity.DeriveFactor.toString()0111
    entity.ExpFactor.ExpFactor(Factor, BigInteger)0111
    entity.ExpFactor.derive()3333
    entity.ExpFactor.toPoly()2222
    entity.ExpFactor.toString()0111
    entity.Expr.Expr(ArrayList)0111
    entity.Expr.derive()1122
    entity.Expr.getTerms()0111
    entity.Expr.setTerms(ArrayList)0111
    entity.Expr.toPoly()1122
    entity.Expr.toString()1122
    entity.ExprFactor.ExprFactor(ArrayList, String)0111
    entity.ExprFactor.derive()1122
    entity.ExprFactor.toPoly()2222
    entity.ExprFactor.toString()0111
    entity.FunFactor.FunFactor(String)0111
    entity.FunFactor.derive()0111
    entity.FunFactor.toPoly()0111
    entity.FunFactor.toString()0111
    entity.NumFactor.NumFactor(String)0111
    entity.NumFactor.derive()0111
    entity.NumFactor.toPoly()0111
    entity.NumFactor.toString()0111
    entity.PowerFactor.PowerFactor(String, String)0111
    entity.PowerFactor.derive()1122
    entity.PowerFactor.toPoly()0111
    entity.PowerFactor.toString()0111
    entity.Term.Term(ArrayList, int)0111
    entity.Term.derive()6144
    entity.Term.getFactors()0111
    entity.Term.getMark()0111
    entity.Term.toPoly()3233
    entity.Term.toString()3133
    utils.Definer.addFunc(String)4125
    utils.Definer.callFunc(String, ArrayList)1122
    utils.Definer.getTrans(String, Integer)0111
    utils.Definer.printMap()3133
    utils.Lexer.getTokens()0111
    utils.Lexer.inputToTokens(String)711919
    utils.Lexer.isNotFinished()0111
    utils.Lexer.move()0111
    utils.Lexer.now()0111
    utils.Lexer.nowOffset(int)0111
    utils.MyOutputer.getSs(Expr)5244
    utils.MyOutputer.shared(Expr)5144
    utils.Parse.Parse(Lexer)0111
    utils.Parse.parseDerive_06()0111
    utils.Parse.parseExpr()7157
    utils.Parse.parseFactor()1777
    utils.Parse.parseFactorExp_04()20477
    utils.Parse.parseFactorExpr_07()14367
    utils.Parse.parseFactorFun_05()2133
    utils.Parse.parseFactorNum_01()1122
    utils.Parse.parseFactorPower_03()20477
    utils.Parse.parseFactorSignedNum_02()6244
    utils.Parse.parseTerm(int)2133
    utils.PreProcesser.inputToStd(String)331812
    utils.Simplify.simplify(String)2212
    Total306129288243
    Average3.521.482.622.79
    • 这里重点来看圈复杂度COGC
      • 圈复杂度的值等于程序的控制流图中边数减去节点数,再加上2(每个判断语句(如if语句)和循环语句(如for循环)都会增加控制流图中的节点数和边数)
      • 圈复杂度表示了代码中独立路径的数量,即代码执行的可能路径数。
      • 高圈复杂度的代码往往难以理解和维护。当代码的复杂性增加时,开发者需要花费更多的时间和精力来理解代码的逻辑和执行路径。
      • 一般认为圈复杂度在1-10比较好,当圈复杂度>30的时候认为代码状况不可读,代码可测性不可测,代码维护成本非常高
      • 从上表中可以得出本次作业中有个别方法的圈复杂度比较高,为了解决这个问题,应该减少条件语句的嵌套,把复杂的函数进行拆分。
  • 3. 代码依赖复杂度分析

    Class循环依赖该类直接依赖Dcy*直接依赖该类Dpt*PDcyPDpt
    Main06200020
    baseclass.Exp21361513
    baseclass.Mono00061603
    baseclass.Poly233111513
    baseclass.Token0112411
    baseclass.Token.Type0003502
    baseclass.Unit22351513
    entity.DeriveFactor0390020
    entity.ExpFactor0791321
    entity.Expr23851023
    entity.ExprFactor25841022
    entity.FunFactor16191231
    entity.NumFactor05541122
    entity.PowerFactor0791321
    entity.Term23871022
    utils.Definer04122322
    utils.Lexer0223313
    utils.MyOutputer06112432
    utils.Parse112192232
    utils.PreProcesser0003603
    utils.Simplify0111511
    • 这里重点来看循环依赖
      • 循环依赖指模块间的属性存在相互依赖的关系
      • 循环依赖会导致以下问题:
        1. 导致相互依赖的模块紧耦合
        2. 导致模块间的多米诺效应,一个很小的改动引发其他模块甚至全局的不可用,例如导致无限循环或其他不可预期的异常发生。
      • 我们在包或者组件的依赖图谱中不应该存在循环
      • 根据上表可得,本次作业的代码中存在部分的循环依赖情况,应该对每个pakage内的类方法进行进一步的聚合,排除循环依赖的情况
  • 4. 类图

    img

第一个Pakage,entity内都是有关表达式、项、因子的类。

  1. Expr类是Term类的列表聚合
  2. Term类是ExpFactor, NumFactor, FunFactor, PowerFactor, ExprFactor, DeriveFactor的列表聚合
  3. 各个类均实现了Factor接口,这是因为无论是表达式,项还是各种因子都可能会遇到需要求导的情况;并且在最终转换为字符串输出的时候都要用到toPoly()方法和toString()方法

img

第二个Pakage,baseclass内都是有关合并同类项的类,用于对各类项都实现统一的定义( Unit = ax^nexp(\sum Unit) ),因此设计Unit类时有xi,exp,poly三个属性分别代表a,n和exp的指数。各个类具体设计考虑如下:

  1. Poly类是Unit类的两层HashMap聚合
  2. Unit类是每个项的通用形式化表达
  3. Mono类专门用于实现Unit形式化表达中x的指数
  4. Exp类专门用于实现Unit形式化表达中exp的poly
  5. Token类是用于解析表达式的时候对每一个部分打上对应的token标签

img

第三个Pakage,utils是一些工具类,他们的用途如下:

  1. Definer用于记录自定义函数的定义,并且通过callFunc()方法实现实参的代入
  2. Lexer用于在解析表达式的过程中遍历tokens
  3. MyOutputer用于把解析后的表达式转化成字符串
  4. Parse用于解析表达式,通过Expr,Term,Factor层层下降,最后在Factor层面分成7个分支用于解析因子的七种情况parseFactorNum_01(),parseFactorSignedNum_02(),parseFactorPower_03(),parseFactorExp_04(),parseFactorFun_05(),parseFactorDerive_06(),parseFactorExpr_07()
  5. Simplify类用于对Expr转成的字符串进行化简,最后交给MyOutputer中的shared()方法完成输出
  6. PreProcesser类用于对于初始的输入进行一些预处理,包括去除空白符和合并连续的+-号
  • 优点:

    • 本设计框架很好的实现了各种操作的一般化,包括合并同类项操作和求导操作,无论是Expr,Term还是各种Factor都实现了统一的处理方法,这种统一化的思想可以很好的适应递归地解析表达式
    • 本设计框架对本次作业解剖成三部分分工,分别是处理字符串,解析字符串,合并同类型,每一部分的功能集中到一个pakage中,实现了pakage之间的耦合性较低,同一个pakage内功能又有较高的专一性。
    • 本设计框架很好的适应了本次作业的三次迭代,没有出现框架不合理到需要重新设计的情况。
  • 缺点:

    • 在表达式化简这一部分主要采用正则匹配和字符串替换,没有做递归解析层面的化简,也没有做提公因式的操作,有待改进
    • 有些方法的圈复杂度过高,没用很好的进行功能上的拆分,尤其是合并同类项中的poly判等方法运用了4层if嵌套

二、架构设计体验

1. 结合三次作业的迭代介绍自己的架构如何逐步成型

  • 第一次作业
    • 第一次作业就觉得很难。我的思考过程如下。
    • 首先是如何解析输入的表达式。一开始想到的方法是使用正则表达式,并且参考了一位学长的方法,但是在阅读了一天的代码之后觉得进展十分缓慢,使用这种方法有可能会耗费大量的时间,因为要考虑非常多的分支,会使得代码难以阅读、理解、维护、扩展。继而思考使用递归下降的方法解析表达式,这种方法让思路豁然开朗,因此很快就完成了由 "表达式->tokens->表达式树" 的代码
    • 然后是思考如何合并同类型,这里主要参考了往届学长的做法。对所有项都建立了通用的形式化表达( Term = a*x^n ), 所以针对Term实现了对应的Mono类,也就是单项式类;然后再实现了Poly类,也就是多项式类。
    • 类图如下:

img


  • 第二次作业

    • 第二次作业难度依旧很大。我的思考过程如下。

    • 首先是如何合并同类项。在第二次作业的形式化表达( Unit = ax^nexp( \sum Unit ) )下,判断两个单项式相等不仅要判断a和n,还要判断exp中的(\sum Unit)是否相等,而每个(Unit)中仍有可能继续嵌套(\sum Unit)。参考学长的做法,以及和同学们思考讨论之后,得出了递归判等的方法。伪代码如下:

      1. 首先对两个Unit的a和n判断是否相等
      2. 然后对Unit中exp()内的Poly判断是否相等
        • 把Poly中的(\sum Unit)映射成一个二级HashMap,第一层Key是x的系数n,第二层Key是exp()内的多项式poly,最后映射到的Value是在这个n和poly下对应的Unit
        • 通过对两个Unit的两层HashMap进行四层的循环遍历判等,如果遇到poly不为空就要递归调用
      3. 设置递归出口
        • 最终递归到Unit的exp()中poly没有含有有效的项,此时就可以只要通过a和x的指数n来判断两个Unit是否是同类项
    • 在完成了( Unit = ax^nexp( \sum Unit ) )的同类项判断之后,在第一次作业的基础上修改用于合并同类项的方法addPoly(),powPoly(),addUnit()即可

    • 类图如下:

img


  • 第三次作业
    • 第三次作业也依旧具有一定难度,这里参考了实验课上的方法。
    • 首先第一个任务就是调整架构,在前两次的作业中Expr、Term和Factor相对独立;但是在第三次的作业中,求导在Expr、Term和Factor上均可发生,如果继续维持前两次作业的架构,那么对于Expr,Term和Factor就要分别设计求导方法,最后toPoly的时候会极其复杂。因此作出的第一个调整就是让所有的entity包中的类都继承Factor接口,然后实现derive()方法。然后根据求导法则实现每种类的derive()方法。
    • 其次就是toString()方法的规范化,之前的作业中我没有对每个类都实现toString()方法,并且也没有使用@Override覆盖自带的toString()方法,这样可能引发一些潜在的bug,因此在这次作业中做了统一的规范化处理。
    • 类图如下:

img


2. 自定一个新的迭代情景,并说明你的最终设计在这个新迭代情景上的可扩展性

  • 新的迭代情景:
    对表达式进行定积分( \int_{0}^{x} )

  • 可扩展性:
    和求导的设计思路类似,对每个继承了Factor接口的类实现一个integral()方法,按照定积分的积分法则对每种情况进行积分即可,比如:

    1. ( \int_{0}^{x} Expr * dx ) = ( \sum \int_{0}^{x} Term * dx )
    2. ( \int_{0}^{x} Term * dx ) = ( \int_{0}^{x} (\prod Factor) * dx)
    3. 再对( \int_{0}^{x} (\prod Factor) * dx)采用分部积分的方法

三、分析自己程序的bug

1. 分析未通过的公测用例和被互测发现的bug:特征、问题所在的类和方法,为什么会出现这样的问题?你能否通过更好的设计避免这样的问题?

  • 第一次作业bug:

    • /
  • 第二次作业bug:

    1. 添加括号

      1. FactorFun返回代入后的表达式需要在外层添加括号
      2. Poly的toString要在两侧加上括号

      这是因为没有考虑到返回的多项式/自定义函数代入后的表达式是可能会作为 "因子" 的,比如作为FactorExp的因子,这时根据括号必要性的要求,必须在外面加上一层括号,否则会导致FactorExp不能正确的解析。
      原因: 对括号必要性的要求理解的不是非常彻底

    2. poly的add的时候,如果unit.getXi()是0的话,就不要add进polys了,保持polys为空

      这是为了维持判断poly判等的递归出口条件,当系数a=0的时候,要清空这个polys列表,才能成功达到递归出口,如果再以a=0的形式加入一个Unit斤polys列表,那么就会一直同类项判等就会递归下去

    3. x的指数会超过Integer

      这是没有注意到第二次作业没有对最终结果的指数加以限制,所以没有把系数的数据类型及时改成BigInteger

  • 第三次作业bug:

    1. exp(FactorExpr)^n的求导法则书写错误
      对求导法则没有完全正确书写,正确的方式是n*exp(FactorExpr)^(n-1)*dx(FactorExpr)
  • 总结:

    1. 要考虑更加全面,尤其是数据方面
    2. 要构造足够多的测试样例,人工构造总是有限的,需要搭建有效的测评机

2. 对比分析出现了bug的方法和未出现bug的方法在代码行和圈复杂度上的差异,并思考可以通过怎样的方式来降低方法的复杂度

  • 三次作业中的bug不因为方法的复杂度产生。
  • 圈复杂度高主要导致了代码写的难度比较大,降低圈复杂度可以降低写出bug的可能性,可以降低出现bug后debug的难度。

四、分析自己发现别人程序bug所采用的策略

1. 列出自己所采取的测试策略及有效性,并特别指出是否结合被测程序的代码设计结构来设计测试用例

  • 第一次作业:
    • 测试策略: 测评机
    • 有效性: 有效
    • 是否根据代码结构设计: 是
      • 第一次搭建的测评机主要分为两个部分:数据生成gene.py和运行程序main.py
      • gene.py根据递归下降解析表达式的思想,分别设计了gene_expr(), gene_term(), gene_factor(), gene_num(), gen_power()等方法,递归地生成测试数据
        并且为了控制 括号嵌套在一层 专门设计了一套gene_expr(), gene_term(), gene_factor(), gene_num(), gen_power()的副本,用于生成不会含有FactorExpr的表达式,以保持括号不会多层嵌套
      • main.py通过比对sympy对测试数据和java程序对测试数据的化简结果,判断次测试数据是否被正确化简

img


  • 第二次作业:
    • 测试策略: 测评机
    • 有效性: 有效
    • 是否根据代码结构设计: 是
      • 第二次搭建的测评及沿用第一次测评机的框架
      • gene.py做比较大的逻辑上的调整
        由于第二次作业中会出现多层括号嵌套,因此可以使用同一套生成方法。
        但是嵌套层数过多会导致递归过深,从而导致生成测试数据的程序被强制停止,因此仍然需要设置嵌套深度,创造递归出口。
        我通过一个嵌套深度的参数来解决这个问题。
        在gene_expr()方法中传递一个参数depth,当调用到gene_factor()的时候depth--,并且继续向下传递;当depth==0的时候需要在gene_factor()禁用gene_factor_expr(),gene_factor_fun()等方法,停止继续嵌套

img


  • 第三次作业:
    • 测试策略: 人工构造数据
    • 有效性: 有效
    • 是否根据代码结构设计: 否
    • 第三次作业没有继续搭建更强大的测评机,因此测试的不够充分,出现了exp求导规则设计错误的bug

五、分析自己进行的优化

1. 你做了什么优化,怎么做到的

  • 架构优化:
    主要是在第三次作业中重新调整了架构,统一实现了Factor接口,便于书写更简洁并且更具有扩展性的代码
  • 表达式化简优化:
    主要通过14个正则表达式的匹配和替换来对最终字符串进行化简
    1. 去除空的exp(因子)
      public class Simplify {
       public static String simplify(String str) {
           // 再化简一下加减号,省略^1和去掉*x^0 1*
           String suf1 = PreProcesser.inputToStd(str);
           String suf22 = suf1.replace("*exp()", "");
           String suf2 = suf22.replace("*exp(())", "");
      
    2. 去除^1
           String suf3 = suf2.replaceAll("\\^1$", "");
           String suf4 = suf3.replaceAll("\\^1\\+","+");
           String suf5 = suf4.replaceAll("\\^1-","-");
           String suf66 = suf5.replaceAll("\\^1\\)",")");
           String suf6 = suf66.replaceAll("\\^1\\*","*");
      
    3. 去除 *x^0 和 [+-]1*
           String suf7 = suf6.replace("*x^0", "");
           String suf8 = suf7.replace("+1*", "+");
           String suf9 = suf8.replace("-1*", "-");
           String suf10 = suf9.replace("(1*", "(");
           String suf11 = suf10.replaceFirst("^1\\*", "");
           String suf12 = suf11.replaceFirst("^-1\\*", "-");
      
    4. 去除所有系数是0的项
           String suf13 = suf12.replaceAll("[+-]0\\*x\\^\\d+", "");
           String suf14 = suf13.replaceAll("^0\\*x\\^\\d+", "");
           String suf15 = suf14.replace("exp((x))", "exp(x)");
      
    5. 考虑字符串可能为空,此时打印"0"
           if (suf15.isEmpty()) {
               return "0";
           } else {
               return suf15;
           }
       }
      }
      

2. 你的优化能否保证你代码的简洁性与正确性?如果能,为什么。如果不能,思考可能的解决方案

  • 可以保证正确性,因为都是做的最表层的字符串替换,不会造成表达式错误
  • 不能保证简洁性,因为做的都是最表层的字符串替换,没有深入解析表达式进行提公因式等操作
  • 解决方案:
    1. 使用Term判等的方法,递归的判断是否可以提公因式,然后化简
    2. 考虑把符号为正的项调整到最前面,以省略一个符号

六、心得体会

1. 本单元学习的心得体会,真情实感

  1. 学习到了分层设计,递归设计的思想
    对于表达式如何解析,一开始觉得难度很大,但是接触到递归下降的方法之后觉得十分有道理;然后一层层地从Expr,到Term,再到各种Factor的情况分层设计相应的解析方法。
  2. 学习到了搭建测评机,评测程序的方法
    对于生成测试数据,判断程序是否正确的评测机搭建技能进行了一定的学习,觉得相比人工制造测试数据要方便很多,覆盖性也更强

七、未来方向

1. 你觉得可以如何修改第一单元的课程,让大家更好的进行第一单元的学习?

  • 可以在给出题目的基础上给同学们几份代码框架,其中有好有坏,然后让同学们去实现多种框架来完成程序的设计,体会不同框架对于同一个问题解决上的差异
...全文
73 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧