BUAA_2022_OO_第一單元總結
摘要
本部落格主要介紹本人針對面向物件第一單元作業(表示式化簡)的架構以及一些心得體會。因本人在第二次作業時的架構已滿足第三次作業要求且後續沒有明顯變動,故後兩次作業合併分析。
1. 第一次作業
1.1 作業簡介
第一次作業只針對含單層括號且只包括冪函式(常數可以看作0次冪)的表示式進行化簡,最終得到的結果為多個冪函式相加。
1.2 架構設計
1.2.1 類圖展示
1.2.2 類設計分析
paraser類:
paraser類集成了詞法分析和遞迴下降功能,解析得到一棵表示式樹。
exp類:
exp類作為樹節點時其子節點均為term,表示exp由多個term相加組成。
term類:
exp類作為樹節點時其子節點均為factor,表示term由多個factor相乘組成。
factor類:
factor類為一抽象父類,其子類包括var,const,expfactor。
const類:
代表常數類,使用一BigInteger型別屬性儲存對應的常數值。
var類:
代表冪函式,使用一BigInteger型別屬性儲存對應的冪次,使用String型別屬性儲存對應的符號(本次作業中均為x)。
expfactor類:
代表表示式因子,使用一BigInteger型別屬性儲存對應的冪次,使用exp類屬性儲存其對應的表示式。
1.3 程式結構分析
1.3.1 各類程式碼量
1.3.2 方法複雜度
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
Const.Const(BigInteger) | 0 | 1 | 1 | 1 |
Const.getCons() | 0 | 1 | 1 | 1 |
Const.getType() | 0 | 1 | 1 | 1 |
Const.testOutPut() | 0 | 1 | 1 | 1 |
Exp.addTerm(Term) | 0 | 1 | 1 | 1 |
Exp.calculate() | 1 | 1 | 2 | 2 |
Exp.mergeAdd(HashMap<BigInteger, BigInteger>, HashMap<BigInteger, BigInteger>) |
8 | 2 | 4 | 4 |
Exp.testOutPut() | 2 | 1 | 2 | 3 |
Exp.toString() | 22 | 4 | 10 | 12 |
ExpFactor.calculate() | 5 | 3 | 4 | 4 |
ExpFactor.getPow() | 0 | 1 | 1 | 1 |
ExpFactor.getType() | 0 | 1 | 1 | 1 |
ExpFactor.mergeMult(HashMap<BigInteger, BigInteger>, HashMap<BigInteger, BigInteger>) | 12 | 2 | 5 | 5 |
ExpFactor.setExp(Exp) | 0 | 1 | 1 | 1 |
ExpFactor.setPow(BigInteger) | 0 | 1 | 1 | 1 |
ExpFactor.testOutPut() | 0 | 1 | 1 | 1 |
Factor.getType() | 0 | 1 | 1 | 1 |
Factor.testOutPut() | 0 | 1 | 1 | 1 |
Main.main(String[]) | 0 | 1 | 1 | 1 |
Paraser.Paraser(String) | 0 | 1 | 1 | 1 |
Paraser.blank() | 3 | 1 | 3 | 4 |
Paraser.cons() | 0 | 1 | 1 | 1 |
Paraser.exp() | 4 | 1 | 5 | 5 |
Paraser.expFactor() | 2 | 1 | 4 | 4 |
Paraser.factor() | 5 | 1 | 5 | 5 |
Paraser.getChar(int) | 2 | 2 | 2 | 2 |
Paraser.isAddOrSub(char) | 1 | 1 | 1 | 2 |
Paraser.isNum(char) | 1 | 1 | 1 | 2 |
Paraser.num() | 5 | 1 | 5 | 6 |
Paraser.pow() | 2 | 1 | 3 | 3 |
Paraser.term() | 4 | 1 | 5 | 5 |
Paraser.var() | 2 | 1 | 4 | 4 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.calculate() | 8 | 1 | 7 | 7 |
Term.getSign() | 0 | 1 | 1 | 1 |
Term.mergeMult(HashMap<BigInteger, BigInteger>, HashMap<BigInteger, BigInteger>) | 12 | 2 | 5 | 5 |
Term.setSign(char) | 4 | 1 | 2 | 4 |
Term.testOutPut() | 1 | 1 | 2 | 2 |
Var.getPow() | 0 | 1 | 1 | 1 |
Var.getType() | 0 | 1 | 1 | 1 |
Var.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Var.testOutPut() | 1 | 2 | 1 | 2 |
總的來看,除了tostring函式因為做了較多的特殊判斷認知複雜度較高,其他方法均較為合理。
2. 第二、三次作業
2.1 作業簡介
第二、三次作業在第一次作業的基礎上增加了三角函式、sum函式、自定義函式三項。
2.2 架構設計
2.2.1 類圖展示
2.2.2 類設計分析
由第一次作業程式迭代而來,故只分析新增類。
Sin類:
factor的子類,Factor元素儲存其因子,pow儲存其冪次。
Cos類:
factor的子類,Factor元素儲存其因子,pow儲存其冪次。
Calresult類:
為便於因式合併,將除係數以外的項的組成(Sin因子,Cos因子,冪函式)抽象為一個類,複寫其equals,hashCode方法,便於後續因式合併。使用HashMap儲存sin因子、cos因子,使用BigInteger儲存冪函式指數。
Calculate類:
發現加法乘法函式在許多類中都需要使用,故抽象至一計算類中。
Func類:
對應自定義函式,使用Exp儲存自定義函式的表示式,ArrayList<String>儲存函式形參。
FuncFactor類:
對應自定義函式呼叫,使用Func儲存對應的自定義函式,ArrayList<Factor>儲存函式呼叫時的實參。
Sum類:
對應sum函式,兩BigInteger屬性begin與end對應求和起始與終止值。Factor對應需要求和的因子。
2.3 程式結構分析
2.3.1各類程式碼量
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
CalResult.CalResult() | 0 | 1 | 1 | 1 |
CalResult.CalResult(HashMap<HashMap<CalResult, BigInteger>, BigInteger>, HashMap<HashMap<CalResult, BigInteger>, BigInteger>, BigInteger) | 0 | 1 | 1 | 1 |
CalResult.addCos(HashMap<CalResult, BigInteger>, BigInteger) | 0 | 1 | 1 | 1 |
CalResult.addSin(HashMap<CalResult, BigInteger>, BigInteger) | 0 | 1 | 1 | 1 |
CalResult.equals(Object) | 3 | 3 | 3 | 5 |
CalResult.getCoss() | 0 | 1 | 1 | 1 |
CalResult.getPow() | 0 | 1 | 1 | 1 |
CalResult.getSins() | 0 | 1 | 1 | 1 |
CalResult.handleTri(StringBuilder, HashMap<CalResult, BigInteger>) | 11 | 1 | 7 | 7 |
CalResult.hashCode() | 0 | 1 | 1 | 1 |
CalResult.isExpr(HashMap<CalResult, BigInteger>) | 16 | 8 | 4 | 8 |
CalResult.mult(CalResult) | 0 | 1 | 1 | 1 |
CalResult.multTri(HashMap<HashMap<CalResult, BigInteger>, BigInteger>, HashMap<HashMap<CalResult, BigInteger>, BigInteger>) | 4 | 1 | 3 | 3 |
CalResult.setPow(BigInteger) | 0 | 1 | 1 | 1 |
CalResult.toString(boolean) | 14 | 2 | 11 | 11 |
Calculate.mergeAdd(HashMap<CalResult, BigInteger>, HashMap<CalResult, BigInteger>) | 7 | 1 | 4 | 4 |
Calculate.mergeMult(boolean, HashMap<CalResult, BigInteger>, HashMap<CalResult, BigInteger>) | 18 | 3 | 6 | 7 |
Const.Const(BigInteger) | 0 | 1 | 1 | 1 |
Const.calculate(HashMap<String, Factor>) | 0 | 1 | 1 | 1 |
Const.getCons() | 0 | 1 | 1 | 1 |
Const.getType() | 0 | 1 | 1 | 1 |
Const.testOutPut() | 0 | 1 | 1 | 1 |
Cos.Cos(Factor) | 0 | 1 | 1 | 1 |
Cos.calculate(HashMap<String, Factor>) | 14 | 7 | 11 | 12 |
Cos.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Cos.testOutPut() | 1 | 1 | 1 | 2 |
Cos.toString() | 1 | 1 | 1 | 2 |
Exp.addTerm(Term) | 0 | 1 | 1 | 1 |
Exp.calculate(HashMap<String, Factor>) | 1 | 1 | 2 | 2 |
Exp.testOutPut() | 2 | 1 | 2 | 3 |
Exp.toString(boolean) | 43 | 6 | 13 | 15 |
ExpFactor.calculate(HashMap<String, Factor>) | 5 | 3 | 4 | 4 |
ExpFactor.getPow() | 0 | 1 | 1 | 1 |
ExpFactor.getType() | 0 | 1 | 1 | 1 |
ExpFactor.setExp(Exp) | 0 | 1 | 1 | 1 |
ExpFactor.setPow(BigInteger) | 0 | 1 | 1 | 1 |
ExpFactor.testOutPut() | 1 | 1 | 1 | 2 |
Factor.calculate(HashMap<String, Factor>) | 0 | 1 | 1 | 1 |
Factor.getType() | 0 | 1 | 1 | 1 |
Factor.testOutPut() | 0 | 1 | 1 | 1 |
Func.Func(String) | 0 | 1 | 1 | 1 |
Func.Func(String, Exp, ArrayList<String>) | 0 | 1 | 1 | 1 |
Func.addFuncFParam(String) | 0 | 1 | 1 | 1 |
Func.getExp() | 0 | 1 | 1 | 1 |
Func.getFuncFParams() | 0 | 1 | 1 | 1 |
Func.getFuncName() | 0 | 1 | 1 | 1 |
Func.setExp(Exp) | 0 | 1 | 1 | 1 |
Func.toString() | 1 | 1 | 2 | 2 |
FuncFactor.FuncFactor(Func) | 0 | 1 | 1 | 1 |
FuncFactor.addFuncRParams(Factor) | 0 | 1 | 1 | 1 |
FuncFactor.calculate(HashMap<String, Factor>) | 2 | 1 | 3 | 3 |
FuncFactor.testOutPut() | 1 | 1 | 2 | 2 |
Main.main(String[]) | 2 | 1 | 3 | 3 |
Paraser.Paraser(String) | 0 | 1 | 1 | 1 |
Paraser.addFunc(String, Func) | 0 | 1 | 1 | 1 |
Paraser.blank() | 3 | 1 | 3 | 4 |
Paraser.cons() | 0 | 1 | 1 | 1 |
Paraser.exp() | 4 | 1 | 5 | 5 |
Paraser.expFactor() | 2 | 1 | 4 | 4 |
Paraser.factor() | 8 | 1 | 8 | 8 |
Paraser.func() | 1 | 1 | 2 | 2 |
Paraser.funcUse() | 2 | 1 | 3 | 3 |
Paraser.getChar(int) | 2 | 2 | 2 | 2 |
Paraser.isAddOrSub(char) | 1 | 1 | 1 | 2 |
Paraser.isFunc(char) | 1 | 1 | 1 | 3 |
Paraser.isNum(char) | 1 | 1 | 1 | 2 |
Paraser.isSum(int) | 1 | 1 | 2 | 2 |
Paraser.isTri(int) | 2 | 1 | 3 | 3 |
Paraser.isVar(char) | 1 | 1 | 1 | 4 |
Paraser.num() | 5 | 1 | 5 | 6 |
Paraser.pow() | 2 | 1 | 3 | 3 |
Paraser.sum() | 0 | 1 | 1 | 1 |
Paraser.term() | 4 | 1 | 5 | 5 |
Paraser.tri() | 8 | 2 | 8 | 8 |
Paraser.var() | 2 | 1 | 4 | 4 |
Sin.Sin(Factor) | 0 | 1 | 1 | 1 |
Sin.calculate(HashMap<String, Factor>) | 16 | 7 | 13 | 14 |
Sin.getFactor() | 0 | 1 | 1 | 1 |
Sin.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Sin.testOutPut() | 1 | 1 | 1 | 2 |
Sin.toString() | 1 | 1 | 1 | 2 |
Sum.Sum(Const, Const, Factor) | 0 | 1 | 1 | 1 |
Sum.calculate(HashMap<String, Factor>) | 3 | 2 | 4 | 4 |
Sum.getType() | 0 | 1 | 1 | 1 |
Sum.testOutPut() | 0 | 1 | 1 | 1 |
Term.addFactor(Factor) | 0 | 1 | 1 | 1 |
Term.calculate(HashMap<String, Factor>) | 5 | 1 | 5 | 5 |
Term.getSign() | 0 | 1 | 1 | 1 |
Term.mergeMult(HashMap<CalResult, BigInteger>, HashMap<CalResult, BigInteger>) | 17 | 2 | 6 | 6 |
Term.setSign(char) | 4 | 1 | 2 | 4 |
Term.testOutPut() | 1 | 1 | 2 | 2 |
Var.calculate(HashMap<String, Factor>) | 6 | 3 | 5 | 5 |
Var.getPow() | 0 | 1 | 1 | 1 |
Var.getType() | 0 | 1 | 1 | 1 |
Var.setPow(BigInteger) | 0 | 1 | 1 | 1 |
Var.setSymbol(String) | 0 | 1 | 1 | 1 |
Var.testOutPut() | 1 | 2 | 1 | 2 |
Var.toString() | 1 | 2 | 1 | 2 |
乘法、加法因為要兼顧合併同類項,認知複雜度較高;輸出函式要進行一定的化簡,分支語句較多,認知複雜度較高,其他方法複雜度均較低。
3. Bug測試
本人在三次作業的強測、互測中均沒有出現bug,並在三次互測中均發現了其他同學的bug,採用的測試方式如下
3.1 自動化測試
資料點的生成同樣採用了遞迴下降的方法,可以看作對解析表示式求逆,即遞歸向下生成表示式。產生的資料使用subProcess.Popen函式重定向輸入到jar包中,將兩個jar包的輸出使用eval函式對拍。但該方法存在缺點,即不能代入sin(x)**2 = 1 - cos(x)**2等複雜變換做正確性判斷,可以考慮改成撒點代入數值的方式。同時沒有對輸出的格式的正確性做判斷,可以將輸出結果輸入官方包做判斷。
3.2 邊界資料構造
通過閱讀文法規則和資料限制,構造一些邊界資料,如數值大小超越int值;針對零次冪、二次冪等優化的測試資料。受時間限制,並沒有閱讀程式碼然後構造針對資料,更多的是先使用一些資料測試其他同學的程式做了哪些工作,然後針對測試。
4. 架構迭代
為便於後續程式迭代,避免重構,在第一次作業設計時就預想了後續可能會出現其他種類因子等可能的情況,儘量只根據文法書寫程式而不是利用一些題面給的限制條件(如冪次不超過8)。第二次作業注意到第一次作業的合併同類項方式不能繼續使用,而合併同類項本質即為將係數以外的特徵抽象出來,故重新設計了計算函式,在此基礎上完成了新的合併同類項,同時完成了增量開發,此時的程式已滿足三次作業的要求。
5. 心得體會
本單元作業對我個人而言並不算難,但作為一名重修生,學習下來依然有很多心得體會。
因為同時體驗過去年和今年個題目,我認為這次第一單元題目的更改還是較為成功的。上一屆的第一單元第一次作業我認為有過強的正則表示式引導傾向,導致許多同學第二單元重構;而本次作業在一開始就對使用遞迴下降做了一個好的引導,也能為學弟學妹們將來編譯原理的學習做一定的鋪墊(個人認為本單元的任務類似於編譯原理pcode任務),我認為對作業題目的修改還是成功的。不過從另一種程度上講第一次作業難度增加對假期沒有完成pre且java開發不熟練的同學更不友好了,我本人就遇到了在第一次作業顯得較為掙扎的學妹,最後也是壓線才通過了中測,因此我依然會在這篇部落格中重申我的觀點,如果你不是奆佬,請提前認真完成pre的學習!