OO第一单元博客总结

周执竞-24373069 2026-04-01 23:01:47

第一单元 OO 总结

1. 基于度量的程序结构分析

在这三次作业中,我的代码架构发生了颠覆性的变化。以下分别对三次作业的代码规模与复杂度进行度量分析。

1.1 第一次作业 (HW1)

  • 架构特征:此时的架构完全基于 HashMap<Integer, BigInteger> 进行单变量多项式的运算,没有引入真正的词法与语法分析,且没有使用真正的 Lexer,解析强依赖于 String.substring
  • 类图如下:

img

  • 各类的规模与控制分支复杂度如下表所示:
类名属性个数方法个数平均方法规模(LOC)最大控制分支数 $v(G)$ (对应方法)类总行数
MainClass015 行1 (main)10 行
Operator0511 行4 (mutiply)55 行
Parser1924 行18 (processSigns)220 行
  • 内聚与耦合情况具体分析:

1. 类的内聚性

  • **Operator (高内聚)**:这是我第一次作业中最具“面向对象”思维的类。它将多项式严格抽象为 HashMap<Integer, BigInteger>,所有的方法(plus, minus, mutiply, power)都严格围绕这个数据结构进行纯代数运算,没有任何越界操作。
  • **Parser (低内聚)**:内聚性较差。它承担了太多本不属于它的职责:
    1. 它是预处理器(包含了 removeWhitespace, processSigns 等逻辑)。
    2. 它是词法/语法分析器(通过游标和 substring 强行分割 Expr, Term, Factor)。
    3. 它是输出格式化器(solve 方法负责极其复杂的打印逻辑)。

2. 类的耦合度

  • **ParserOperator (单向数据耦合)**:Parser 依赖 Operator 进行数学计算。这是一个相对健康的耦合,Parser 只负责将字符串拆解为 HashMap,然后扔给 Operator 处理,获取结果。
  • **Parser 内部逻辑 (高度耦合)**:在 parseExprparseTerm 中,我通过手动维护 pos 游标、括号计数器 flag 以及起始点 start 来进行字符串截取。这种解析逻辑与字符串的绝对位置绑定,一旦遇到异常符号,极易发生 StringIndexOutOfBoundsException

1.2 第二次作业 (HW2)

  • 架构特征:引入了 Lexer ,彻底重构了真正的词法和语法分析,并将第一次作业的预处理操作和强行展开自定义函数的方法融为了 Preprocess 类。将之前完全基于 HashMap<Integer, BigInteger> 进行的运算,改为了HashMap<Factor, BigInteger>,并包装为了一个新的类Expr,与Factor类递归嵌套。

  • 类图:

img

  • 各类的规模与控制分支复杂度如下表所示:
类名属性个数方法个数平均方法规模(LOC)最大控制分支数 $v(G)$ (对应方法)类总行数
Lexer3412.06 (next)48
MainClass0119.02 (main)19
Parser1735.718 (solve)250
Preprocess1626.512 (processSigns)159
Expr11215.49 (plus)185
Factor368.05 (equals)48
  • 内聚与耦合情况具体分析:
  • 1. 类的内聚性*

内聚性衡量一个模块内部各个元素彼此结合的紧密程度。本项目整体达到了较高的功能内聚通信内聚

  • Lexer(功能高内聚):

    • 该类职责非常单一,仅负责将原始字符串输入转化为 Token 流。没有掺杂任何逻辑计算或语法解析,高内聚。
  • Preprocess(高内聚):

    • 负责对输入字符串进行清洗(去空格、处理连续正负号、函数调用替换)。
    • 类中所有方法(removeWhitespace, processSigns, functionCall)都操作相同的输入数据流。虽然处理逻辑多样,但目标一致,为后续解析提供规范化字符串。
  • Expr & Factor(高内聚):

    • Expr 类维护 HashMap<Factor, BigInteger>,而 Factor 描述因子的具体属性,x的幂、exp项、嵌套表达式。
    • 这些类不仅存储数据,还包含了与该数据紧密相关的操作,如 Expr.plus(), Expr.isSimple()
  • Parser:(中内聚)

    • Parser 类通过递归下降算法,parseExpr, parseTerm, parseFactor,实现了从 Token 到表达式对象的转换。
    • 高度依赖 Lexer 的输出,执行步骤具有严格的顺序性。

2. 类的耦合度

  • MainClass 与各组件:(低数据耦合)

    • MainClass仅负责实例化对象并按流程调用方法(preprocess -> lexer -> parser)。
    • 不直接干预解析细节,类间交互主要通过简单的字符串或对象传递完成,耦合度较低。
  • Parser 与 Lexer:(中耦合)

    • Parser 内部有一个 Lexer 引用。Parser 的解析逻辑完全依赖于 Lexer 提供的 peek()next() 接口。
    • 但如果 Lexer 的 Token 定义发生重大变化,Parser 必须同步修改。
  • Expr 与 Factor:高耦合,递归依赖

    • Expr 由多个 Factor 组成,而 Factor 内部可能又包含一个 Expr用于处理exp(...)

1.3 第三次作业 (HW3)

  • 架构特征:抛弃预处理类,抛弃直接替换自定义函数字符串,完全依赖递归下降和基于 HashMap 的标准化多项式存储(更名为Poly + Mono)。支持双变量和递归函数记忆化。

  • img

  • 度量数据表

类名属性个数方法个数平均方法规模(LOC)最大控制分支数 $v(G)$ (对应方法)类总行数
Lexer341510 (next)65
MainClass46105 (main)63
Mono37126 (derive)69
Parser4152014 (parseFactor)277
Poly1141510 (isSimple)165
  • 类的内聚性MonoLexer 达到了极高的内聚。Lexer 只负责游标移动,Mono 只负责封装单项式的基本属性(指数与嵌套的 Poly)并提供微观求导法则。它们不关心外界的逻辑。
  • 类的耦合度Parser 的耦合度依然偏高。它不仅需要调用 Lexer 获取信息,还要实例化 Poly,并在解析函数因子ff{n}时递归实例化新的 Parser

2. 架构设计体验与迭代演进

2.1 重构体验

在第一次作业早期,我就直接使用了 HashMap<Integer, BigInteger> 来映射多项式。它可以摆脱了同类项合并的文本处理难题,但整个系统的脆弱性全部集中在 processSigns 方法中,我试图在“解析之前”用纯文本替换的方式解决所有符号优先级和结合律的问题,这种 “面向过程的暴力破解” 违背了树形结构的解析规律。没有 Lexer 充当缓冲层,导致第一次作业的 Parser 在处理 +- 时,无法区分它们到底是“加减运算符”还是“正负号因子”,最终导致逻辑重构。
第二次作业通过 Lexer 词法分析与 Parser 语法分析的分离,遵循了经典结构。
Factor 类中包含了对 Expr 的引用,能够通过递归下降法处理嵌套括号和 exp() 内部的复杂表达式。使用 HashMap<Factor, BigInteger> 作为多项式的存储结构,简化了项的合并逻辑。但是Parser.solve 中包含了过多的打印优化逻辑,并且Preprocess 中的字符串频繁 replacedelete 在极端数据下可能有性能瓶颈。
HW2 最大的教训是,只基于字符串替换的架构在面对递归与嵌套时是不堪一击的。
在 HW2 中,为了处理多层函数调用(如 $f(f(f(x)))$),我原本的 Preprocess 类会将表达式展开成难以置信的长度,导致严重超时TLE。痛定思痛后,我在 HW3 彻底删除了 Preprocess
第三次作业再次重构后,函数调用变为“运行时解析”。当 Parser 遇到 f 时,实例化一个新 Parser 去解析函数定义式,并将当前解析出的实参 Poly 传递进去。遇到自变量 $x$ 时,直接返回该实参 Poly。这种“变量代换”的思想不仅彻底解决了 TLE,还让代码量不增反降,逻辑变得异常清晰。这让我感受到良好的对象模型本身就是最佳的性能优化。

2.2 新迭代情景与可扩展性

假定新情景:引入符号积分算子 int(Expr, var)(假设仅对初等多项式进行积分,忽略无法解析的 $\exp$ 积分)。
扩展性评估:现有的 Poly + Mono 架构具有极强的扩展能力。

  1. 只需在 Lexer 增加对 int 的识别。
  2. Parser.parseFactor 中新增一行 parseIntegralFactor
  3. PolyMono 中增加 integrate(char var) 方法。利用现成的 HashMap 遍历,针对对应变量的指数进行 $+1$ 即可。整个过程完全不需要侵入或修改现有的加减乘规则。

3. Bug 分析与深层思考

3.1 Bug 复盘

  1. HW2:Lexer 符号解析陷阱
    • 特征与定位:抛出解析异常。问题出在词法预处理与 Lexer 的交互上。由于预处理时专门把因子前的符号想处理掉而又漏了情况,导致出现类似 ^+ 反而无法处理的非法序列,使因子解析崩溃。
    • 根因:职责划分不清,字符串预处理污染了原本该由 Lexer 严格控制的 Token 流。
  2. HW2:贪婪解析导致的 TLE 问题
    • 特征与定位:强测与互测中出现 Time Limit Exceed。问题在 Parser.parseChoiceFactor
    • 根因:在处理 [A==B?C:D] 时,无论条件是否成立,代码都将 CD 完整解析为多项式对象后再做选择。若废弃分支极为复杂,则浪费极大算力。
    • 修复:改为惰性解析,条件判断后,直接用 while 循环和 lexer.next() 跳过无用分支的代码流。
  3. HW3:多变量 exp 漏加括号
    • 特征与定位:$\exp(x*y)$ 被错误优化为 exp(x*y),缺少必要的内层括号。问题在 Poly.isSimple() 方法。
    • 根因:在 HW2 时我写了去括号优化逻辑(单项自变量不需要括号)。但 HW3 引入变量 $y$ 后,我没有同步收紧该判断条件,导致 $x \cdot y$ 这种并非“单一因子”的项被误判。

3.2 复杂度差异与降低策略

  • 代码行与圈复杂度差异:出现 Bug 的 parseFactorisSimple 方法,其圈复杂度(v(G))均超过 10,代码行数逼近 50 行,内部充斥着嵌套的 if-else。相反,未出现 Bug 的 Poly.multiplyMono.derive,复杂度均低于 5,且逻辑高度线性。
  • 策略:对于 parseFactor 的高复杂度,应当引入多态或工厂模式。定义一个 Factor 接口,然后让 ExprFactorVarFactorDeriveFactor 各自实现 parse 逻辑。解析器只需查表或使用 Map 映射决定调用哪个具体的子类去解析,从而彻底解决庞大的 if-else 树。

4. 互测策略分析

在互测中,我主要采用了以下策略:

  1. TLE 压力测试:结合我自己修复 Bug 的经历,我专门构造了深层嵌套的三目运算 以及多层递推函数 $f(f(f(x)))$。这样来试图hack房间里部分依然在用字符串替换、且没有做惰性解析的同学。
  2. 优化逻辑针对性测试:很多 Bug 都是“过度优化”带来的。我阅读同房间代码时,专盯负责 print 优化的模块。如果看到对 $\exp$ 内部括号判断逻辑写得含糊的,直接构造边缘用例进行测试。
  3. 符号组合测试:通过随机编写带有前导零、连续正负号交替的项(如 ---+++002*x),测试对方 Lexer 的鲁棒性。

5. 优化策略

  1. 运算时优化:在 Poly.plus/minus 中,计算完后立刻判断系数是否为 BigInteger.ZERO 并将其从 HashMap 移除。这一优化保证了简洁性与正确性,因为它让多项式永远处于最简标准型,减少了后续运算与打印的遍历负担。
  2. 解析时优化(惰性求值):三目运算符的短路跳过。保证正确性,提高性能。
  3. 输出时优化(精简括号、正号项提前,系数1简化等等):括号精简用 isSimple 方法,省去 $\exp$ 内部冗余的括号。正号项提前需要先遍历一遍找到大于0项,先输出。

6. 大模型相关使用

6.1 AI 代码生成率

基本都是0%,自己写的,要不然不会把架构写成一坨屎

6.2 辅助测试与开发评价

我使用了 AI 辅助度量分析代码复杂度,帮忙给出了度量数据。并用大模型辅助找bug,但是最终效果不好,真正找到bug是在强测和互测中被发现。
效果评价:大模型辅助分析程序结构,给出度量数据,极大地提高了我复盘架构效率。然而,在辅助解决 Java 代码中深层递归状态的 Bug 时,AI 往往只能泛泛而谈,最终仍需我自行断点 Debug。

6.3 互测房间的AI 观察

不少同学都有疑似AI生成的注释,但是忘了谁了,懒得把代码下载回来找了


7. 心得体会

第一单元是一场面向对象思维的洗礼。最初试图用面向过程的字符串替换思维,结果被 TLE 击垮。反复痛心删掉数百行旧代码进行彻底重构,到最终看着控制台准确输出表达式,有着巨大成就感。

OO 不能只是纸上谈兵的“封装、继承、多态”,而需要在面对需求爆炸、逻辑嵌套时,善用面向对象思维。主动思考解耦,提高扩展性,试图提高性能。需要我们对数学规律和思维逻辑的深刻建模。


8. 未来方向建议

建议明确性能优化的边界:许多同学为了微不足道的性能分,写了大量晦涩、极易出错的去括号逻辑,导致正确性崩盘。建议在指导书中明确限定“哪些化简是鼓励的,哪些是不必要的”,避免大家陷入无效的文本优化内卷。

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

302

社区成员

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

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