2024-OO-Unit1

孙雯新-22373476 学生 2024-03-22 16:56:04

程序结构分析

一言以蔽之,我采用的方法是:基本项+边解析边计算,都是用ArrayList存的

img

详细来说,我最终的架构是:

  • 数据结构:Factor接口下有Expr、Term和Unit三个类
    • Expr:表达式(兼任大家的Poly类),在parseExpr之后就会变成标准的Poly,再toString输出即可
    • Term:项,仅存在于读取过程中。(parseTerm会把Term乘开并化简,所以返回值只可能是Unit或者Expr,不会再存在Term)
    • Unit:基本项,是存储和计算的主力军
  • 化简计算:Operator工具类
    • 预处理方法:preProcess(String input)
    • 计算方法:Mult(Factor A, Factor B)Add(Factor A, Factor B)Deriv(Factor factor)
    • 化简方法:CombineTerms(Expr expr)MultFactors(Term term)
  • 输入解析:Definer、Lexer和Parser类
    • Lexer:只是实验代码的拓展,不再赘述
    • Parser:递归下降解析,每一步解析后都调用Operator中的方法进行计算化简(所以parseExpr.toSting()就是最终的输出结果)
    • Definer:用于存储、替换自定义函数。

优缺点

优点:实现了比较好的归一化访问,整体结构(在我看来)比较清晰

缺点:(为了在上层隐藏细节),底层的方法会包含较多的分支判断,脑子不够清晰的时候非常容易写出bug来,(还好这样的bug也比较好定位)

代码规模

img

可以看到,行数最多的除了工具类Operator,就是基本项Unit了。(主要是由于toString,equals和addable等方法的实现可能不够简洁)

复杂度分析

下面是类的圈复杂度分析。可以看到比较底层的部分复杂度都爆红了(x)也印证了上面我说的缺点。

img

迭代过程

  • 第一次作业

    • 纠结了很久要不要用基本项,最终还是没用 x 当时觉得按照面向对象的思想,不同类型的东西就得分开成不同的类,才比较清晰优雅,就直接写下去了
    • 在计算中采取了incetanceof判断类别,再分类讨论的做法,(例如Factors相乘时,分为$Number \times Number$ 、$Number \times Expr$ 、 $Power \times Power$ 、$Power \times Expr$),每种计算写了一个方法,但是并没有把计算过程抽象出来,耦合在parser的解析过程里面了,非常丑陋
    • 题外话:半天才搞懂接口数组factors到底是个啥东西,但是真的妙哇!因为Factor本身不存储数据,所以设计成接口(个人理解);Expr和Term里用接口版本的多态数组factors可以存储不同类的实例,调用的时候先instanceof判断类别,类型转换之后,再调用对应类的方法
  • 第二次作业

    • 再不用基本项就要分类讨论死了(bushi),想了想用基本项确实在化简合并的时候会方便很多,于是就重构了!(重构时)去除Num、Mono、Exp等,Factor下只保留Expr、Term和Unit类
    • 为了在计算、输出等需要的地方判断Factor的类别,设置了Type属性用于记录,可以通过getType()获取(代替了inctanceof判断,因为Unit底下还分了很多类别,而inctancecof没办法用了)
    • 为了判断指数函数是否相等,就要判断里面的表达式因子是否相等,而表达式因子里面又可能有指数函数……总之重写了equals方法,然后Unit.equals()Expr.equals()反复互相调用,粗暴的解决了这个问题
  • 第三次作业

    • 最快乐的一集,parser加了几行,Operator里写了一个Deriv方法就愉快结束了!
    • 不快乐的是hw2留下的历史遗留bug(x)

新的迭代情景

比如说,加入三角函数。主要的工作是:添加基本项的构成,新增分类Type,Parser里新增解析方法,Operator里新增三角函数相关的计算方法,新增三角函数相关的化简方法。

可以看到,大部分的修改都是新增方法or新增分支,不涉及修改原来的代码和逻辑等,可拓展性还是比较高的。

自己的bug

基本上都是分支的问题)

出现bug最多的是合并同类项方法CombineTerms,因为我在计算过程中会根据因子的不同类型调用不同的方法,所以对于化简方法的返回值要求很高,如果化简不当出现例如Expr嵌套Term嵌套Exp这种情况,后面的计算就会无法进行。

例如在第二次作业,因为我的基本项Unit在不存在exp项的时候默认为null,出现了两个相关bug:1.在某几个地方访问前忘记判断,导致NullPointerException;2. 如果化简之后exp被消掉了,我的exp项存储的内容会是计算结果0,而不是默认值null,但是我在后续计算中,判断Unit类型时,只认为expContent是null的时候才不存在exp项,导致出现了非常大的问题,强测寄完了x (但还是很纳闷,为什么这么严重的漏洞,中测和评测机的数据都没有没测出来)(还是太大意了,应该多跑一点数据的)

后面通过增加判断的方式修好了这些bug,但是还是感觉是在打补丁,实在不是很优雅。可能换一种判断和存储的方式能避免这个问题x 但是暂时没想到有什么很好的改进方法。

发现别人程序bug所采用的策略

  • 尝试构造一组对自己代码行覆盖率接近100%的数据,一定程度上能保证这个数据涉及了可能出现bug的多个位点

  • 保存对自己代码进行测试的时候,出现bug的数据

  • 尝试在cost范围内手搓构造压力比较大的数据

  • 并没有根据被测数据的代码来设计数据,主要是考虑进入互测的同学,代码的bug都隐藏的比较深;再加上本人阅读代码的效率并不是太高,这种方法的成本就有点太大了(虽然也读了一些同学的代码,但是都是本着学习的本意去读的,也没看出来bug)

自己的优化

  • 调整表达式顺序,如果存在正数,使得第一项总是正数(可以节约一个负号)
  • 合并同类项到最简
  • 将exp(0)、x^0等计算为1
  • 对exp,保证括号层数最少

能保证简洁性和正确性,因为我的优化比较少(划掉),因为我没有选择风险大的优化,对目前新增的优化可能出现的问题也做了充分测试。

心得体会

  • 评测机相关

    分享评测机的大佬们yyds!!!!!!!!!!特别真情实感

    不过,我觉得还是不能过于依赖评测机。在hw3中我出现了几个bug,跑了很多组评测数据也没有测出来,(因为之前都是无脑面对评测机数据debug),自己debug的时候就挺不知所措的 x 最后自己写junit多错了几组数据,把覆盖率堆上去之后就发现bug了(虽然bug不是数据测出来的,而是在堆覆盖率过程中看代码看出来的)

  • 再次非常感谢写博客的学长学姐 ORZ 以及课程组的引导,少走了很多弯路

  • 写hw3的时候一直有一个很纠结的点,就是我的本能情感真的不是很想用基本项()我觉得一定有不用基本项,也可以写的很优雅的架构,但是显然我的不够优雅,也不知道怎么才能实现 x

未来方向

或许可以给一个官方的学长学姐blog推荐(?然后也做一些不同架构的分析和讲解?

不过现在的学习体验感已经很好了(确信

OO,快乐的OO!

...全文
78 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

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

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