BUAA OO Unit1 Summary

郭兆丰-24373163 2026-03-28 16:04:06

代码分析

代码架构解析

总体来说,代码分为 4 个模块:词法分析,语法分析,计算,输出。其中,词法分析模块负责将输入的字符串转换成一个个的 token,语法分析模块负责将 token 解析成一个抽象语法树,计算模块负责根据抽象语法树计算出结果,输出模块负责将结果转成String输出到控制台并进行优化。

代码架构简图如下:

img

基于度量的分析

ClassOCavgOCmaxWMC
Derive1.536
Exp114
Expression1.437
Function114
FunctionF114
Input666
Lexer3915
MainClass111
Mono1.5312
Number114
Output4.561341
Parser2.13732
Polynomia2.09323
RecurFunc1.648
RecurFunc1116
Select1.2525
Term1.7547
Variable1.536

通过上述类复杂度指标我们可以得出以下结论:

  • 高复杂度核心类是 OutputParser,这两个类的 OCavg 和 WMC 都较高,说明他们的中某些方法的逻辑非常深,且方法数量较多,可能存在一些重复代码或者过于复杂的逻辑。虽然这与他们本身的职责相关,但是我们可以考虑将他们的职责进一步细化。
  • Polymial 的 WMC 虽然较高,但是但是最高单项(OCMax)只有3,说明多项式类包含很多简单的小方法.方法拆分较为合理.
方法CogCev(G)iv(G)v(G)
Derive.Derive(Factor, String, int)0111
Derive.setExp(BigInteger)0111
Derive.setSign(int)0111
Derive.toPoly()3133
Exp.Exp(Factor, BigInteger, int)0111
Exp.setExp(BigInteger)0111
Exp.setSign(int)0111
Exp.toPoly()0111
Expression.Expression()0111
Expression.addTerm(Term)0111
Expression.setExp(BigInteger)0111
Expression.setSign(int)0111
Expression.toPoly()2133
Function.Function(Factor, int, BigInteger)0111
Function.setExp(BigInteger)0111
Function.setSign(int)0111
Function.toPoly()0111
FunctionF.FunctionF()0111
FunctionF.getInstance()0111
FunctionF.getPoly()0111
FunctionF.recordF(Polynomial)0111
Input.inputHandler(Scanner)9166
Lexer.Lexer(String)0111
Lexer.getNumber()2133
Lexer.match(String)2222
Lexer.next()1522021
Lexer.peek()0111
MainClass.main(String[])0111
Mono.Mono(BigInteger, BigInteger, Polynomial)0111
Mono.derive(String)2133
Mono.equals(Object)4346
Mono.getXexp()0111
Mono.getYexp()0111
Mono.getZexp()0111
Mono.hashCode()0111
Mono.substitute(Polynomial)0111
Number.Number(BigInteger)0111
Number.setExp(BigInteger)0111
Number.setSign(int)0111
Number.toPoly()0111
Output.appendTerm(Mono, BigInteger, StringBuilder, Boolean, Map<Polynomial, String>)1921416
Output.divideByCommon(Polynomial, BigInteger)1122
Output.getCommonFactor(Polynomial)3133
Output.isEmpty(Polynomial)2223
Output.isFactor(Polynomial)1471718
Output.newExp(Polynomial, Map<Polynomial, String>)2212
Output.optimizeExp(Polynomial, Map<Polynomial, String>)3123
Output.optimizePoly(Polynomial)0111
Output.optimizePolyInternal(Polynomial, Map<Polynomial, String>)8558
Parser.Parser(Lexer)0111
Parser.parseDerive(int)0111
Parser.parseExp(int)0111
Parser.parseExponent()3133
Parser.parseExpression()2133
Parser.parseFactor()971010
Parser.parseFunction(int)0111
Parser.parseGroupedExp(int)0111
Parser.parseLeadingSign()4134
Parser.parseNum(int)0111
Parser.parseRecursive()4133
Parser.parseSelect(int)0111
Parser.parseTerm()1122
Parser.parseVariable(int)0111
Parser.recursiveParseLeading()4134
Polynomial.Polynomial()0111
Polynomial.Polynomial(Mono, BigInteger)2122
Polynomial.add(Polynomial)1122
Polynomial.addTerm(Mono, BigInteger)3224
Polynomial.derive(String)1122
Polynomial.equals(Object)3324
Polynomial.getTerms()0111
Polynomial.hashCode()0111
Polynomial.mult(Polynomial)3133
Polynomial.pow(BigInteger)4133
Polynomial.substitute(Polynomial)1122
RecurFunc.RecurFunc(int, Factor, int, BigInteger)0111
RecurFunc.recurCalc()3324
RecurFunc.setExp(BigInteger)0111
RecurFunc.setSign(int)0111
RecurFunc.toPoly()0111
RecurFuncF.RecurFuncF()0111
RecurFuncF.getArg1()0111
RecurFuncF.getArg2()0111
RecurFuncF.getExpr()0111
RecurFuncF.getF0()0111
RecurFuncF.getF1()0111
RecurFuncF.getInstance()0111
RecurFuncF.getNum1()0111
RecurFuncF.getNum2()0111
RecurFuncF.setArg1(Factor)0111
RecurFuncF.setArg2(Factor)0111
RecurFuncF.setExpr(Factor)0111
RecurFuncF.setF0(Polynomial)0111
RecurFuncF.setF1(Polynomial)0111
RecurFuncF.setNum1(BigInteger)0111
RecurFuncF.setNum2(BigInteger)0111
Select.Select(Factor, Factor, Factor, int)0111
Select.setExp(BigInteger)0111
Select.setSign(int)0111
Select.toPoly()2122
Term.Term()0111
Term.addFactor(Factor)0111
Term.setSign(int)0111
Term.toPoly()5244
Variable.Variable(int, BigInteger, String)0111
Variable.setExp(BigInteger)0111
Variable.setSign(int)0111
Variable.toPoly()3223

通过上述方法复杂度地指标我们可以得出以下结论:

  • Lexer.next(...) 的圈复杂度较高,达到了21,远超推荐值10,说明该方法的逻辑较为复杂,存在多个if-else分支,但是这也正式词法分析器的职责所在。
  • output.isFactor(...) 的圈复杂度较高,达到了7,说明该方法内部结构较为混乱,在修改时极易产生bug,我在迭代过程中对该方法进行修改时就遇到了此类问题。

类的总代码规模如下表所示:

ClassCLOCJLOCLOC
Derive0032
Exp0029
Expression3034
Function2030
FunctionF0015
Input0037
Lexer2072
MainClass0012
Mono6066
Number1020
Output90166
Parser90206
Polynomia60111
RecurFunc2062
RecurFunc0057
Select0038
Term1034
Variable0027

从中可以得到此次作业的总代码规模为 1000 LOC,其中 Parser 和 Output 的代码规模较大,分别为 206 LOC 和 166 LOC,这与他们的职责相关,也许我们可以考虑将他们的职责进一步细化,以降低单个类的代码规模,提高代码的可维护性。

关于各个类之间的耦合情况如下表所示:

ClassCyclicDcyDcy*DptDpt*PDcyPDpt
Derive0331311
Exp0331311
Expression0443311
Function0441311
FunctionF0122411
Input05161111
Lexer0003301
MainClass06180010
Mono111111711
Number0331311
Output0221111
Parser012152211
Polynomia111151711
RecurFunc0441311
RecurFunc0233411
Select0331311
Term0332411
Variable0331311

通过观察可以发现 Polynomia (被 15 个类依赖)、Mono (被 11 个类依赖) 这意味这他们的正确性将是整个项目的重中之重。

基于架构的分析

在架构简图的基础上,我通过 plantuml 对代码架构进行了分析,得到了以下结果:

img

在通过度量工具分析了代码的复杂度和耦合度之后。我对自己的代码架构有了更加清晰的认识,其优缺点如下:

优点:

  • 模块划分清晰,职责明确,符合单一职责原则。
  • 代码结构层次分明,易于理解和维护。

缺点:

  • 部分核心类的复杂度较高,可能存在一些重复代码或者过于复杂的逻辑。
  • 部分类的代码规模较大,可能需要进一步细化职责以降低单个类的代码规模,提高代码的可维护性。
  • 部分方法的圈复杂度较高,极易在修改过程中因情况考虑不周而导致bug的产生。

架构设计体验

HW1

在 HW1 中,我的架构设计较为简单,主要分为 lexer , parserpoly 三个模块,没有定义输入输出的模块,这也就导致了在第二次作业中输入输出复杂化之后不得不将这两个模块从Mainclass中提取出来,进行代码重构。

HW2

在 HW2 中,我的架构设计基本已经形成了一个较为清晰的模块划分,主要分为 词法分析,语法分析,计算,输出 四个模块。为后来 HW3 的扩展提供了很好的基础。

HW3

在 HW3 中,我只需要新增递推函数的factor和求导的factor即可,基本上没有对之前的代码进行大规模的修改,说明我的架构设计在扩展性方面表现良好。

新的迭代场景

比如在HW4中,我们需要新增一个g(x)的函数,因为之前f(x)采用了单例模式,所以我们需要将之前的FunctionF类进行重构,改成一个工厂类来管理所有的函数,这样就可以在不修改之前代码的基础上新增g(x)函数了。

程序bug

自己的bug

我的程序bug主要集中在TLE方面,第二次作业由于在选择表达式计算过程中直接将命中和未命中的结果一起算了出来,导致了当未命中的结果复杂度极高时不必要的计算带来的TLE问题。在第三次作业中,由于exp的优化时存在方法的嵌套调用导致了递归过深,从而TLE。

针对第二次作业的问题,我只计算命中的结果:

Polynomial polyA = leftCond.toPoly();
        Polynomial polyB = rightCond.toPoly();
        Polynomial tmp = new Polynomial();

        if (polyA.equals(polyB)) {
            tmp = trueBranch.toPoly();
        } else {
            tmp = falseBranch.toPoly();
        }

针对第三次作业的问题,我将exp的优化方法进行了一些调整,改成了记忆化的方式来进行优化:

public static String optimizePoly(Polynomial polynomial) {
        Map<Polynomial, String> memo = new HashMap<>();
        return optimizePolyInternel(polynomial, memo);
    }

别人的bug

  • 通过测评机黑盒测试,直接与标准程序对拍发现错误
  • 通过浏览代码的易错模块看看是否存在潜在的bug

优化

我的优化主要就是两点,对普通表达式将的情况提到结果的前面,对exp内部表达式提公因式。至于对exp内部表达式的进一步详细优化个人觉得费时费力,得不偿失,没有必要。

对于第一种优化,代码如下:

for (Mono mono : tmpMap.keySet()) {
            if (tmpMap.get(mono).signum() > 0) {
                firstMono = mono;
                break;
            }
        }

对于第二种优化,代码如下:

private static String optimizeExp(Polynomial eexp, Map<Polynomial, String> memo) {
        String best = nowExp(eexp, memo);

        // 提公因式
        BigInteger commonFactor = getCommonFactor(eexp);
        // System.out.println("common factor: " + commonFactor);
        if (commonFactor.compareTo(BigInteger.ONE) > 0) {
            Polynomial newInner = divideByCommon(eexp, commonFactor);
            String result = nowExp(newInner, memo) + "^" + commonFactor;
            if (result.length() < best.length()) {
                best = result;
            }
        }
        return best;
    }

大模型相关的使用

idx正确性性能
120%5%
220%10%
30%0%

此外,大模型还主要用于辅助我搭建评测机,以及对我的项目架构提出建议等方面。大模型搭建的评测机主要还是在于要让大模型清晰的理解标准的输入格式,这一点可以让他根据我们自己的代码来学习。在项目架构的改进建议方面大模型确实对我帮助很大,有效地避免了后续的代码重构。

心得体会

通过这个单元的学习,我最深刻的体会主要有两个方面:

  1. 相信递归:之前我总是对递归函数敬而远之,总觉得递归函数不仅性能差而且易出错,但是通过这次作业的学习,我发现递归函数在某些场景下是非常自然且高效的解决方案,尤其是在处理树形结构或者分治问题时,递归函数能够让代码更加简洁和易读。而我们要做的就是相信递归的正确性。
  2. 好的架构胜过一切:好的架构不仅便于你对于自己项目的维护和扩展,还能够在短时间内快速向他人解释代码的设计思路,在代码走查过程中能快速定位问题所在。同时,在AI是时代,也许你只需要提供一个好的架构设计,AI就能够帮你完成剩下的代码实现了。

未来方向

也许可以更显式地引导同学们的代码架构设计,避免后续的重构。

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

309

社区成员

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

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