三次作業設計分析
第一次作業
類圖:


第一次作業思路:在第一次作業中有三個類,入口函數是CalPoly,讀取參數、進行求導、輸出。在第一次作業中模仿課堂上機矩陣運算的形式,在Poly類中進行各種運算,檢查格式正確性、分離項、指數系數分離、化簡等等。Poly是由Polyitem構成,求導過程在因子層面進行。
優缺點分析:
優點:第一次作業中能夠從表達式-項-因子層面進行思考,在判斷方面采用正則表達式進行分析,雖然是正則表達式,但是每個因子從小到大分開寫,再合並增強可讀性。
缺點:作業不寫注釋,當我在互測過程中看到不寫注釋的代碼,如果說不是特別認真看的話確實比較費力;readme.md文檔也沒有兩句話,這是編程陋習。其次是類有點少、沒有InputHandle單獨處理輸入結構,檢查格式是否正確放在Poly類中不太合適,分離指數系數也不是Poly類應該做的事兒,當時沒有考慮到構造形式可以多樣化,因此代碼糅雜在一塊。判斷格式時采用大正則判斷,不太合適。互測過程中看到用Pattern、matcher和group的方法,值得學習。
度量分析:采用IDEA自帶插件Metrics
方法復雜度:


之所以checkPoly復雜度如此高,重要原因是當時我判斷 全部字符屬於合法字符采取循環判斷模式,沒有用到正則表達式,之后判斷是否合法就采取正則表達式的方法了。
類復雜度:


第二次作業
類圖:


類復雜度:


MergePoly類復雜度過高,詳細情況如下:


第二次作業思路:因子-項-表達式,但是實現過程中由於項建立的對象是鏈表,導致我重新建立一個StandPoly的類,里面存取的是一項的系數和三個指數。
優缺點分析:
優點:本次作業有GetInput類,單獨處理輸入,檢查格式類別也已分開,代碼中有注釋,readme也對自己每個類各個方法進行說明。正則表達式引入Pattern和Matcher進行匹配,同時利用Group進行分離,很容易提取指數和系數。
缺點:但是到第三次作業我才發現自己理解有問題,還是在入口函數進行讀取字符串,傳給GetInput類是字符串,只是單純建立一個輸入類別,沒有真正達到效果。在類中數據類型沒有考慮好,導致進行求導的過程中得轉化為另一個標准項類,造成代碼冗雜。在化簡中漏掉 cos(x) ^ 2 = 1 - sin(x) ^ 2。
第三次作業
類圖:


度量分析圖:


第三次作業思路:
由於短時間內擔心無法用好接口和繼承,本次作業沿用第二次作業,類基本一致,增加Expression類別進行遞歸求導,求導的終止條件是判斷表達式是作業2中的表達式;CheckFormat類對格式進行判斷;SplitPoly類對項進行分離。
優缺點分析:
優點:第三次作業沒什么優點。
缺點:由於是沿用第二次作業,因此類別依舊冗雜,沒有用到接口,遞歸下降應該是一個比較好的思路,戰略性放棄了。沒有花時間進行化簡,輸出很長。
三次作業總結
三次作業讓我從正則表達式小白逐漸成長,從大正則到利用matcher和group進行分步判斷最后迫於不會用遞歸下降法而再次使用大正則,確實讓我在一次次debug中學到很多東西,同時仍尚存很多疑惑。通過作業迫使我不斷思考該如何構建自己的程序,實現可移植性、可修改性等等。而面向對象,我在第二次作業中感到自己簡直是在面向過程,思考求導我應該做什么,分離-求導-化簡合並-輸出。甚至類別二都是如此,比如單獨類別用來進行化簡,修改層面確實比較合適,但是是否真正是從對象進行考慮,我不太清楚。對於代碼風格,命名上時常感到詞窮,無法直觀從命名上看出變量的作用,這也是需要鍛煉的。
分析自己程序中的Bug
在第一次作業中,公測和互測均未發現Bug。
第二次作業中,雖然強測中沒有發現問題,但是互測中同學給我指出問題,我發現在除去多於正負號的時候,我是采取如下方式:
String strSimpOp1 = strDetWhi.replaceAll("([+]{2})|([-]{2})", "\\+");
strSimpOp1 = strSimpOp1.replaceAll("([+]{2})|([-]{2})", "\\-");
String strSimOp2 = strSimpOp1.replaceAll("(\\+\\-)|(\\-\\+)", "\\+");
strSimOp2 = strSimOp2.replaceAll("(\\+\\-)|(\\-\\+)", "\\-");
可以發現如果出現+-+的情況其實是沒有完全進行化簡的,而且我發現自己的程序竟然沒有拋出異常,而是進行錯誤的計算。后來我采取其他同學提供的建議,重新調整替換順序,得到正確化簡形式。即如下代碼:
String strSimpOp1 = strDetWhi.replaceAll("([+]{2})|([-]{2})", "\\+");
strSimpOp1 = strSimpOp1.replaceAll("(\\+\\-)|(\\-\\+)", "\\-");
String strSimOp2 = strSimpOp1.replaceAll("([+]{2})|([-]{2})", "\\+");
strSimOp2 = strSimOp2.replaceAll("(\\+\\-)|(\\-\\+)", "\\-");
同時這也告訴我在課下測試中應該對自己的程序有較強的測試,在第一次oo分享課中同學談道對自己代碼進行逐行解釋,我認為這雖然比較耗時間但是確實能對自己的代碼全面分析找bug。
第三次作業就比較有意思,傍晚6點開放互測窗口,最初兩小時我因為x ^ 10000*x ^ 10000的數據被hack,當時我只能看見自己被hack,但並不清楚原因,直到周五偶然測x ^ 10000 * x ^ 1發現全組中出現一個wf,是自己的名字,結果發現這種類型的數據竟然已經交不上去了。百思不得其解,這種數據到底是合法還是非法的呢?之前助教的意思是不能提交Wrong Format 的數據,但我知道根據指導書的意思這種數據應該是正確類型,是自己的問題。這個問題的修復我開始想得很復雜,因為我判斷指數是否合法的情況是在因子里邊進行的,即PowFactor和TriFactor兩個類都進行判斷,實際上在求導前我進行同類項的化簡,導致我后來再構建多項式過程中引用PowFactor和TriFactor兩個類時,指數可能會大於10000而出錯。最初我只想着在因子層面去判斷指數范圍,但是我發現我總會引用多項式類,而多項式的構建是基於各個項,項是由因子構成,但實際上我只想在最初的時候對指數進行判斷之后。思考再三,既然只是修復這個bug,我決定在判斷多項式格式正確性上進行改動,即利用正則表達式提取指數進行判斷:
private static final String NUMBER = "(\\s*\\^\\s*[+-]?\\s*(\\d+)\\s*)";
private static final BigInteger MAXSIZE =
new BigInteger("10000");
Pattern pattern = Pattern.compile(NUMBER);
Matcher matcher = pattern.matcher(nowPoly);
while (matcher.find()) {
if (matcher.group(2) != null) {
BigInteger bigInteger = new BigInteger(
matcher.group(2)
);
if (bigInteger.compareTo(MAXSIZE) > 0) {
System.out.println("WRONG FORMAT!");
System.exit(0);
}
}
}
這大概就是測試中遇到的問題。在寫代碼中也遇到一些bug,比如第二次作業中第一次和第二次提交時第4個和最后一個測試點我都沒有通過,在對輸入格式進行逐行排查時發現了問題成功修復;還有就是我是用空格將每項進行分隔,但漏考慮*+(整數)的情況,導致此類數據雖然能計算但是是錯誤輸出。而后者是通過同學進行覆蓋性測試向我提出的,因此我認為寫測試數據還是十分有必要的!
自己發現別人程序bug所采取的策略
第一次作業是可以看到別人被hack多少回,因此在經歷最初的盲測后就從被hack最多的人代碼開始看起,由於水平有限,在看完該同學代碼后我覺得寫得真好,怎么被找出這么多bug,難以置信,因為我真的一個bug都沒找出來。接着就依次看hack次數由多到少的同學的代碼,看完第三位同學代碼便放棄了,之后沒有考慮過他們的設計結構。第二次作業由於在互測前寫了一部分數據,因此測起來還是比較方便,同時因為代碼量的增多,想要逐份代碼閱讀我覺得不太現實,結果別人程序在我設計的數據集中並沒有出錯。於是我選取一份我覺得邏輯比較清晰的代碼進行閱讀學習,領會面向對象的思想。我認為如果在短時間內想要將屋子里7個人代碼全部讀透,這不太現實。雖然不太清楚之后的程序是否是類似可采取自動化測試,如果說可以的話,我們可以借助自己構造的數據在發現別人輸出不正確的基礎上找到根源,也是一種能力的提升。當然,時間允許的話我希望能夠理解別人作業的架構,理解別人的思想,也希望大家能夠好好利用readme,將自己的思路大致描述一下,給其他同學有理解的基礎。
Applying Creational Pattern
通過三次作業在求導功能上的逐漸復雜,出題人本意是想讓同學們逐漸領會到面向對象編程的思想。但是我覺得自己還是寫出了面向過程的味道。在第三次作業中我沒有用到接口和類,求導遞歸終止條件是第二次作業的基本表達式形式,實際上是沒有達到鍛煉效果的。我在互測過程中看到組里面有一個接口寫得挺清楚的代碼,是值得我學習的!通過最近對創建對象模式的學習,我發現至今仍是處於簡單工廠模式,有需求產生時就new對象。而繼承和接口的使用則會將程序轉為工廠方法模式,讓實例化推遲到子類,我認為此類模式能夠實現更好地封裝性。希望在接下來的學習中能夠好好領悟。
總結
