主要分析了前兩次作業,第三次作業做到一半不知道後面應該如何處理表達式,因為對“遞歸下降”和“樹”的概念不太了解
一.程式結構分析
第一次作業複雜度分析:

能夠看出,方法結構化程度不好,類的平均循環複雜度及總複雜度有些高(因為設計不夠好,隻有一個類),方法規模一般且數量較少(第一次比較簡單),省略了類圖
最大的缺點:一個類,難以維護,擴充性不強
優點:面向過程,好寫
第二次作業複雜度分析:
隻截取了标紅的部分,能看出Term.getState()和Splitter.reCog()方法結構化程度差,耦合度高,原因是自己的設計中計算和判斷格式的方法比較笨,同一方法要使用多次,沒有想到用Hashmap,可以直接以各個因子的index作為索引,進而提高效率。也是以導緻Output和Splitter子產品循環複雜度較高。
類圖如下:
似乎有了一點點點點點點OO的思想,但是各個類的關系好像還不太明确,仍需改進,但比第一次有了一點進步,可擴充性提高了一點點點點點點點。方法個數有了提升,部分方法規模有點大,但總體還行,且較好地分布在各個類中
二.關于程式bug的分析
第一次作業:簡單的x、x^num、num相加減,并且求導
測試結果:中強測全部通過,互測中被hack11次,最終合并為了3個同質bug
不足:第一次正式作業,不像寒假的A+B問題,難度和要求都有所提升(不可以一main到底)。但當時對Java的類,方法等概念不是太清楚,是以整個程式隻有一個類,而且所有的方法都是private static,等于用C面向過程地寫了一遍,不是特别面向對象。同時,一個正規表達式平均有兩三行,也沒掌握正規表達式正确的用法
bug分析:首先,第一個bug是因為不細心,本來應該删去的一行代碼“m = r.matcher(str)”(類似這樣)忘記删去,導緻對于含有多個重複項的表達式,如-x-x-x-x-x-x,所有-x隻會被計算一次,得到-1。同時也發現了對于matcher中的find()方法,如果不對matcher進行更新,無論對字元串做什麼樣的改變,find方法始終作用于之前的字元串
剩餘的bug是因正規表達式比對出錯的。如我的正規表達式會比對到+ + 1這種項,并認為這是正确的項,實際上根據指導書這是錯誤的,是以修複bug時主要針對正規表達式進行了更改。
另外,我起初的想法是:提取出所有正确的項(正确的項用空串替換),剩餘串為空,則表達式正确,否則就是錯誤的。随之産生了一個問題,類似+++1+1這種項,會先比對到++1,認為他是正确的,替換後又産生了一個++1,,又認為他是正确的。是以,認為表達式正确。
綜上,産生的bug大部分在輸入格式處理上,實際計算的bug并不算太多(這次計算比較簡單),以及自己的粗心。
用正規表達式直接比對每一項确實不是一個明智的方法,這樣寫出來的正則不僅長而且複雜,容易出錯,也可能爆棧。對于爆棧的原因,也上網查了一些資料,同時也有同學在讨論課上指出,這是由于正規表達式比對時的回溯。這次沒有爆棧是因為項都比較簡單,不是太複雜。
第二次作業:在第一次作業基礎上加入sin(x)和cos(x),可以帶幂,并且求導
測試結果:中測通過,強測和互測共10個bug,合并修複為兩個同質bug,兩個bug都在Splitter類中
bug分析:第一個bug,又是因為沒細心,忘記除了負項,導緻表達式中隻要含有負項就會輸出WRONG FORMAT!
另一個bug源于方法問題,是關于split()函數的使用。這一次的思想是通過預處理,用" + "先切割表達式,得到每個乘積表達項,他們都可以表示為kxasin(x)bcos(x)c,然後再用" * "切割,得到每個因子,這一段一開始是這麼寫的:
for (String ret1 : strSpl.split("\\+")) {
if (!ret1.equals("")) {
Term term = new Term();
for (String ret2 : ret1.split("\\*")) {
reCog(ret2, term);
}
是以,像2+,sin(x)*這樣的表達式,我都會認為是正确的。在網上搜尋了一下,發現split()方法在傳遞參數時,還有一個limit參數,就是最多可以得到的字元串數組個數,一個特殊的情況是limit = -1,此時split()方法會對字元串徹底進行切割,若最後n位均為切割符,仍會繼續切割,舉個例子(源于CSDN):
String line = "a b c ";
String [] tmp = line.split(" ");
System.out.println(tmp.length+"------");
for(int i=0;i<tmp.length;i++){
System.out.println(i+"="+tmp[i]);
}
String [] items = line.split(" ",-1);
System.out.println(items.length+"========");
for(int i=0;i<items.length;i++){
System.out.println(i+"="+items[i]);
}
結果:
4------
0=a
1=b
2=
3=c
==========
4=
5=
6=
7=
8=
9=
10=
11=
12=
随後對split()方法的使用進行了更改,修複了這一bug
三.如何發現别人的bug?
1.對拍是一種不錯的辦法,很省力,把髒活都交給電腦去做。但是如同學們在研讨課指出,對拍可能産生許多同質bug,但也會針對一些臨界值給出測試,有利有弊。
2.通過看代碼,了解設計者的思路,發現細節錯誤,如幾次作業的“正規表達式”,一旦發現問題,很容易找出bug
3.手動輸入一些特殊的資料,如一些極特殊的情況,或是自己都沒想到的情況
我采用的是第二三種方式,因為比較懶,不想寫對拍不想看代碼我認為第一種找bug的方式有些太暴力,對拍什麼的硬找bug還是自己完成比較好(總有人會對拍,不如自己先拍)······對于第二種方式,有時别人的代碼比較複雜或者看不太懂的時候,我就直接用第三種方式,一般都能找出一兩個bug。不過我的測試樣例一般都是自己突然想到的,沒有較好地結合被測程式的代碼設計結構
四.三次作業對比分析
做前幾次作業時,在努力給自己灌輸OO的思想,盡量地把代碼寫的面向對象。因為并沒有實際參與過一些大型工程,是以對一些必要的複雜的結構性設計還不太了解。
第一次作業沒有任何結構,等于用C寫了一遍,沒什麼擴充性,于是第二次作業需要重構。
第二次作業中,把整個具體過程分為了輸入檢查處理,分割,得到項,求導輸出,是以有了四個類(除了main,但對于這四個類的劃分還有些問題)。
第三次作業,保留了第二次作業輸入檢查處理及項的類,因為求導部分不适應這次作業,分割的方法也不适用。可以看出,因為對類有了一些劃分,各類的功能比較明确,代碼可擴充性稍微高了一點,不用完全重構。但仍沒有用到接口,繼承等有Java特色的東西,還需要慢慢體會并了解他們的作用。另外,這次對函數,各種因子建立了類,對括号的處理也單獨成類。但遺憾的是,括号處理完後,不太會對多層嵌套的遞歸下降處理,也不知道有什麼别的方法,後一半不知道怎麼寫······
以後會繼續努力,繼續體會OO的思想,減少面向過程程式設計的想法,盡力做好每次作業