BUAA-2026-OO U1总结

李欣桐-24373274 2026-03-27 08:33:25

程序结构整体分析

架构内容

整体采用以下思路:

  1. 由lexer将input拆分为Tokens
  2. 由Parser将Tokens解析为AST
  3. 由AST各组成成分的内部方法将AST转化为Fxpr
  4. 由Outputter将Fxpr输出

拆分与解析

img

AST组分

img

输出Fxpr

img

img

待改进缺点

目前的输入输出还是太过笨重:

  • 输入直接在MainClass里实现,主类同时承担了读入、调度和程序入口等职责,模块划分不够清晰。若后续输入格式继续扩展,主类容易变得臃肿,也不利于单独测试输入处理逻辑。
  • 输出部分虽然能够完成结果生成,但当前Outputter承担了较多格式判断工作,例如括号是否省略、exp内部结构如何输出等,导致这一模块实现较复杂,也成为bug的高发区域。

复杂度

Method Metrics

methodCogCev(G)iv(G)v(G)
==Average==1.171.391.591.88
  • 经检查,基本每个方法都满足模块设计复杂度(iv(G)小于v(G)),且整体复杂度都较低

Class Metrics

ClassOCavgOCmaxWMC
==Average==1.742.899.82
io.Outputter2.757.033.0
  • 整体来看,WMC处于中等水平
  • 但Outputter的WMC明显过大,这也刚好反映了之前分析的Outputter承担功能太多的问题

架构设计体验与优化

迭代1-2

  • factorParser()的优化

    //Parser
    public Factor parseFactor() {
        for (FactorParser p : FactorParserRegistry.DEFAULT.parsers()) {
            if (p.canParse(lexer)) {
                return p.parse(this);
            }
        }
        throw new IllegalArgumentException("Unexpected token in factor: " + peek());
    }
    
    • 在第一次作业中,Parser是一个含有exprParser()/itemParser()/factorParser()的巨类,且factorParser()内部的if-else结构导致该方法需要反复更改,且逐渐笨重。
    • 因此我引入了FactorParserRegistry,引入新因子时,只需要注册新FactorParser即可,这增加了程序的可拓展性。
  • 由Poly到Fxpr

    • 第一次作业到第二次作业迭代,单项式结构由a*x^b变为a*x^b*exp(c),带来原先的PolytoPoly()方法的重写(变为FxprtoFxpr()
  • 自定义函数
    -==替换机制==:先替换,再和原来一样化简Fxpr

    • 在AST每层都设置substitute方法
    • 真正replace的地方:ParamFac.substitute(arguments)
      • ParamFac:占位符,在读入函数定义时使用

迭代2-3

  • Mono的进一步迭代
    由于变量因子的扩展,原先只记录单指数b的设计不够用了,于是拓展为HashMap记录各变量对应指数

    HashMap<String, BigInteger> newVars = new HashMap<>(this.varPowers);
    
  • 新增求导算子

    • input to AST:正常增加新因子即可解析
    • AST to Fxpr:需要为各个组成部分统一增加toDiff()方法
  • 自定义迭代函数

    • 整体思路类似第二次作业中的自定义函数

    • 问题:普通函数本质是单次参数替换,保留 AST 很自然;递推函数本质是多层展开求值,直接走 Fxpr 更方便。

      • 普通函数 FunctionDef.instantiate(...) 继续返回 Expr
      • 递推函数 RecursiveFuncDef.expand(...) 直接返回 Fxpr

      但是,这样会引出返回值不统一的问题,例如:

      • RecFuncCallFac(递归函数式因子,继承了Factor)在替换后已经被转换为Fxpr
      • Factor接口原本定义的方法都需要返回Factor
      • 但Fxpr无法被反向转换为AST,因此无法实现这些需要Override的方法

      解决:将Fxpr也包装成Factor(即:新建一个FxprFac类)

自定义迭代

  • 新迭代场景:现有课设要求仅为1个自定义函数/迭代函数,实际上完全可以引入不同自定义函数
    • 可拓展性:已经设计了自定义函数表(FunctionTable),调用即可

bug分析

自己程序的bug

三次迭代下来,我的bug基本都集中于Outputter对exp括号内输出格式的处理上

  • 改变了Mono的形式,但却没有随之改变Outputter中判断是否需要加括号的函数
    • 于是出现:exp(x*y)/exp(x*exp(x))这样的错误

他人程序的bug

没有发现太重的错误,和自己一样多为输出格式错误,例如把exp(1)直接按e输出

大模型使用

  • 代码使用情况:
    • Outputter和某些具体方法(比如求导)借助了大模型的实现
  • 代码之外
    • 互测时,使用agent找bug十分高效
    • 尝试过使用大模型搭建评测机,但是搭建效果一般

心得体会

在理解了递归下降解析法的前提下,第一单元的设计并没有太难理解的地方。设计好拆分-解析-输出的大框架后,后续两次迭代虽然增加了不少功能,但总体上还是在原有架构基础上进行的,没有大规模重构。所以说,在上手码代码之前,一定要明确自己是在干什么、要实现什么,而不是一头雾水地就开始赶作业敲键盘了。

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

302

社区成员

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

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