301
社区成员
发帖
与我相关
我的任务
分享软件在开发之初即以构建一个普适的计算机数学系统为最终目标,最终成品目标为一个支持所有数学运算的、逻辑清晰的、架构灵活容易扩展的表达式解析和运算器。
统计数据
总代码行数:1695
源代码行数:1367(81%)
注释行数:116(7%)
空白行:212(13%)
复杂度
平均圈复杂度(v(G)avg):2.56

软件结构大致分为三部分:表达式解析器,表达式数据管理以及工具包。
表达式解析器先将输入的表达式序列经预处理器PreProcessor代入函数,接着使用解析器Parser对表达式进行解析,在解析过程中创建数据管理类型并构建其关系,最终构造一个严格符合原输入表达式的表达式树结构;表达式化简时,由Main直接调用根表达式Calc的化简方法,并由表达式数据类型自动递归调用其内部的化简方法来实现。
设计较好地实现了模块化,尝试和努力实现松耦合设计。在表达式数据结构内,由于数学表达式天然的逻辑依赖性,其耦合度自然较高,因此采用为一个模块,在非数学逻辑上的组合之外尽力实现依赖反转,为内部控制和外部控制提供抽象,例如Main对Calc和Calc对下层表达式类型的抽象的控制使得表达式数据结构成为了控制部件的一个“插件”。
设计中数据管理类均采用了可变对象,即在化简过程中对原表达式树的结构及属性进行修改。其优点是架构逻辑清晰,缺点是实现复杂度相对较高。
应当指出的是,上述架构仍然需要进一步重构和优化以达到最优状态,这是由于第三次迭代开发之后已经无需进行这项工作。
| 类名 | 功能 | 其他信息 |
|---|---|---|
| Main | 进行顶层控制。 | |
| PreProcessor | 接受输入表达式序列,输出已经代入函数的表达式。 | |
| Lexer | 接受输入表达式,解析为词法单元序列,接受操作和输出指定的词法单元。 | |
| Parser | 接受输入表达式,解析为表达式结构树,输出这个表达式树。 | |
| Func | 由预处理器使用,存放解析自定义函数得到的信息。 | |
| Calc | 表示任一类型的数学表达式语法项。 | |
| Expr | 表达式,表达为项的和。 | |
| Term | 项,表达为因子的积。 | |
| Factor | 因子,表达为底数的幂。 | |
| Base | 因子的底数。 | |
| Num | 数字类型,内部包含一个BigInteger,提供运算方法。 | 也是因子的幂。 |
| Var | 变量类型。 | 一切非关键字的词在解析器中都会被解释为变量。 |
| Diff | 需要一阶求导的表达式。 | |
| Exp | 自然指数表达式。 | |
| BigIntegerMultiLooper | 工具类,为因子的多项式定理展开提供不定长向量遍历工具。 | |
| Debugger | 工具类,提供可配置的调试信息输出方法。 |
代码高质量的内涵有以下几个要点:
高质量的代码有一种“优雅”的气息。在开发过程中不断探索和追求优雅的代码,培养对高质量代码的敏锐嗅觉,能够形成对高质量代码的自然感觉。经过本次开发实践的过程,我对这些要点有了更深的认识和认同。
| 错误内容 | 作业次数 | 错误分析 |
|---|---|---|
| 在化简过程中正负符号合并处理有误 | 第一次 | 这是实现的最后一个模块,在实现完成之后进行的代码检查和测试不够充分。另外,在第一次作业时,尚未形成自动化测试,只开发了数据生成器,在中测提交截止互测开启后自动测试才完成开发,并检测出了这一错误。 |
在化简过程中对exp()内的括号层数把握不当 | 第二次 | 架构本身并非严格按照文法进行,而是在文法与自然的数学逻辑不相符时,选择采用了更加普适化的数学逻辑。因此,满足这一输出格式的处理是在重写Exp类的toString()方法时进行判断的,然而在判断时却对文法理解有误,误认为项数为1即可不必嵌套括号,因此出现了错误。 |
| 在化简过程中超时 | 第二次 | 在处理表达式的幂运算时,使用的是将其展开为表达式相乘的形式再进行乘法运算的方法,使得在幂次增加时化简用时急剧增加。相关的优化见下文“计算时间性能优化”部分。 |
软件出现的错误基本上与架构设计无关,一般是细节性错误,因此无法分析架构的影响以及复杂度的影响。
在软件开发过程中,同步开发了自动化整体测试:kINo204/oo-test-project: A test project for Beihang University 2024 Object Oriented course. (github.com),其原理是生成数据后,不断将数据在被测程序和sympy库中运行的结果进行比对,将所有错误结果写入错误日志errs.log,从而测试被测程序输出的数学逻辑正确性。测试支持对数据生成器进行各方面配置,以降低生成数据的复杂度。
该方法无法完全检测题目要求的正确性,例如多余的括号会被该sympy库化简约去,从而失去括号层数的信息(例如,这一方法无法检测出上述第二项错误)。
在互测中,直接将被测对象的程序打包为jar包,配置合适的运行参数并运行测试即可得到其错误数据。若运行一段时间未发现错误,可以认为其没有数学逻辑的错误。
在表达式性能优化方面,主要随表达式化简进行了一些符合数学逻辑的优化,作为合并同类项、进行可乘项相乘、进行幂次运算等方法的一部分实现。由于这一优化为类的私有方法之一,外部不可见,且由simplify()这一公有方法负责调用执行,因此对上层完全透明,不影响代码的简洁和正确性。
由于完全针对表达式长度的性能优化,其根本考察点为长度,则优化方法必然向在多种枚举结果中选取最短此类操作的方向靠拢,复杂度较高,因此优化选择按照某种化简原则(即展开)为方向进行,没有进行进一步的优化。
迭代开发过程中,曾针对表达式的多次幂运算进行了专门优化。优化过程如下:
课设项目开发练习是手段而不是目标,应当始终记住要在开发过程中学习、运用和体会软件开发原则和方法,否则只利用现阶段有限的知识和“创造力”进行开发,即使代码运行良好,其质量也远远达不到生产实际的要求,这样开发过程并不能起到提高软件开发能力的作用。
面向对象起初作为基于利用堆内存进行数据驻留的一项技术被提出,其价值在于通过对多态的支持,提供了依赖反转的工具,让架构设计人员能够随心所欲地操控依赖方向,以实现松耦合、灵活可扩展的架构设计。SOLID架构设计原则被提出,以指导设计人员应如何操控依赖方向,才能使得架构具有可扩展的特征。
第一单元对数据管理类的递归使用,使得这些数据管理类之间存在紧密耦合,实际上不利于体会面向对象通过依赖反转实现松耦合的核心思想;应当加强对这一方面的引导。