BUAA_OO_第一单元总结
思路总结
第一次作业
第一次需要完成的作业为简单多项式导函数的求解
思路分析
第一次作业给出来一系列以加法运算符和减法运算符相连的若干项作为表达式,其中,每一项由乘法运算符连接的幂函数和有符号数构成。因此,可以以项Term作为单位,对表达式进行分析。
在进行表达式的解析时,我选用了正则表达式根据正负号将输入的字符串以项的形式分割,作为Term构造函数的输入。在构造Term的过程中,我仍旧使用正则表达式将其分成因子的形式进行解析。
在数据结构方面,为了降低多项式的复杂度,便于进行合并,我采用了HashMap存储Term。
具体实现
字符串预处理
在解析表达式之前,我先对输入字符串进行了预处理。由题目可知,输入的字符串中可能含有空白字符,由于在这次作业中,空白字符对于表达式的意义没有影响,所以我先将所有空白字符去除掉。为了避免输入的表达式存在连续正负号的情况,我将连续多个正负号合并为一个,以便后续解析。
表达式解析
首先,我使用正则表达式将字符串拆分为一个个项的形式,开始构建Term,分别获取项中的系数coe和指数exp,在Term中存入,将其作为Box类中HashMap的元素,以指数exp作为key。
优化
在进行解析过程中,我直接对指数相同的项进行了合并处理,以此降低输出字符串的长度。
第二次作业
第二次需要完成的任务为包含简单幂函数和简单正余弦函数的导函数的求解
思路分析
第二次作业在第一次作业的基础上加入了正弦函数和余弦函数以及括号嵌套的表达式,由于第一次思路较为简单,完全没有考虑后续拓展,所以我对程序进行了重构。
在第二次作业中,我依旧选择采用正则表达式对字符串进行分割,仍旧将一个项Term作为基本类,其中包含sin,cos,poly,power和num因子。
在数据结构方面,我仍旧采用HashMap对Term进行存储,此外,我构造了一个类key作为HashMap中的索引,其包含了一个Term中幂函数的指数,sin函数的指数以及cos函数的指数,以便于进行合并,简化输出。
具体实现
具体实现过程和上一次作业类似,只不过在求导方面和正则表达式上由部分不同。
第三次作业
第三次需要完成的作业为包含简单幂函数和简单正余弦函数及其嵌套组合函数的导函数的求解
思路分析
第三次作业在第二次作业的基础上进行了拓展,整体思路仍旧沿用第二次作业的方法,没有进行过大改动。
在第三次作业中,比较重要的是格式检查部分。我首先排除了出现* *,s in这样一些简单的情况,然后将三角函数和表达式括号内的因子提取出去分别存储进行相应类别的检查,对于原本的表达式,则将括号内因子分别换为#和@,并对其进行格式检查。
具体实现
在解析表达式和求导方面,第三次作业和第二次作业没有太多区别。
在进行格式检查的过程中,首先对空字符出现的情况进行判断,并将利用正则表达式匹配指数因子,排除因子大于50的情况。当排除这些情况之后,可以将字符串中所有空字符去除,进行其他格式的检查。先利用正则表达式将字符串中的poly和因子factor分离分别进行检查,根据指导书中的形式化表述,将其翻译为正则表达式进行匹配,如果发生匹配错误,则输出Wrong Format!
基于度量的程序结构分析
第一次作业
程序结构
从上图中可以看出,本次作业代码整体较为简单,代码行数较少,没有对后续拓展进行太多考虑,总体规模较小。
从上面的度量图可以看出,虽然整体代码量较少,设计简单,方法个数较少,但仍有部分方法复杂度过高,程序整体基本复杂度较低,具有结构化的特征。
类图分析
在第一次作业中,由于作业较为简单,没有使用继承等关系,结构比较清晰,但并没有体现面向对象的特性。主要工作都在Box中完成,类内方法耦合度较高。
优点:本次作业结构较为简单,整体条理清晰
缺点:部分方法复杂度过高,代码没有体现面向对象的思维方式,没有任何拓展性。
第二次作业
程序结构
Class | CLOC | JLOC | LOC |
---|---|---|---|
Cos | 0 | 0 | 21 |
Elem | 0 | 0 | 60 |
Element | 0 | 0 | 11 |
Elemfactory | 0 | 0 | 15 |
Item | 9 | 0 | 211 |
Key | 0 | 0 | 24 |
Mainclass | 11 | 0 | 35 |
Num | 0 | 0 | 41 |
Poly | 9 | 0 | 169 |
Power | 0 | 0 | 27 |
Sin | 0 | 0 | 26 |
从表格中,我们可以看到,本次作业的代码量大大增加,尤其是Poly和Item两个类达到了两百行左右的代码量,可以看出作业的类长度过大,没能够很好的将不同的部分进行模块化分离。
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Cos.Cos(BigInteger,boolean) | 0 | 1 | 1 | 1 |
Cos.Cos(String) | 0 | 1 | 1 | 1 |
Cos.multi(Object) | 2 | 2 | 2 | 2 |
Cos.toString() | 0 | 1 | 1 | 1 |
Elem.Elem(BigInteger,boolean) | 0 | 1 | 1 | 1 |
Elem.Elem(String) | 7 | 1 | 2 | 4 |
Elem.add(Object) | 0 | 1 | 1 | 1 |
Elem.diff(Object) | 0 | 1 | 1 | 1 |
Elem.getExp() | 0 | 1 | 1 | 1 |
Elem.getFlag() | 0 | 1 | 1 | 1 |
Elem.multi(Object) | 2 | 2 | 2 | 2 |
Elem.setFlag() | 2 | 1 | 1 | 2 |
Element.add(Object) | 0 | 1 | 1 | 1 |
Element.diff(Object) | 0 | 1 | 1 | 1 |
Element.multi(Object) | 0 | 1 | 1 | 1 |
Elemfactory.getElem(String) | 7 | 5 | 6 | 7 |
Item.Item(BigInteger,BigInteger,BigInteger,BigInteger) | 0 | 1 | 1 | 1 |
Item.Item(String) | 14 | 4 | 5 | 6 |
Item.add(Object) | 0 | 1 | 1 | 1 |
Item.diff() | 0 | 1 | 1 | 1 |
Item.getCos() | 0 | 1 | 1 | 1 |
Item.getNum() | 0 | 1 | 1 | 1 |
Item.getPoly() | 7 | 2 | 4 | 4 |
Item.getPower() | 0 | 1 | 1 | 1 |
Item.getSin() | 0 | 1 | 1 | 1 |
Item.getin(Object) | 11 | 1 | 7 | 7 |
Item.multi(int) | 0 | 1 | 1 | 1 |
Item.sSym() | 3 | 1 | 4 | 4 |
Item.toString() | 8 | 2 | 5 | 5 |
Key.Key(Item) | 0 | 1 | 1 | 1 |
Key.equals(Object) | 2 | 2 | 2 | 2 |
Key.hashCode() | 0 | 1 | 1 | 1 |
Key.toString() | 0 | 1 | 1 | 1 |
Mainclass.main(String[]) | 2 | 1 | 2 | 2 |
Num.Num(BigInteger) | 0 | 1 | 1 | 1 |
Num.Num(String) | 0 | 1 | 1 | 1 |
Num.add(Object) | 2 | 2 | 2 | 2 |
Num.diff(Object) | 0 | 1 | 1 | 1 |
Num.getCoe() | 0 | 1 | 1 | 1 |
Num.multi(Object) | 1 | 2 | 2 | 2 |
Num.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
Num.toString() | 0 | 1 | 1 | 1 |
Poly.Poly() | 0 | 1 | 1 | 1 |
Poly.Poly(String) | 74 | 5 | 23 | 31 |
Poly.add(Object) | 9 | 1 | 6 | 6 |
Poly.changeS() | 1 | 1 | 2 | 2 |
Poly.diff() | 1 | 1 | 2 | 2 |
Poly.getMap() | 0 | 1 | 1 | 1 |
Poly.getp(String) | 2 | 1 | 3 | 3 |
Poly.multi(Object) | 7 | 2 | 4 | 4 |
Poly.toString() | 7 | 3 | 4 | 6 |
Power.Power(BigInteger,boolean) | 0 | 1 | 1 | 1 |
Power.Power(String) | 0 | 1 | 1 | 1 |
Power.diff() | 0 | 1 | 1 | 1 |
Power.multi(Object) | 2 | 2 | 2 | 2 |
Power.toString() | 0 | 1 | 1 | 1 |
Sin.Sin(BigInteger,boolean) | 0 | 1 | 1 | 1 |
Sin.Sin(String) | 0 | 1 | 1 | 1 |
Sin.diff() | 0 | 1 | 1 | 1 |
Sin.multi(Object) | 2 | 2 | 2 | 2 |
Sin.toString() | 0 | 1 | 1 | 1 |
在上面的度量图中可以看到,本次代码中方法个数极多,但大部分方法复杂性较低,控制分支数目也较少,独立性较强,但在Poly类的构造函数Poly(String s)中,判定结构较为复杂,与其他模块间的耦合性也较高,给程序复杂度带来了极大的提升,也会带来更多bug。(本次作业的bug就出在这一部分qaq)
类图分析
这张类图展示了本次作业的基本结构,我仍旧遵循上一次作业中使用的方法对项、因子进行分割,分别存储。
优点:本次作业实现较为简单(其实没什么优点)
缺点:方法之间耦合度过高,思维仍旧没有转向面向对象,在Poly类和Item类之间出现了循环依赖现象,逻辑不够清晰。在进行表达式的分割与判断的过程中,出现大量的条件嵌套,降低了代码可读性。(真的很难debug!也很难拓展!!)
第三次作业
程序结构
Class | CLOC | JLOC | LOC |
---|---|---|---|
Cos | 0 | 0 | 82 |
Elem | 0 | 0 | 12 |
Element | 0 | 0 | 11 |
Elemfactory | 0 | 0 | 15 |
FormatCheck | 15 | 0 | 239 |
Item | 0 | 0 | 79 |
Key | 24 | 0 | 25 |
Mainclass | 0 | 0 | 32 |
Num | 0 | 0 | 40 |
Poly | 44 | 0 | 207 |
Power | 0 | 0 | 59 |
Sin | 0 | 0 | 82 |
从上表可以看出,本次作业的代码量比起上一次而言主要增加在格式判断的部分,其余部分只做了一定修改,大体结构没有改变。这次作业仍旧存在部分类代码过长的问题,尤其是FormatCheck部分,逻辑较为复杂,代码量也较大。
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Cos.Cos() | 0 | 1 | 1 | 1 |
Cos.Cos(String) | 4 | 1 | 3 | 4 |
Cos.diff() | 1 | 1 | 1 | 2 |
Cos.multi(Object) | 0 | 1 | 1 | 1 |
Cos.toString() | 4 | 2 | 2 | 3 |
Cos.yep() | 4 | 2 | 2 | 3 |
Elem.Elem() | 0 | 1 | 1 | 1 |
Elem.add(Object) | 0 | 1 | 1 | 1 |
Elem.diff() | 0 | 1 | 1 | 1 |
Elem.multi(Object) | 0 | 1 | 1 | 1 |
Element.add(Object) | 0 | 1 | 1 | 1 |
Element.diff() | 0 | 1 | 1 | 1 |
Element.multi(Object) | 0 | 1 | 1 | 1 |
Elemfactory.getElem(String) | 11 | 5 | 10 | 11 |
FormatCheck.changeType(String,ArrayList<String>,ArrayList<String>) | 34 | 1 | 9 | 13 |
FormatCheck.check1(String) | 9 | 10 | 1 | 10 |
FormatCheck.check2(String) | 3 | 3 | 2 | 3 |
FormatCheck.check3(ArrayList<String>) | 21 | 3 | 5 | 10 |
FormatCheck.checkall(String) | 9 | 6 | 4 | 7 |
FormatCheck.checkp(String) | 2 | 2 | 1 | 2 |
Item.Item(String) | 14 | 4 | 5 | 6 |
Item.diff() | 19 | 1 | 6 | 7 |
Item.toString() | 4 | 1 | 3 | 3 |
Mainclass.main(String[]) | 6 | 1 | 4 | 4 |
Num.Num(BigInteger) | 0 | 1 | 1 | 1 |
Num.Num(String) | 0 | 1 | 1 | 1 |
Num.add(Object) | 2 | 2 | 2 | 2 |
Num.diff() | 0 | 1 | 1 | 1 |
Num.getCoe() | 0 | 1 | 1 | 1 |
Num.multi(Object) | 1 | 2 | 2 | 2 |
Num.setCoe(BigInteger) | 0 | 1 | 1 | 1 |
Num.toString() | 0 | 1 | 1 | 1 |
Poly.Poly() | 0 | 1 | 1 | 1 |
Poly.Poly(String) | 49 | 5 | 15 | 19 |
Poly.add(Object) | 4 | 1 | 4 | 4 |
Poly.diff() | 6 | 1 | 3 | 4 |
Poly.getArray() | 0 | 1 | 1 | 1 |
Poly.getp(String) | 2 | 1 | 3 | 3 |
Poly.multi(Object) | 0 | 1 | 1 | 1 |
Poly.red(String,String) | 16 | 1 | 6 | 13 |
Poly.toString() | 6 | 1 | 3 | 4 |
Power.Power() | 0 | 1 | 1 | 1 |
Power.Power(String) | 7 | 1 | 2 | 4 |
Power.diff() | 1 | 1 | 1 | 2 |
Power.multi(Object) | 0 | 1 | 1 | 1 |
Power.toString() | 4 | 2 | 1 | 3 |
Sin.Sin() | 0 | 1 | 1 | 1 |
Sin.Sin(String) | 4 | 1 | 3 | 4 |
Sin.diff() | 1 | 1 | 1 | 2 |
Sin.multi(Object) | 0 | 1 | 1 | 1 |
Sin.toString() | 4 | 2 | 2 | 3 |
Sin.yep() | 4 | 2 | 2 | 3 |
根据上面的度量图我们可以看到,本次作业的复杂度比上次作业大大增加,FormatCheck部分的方法判定复杂度较高,有多层逻辑嵌套,较为复杂,极容易因为考虑情况不足而产生bug。此外,模块之间的依赖程度也较高,尤其是poly类和FormatCheck类结构化程度较差,方法之间耦合程度过高,没有体现面向对象的思维。
类图分析
以上类图展示了本次作业的结构,基本上沿用了上次的结构。由于在本次作业中,三角函数内部也可以含有表达式因子,直接使用HashMap判断项之间是否能够合并很困难,因此,我使用ArrayList来存储Elem类型,在每一个Elem求导之后,返回一个字符串,在Poly中直接将Item类中求导得到的字符串相加得到求导结果,这样的方式也使得我最终的得到的结果基本没有太多优化,输出结果较长。
优点:整体结构较为清楚
缺点:直接以字符串形式拼接求导结果,优化困难;逻辑嵌套过多,难以阅读分析,容易出现bug
BUG分析
自己程序的bug
-
在第一次作业中,公测阶段为被查出bug,但在互测阶段,由于没有考虑+-+符号串出现的可能性,我被测出了大量bug(虽然都是同质的)在预处理了这种情况后,我顺利修复了所有bug。本次出现bug在于字符串预处理阶段,故没有逻辑问题。
-
在第二次公测过程中,我被测出了两个bug,均是由于上述分析中Poly的构造函数逻辑过于复杂导致的判断不完全的情况。在互测过程中,仍旧是这一部分代码中出现了问题。
Method CogC ev(G) iv(G) v(G) Poly.Poly(String) 74 5 23 31 如图是出现bug部分的方法的度量分析,相比于其他方法,该方法逻辑较为复杂,条件判断嵌套极多,且规模较大。出现问题的过长的构造函数和嵌套式的条件判断让程序的逻辑变得难以分析,不仅在写代码的过程中会带来困难,更在后续的bug分析带来压力。由此我发现,简短的函数和低复杂度的逻辑判断能够使得代码更加清晰,也更利于从逻辑角度分析代码,避免情况判断不完全的问题。
-
在第三次公测中,我没有被测出正确性问题,但由于输出优化较差,得到的性能分极低。在互测阶段,我仍旧被测出了bug,原因是在poly类中进行输出处理的过程中,某些表达式因子前的符号处理错误,使得最终得到的结果出现了符号错误。这个错误恰好就出现在了前述程序结构部分复杂度较高的poly类中,与第二次出现bug的地方相同,由此可见,降低判断结构的复杂程度和方法之间的耦合度能够有效减少bug的出现。
发现别人bug所用策略
-
在测试别人bug的过程中,我使用了自己在做测试时的测试案例以及互测过程中通过阅读别人代码发现的错误进行测试,发现了别人代码中与我相同的错误。
-
此外,我还根据此次作业给出条件的边界构造了测试样例对程序进行测试。在比对正确性的时候,我利用python中的sympy库构建了一个对拍程序对互测屋中的程序进行评测,简单的自动评测机能够有效提高评测的效率。
三次作业程序依赖性分析
Cyclic:和类直接或间接相互依赖的类的数量。
Dcy:该类直接依赖的类的数量
Dcy*:该类直接依赖的类和间接依赖的类的数量
Dpt:直接依赖该类的数量
Dpt*:直接依赖和间接依赖该类的数量
第一次作业
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* | PDcy | PDpt |
---|---|---|---|---|---|---|---|
Box | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
Main | 0 | 1 | 2 | 0 | 0 | 1 | 0 |
Term | 0 | 0 | 0 | 1 | 2 | 0 | 1 |
第二次作业
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* | PDcy | PDpt |
---|---|---|---|---|---|---|---|
Element | 0.0 | 0.0 | 0.0 | 5.0 | 10.0 | 0.0 | 1.0 |
Elem | 0.0 | 1.0 | 1.0 | 6.0 | 8.0 | 1.0 | 1.0 |
Num | 0.0 | 1.0 | 1.0 | 3.0 | 5.0 | 1.0 | 1.0 |
Cos | 0.0 | 1.0 | 2.0 | 2.0 | 5.0 | 1.0 | 1.0 |
Power | 0.0 | 1.0 | 2.0 | 2.0 | 5.0 | 1.0 | 1.0 |
Sin | 0.0 | 1.0 | 2.0 | 2.0 | 5.0 | 1.0 | 1.0 |
Elemfactory | 3.0 | 6.0 | 10.0 | 1.0 | 4.0 | 1.0 | 1.0 |
Item | 3.0 | 10.0 | 10.0 | 2.0 | 4.0 | 1.0 | 1.0 |
Key | 3.0 | 2.0 | 10.0 | 2.0 | 4.0 | 1.0 | 1.0 |
Poly | 3.0 | 5.0 | 10.0 | 3.0 | 4.0 | 1.0 | 1.0 |
Mainclass | 0.0 | 1.0 | 11.0 | 0.0 | 0.0 | 1.0 | 0.0 |
Total | |||||||
Average | 1.0908 | 2.6362 | 5.363 | 2.5454 | 4.909 | 0.9091 | 0.90909091 |
第三次作业
Class | Cyclic | Dcy | Dcy* | Dpt | Dpt* | PDcy | PDpt |
---|---|---|---|---|---|---|---|
Element | 0.0 | 0.0 | 0.0 | 5.0 | 9.0 | 0.0 | 1.0 |
FormatCheck | 0.0 | 0.0 | 0.0 | 1.0 | 1.0 | 0.0 | 1.0 |
Key | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
Elem | 0.0 | 1.0 | 1.0 | 3.0 | 7.0 | 1.0 | 1.0 |
Num | 0.0 | 1.0 | 1.0 | 1.0 | 6.0 | 1.0 | 1.0 |
Power | 0.0 | 1.0 | 2.0 | 1.0 | 6.0 | 1.0 | 1.0 |
Cos | 4.0 | 2.0 | 9.0 | 1.0 | 5.0 | 1.0 | 1.0 |
Elemfactory | 4.0 | 6.0 | 9.0 | 1.0 | 5.0 | 1.0 | 1.0 |
Item | 4.0 | 3.0 | 9.0 | 1.0 | 5.0 | 1.0 | 1.0 |
Poly | 4.0 | 2.0 | 9.0 | 4.0 | 5.0 | 1.0 | 1.0 |
Sin | 4.0 | 2.0 | 9.0 | 1.0 | 5.0 | 1.0 | 1.0 |
Mainclass | 0.0 | 2.0 | 11.0 | 0.0 | 0.0 | 1.0 | 0.0 |
Total | |||||||
Average | 1.66667 | 1.6666667 | 5.0 | 1.583333 | 4.5 | 0.75 | 0.833334 |
从上面的表格中可以看出,第二次程序的依赖性比起第一次而言大大增加,第三次和第二次程序依赖性基本相差不大,甚至第三次作业类之间的依赖性更低一些。但在第二次和第三次作业之中,都存在循环依赖的问题,功能划分不够清楚,需要在整体逻辑方面进行部分改进。
重构经历总结
在第一单元作业过程中,我在第二次作业进行了一次重构。由于刚开始完成第二次作业的时候思路不够清晰,我希望能够继续复用第一次的作业结构,使用大正则表达式来解析字符串,在进行了诸多实践之后,我发现得到的结果错误过多,且在较长的数据输入是会出现TLE现象,因此,我将大正则改为了小正则,将其分成一段一段的字符串进行括号拆分计数。
在进行重构之前,第二次作业的想法基本如类图所示,会给表达式解析带来极大难度。为了避免解析困难的问题,我改变了一部分结构,建立了一个Elem父类,将poly作为Item的属性,类图如下所示,虽然仍旧有循环复用的问题,但与重构之前比较,能够有效减少bug的发生(亲身经历·-·):
心得体会
-
经历了这一单元的作业,我的编程能力得到了极大的提高,对于继承的特性、正则表达式的使用、面向对象的思维有了更深刻的了解。在考虑问题的过程中开始用面向对象的思维思考(开始将一个问题划分出不同的对象进行处理而不是根据逻辑顺序处理问题)。
-
同时,我发现在完成作业的过程中,重构是常常发生的事情,如果发现程序结构不好,就要想办法寻找更合适的结构和方法,一定不能偷懒,否则会给后续作业带来更多的麻烦!!!