301
社区成员
发帖
与我相关
我的任务
分享
总行数1093行,源码992行。我会在写出某一功能模块的代码后进行一定的测试,从而在一定程度上减少出现bug的可能,不过我会将一些测试用的代码注释掉。

可以看到,Simplify, Poly, Func等类为低内聚类。Simplify类的功能是对读入的一行字符串进行化简,去除多余的空白符号、+/-号等,复杂度较高。Poly类定义了HashMap存储结构下所有的多项式计算方法,复杂度高。Func类定义了所有的与自定义函数有关的行为,包括读入、化简、代入参数等等。Expr类定义了表达式的存储结构、打印等功能。Parser类负责执行递归下降的过程。
值得关注的是,MainClass类的内聚性不尽人意,可能的原因是我在编写代码时还是残留了一定的面向过程编程的思维,在之后的学习中我会更加彻底地贯彻面向对象设计与构造的思想。
另外,可以观察到Expr类和Parser类的WMC较大,即拥有过多的方法,原因是Expr类承担的功能太多,而Parser类是因为我把很多分支的执行过程也抽象成了独立的方法,这是为了满足方法最大行数的限制。



可以看到,我的代码中一些方法不符合高内聚低耦合的要求。Expr.link()的功能是将一个用HashMap存储的形如a*x^b的单项式转化为字符串,是我的代码的核心功能,所以实现后的复杂度较高。Expr.getGcd()则是提取一个exp()括号中的表达式的所有项的系数的最大公因数,由于存储方式的原因,该方法的实现较为复杂。Expr.specialLink()的功能是保证exp()中的表达式的括号层数的正确性,是hw2代码的核心部分。
同时,我注意到某些方法的耦合性较高,考虑到这些方法多是具有综合性功能的模块,所以是可以理解的。`


MainClass类:读入数据,并调用其他类,生成最上层的表达式。Simplify类:对读入的表达式和初步生成的表达式字符串进行化简,去除多余的部分。Lexer类:分析字符串,将其转化为curToken字符来逐一识别并处理。Parser类:递归下降识别表达式的每一部分,并返回Expr类或Term类的实例。Expr类:定义了所有的表达式的基本形式为(多项式)*exp(多项式),并用HashMap存储,同时实现了一系列配套的功能,如基于表达式生成字符串,表达式合并,求多项式的最大公因数等等,继承Factor接口。Term类:定义为项,会被Expr用新定义的表达式加法合并,继承Factor接口。Poly类:方法类,定义了一系列的计算用的方法,如表达式的加法和乘法等等。Func类:定义了自定义函数的存储方式和一系列相应的功能,如读入自定义函数,代入参数等等,继承Factor接口。Variable类:定义了字符串类型的变量的行为,继承Factor接口。Factor接口:接口,定义了getMonomial()等行为。
HashMap<Expr,HashMap<BigInteger,BigInteger>>的标准形式,降低了构建架构的思维难度,逻辑清晰明了。没有将所有形如(a*x^b+c*x^d)*exp(表达式)中的前一个多项式的括号拆掉。
最大的原因是我没有完全读懂题目,以后一定认真读题。
次要原因是我存储表达式的方式导致了我没有在编写代码时想到拆开这种括号。在主函数的parser.expr()执行后,所有表达式都会以(a*x^b+c*x^d)*exp(表达式)的形式存储,然而我在输出时直接以这种形式输出了表达式,并没有运用乘法分配律拆开括号。所以也可以说,我的架构的缺陷是产生bug的原因之一。
具体来说,问题出在两个多项式相乘的计算方法上。运用乘法分配律,当一个新的乘法结果被模拟加入到作为结果的HashMap中时,如果某个指数对应的系数将会变为0,那么实际上不会执行加入的操作,但实际上还应该进行将原来的key-value映射删除的操作。这导致了bug,下面是一个简单的例子:
Input:
0
(x+1)*(x-1)
Output:
x^2+x-1
显示出现错误,正确的输出应该为x^2-1。+x在遇到-x后本应该被抵消,但实际上留存在了result中。
我的互测体验较为良好,虽然没有hack成功过别人,但也让我在阅读他人代码时了解到了新的创意,也在构造测试数据时想到了没有涉及过的分支情况。
我比较喜欢结合他人的代码结构来设计构造测试用例,比如对时间复杂度较高的架构,我会构造时间复杂度尽可能高的测试数据。然而受制于数据代价,我构造的一些数据虽然可以卡爆他人的程序,但无法提交到互测中。
我在hw1时最初的设想是用ArrayList存储读取到的表达式,但是发现时间复杂度极大,并且难以合并同类项,所以我在思考选择了HashMap<Integer,BigInteger>来存储形如a*x^b+c*x^d表达式,key为指数,value为系数。这样大大提高了程序的运行速度,使得我轻松通过了第一次测试(然后似在互测)。
但是,hw1的架构存在着隐患。
在hw2中,exp()加入了表达式体系中,我的思维一度卡在如何存储exp()上。然而,当我重新审视我的hw1架构时,我惊喜地发现如果在原来的HashMap<>的外面再套一层HashMap,就可以完美地进行迭代开发,原来的一切方法和容器都可以直接用而无需修改,只需要进行新增内容的开发。同时,新的存储容器HashMap<Expr,HashMap<BigInteger,BigInteger>>使用了递归定义的形式,一定程度下降低了开发难度。
从hw2的开发中,可以看出我最初定下的架构的可迭代性较强,也是一件幸运的事情(然后似在弱测,无效作业闹麻了)。
hw3的迭代开发是最简单的一次,我仅用了一小时左右就完成了初步编写。我在开始开发hw3之前,认为自己的hw2架构已经可以处理自定义函数的相互引用定义,因此只编写了表达式求导部分相关的代码。
但是在之后的本地测试中我测出了bug,经过重重排查,我发现了hw2中的一行判定代码的写法导致了无法识别自定义函数的相互引用定义,这是我在编写时遗漏的,不过若是我在hw2中就采用更好的判定写法,或许可以减少这种情况的发生。
若是没有上过OO-pre,我大概率会似在hw1中(似在hw2了)。有了在先导过程中迭代开发和构造数据的经验,我的Unit1学习还是磕磕绊绊地通过了。
在之后的开发中,我决定贯彻“一个方法一个功能”的原则,绝不将属于下层方法的功能写在本层方法中(不然太丑了)。
一定要选择一个好架构,至少可迭代性一定要强。
一定要做好单元测试,确保新方法的正确性,并与上述的简化方法这一点相结合。