2026_OO_Unit1 博客总结

俞博文-23373081 2026-04-01 22:44:20

2026_OO_Unit1 博客总结

程序架构度量分析

UML类图

img

img

img

Main Structure

类名主要属性主要方法
MainClassmain(String[] args)
ProcessorexpressionFormatter: ExpressionFormatterprocess()

Preprocessing

类名主要属性主要方法
Preprocessorpreprocess(String input)
ProgramInputnormalFunctionLines recursiveFunctionDefs expressionLinegetter 系列

Parsing & AST

类名主要属性主要方法
Parserlexer: LexerparseExpr() parseNormalFunctionDefinition() parseFactor()
Exprterms: ArrayList<Term>addTerm() toPoly(FunctionProcessor, Poly)
Termfactors: ArrayList<Factor> signal: intaddFactor() toPoly(FunctionProcessor, Poly)
FunctionDeffunctionName parameterName body: Exprgetter 系列
RecursiveFunctionDefbaseZeroLine baseOneLine recurrenceLinegetter 系列

Factors

类名主要属性主要方法
Factorexp: BigIntegergetExp() setExp() toPoly()
NumFactorbase: BigIntegertoPoly(FunctionProcessor, Poly)
VarFactorbase: StringtoPoly(FunctionProcessor, Poly)
ExprFactorbase: ExprtoPoly(FunctionProcessor, Poly)
ExpFactorbase: FactortoPoly(FunctionProcessor, Poly)
FuncCallFactorfunctionName argument recursiveIndextoPoly(FunctionProcessor, Poly)
ChoiceFactorleft/right/trueBranch/falseBranchtoPoly(FunctionProcessor, Poly)
DeriveFactoroperatorName baseExprtoPoly(FunctionProcessor, Poly)

Lexer

类名主要属性主要方法
Lexertokens: ArrayList<Token> index: intgetCur() next()
Tokentype: TokenType content: StringgetType() getContent()
TokenType词法枚举

Midend

类名主要属性主要方法
Polymonos: ArrayList<Mono> hash: intaddMono() addPoly() mulPoly() powPoly() scale() deriveByX() deriveByY() grad()
Monocoe xExp yExp expArggetter 系列 sameLiteralPart() clone()
FunctionProcessornormalFunctionDef recursiveFunctionDef recursiveCacheevaluateNormalFunction() evaluateRecursiveFunction()

Backend

类名主要属性主要方法
ExpressionFormatterformat(Poly) formatExpArgumentOnly(Poly)

主要继承与关联关系

类/模块关系说明
Parser -> LexerParserLexer 中读取 token 流
Expr -> TermExpr 由多个 Term 组成
Term -> FactorTerm 由多个 Factor 组成
Factor -> 各具体因子子类各具体因子统一继承 Factor
FunctionDef -> Expr普通函数定义右部保存为 Expr
RecursiveFunctionDef -> String递推函数定义先保留三行原始文本
Poly -> MonoPoly 由规范化后的若干 Mono 组成
Mono -> PolyMono 通过 expArg 引用指数函数参数
FunctionProcessor -> FunctionDef/RecursiveFunctionDef函数处理器统一处理普通函数与递推函数
ExpressionFormatter -> Poly/Monobackend 负责最终字符串输出

度量数据

核心度量结果
复杂度最高的几个类如下:

字段方法LOCWMCCBO(近似)结论
ExpressionFormatter119288672输出规则集中点,内聚高,但复杂度最高
Poly228281601中间表示核心类
Lexer29159420局部复杂度高,主要集中在分支识别
Mono516175390承担了部分格式化责任
Parser1172083714负责构造 AST 数
FunctionProcessor1214213376统一函数处理器,递推解析集中点
Processor1101442810流程控制类

规模和分支最突出的几个方法:

  • Lexer.tryTokenizeSingleChar():45 行,16 个分支
  • FunctionProcessor.parseRecurrenceDefinition():42 行,4 个分支
  • ExpressionFormatter.buildGreedyExpPlan():31 行,4 个分支
  • Poly.addMono():28 行,5 个分支
  • Parser.parseFactor():24 行,7 个分支

架构设计体验

总体架构设计

我代码工作的主流程可以概括为:

  1. Processor 读取 n 个普通函数定义、m 组递推函数定义和最终主表达式。
  2. Preprocessor 对所有输入行进行空白和连续正负号归一化。
  3. Parser 解析普通函数定义和主表达式,完成前端建树。
  4. FunctionProcessor 统一接收普通函数定义和递推函数定义,并在 midend 中承担函数调用处理。
  5. AST 节点通过 toPoly(FunctionProcessor, Poly) 递归求值为规范化的 Poly
  6. Poly/Mono 负责代数运算、单项合并、偏导、递推展开后的结果表示。
  7. ExpressionFormatter 在 backend 中对最终 Poly 进行字符串格式化和显示级优化。

输入 -> 预处理 -> 词法 -> 语法 -> AST -> midend 求值 -> backend 输出

Homework1

我参考了 s7h 同学在博客中的架构设计,设计的总体流程按4步走,分别是:预输入处理 -> 语法解析 -> 合并项 -> 生成并输出字符串。

预输入处理

预处理模块到的功能是:

  1. 去除空白字符:.replaceAll("[ \t]", "") / .replaceAll("\s+", "");
  2. 合并连续的加减号。

语法解析

解析模块按照 parseExpr -> parseTerm -> parseFactor 建立。将 token 解析成 ast 语法树。

Expr   ::= Term {( '+' | '-' ) Term} 
Term   ::= [ '+' | '-' ] Factor { '*' Factor } 
Factor ::= NumFactor | VarFactor | ExprFactor 
NumFactor  ::= [ '+' | '-' ] NUMBER 
VarFactor  ::= VARIABLE [Exp] 
ExprFactor ::= '(' Expr ')' [Exp]
Exp        ::= '^' ['+'] NUMBER | ε 

合并项

通过 ast 节点的 toPoly 方法,将 ast 数转化成展开中间表达式,并在构建的过程中,实现规范化和合并。

生成并输出字符串

通过中间表达式的 toString 方法生成表达式并输出。

Homework2

新增指数函数因子

工程中使用 ExpFactor 表示指数函数因子。

  1. Parser 遇到 exp( 时会建立 ExpFactor 节点;
  2. ExpFactor.toPoly() 先递归求内部因子得到 Poly
  3. 若内部结果为 0 或外部指数为 0,则直接返回常数 1
  4. 否则构造 Poly.exp(inner.scale(exp))

在中间表示层,Mono 用一个可空的 expArg 表示规范化后的指数函数参数。

新增自定义函数

f(实参) 求值时,逻辑上并不需要修改函数体本身的结构,而只需要在函数体求值时临时规定:

x = 实参对应的表达式值

然后再用这个绑定后的语义环境去求整个函数体。

因此:

  1. exp(a)^n 会在 ExpFactor.toPoly() 中求值为 exp(n*a)
  2. exp(a) * exp(b) 会在 Poly.mulPoly() 的单项乘法中自动折叠为一个新的 expArg = a + b

新增选择表达式因子

选择式的抽象求值过程可以写成:

  1. 分别求出 AB 的结果;
  2. 计算 (A-B)
  3. 若结果为 0,则选择 C
  4. 否则选择 D

工程中使用 ChoiceFactor 表示选择式因子。

  1. Parser 在读到 [(A==B)?C:D] 时建立 ChoiceFactor 节点;
  2. ChoiceFactor.toPoly() 先分别调用 left.toPoly(context)right.toPoly(context)
  3. 再计算 difference = leftPoly.addPoly(rightPoly.negate())
  4. difference.isZero(),则返回真分支的 toPoly(context)
  5. 否则返回假分支的 toPoly(context)

Homework3

本次迭代的核心目标,是在 Homework2 的表达式系统上继续扩展三个能力:

  1. 将变量系统从单变量 x 拓展到双变量 x/y
  2. 新增偏导与梯度算子 dx/dy/grad
  3. 新增自定义递推函数 f{n}(x)

变量拓展

在 Homework2 中,一个单项 mono 可以抽象为:

coe * x^a * exp(f)

而在 Homework3 中,一个单项 mono 应被抽象为:

coe * x^a * y^b * exp(f)

因此,除了拓展前端的 token 定义和 ast 节点属性,更主要的是在中间层的 poly 和 mono 的运算、展开以及合并的处理上,做出以下的改变。

  1. Poly.addMono() 的合并条件从“x 指数和 expArg 一致”变成“xExpyExpexpArg 同时一致”。
  2. Poly.mulPoly()powPoly()equals()hashCode() 等基础代数运算全部围绕新的双变量结构重写。

导数运算实现

本次迭代中,求导功能的核心设计思想是:求导属于表达式的语义运算,而不是语法节点的行为。

因此,我的设计思路是:

  1. 先把待求导表达式整体求值为统一的中间表示 Poly
  2. 再对 Poly 进行偏导运算。

这样,dx(E)dy(E)grad(E) 就都变成了对中间表达式的进一步运算,而不是在初始语法树进行直接改写。

新增自定义递推函数

普通函数 f(x) 可以看作“只有一层函数体、没有序号依赖”的特殊递推函数定义。所以我在实现上选择普通函数和地推函数共享同一个函数处理入口,都由函数处理器负责形参绑定和函数体求值,只是递推函数多了自定义递推模板处理和求值展开。

Bug分析

Homework1 未出现 bug。

Homework2 的 bug 主要在性能优化不足导致的超时上,在下方的优化分析部分统一分析。

Homework3 出现的bug如下:

  1. FunctionProcessor.java 之前会把第二个递推项后面的第一个 +/- 单独拆出来,再把剩余部分整体取反,这将 -exp(x)^8+exp(x) 误解成 -(exp(x)^8+exp(x))。现在改成从正负符号开始把整段都按一个 Expr 解析,求值时直接加回去。
  2. ExpressionFormatter.javaexp((x*y)) 错打成 exp(x*y)

优化分析

Homework1 能做的只有展开、合并以及正数提前这些减少字符串长度的优化,并未在性能上有太大开销。

而到了 Homework2 以及 Homework3 我的代码频繁触发超时,因此,我做出了以下优化:

在 Homework2 以及 Homework3 实现过程中,实际暴露出的性能 BUG,是中间表示对象复制过程中的重复深拷贝。

问题触发点来自深层嵌套普通函数,例如:

  1. 普通函数体中存在多层 exp(exp(...))
  2. 主表达式继续多次嵌套调用该普通函数。

在这种情况下,Poly.clone() 会递归触发 Mono.clone(),而 Mono 又可能持有深层 expArg。如果复制链条中对同一层 expArg 做了重复深拷贝,那么实际拷贝量会呈指数级放大。

下面为优化方式:

  1. 保证 Mono.clone() 只保留一次必要拷贝。
  2. 让构造函数承担唯一一次化复制。
...全文
34 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

302

社区成员

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

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