1. 程式人生 > 其它 >2022_BUAA_OO 第一單元總結

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, String) 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^2a-bsin^2a-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, String) 0 1 1 1
factor.Vba.parseFactor(Lexer) 5 1 5 5
module.Element.Element() 0 1 1 1
module.Element.Element(BigInteger, HashSet, boolean) 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, Element) 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, boolean) 9 1 11 12
module.Operator.pow(Operator) 1 1 2 2
module.Operator.searchEle(HashSet, HashSet) 5 3 2 4
module.Operator.simplify1(Term, HashSet, boolean) 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, boolean, boolean, boolean, boolean) 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大多是我在做一些簡單的優化是沒有考慮周全導致的,優化無罪,不進行全面的測試才是原罪。

  • 走一步想兩步,要保證自己程式碼的可擴充套件性,畢竟重構多不是一件美事。