2022_BUAA_OO 第一單元總結
2022_BUAA_OO 第一單元總結
OO第一單元的作業主題是表示式化簡,主要的學習任務是熟悉Java語言基礎操作,掌握面向物件的思想,學會並習慣用類來管理資料,實踐分工協作的行為設計理念。以下便是我第一單元的學習心得與實踐總結。
Homework1
程式碼結構分析
第一次作業涉及的表示式結構較為單一,實現的程式碼結構也相對簡單。UML類圖如下:
-
架構分析
這次作業中我使用了四個類,分為主函式類、Lexer類、Parser類以及儲存單元運算元類,主要借鑑了第一次單元訓練中給出的遞迴下降的結構。利用Lexer、Parser類內部方法解析表示式,考慮到化簡後表示式的基礎項可以表示為
ax^b
的形式,而具有相同指數的項之間又可以合併,故基礎儲存單元是利用指數檢索係數的HashMap結構來儲存所有基礎的表達單元。 -
方法呼叫
在主類中利用輸入的表示式字串建立Lexer類物件,在Parser類物件中呼叫lexer方法來實現對錶達式中的每個語義塊進行提取,再用Parser中的方法遞迴下降,基礎運算方法定義在了運算元類中。
-
複雜度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
Lexer.getCurToken() | 0 | 1 | 1 | 1 |
Lexer.getNumber() | 2 | 1 | 3 | 3 |
Lexer.next() | 3 | 2 | 2 | 3 |
Lexer.process(String) | 0 | 1 | 1 | 1 |
MainClass.main(String[]) | 1 | 1 | 2 | 2 |
Operator.Operator() | 0 | 1 | 1 | 1 |
Operator.Operator(BigInteger, BigInteger) | 1 | 1 | 2 | 2 |
Operator.add(Operator, Boolean) | 3 | 1 | 3 | 3 |
Operator.getCoef(BigInteger) | 0 | 1 | 1 | 1 |
Operator.getMap() | 0 | 1 | 1 | 1 |
Operator.merge(BigInteger, BigInteger) | 5 | 1 | 3 | 3 |
Operator.mul(Operator) | 8 | 1 | 7 | 7 |
Operator.output(StringBuilder, BigInteger, String) | 4 | 1 | 2 | 4 |
Operator.power(Operator, BigInteger) | 1 | 1 | 2 | 2 |
Operator.toString() | 13 | 4 | 5 | 8 |
Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
Parser.getNum() | 3 | 1 | 4 | 4 |
Parser.parseExpr() | 6 | 1 | 5 | 6 |
Parser.parseFactor() | 8 | 5 | 5 | 5 |
Parser.parseTerm() | 1 | 1 | 2 | 2 |
Class | OCavg | OCmax | WMC |
---|---|---|---|
Lexer | 1.6 | 3 | 8 |
MainClass | 2 | 2 | 2 |
Operator | 3.6 | 13 | 36 |
Parser | 3.2 | 5 | 16 |
從程式碼複雜度分析資料中可以看到,絕大多數方法的圈複雜度以及模組設計複雜度都相對合理,只有Operator類中的toString
方法的基本複雜度較高,主要原因是該方法的實現較為隨意,缺乏較好的設計,同時追求在該方法內完成表示式的全部化簡工作,導致諸多選擇結構、迴圈結構等不完整。整體上來看,各方法的iv(G)都較低,各類的OCavg也處於正常水平,說明整體設計的內聚度還是較高的。
評測方面
由於結構相對簡單,本次作業在各次測試中均未出現bug,整個房間也沒有出現成功hack的樣例,幾無收穫。。。
Homework2
程式碼結構分析
本次作業加入了三角函式、求和函式以及自定義函式因子,表示式結構的複雜度驟然上升,我不得已選擇了重構。UML類圖如下:
-
架構分析
在本次作業中,我新增抽象的因子類。常數因子、冪函式因子、三角函式因子等都繼承於Factor類。
與第一次作業相比,主要區別在解析因子過程以及儲存單元設計。
解析因子方面,由於因子的種類較多,所以我借鑑了工廠模式的設計思路,在對遞迴下降到對因子進行解析時,採用一個FactorFactory類來判別因子種類,繼而返回不同因子的解析結果,對於表示式因子複用外層的Parser類中的parseExpr方法來提高程式碼的內聚度。
儲存單元設計方面,本次作業將基本項取為
冪函式(三角函式項+...)
,底層Element用於存放單個三角函式內部指數(或常數)以及外部指數,Term類存放單個三角函式項的sin集,cos集以及其係數,Operator類則是利用外部指數索引三角表達式,三角表達式是三角項的ArrayList集。對於自定義函式,我定義了一個靜態的FunctionSet類用來存放不同自定義函式的相關資訊,在自定義函式因子中進行引用解析。
-
表示式解析
我採用的是邊解析邊下沉的方法。對錶達式首先對其規範化(將**替換為^,去除空白字元等)。在parseExpr方法中解析出各個項,在parseFactor方法中,解析出各個因子,並將因子字串傳入FactorFactory中解析出對應的因子型別。
-
複雜度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
MainClass.main(String[]) | 1 | 1 | 2 | 2 |
factor.Constant.parseFactor(Lexer) | 12 | 1 | 8 | 8 |
factor.Expr.parseFactor(Lexer) | 1 | 2 | 2 | 2 |
factor.FactorFactory.FactorFactory(Lexer) | 0 | 1 | 1 | 1 |
factor.FactorFactory.getFact() | 6 | 6 | 6 | 6 |
factor.Power.parseFactor(Lexer) | 2 | 2 | 2 | 2 |
factor.Sum.parseFactor(Lexer) | 18 | 2 | 11 | 12 |
factor.Triangle.parseFactor(Lexer) | 23 | 3 | 7 | 11 |
factor.Vba.Vba(ArrayList |
0 | 1 | 1 | 1 |
factor.Vba.parseFactor(Lexer) | 11 | 1 | 8 | 9 |
module.Element.Element(BigInteger, BigInteger, BigInteger) | 0 | 1 | 1 | 1 |
module.Element.Element(Element) | 0 | 1 | 1 | 1 |
module.Element.compareTo(Element) | 4 | 5 | 4 | 5 |
module.Element.elementEqual(Element) | 1 | 1 | 3 | 3 |
module.Element.getCoefficient() | 0 | 1 | 1 | 1 |
module.Element.getInnerIndex() | 0 | 1 | 1 | 1 |
module.Element.getOutIndex() | 0 | 1 | 1 | 1 |
module.Element.setOutIndex(BigInteger) | 0 | 1 | 1 | 1 |
module.FunctionSet.addEle(String) | 3 | 1 | 3 | 3 |
module.FunctionSet.getEle(String) | 0 | 1 | 1 | 1 |
module.Operator.Operator() | 0 | 1 | 1 | 1 |
module.Operator.Operator(BigInteger, ArrayList |
0 | 1 | 1 | 1 |
module.Operator.Operator(BigInteger, BigInteger) | 2 | 1 | 1 | 2 |
module.Operator.Operator(BigInteger, BigInteger, BigInteger, boolean, boolean, boolean) | 0 | 1 | 1 | 1 |
module.Operator.add(Operator, Boolean) | 16 | 1 | 8 | 8 |
module.Operator.getConstant() | 0 | 1 | 1 | 1 |
module.Operator.getData() | 0 | 1 | 1 | 1 |
module.Operator.getInnerIndex() | 0 | 1 | 1 | 1 |
module.Operator.getTri(StringBuilder, Element) | 6 | 1 | 4 | 4 |
module.Operator.isConst() | 0 | 1 | 1 | 1 |
module.Operator.merge(Term, ArrayList |
9 | 4 | 5 | 5 |
module.Operator.mul(Operator) | 10 | 1 | 5 | 5 |
module.Operator.pow(Operator) | 1 | 1 | 2 | 2 |
module.Operator.toString() | 42 | 3 | 12 | 14 |
module.Term.Term(BigInteger) | 0 | 1 | 1 | 1 |
module.Term.Term(BigInteger, BigInteger, BigInteger, boolean, boolean, boolean) | 8 | 1 | 3 | 5 |
module.Term.Term(Term) | 2 | 1 | 3 | 3 |
module.Term.addAble(Term) | 15 | 7 | 5 | 8 |
module.Term.getCoefficient() | 0 | 1 | 1 | 1 |
module.Term.getCos() | 0 | 1 | 1 | 1 |
module.Term.getIndex(Element, boolean) | 14 | 6 | 8 | 8 |
module.Term.getSin() | 0 | 1 | 1 | 1 |
module.Term.setCoefficient(BigInteger) | 0 | 1 | 1 | 1 |
module.Term.termMul(Term) | 26 | 1 | 9 | 9 |
proocess.Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
proocess.Lexer.getCurToken() | 0 | 1 | 1 | 1 |
proocess.Lexer.getEle(boolean) | 8 | 1 | 6 | 6 |
proocess.Lexer.next() | 4 | 2 | 3 | 4 |
proocess.Lexer.process(String) | 0 | 1 | 1 | 1 |
proocess.Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
proocess.Parser.parseExpr() | 6 | 1 | 5 | 6 |
proocess.Parser.parseTerm() | 1 | 1 | 2 | 2 |
Class | OCavg | OCmax | WMC |
---|---|---|---|
MainClass | 2 | 2 | 2 |
factor.Constant | 8 | 8 | 8 |
factor.Expr | 2 | 2 | 2 |
factor.Factor | n/a | n/a | 0 |
factor.FactorFactory | 3.5 | 6 | 7 |
factor.Power | 2 | 2 | 2 |
factor.Sum | 9 | 9 | 9 |
factor.Triangle | 11 | 11 | 11 |
factor.Vba | 4.5 | 8 | 9 |
module.Element | 1.5 | 5 | 12 |
module.FunctionSet | 2 | 3 | 4 |
module.Operator | 3.79 | 16 | 43 |
module.Term | 3.5 | 9 | 35 |
proocess.Lexer | 2.2 | 4 | 11 |
proocess.Parser | 2.33 | 4 | 7 |
從程式碼複雜度分析資料可以看出,Operator類的toString方法的基本複雜度和模組設計複雜度都嚴重超標,這是由於基本儲存單元模組設計較為複雜,導致輸出需要考慮的情況眾多,同時反映出我沒有使用工程化思想設計該方法,主要是針對測出的bug進行縫縫補補。Term類中的termMul方法的基本複雜度和模組設計複雜度都較高,究其原因是我沒有對於sin和cos集提取方法來統一處理。而是利用較多重複程式碼來處理,同時由於管理sin,cos集的是ArrayList,我需要在該類中對這兩類的順序進行維護。
從類複雜度分析資料可以看出,由於儲存單元的變化,基本計算方法的實現過程的判斷條件眾多,再加上toString的不良設計,導致其總圈複雜度超標。
測評方面
本次強測出現了一個bug,是由於我初定的三角函式內部只能是冪函式或常數類,而為了能夠處理常數的常數次冪的形式,我在自定義函式內用實參替換形參時均添加了括號,而這導致了三角函式內部可能出現了表示式因子,無法進行解析。這是出現bug方法的基本情況:
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
factor.Triangle.parseFactor(Lexer) | 23 | 3 | 7 | 11 |
可以看出其圈複雜度還是處於正常範圍,但其程式碼行數較長,這也導致我不易發現該bug。
在互測環節,我被測出了一個bug,由於本次作業中我的優化做的相對草率,對於sin()0進行無腦替換,沒有考慮指數可能帶有前導0。同時我測出了其他同學沒有正確處理類似於sin(-1)2的形式,由於優化考慮不到位而輸出-sin(1)^2,還有同學沒有考慮toString輸出為空的情形而出錯。
Homework3
程式碼結構分析
本次作業支援三角函式內部巢狀因子型別,以及自定義函式可以進行巢狀,我選擇在第二次作業的基礎上進行增量開發。UML類圖如下:
-
架構分析
由於第二次作業我已經對於巢狀的自定義函式進行了考慮,所以不需要修改太多。而為了能夠儲存巢狀的三角函式型別,基礎儲存單元的大體結構並沒有改變,只是在最底層的Element類內部存放了三角函式內部存放的表示式的全部項,以HashSet
型別進行儲存,這樣就無需修改上層的計算方式。 而且體驗過第二次作業中使用的ArrayList管理的諸多不便,我將其多修改為了HashSet,並且在Element類中對equals和hashcode方法進行了重寫,這樣大大簡化了查詢和比較過程,同時為了方便簡化,我對於每個Element類中儲存的項單元放入List中進行了排序,保證其具有一定的順序,在進行巢狀時按照順序連線各項以字串形式與外層的項共同儲存。
-
效能改善
除了基礎的0次方、三角函式的簡單優化外,我只對
asin^2+bcos^2
、a-bsin^2
、a-bcos^2
等情況進行了優化,我採用的方式是在merge方法內,每有一項要合併入當前的主體表達式,若不能與某一項直接合並,就分別檢測其是否滿足上述三種形式若滿足其中某種形式,與主體表達式的某一項合併後再將該項從主體表達式中取出,再遞迴呼叫merge函式以進行徹底的化簡,每滿足一項就不再進行其他形式的檢測。若不滿足則直接加入表示式內。這樣做的程式碼量顯著減少,同時簡化效果較好,但由於沒限制遞迴層數,極易出現TLE型別錯誤,或許應該增加測定函式執行時間的函式。 -
複雜度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
MainClass.main(String[]) | 1 | 1 | 2 | 2 |
factor.Constant.getNum(Lexer) | 3 | 1 | 4 | 4 |
factor.Constant.parseFactor(Lexer) | 2 | 1 | 2 | 2 |
factor.Expr.parseFactor(Lexer) | 1 | 2 | 2 | 2 |
factor.FactorFactory.FactorFactory(Lexer) | 0 | 1 | 1 | 1 |
factor.FactorFactory.getFact() | 6 | 6 | 6 | 6 |
factor.Power.parseFactor(Lexer) | 2 | 2 | 2 | 2 |
factor.Sum.parseFactor(Lexer) | 18 | 2 | 11 | 12 |
factor.Triangle.parseFactor(Lexer) | 21 | 3 | 6 | 11 |
factor.Vba.Vba(ArrayList |
0 | 1 | 1 | 1 |
factor.Vba.parseFactor(Lexer) | 5 | 1 | 5 | 5 |
module.Element.Element() | 0 | 1 | 1 | 1 |
module.Element.Element(BigInteger, HashSet |
0 | 1 | 1 | 1 |
module.Element.Element(Element) | 0 | 1 | 1 | 1 |
module.Element.compareTo(Element) | 4 | 5 | 4 | 5 |
module.Element.elementEqual(Element) | 1 | 1 | 2 | 2 |
module.Element.equals(Object) | 3 | 3 | 2 | 4 |
module.Element.getFactors() | 0 | 1 | 1 | 1 |
module.Element.getIndex() | 0 | 1 | 1 | 1 |
module.Element.hashCode() | 0 | 1 | 1 | 1 |
module.Element.isExpr() | 0 | 1 | 1 | 1 |
module.Element.setIndex(BigInteger) | 0 | 1 | 1 | 1 |
module.Element.sumLength() | 1 | 1 | 2 | 2 |
module.FunctionSet.addEle(String) | 3 | 1 | 3 | 3 |
module.FunctionSet.getEle(String) | 0 | 1 | 1 | 1 |
module.Operator.Operator() | 0 | 1 | 1 | 1 |
module.Operator.Operator(BigInteger, BigInteger) | 0 | 1 | 1 | 1 |
module.Operator.Operator(BigInteger, HashSet |
0 | 1 | 1 | 1 |
module.Operator.Operator(Operator, BigInteger, boolean, boolean, boolean, boolean) | 0 | 1 | 1 | 1 |
module.Operator.add(Operator, Boolean) | 16 | 1 | 8 | 8 |
module.Operator.factorExtract(BigInteger, Term, StringBuilder) | 34 | 1 | 11 | 11 |
module.Operator.findRepeat(HashSet |
3 | 3 | 3 | 3 |
module.Operator.getConstant() | 0 | 1 | 1 | 1 |
module.Operator.getData() | 0 | 1 | 1 | 1 |
module.Operator.getTri(Element, boolean) | 12 | 3 | 9 | 11 |
module.Operator.merge(Term, HashSet |
12 | 4 | 6 | 6 |
module.Operator.modeCheck(String) | 14 | 3 | 8 | 13 |
module.Operator.mul(Operator) | 10 | 1 | 5 | 5 |
module.Operator.nonEquSquare(Term, Term, HashSet |
9 | 1 | 11 | 12 |
module.Operator.pow(Operator) | 1 | 1 | 2 | 2 |
module.Operator.searchEle(HashSet |
5 | 3 | 2 | 4 |
module.Operator.simplify1(Term, HashSet |
54 | 5 | 13 | 18 |
module.Operator.simplify2(Term, HashSet |
35 | 11 | 14 | 17 |
module.Operator.splitFactors() | 13 | 1 | 6 | 6 |
module.Operator.toString() | 13 | 6 | 7 | 9 |
module.Term.Term() | 0 | 1 | 1 | 1 |
module.Term.Term(BigInteger) | 0 | 1 | 1 | 1 |
module.Term.Term(BigInteger, HashSet |
8 | 1 | 3 | 5 |
module.Term.Term(Term) | 2 | 1 | 3 | 3 |
module.Term.addAble(Term) | 1 | 1 | 1 | 2 |
module.Term.findElement(Term, Element, boolean) | 12 | 6 | 4 | 6 |
module.Term.getCoefficient() | 0 | 1 | 1 | 1 |
module.Term.getCos() | 0 | 1 | 1 | 1 |
module.Term.getSet(boolean) | 2 | 2 | 1 | 2 |
module.Term.getSin() | 0 | 1 | 1 | 1 |
module.Term.orderIn(Boolean) | 2 | 1 | 2 | 2 |
module.Term.setCoefficient(BigInteger) | 0 | 1 | 1 | 1 |
module.Term.termMul(Term) | 8 | 1 | 5 | 5 |
module.Term.toString() | 6 | 1 | 5 | 5 |
proocess.Lexer.Lexer(String) | 0 | 1 | 1 | 1 |
proocess.Lexer.getCurToken() | 0 | 1 | 1 | 1 |
proocess.Lexer.getEle(boolean) | 8 | 1 | 6 | 6 |
proocess.Lexer.next() | 4 | 2 | 3 | 4 |
proocess.Lexer.process(String) | 0 | 1 | 1 | 1 |
proocess.Parser.Parser(Lexer) | 0 | 1 | 1 | 1 |
proocess.Parser.parseExpr() | 6 | 1 | 5 | 6 |
proocess.Parser.parseTerm() | 1 | 1 | 2 | 2 |
Class | OCavg | OCmax | WMC |
---|---|---|---|
MainClass | 2 | 2 | 2 |
factor.Constant | 3 | 4 | 6 |
factor.Expr | 2 | 2 | 2 |
factor.Factor | n/a | n/a | 0 |
factor.FactorFactory | 3.5 | 6 | 7 |
factor.Power | 2 | 2 | 2 |
factor.Sum | 9 | 9 | 9 |
factor.Triangle | 10 | 10 | 10 |
factor.Vba | 3 | 5 | 6 |
module.Element | 1.58 | 5 | 19 |
module.FunctionSet | 2 | 3 | 4 |
module.Operator | 3.65 | 14 | 43 |
module.Term | 2.5 | 6 | 35 |
proocess.Lexer | 2.2 | 4 | 11 |
proocess.Parser | 2.33 | 4 | 7 |
從程式碼複雜度分析資料可以看出,由於本次對於toString方法內部提取了若干方法,同時進行了分層的封裝處理,這個毒瘤終於被拿掉,但是簡化相關的方法由於判斷條件眾多,簡化形式眾多而表現出較高的圈複雜度和模組設計複雜度、
從類複雜度分析來看,本次作業與上次情況大致相同,各類的內聚度依然較小。
評測方面
強測方面,由於忘記考慮三角函式內部巢狀的sum或自定義函式的解析結果可能是表示式,忘記新增括號,導致輸出格式錯誤。這是出現bug方法的基本情況:
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
factor.Triangle.parseFactor(Lexer) | 21 | 3 | 6 | 11 |
其圈複雜度和程式碼行數與其他方法相比並無異常,這主要是我考慮不周以及測試不到位引起的
互測方面,又由於sum內部迴圈變數的資料型別使用的是int類,而被房間的同學群起而攻。同時我發現有較多同學沒有考慮常數的常數次冪的情況。
發現BUG策略
為實現覆蓋性測試,我認為應當對知道數的形式化描述進行細緻分析,然後從最底層的因子開始向上分析構建樣例,儘量覆蓋全部情況,同時注意指導書中給出的一些情況的特殊處理,如sum函式內的求和上限低於下限的情況。再次要在一些邊緣資料上下功夫,如測試一些0特殊情況、因子單獨出現、考慮選取的資料型別的範圍。由於時間問題,我沒有嘗試構建自動化測試。
心得體會
-
提前做好寒假的Pre真的很重要(仍然記得第一週埋頭苦學Java面向物件基礎的狼狽樣子),不過速成的效率還蠻高的,只不過對於一些語言特性還要做進一步地深入瞭解。
-
工廠模式具有很強的擴充套件性,它可以使建立過程對使用者透明,在幾次作業中對於單個因子的解析提供了很大的便利。
-
程式碼風格很重要,能夠很大程度上提升程式碼的閱讀效率。
-
討論區真的有不少寶藏,從那裡看到了不少神奇的化簡方式,也在其引導下查閱資料學習了一些新知(深克隆、序列化、重寫hash等等)。
-
要花足夠的心思在測試環節,後兩次作業出現的bug大多是我在做一些簡單的優化是沒有考慮周全導致的,優化無罪,不進行全面的測試才是原罪。
-
走一步想兩步,要保證自己程式碼的可擴充套件性,畢竟重構多不是一件美事。