BUAA-OO-Unit1总结
程序架构

度量分析
| Class | OCavg | OCmax | WMC |
|---|---|---|---|
| MainClass | 3.00 | 3 | 3 |
| ast.DerivativeFactor | 1.50 | 2 | 3 |
| ast.ExpFactor | 1.50 | 2 | 3 |
| ast.Expression | 1.50 | 2 | 3 |
| ast.ExpressionFactor | 1.00 | 1 | 2 |
| ast.FunctionFactor | 1.00 | 1 | 2 |
| ast.NumberFactor | 1.50 | 2 | 3 |
| ast.SelectorFactor | 1.50 | 2 | 3 |
| ast.Term | 1.33 | 2 | 4 |
| ast.VariableFactor | 1.50 | 2 | 3 |
| core.FunctionDef | 1.40 | 3 | 7 |
| core.Preprocess | 2.00 | 2 | 4 |
| core.Process | 7.00 | 7 | 7 |
| lexer.Lexer | 3.80 | 9 | 19 |
| lexer.Token | 1.50 | 3 | 6 |
| lexer.Token.TokenType | 1.00 | 1 | 2 |
| parser.Parser | 2.62 | 9 | 34 |
| poly.Poly | 4.67 | 13 | 56 |
| poly.TermKey | 2.00 | 6 | 14 |
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| core.Process.process(String, String[], String) | 11 | 1 | 8 | 9 |
| lexer.Lexer.parseLetterTokens(char) | 22 | 7 | 11 | 15 |
| poly.Poly.derivative(char) | 19 | 1 | 10 | 10 |
| poly.Poly.mul(Poly) | 18 | 1 | 9 | 10 |
| poly.Poly.printPow(StringBuilder, TermKey) | 12 | 1 | 8 | 9 |
| poly.Poly.toString() | 28 | 6 | 19 | 22 |
(由于方法过多,此处仅展示CogC > 10的方法)
其中v(G)为圈复杂度,衡量程序中线性独立分支数量(即if、for、while、switch分支越多,该值越高), iv(G)衡量该方法与其他方法间的调用复杂度, ev(G)衡量非结构化复杂度(即break、continue等越多,该值越高) CogC衡量理解起来的困难程度, OCavg衡量类中所有方法的平均复杂度 OCmax衡量最大的方法复杂度, WMC为类中所有方法的v(G)之和。
据此分析,如上方法的圈复杂度过高,除去toString、parseLetterTokens等需要对多种不同情况进行判断的方法,其他方法的复杂度可以进行优化。Poly、Parser等类的复杂度也过高,可以进行职能的删减。
架构设计体验
在第一次作业中,即确定了核心框架(底层Poly类负责运算与输出,Lexer与Parser负责分词与递归下降解析,Core类负责预处理并提供处理入口,MainClass直接与Core交互),并在接下来的迭代中没有经历大规模的重构。在第二次作业中,为实现exp,将Poly类的底层数据结构从存储指数和系数更改为存储系数和Termkey(自定义类,存储x指数和exp多项式,即递归存储),并在其他类中使用了该数据结构的地方进行修改。如果此处对数据结构相关运算做进一步包装,可以减少修改量。在第三次作业中,对Termkey进行更改以适配自变量y,并在底层加入求导运算。
bug分析
在第二次作业中,选择表达式中未被选择的一项如果是多层嵌套f(x),且f(x)本身过于复杂的话,程序会超时。这是因为尽管我在解析的时候先进行选择判断,不去对不选择的分支进行String->Poly的解析,但是由于我对f(x)的做法是预处理直接替换,过长的嵌套导致的不必要的递归替换的效率极低,因而出现超时。此bug在强测及互测中均未被发现,因室友被该测试点hack并探讨原因而发现。此bug是算法决策的问题,与复杂度、耦合度无关。
分析bug策略
利用AI辅助搭建了评测机,但是实测效果并不好,因此放弃。主要还是通过朋友分享和水群分享的思路构造评测点,以及自己在前一次作业中出现的bug(相信一定有人没被测出来)。由于很多bug都是共性问题,所以这些方法确实成功hack。
优化分析
由于本人能力有限,并不会高深的优化算法,担心自己的优化(哪怕是AI辅助)可能带来新的bug,并考虑到优化未必一定得到长度更短的答案,却要付出可能带来的性能下降的代价,因此除去第一次作业的正项提前,没有进行任何优化。
大模型使用
在第一作业中,由于本人对抽象语法树和递归下降一头雾水,因此使用AI辅助理解搭建架构。后续作业使用AI辅助理解题意和需要更改的内容,并让AI帮我做一些杂活(比如把原有的<int, BigInteger>改为<TermKey, BigInteger>时的多处适配工作),以及部分职责明确、算法固定的类的生成(比如快速幂运算)。
与此同时,使用AI搭建评测机、生成数据点、分析bug。但是本人在使用过程中发现,使用AI辅助理解题意,与AI分析bug时考虑的边界情况几乎相同,也就是说,如果存在他没有考虑到的点,他也很难在bug分析中找到。恰好昨天Claude-Code源码泄漏,网上有人分析其中的Prompt工程,发现其使用了很多Prompt限制AI的答案,其中就有设置挑错Agent,或许之后可以借鉴。
心得体会
我觉得本单元最大的困难其实是第一次作业,递归下降已经不太会写,又没有学习过ast相关知识,面对复杂的形式化表述,脑子一团乱,不知道从何下手。或许没有AI,我真的就会写出一大坨屎山代码,然后在后面的作业继续无从下手,甚至可能选择全部重构,推倒重来。在建立了基本框架之后,后面的作业写的就舒服多了,至少脑子是清楚的知道我在干嘛的。
未来方向
或许未来如何平衡AI和古法编程,如何在拥抱AI的同时能更好地学到知识,是我们每一个人应该探索的课题。