2022_BUAA_OO第一單元總結
寫在前面
首先祝賀我自己還算比較平穩地完成了OO的第一單元任務,儘管在強測中丟失了一些分數,但是在完成三次作業後,第一單元的設計過程還是給我帶來了很大的啟發。在本次部落格中,我將從作業架構、Bug分析、課程體會、單元總結四個方面介紹我OO第一單元的旅程。
一、作業架構
從我個人的理解出發,OO作業有一大特點就是"架構依賴"——只有建立起好的架構設計,我們才能在每一個層次舒服地使用資料結構進行資料處理。我與許多位同學討論過架構設計的基本思路,能夠在基準路線上達成一致;至於具體的實現方法,則不盡相同。我認為我的架構並不十分完善,更說不上優秀,但還是有一些優點,還是值得記錄一下,以便日後思考、總結、避雷。
三次作業的UML類圖及分析
此處首先宣告,由於本人的程式碼實現中不存在繼承關係、也不存在介面實現,但是的確具有一定的層次劃分,故而UML類圖中可能會根據需要新增一些箭頭以表示層次間的關係,請不要誤讀。另外,一些用於測試或是沒有使用的方法和變數就不在類圖中列出了。
第一次作業
架構及實現方法簡析
第一次作業的基本思路是利用正則表示式進行自上而下的解析,從而得到結果進行計算。由於不包含巢狀括號,只需一次解析即可計算結果。依照下面的層級進行解析計算即可。
$$表示式\longrightarrow項\longrightarrow因子
$$
為此,設計了Expr、Term、Factor這三個類。首先重寫了Expr的構造方法,在構造Expr時需要給入一個String作為表示式輸入;Expr中的cutExpression
ArrayList<Term>
容器當中(這是參考了第一次實驗的寫法)。通過不斷的遍歷,可以將表示式徹底切分為一個一個的Term,並在Expr層次中儲存,便於在此層次呼叫下一層次的Term。這便是解析的第一步。自然而然地,我們便會聯想到解析的第二步該怎麼做——在Term類和Factor類中再次建立一層這樣的關係。這樣一來,我們就建立了一個三層的層次關係,並且能夠自上而下地將表示式解析為因子。
解析完畢,我們需要的是計算,與剛剛解析過程的自上而下相反,這是一個自下而上的過程。只要解析的思路足夠清晰,就會發現我們只需要在Term層次乘法計算其Factor並返回結果、在Expr層次加法計算Term並返回結果就好了。不過在此處我遇到了一個難題——處理的目標是字串,其中有亂七八糟的x、2*x、x**3這樣的因子,它們要如何做加法?如何做乘法?經過與同學的討論,我在解析的過程中添了一筆——將所有的因子都歸範圍a*x**b的標準形式
除了主要的解析思路之外,為了易於對字串進行處理,我構造了一個Formal類用於存放常用的處理字串的方法,並將它們定義為靜態方法(如除空白符、合併冗餘符號、字串轉BigInteger等);另外,構造了一個Count類存放多項式乘多項式的方法,計算的過程會呼叫這個類的方法。如此一來,整體的架構和層次便比較清晰了。
架構優缺點與艱辛的實現過程
優點:從總體上評價這次架構,是主體通透的的。自上而下和自下而上的兩個過程都比較清晰;靜態方法的構造提高了程式碼的複用率;且這是在我不知道遞迴下降的處理方法時想出來的,這使得我在後面的作業中都可以沿用這一套方法。其它的一些思路也很值得以後參考。至少在架構設計上,避免了"重構"。
缺點:當然,畢竟是第一次作業,缺點總是有的,這裡主要對映在一些細節上。例如,對於因子中的表示式項,我並未打算繼續將其作為表示式繼續解析處理,而是看作一個因子,在當層就計算得到結果並向上返回。這樣做有許多缺點:
-
不能解析含有巢狀括號的表示式(雖然題目沒有要求,但主要原因是我沒有想到)
-
極大削弱了程式碼的可擴充套件性(如果要新增一些其他的因子怎麼辦?又要重寫一遍Factor層的計算?)
-
提高了解析與計算的耦合度
這是一個非常致命的缺點。從架構上不僅不健康,還使得我書寫程式碼的時候思路非常混亂:既要解析,又要計算。這直接導致了我第二次作業的徹底重構。
其它的缺點中比較令我印象深刻的還有:
-
沒提前規範好項和表示式的符號——這裡缺一個"+",那裡又有一個"+"
-
乘法計算的割裂。我將乘法分為單項式相乘、單項式乘多項式、多項式相乘,這其實很笨——但歸根結底,這一缺陷延伸自上面提到的"致命的缺點"。我固執地將只解析了一遍的因子看作"一切的終結",導致我割裂了乘法計算。
-
加法計算寫在瞭解析層次當中。我在Expr內書寫了一個"表示式加法"的方法,其實這樣不好,這會提高計算和解析的耦合度。
-
複用程式碼考慮不周。例如,取出括號中的內容應是非常常用的方法,但我卻在很多類中都寫了一遍這個方法;或許寫作一個靜態方法(當然,更加優秀的結構可以寫作繼承方法)是更好的選擇。
種種......
不過,即便是擁有了正確的架構設計,想要將架構順利實現也是非常有難度的。在第一次作業實現的過程中,我遇到了很多問題,磕磕絆絆地才完成了這一次作業。這裡的難度主要源自:
-
本人剛開始接觸Java,很多方法的使用實在是不熟練,不知道該如何利用資料結構解決問題
-
邊界情況的疏忽。出現0次冪多項式怎麼辦?出現複雜的多項式怎麼辦?
-
種種不熟練導致的設計不統一。如將複用率高的程式碼進行抽離是實現過程中想到的方法,導致程式碼混亂。
-
解析和計算的耦合過高,導致思路混亂。這一點在上面也提到過了。
-
正則表示式的構建過於麻煩,極其容易出錯。
基於上面的種種原因,僅僅在第一次作業,我就經歷了一次重構、一次為時一天的Debug階段,歸根結底其原因是架構的細節沒有設計好。於是乎在第二次作業中,我對其中的一些方面進行了一定的改進。
基於度量分析第一次作業的程式結構(除MainClass)
程式碼規模
Class | 屬性個數 | 方法個數 | 程式碼規模/行 |
---|---|---|---|
Count | 0 | 4 | 66 |
Expr | 2 | 9 | 100 |
Expression | 0 | 2 | 30 |
Factor | 3 | 9 | 59 |
Formal | 0 | 11 | 207 |
Term | 3 | 9 | 96 |
類的OO度量
Class | OCavg | OCMax | WMC |
---|---|---|---|
homework.Count | 2.75 | 4.0 | 11.0 |
homework.Expr | 2.2222222222222223 | 8.0 | 20.0 |
homework.Factor | 1.4444444444444444 | 3.0 | 13.0 |
homework.Formal | 3.5454545454545454 | 11.0 | 39.0 |
homework.Term | 1.8888888888888888 | 4.0 | 17.0 |
homework.Test | 3.0 | 3.0 | 3.0 |
task.Expression | 2.5 | 4.0 | 5.0 |
Average | 2.369565217391304 | 4.75 | 13.625 |
方法複雜度分析
Method | CogC | ev(G) | iv(G) | v(G) |
---|---|---|---|---|
homework.Count.normalNum() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.getExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.getPower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.getRatio(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.getTerms() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.getElements() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.getFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.getIsNormal() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.setElements(ArrayList) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.setFactor(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.setIsNormal(int) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.cleanNote(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.standerString(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.MainClass.main(String[]) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.Term(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.getTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.setTerm(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Expression.cleanNote(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.Expr(String) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Expr.cntExpr() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Expr.cutExpression(String) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Expr.printTerm() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Factor.printElements() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Formal.pickContent(String) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Formal.pickFactors(String) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Term.buildArray(String) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Term.cutTerm(String) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Term.printFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Count.mulSingle(String, String) | 2.0 | 1.0 | 3.0 | 3.0 |
homework.Factor.Factor(String) | 2.0 | 1.0 | 2.0 | 2.0 |
homework.Formal.switchPower(String) | 2.0 | 3.0 | 1.0 | 3.0 |
homework.Term.cntTerm() | 2.0 | 2.0 | 3.0 | 3.0 |
homework.Test.testMulti() | 2.0 | 1.0 | 3.0 | 3.0 |
homework.Count.mulMulti(ArrayList, ArrayList) | 3.0 | 1.0 | 3.0 | 3.0 |
homework.Factor.cutFactors() | 3.0 | 1.0 | 3.0 | 3.0 |
homework.Formal.formalizeFac(String) | 3.0 | 3.0 | 3.0 | 3.0 |
homework.Formal.onePow(String) | 3.0 | 1.0 | 3.0 | 3.0 |
homework.Formal.switchInt(String) | 3.0 | 3.0 | 1.0 | 3.0 |
homework.Formal.cleanString(String) | 4.0 | 1.0 | 3.0 | 4.0 |
task.Expression.cleanString(String) | 4.0 | 1.0 | 3.0 | 4.0 |
homework.Count.mergeFactor(String) | 5.0 | 1.0 | 3.0 | 4.0 |
homework.Term.combine() | 5.0 | 1.0 | 4.0 | 4.0 |
homework.Formal.formalPower(String) | 10.0 | 1.0 | 6.0 | 6.0 |
homework.Expr.mergeTerm() | 11.0 | 2.0 | 8.0 | 8.0 |
homework.Formal.switchExpr(String) | 28.0 | 2.0 | 9.0 | 11.0 |
Total | 102.0 | 55.0 | 100.0 | 109.0 |
Average | 2.217391304347826 | 1.1956521739130435 | 2.1739130434782608 | 2.369565217391304 |
總體的工程量並不算巨大,但由於複用程式碼的規劃不佳,導致我在第一次作業中書寫了許多冗餘的方法,故而第三個表格十分冗長。類與類之間的界限算是比較明確,但是很多細節還是有待提高,這會體現在第二次作業中。
第二次作業
架構方法及實現簡析
第二次作業相比第一次作業,增加了表示式的擴充套件內容。這其中最主要的改動包括:
-
新增自定義函式因子,不可遞迴定義、不可遞迴呼叫。
-
新增求和函式因子,只能單獨使用。
-
新增sin、cos三角函式。
當然,具體到細節層面還包括一些資料限制、IO修改等,不過這些改動只要是做過作業的同學都心知肚明。我認為我們最需要關注的就是因子種類的複雜化和括號層次的加深。經歷了第一次作業的毒打後,我決定先深度設計架構——包括各種細節——然後再進行具體實現。總體的解析思想沒有發生特別大的變化,但我開始進行遞迴解析的嘗試。
$$表示式\longrightarrow項\longrightarrow因子\longrightarrow整數因子、冪函式因子、三角函式因子、表示式因子、自定義函式因子、求和函式因子
$$
上面是一次解析的完整過程。相比於第一次作業,我在因子解析的時候增加了型別判斷的系列方法,並且對每一種因子專門書寫了一個類用於其標準化。為了便於計算,我將因子分出兩個基本類:
$$三角函式因子:[+-][(sin)|(cos)][(expression)]**n
$$
$$
普通因子:[+-]a*x**b
$$
這兩類因子是計算的根源——當然,第二次作業的三角函式因子自帶的表示式內部還不需要計算——進行多輪解析的目標就是為了得到這兩類因子。於是乎,除了這兩類因子外的所有因子,我都將其看作表示式對待,送入一個新的Expr進行解析。進行了足夠輪次的解析後,總能夠得到最基本的兩類因子。然後我們可以通過自下而上的計算,得到最終的結果。
在Expr、Term和Factor類仍舊是寫入了一些變數和解析、計算的方法。另外,在第一次作業的基礎上抽離出了一些複用率高的方法,寫入Formal類成為靜態方法;強化了Count類,用於存放加法和乘法的方法;新建了一個CustomFunction類,用於存放自定義函式,便於各層次使用。
優化
在第二次作業當中,對第一次作業的一些不足之處進行了優化:
-
符號歸一化。每次一解析出的內容,都會經過一個
addNote
方法來新增串首符號,才能夠被繼續解析處理。 -
拋棄正則的切分識別方法,改用模擬棧的迴圈遍歷進行項和因子的切分。這是一個非常大的改進,例如對於表示式:
-a+(b-c)
$$
進行將表示式切分為項的模擬。從第一個字元開始遍歷,並在迴圈之初設定一個int型別的變數pairBracket
,用於記載括號層數,其初始值為0。遇到左括號加上1,遇到右括號減去1;第一次遇到加減號,則進入一個內層迴圈,開始摘取切分個體的內容。摘取過程中,再次遇到加減號且括號層數為0,則結束摘取。即對上面表示式摘取的結果就是得到了兩個項:
-a、+(b-c)
$$
很顯然,這種方法也適用於"將項切分為因子"的過程。當然,遇到一些特殊情況需要特殊的處理,在此不贅述。
-
進一步降低計算和解析的耦合度。本次,我直接將所有的計算方法搬出瞭解析類,於是寫程式碼的時候感到十分的清晰:該寫什麼就寫什麼,不會出現第一次作業的混亂現象。
-
程式碼複用優化。進一步歸納了複用率高的方法,並寫入靜態方法類中。
-
除去了不需要的類與方法
這些優化使得我在實現基本功能時更加順利。但是本次設計在設計和細節上也有許多考慮不周之處,使得我在強測中丟失很不少分數,這會在下面做介紹。
缺點
本次設計的缺點也是很明顯的。
-
採用ArrayList容器放置基本因子,使得加減乘計算、合併同類項以及化簡的操作十分困難。這是因為當時我不會使用HaspMap存放資料,我在這裡吃了很大的虧。
-
沒有考慮一些新增因子對計算帶來的麻煩。這使得我的計算方法也非常醜陋且麻煩,無法將三角函式合併而只能直接追加到已有表示式的後方,丟失了很多效能分。這裡的失誤和第一個缺點也有很大的聯絡。
-
對細節的考慮不周(0次冪的符號、自定義函式的符號都沒有考慮周全)
-
將多項式因子仍看作一個"因子",導致因子層次還需要考慮加法。(這一點在第三次作業中進行了力所能及的優化的嘗試)
-
基本項還不夠基本。將三角函式和冪函式區分開來看待,其實並不優越,反而會帶來很多麻煩,因為這會使得我們缺少統一的處理方法。
總的來講,這一次的設計其實是有一些不全面的——對解析的過程做了很好的規劃設計,但是對計算、化簡的考慮十分不周全,導致到最後只能提交一個破爛的版本;寫好的優化版本也是Bug層出不窮。由於對細節考慮不周,在強測中也失掉了三個點的分數,非常不值得。在第三次作業中,我在這些方面進行了一些優化嘗試,不過結果還是不盡人意,程式設計能力還是有待加強啊。
基於度量分析第二次作業的程式結構(除MainClass)
程式碼規模
Class | 屬性個數 | 方法個數 | 程式碼規模/行 |
---|---|---|---|
Count | 0 | 7 | 110 |
CustomFunction | 1 | 3 | 26 |
Expr | 4 | 6 | 80 |
Factor | 2 | 8 | 244 |
Formal | 0 | 17 | 256 |
Term | 4 | 7 | 92 |
類的OO度量
homework.Count | 2.5714285714285716 | 8.0 | 18.0 |
---|---|---|---|
homework.CustomFunction | 1.3333333333333333 | 2.0 | 4.0 |
homework.Expr | 2.3333333333333335 | 8.0 | 14.0 |
homework.Factor | 4.375 | 8.0 | 35.0 |
homework.Formal | 3.6470588235294117 | 18.0 | 62.0 |
homework.MainClass | 2.0 | 2.0 | 2.0 |
homework.Term | 2.4285714285714284 | 10.0 | 17.0 |
Average | 3.1020408163265305 | 8.0 | 21.714285714285715 |
方法複雜度分析
homework.Count.getPower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
---|---|---|---|---|
homework.Count.getRatio(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Count.normalNum() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Count.plusTerm(String, String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.CustomFunction.addFunctions(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.CustomFunction.getFunctions() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.Expr(String, CustomFunction) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.getFinalExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.setFinalExpression(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Expr.setInitExpression(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Factor.getFinalFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.hideSin(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.mergeNote(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.mergePower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.mergeReturnMinus(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.mergeReturnPower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.shortenExpr(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Formal.showSin(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.Term(String, CustomFunction) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.getFinalTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.getInitTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.setFinalTerm(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.Term.setInitTerm(String) | 0.0 | 1.0 | 1.0 | 1.0 |
homework.CustomFunction.printFunctions() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Expr.cntTerm() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Formal.addNote(String) | 1.0 | 2.0 | 1.0 | 2.0 |
homework.MainClass.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Term.cntTerm() | 1.0 | 1.0 | 2.0 | 2.0 |
homework.Count.typeJudge(String) | 2.0 | 2.0 | 1.0 | 2.0 |
homework.Factor.Factor(String, CustomFunction) | 2.0 | 1.0 | 2.0 | 2.0 |
homework.Formal.cleanWhiteNote(String) | 3.0 | 1.0 | 3.0 | 3.0 |
homework.Formal.fetchContent(String) | 3.0 | 1.0 | 3.0 | 3.0 |
homework.Formal.fetchSum(String) | 3.0 | 1.0 | 3.0 | 3.0 |
homework.Formal.normalizeFactor(String) | 3.0 | 3.0 | 3.0 | 3.0 |
homework.Factor.analysisComplex(String) | 4.0 | 5.0 | 5.0 | 5.0 |
homework.Factor.analysisNormal(String) | 5.0 | 3.0 | 3.0 | 3.0 |
homework.Formal.fetchAfterEqual(String) | 5.0 | 1.0 | 3.0 | 4.0 |
homework.Factor.analysisSum(String) | 6.0 | 2.0 | 3.0 | 5.0 |
homework.Count.multiFactor(String, String) | 7.0 | 1.0 | 4.0 | 4.0 |
homework.Formal.getVariable(String) | 7.0 | 1.0 | 4.0 | 4.0 |
homework.Factor.analysisExpr(String) | 9.0 | 3.0 | 5.0 | 5.0 |
homework.Factor.analysisFunc(String) | 11.0 | 4.0 | 6.0 | 8.0 |
homework.Factor.analysisTri(String) | 12.0 | 2.0 | 6.0 | 6.0 |
homework.Formal.reverseSymbol(char, String) | 15.0 | 2.0 | 5.0 | 7.0 |
homework.Count.getSingle(String) | 19.0 | 8.0 | 8.0 | 8.0 |
homework.Expr.cutExpression() | 19.0 | 8.0 | 8.0 | 8.0 |
homework.Formal.splitExpr(String) | 19.0 | 8.0 | 8.0 | 8.0 |
homework.Term.cutTerm() | 28.0 | 8.0 | 9.0 | 10.0 |
homework.Formal.mergeFactor(String) | 46.0 | 6.0 | 17.0 | 18.0 |
Total | 233.0 | 100.0 | 141.0 | 152.0 |
Average | 4.755102040816326 | 2.0408163265306123 | 2.877551020408163 | 3.1020408163265305 |
由於許多地方採用了迴圈遍歷的分析手段,導致部分類和方法的複雜度有所提高。Formal類和Factor類承擔了最重要的解析任務,故而其複雜度最高。但是我對程式的耦合程度和部分類的複雜度還是比較滿意的——實現解析和計算兩個功能部分的程式碼區分的比較開,且Expr類和Term類的複雜度都不高。
第三次作業
經過第二次作業的考驗,我認為我對解析和計算兩個部分的關係已經有了比較好的瞭解,就沒有再使寫額外的靜態方法進行計算操作,而是全部寫在了類層次當中。
解析的基本思路沒有發生變化,但是為了更好地進行儲存計算,在層次裡新增了一個Basic類,用於應對Factor是多項式的情況;另外,將基本的因子進行了重新規範,我給它起名叫做"基本項":
$$[+-]a*x**b*[((sin)|(cos))(expression)**n]*
$$
這樣的好處不言而喻——我們在計算的時候終於不用左右兼顧,直接對上面的格式寫一個計算的方法就好。為了這樣的優化,Basic類要有這些功能:
-
儲存a*x**b的係數和冪
-
使用容器HashMap儲存三角函式的三角部分和指數部分
-
將儲存的資料組合成字串,向上返回
且由於HaspMap的特性,在解析的時候就可以進行簡單的同類項合併(遍歷key集合即可),我認為這樣的儲存方法和結構是很優越的。
優化
本次作業相對第二次作業的優化其實並不多,因為在主要的資料結構和字串處理上第二次作業已經做得比較好了。比較大的進步是在新增一個Basic層次之後,複雜因子及三角函式的計算變得簡單了起來。其餘還有一些小的修改:
-
改進了自定義函式的引數識別方法。之前是使用
split()
方法直接劈分形如f(x,sin(x),x**3)
這樣的式子得到引數。但當函式能夠遞迴呼叫之後,這樣的方法在遇到f(x,f(x,x))
時便會出錯。對此,我採用了之前提到過的"模擬棧+迴圈遍歷"的辦法解決了問題。(棧真是個好東西!) -
將計算的方法融合到了層次結構中,減少了靜態方法的宣告。
-
優化了帶冪函式的表示式因子的解析方法。原本是直接對其進行展開計算,現在改為展開後繼續解析並計算。
失敗的效能優化嘗試
在修改了儲存結構後,新的結構理論上應該更便於效能優化。然而我由於忙於其他事情,始終沒能抽出時間完成這一部分,在強測中丟失了效能分。這算是一個遺憾吧
基於度量分析第三次作業的程式結構(除MainClass)
程式碼規模
Class | 屬性個數 | 方法個數 | 程式碼規模/行 |
---|---|---|---|
Basic | 4 | 16 | 193 |
CustomFunction | 1 | 3 | 26 |
Expr | 4 | 11 | 158 |
Factor | 4 | 21 | 317 |
Formal | 0 | 9 | 75 |
Term | 4 | 11 | 126 |
Operate | 0 | 4 | 41 |
類的OO度量
task.Basic | 2.8125 | 11.0 | 45.0 |
---|---|---|---|
task.CustomFunction | 1.3333333333333333 | 2.0 | 4.0 |
task.Expr | 3.090909090909091 | 8.0 | 34.0 |
task.Factor | 2.9047619047619047 | 8.0 | 61.0 |
task.Formal | 2.0 | 7.0 | 18.0 |
task.MainClass | 2.0 | 2.0 | 2.0 |
task.Operate | 1.5 | 2.0 | 6.0 |
task.Term | 2.3636363636363638 | 10.0 | 26.0 |
Average | 2.5789473684210527 | 6.25 | 24.5 |
方法複雜度分析
task.Basic.getBasicStr() | 0.0 | 1.0 | 1.0 | 1.0 |
---|---|---|---|---|
task.Basic.getPower() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.getRatio() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.getTriFunc() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.initializeBasic() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.separateTri() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.setBasicStr(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.setPower(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.setRatio(BigInteger) | 0.0 | 1.0 | 1.0 | 1.0 |
task.CustomFunction.addFunctions(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.CustomFunction.getFunctions() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Expr.Expr(String, CustomFunction) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Expr.getFinalExpression() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Expr.setFinalExpression(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Expr.setInitExpression(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.Factor(String, CustomFunction) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.addBasic(Basic) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.analysisFunc(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.getBasics() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.getFinalFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.getInitFactor() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.opedBracket(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.setFinalFactor(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Factor.setInitFactor(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Formal.hideSin(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Formal.mergeNote(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Formal.mergePower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Formal.mergeReturnMinus(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Formal.mergeReturnPower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Formal.showSin(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Operate.getPower(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Operate.getRatio(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.Term(String, CustomFunction) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.addFactor(Factor) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.getFactors() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.getFinalTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.getInitTerm() | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.setFinalTerm(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Term.setInitTerm(String) | 0.0 | 1.0 | 1.0 | 1.0 |
task.Basic.backString() | 1.0 | 1.0 | 2.0 | 2.0 |
task.CustomFunction.printFunctions() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Expr.printFinalTermAndFinalFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Expr.printTerm() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Expr.printTermAndFinalFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Expr.spliceTerms() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Formal.addNote(String) | 1.0 | 2.0 | 1.0 | 2.0 |
task.MainClass.main(String[]) | 1.0 | 1.0 | 2.0 | 2.0 |
task.Operate.fetchContent(String) | 1.0 | 1.0 | 2.0 | 2.0 |
task.Operate.noteJudge(BigInteger) | 1.0 | 2.0 | 1.0 | 2.0 |
task.Term.printFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Term.printFinalFactor() | 1.0 | 1.0 | 2.0 | 2.0 |
task.Basic.Basic(String) | 2.0 | 1.0 | 2.0 | 2.0 |
task.Basic.mulBasic(HashMap, HashMap) | 2.0 | 1.0 | 3.0 | 3.0 |
task.Factor.analysisExpr(String) | 2.0 | 1.0 | 2.0 | 2.0 |
task.Factor.analysisFactor(String) | 2.0 | 1.0 | 2.0 | 2.0 |
task.Factor.analysisTri(String) | 2.0 | 1.0 | 2.0 | 2.0 |
task.Factor.fetchFunction(String) | 3.0 | 1.0 | 3.0 | 3.0 |
task.Factor.getFunction(String) | 3.0 | 1.0 | 4.0 | 4.0 |
task.Formal.cleanWhiteNote(String) | 3.0 | 1.0 | 3.0 | 3.0 |
task.Basic.addTriFunc(String, BigInteger) | 4.0 | 3.0 | 4.0 | 4.0 |
task.Basic.backMergedString() | 4.0 | 1.0 | 3.0 | 3.0 |
task.Factor.analysisComp(String) | 4.0 | 1.0 | 5.0 | 5.0 |
task.Factor.analysisSum(String) | 4.0 | 1.0 | 3.0 | 3.0 |
task.Factor.expandPower(String) | 4.0 | 2.0 | 4.0 | 4.0 |
task.Factor.analysisNor(String) | 5.0 | 1.0 | 3.0 | 3.0 |
task.Term.countFactors() | 7.0 | 1.0 | 5.0 | 5.0 |
task.Expr.countTerms() | 9.0 | 4.0 | 6.0 | 6.0 |
task.Factor.findPos(String, String) | 9.0 | 3.0 | 7.0 | 8.0 |
task.Factor.splitVar(String) | 15.0 | 7.0 | 6.0 | 8.0 |
task.Formal.reverseSymbol(char, String) | 15.0 | 2.0 | 5.0 | 7.0 |
task.Expr.cutSplicedTerms(String) | 17.0 | 8.0 | 6.0 | 8.0 |
task.Factor.cutBasics(String) | 17.0 | 8.0 | 6.0 | 8.0 |
task.Expr.cutExpression() | 19.0 | 8.0 | 8.0 | 8.0 |
task.Basic.splitBasicStr() | 20.0 | 10.0 | 10.0 | 11.0 |
task.Basic.normalMerge() | 24.0 | 11.0 | 10.0 | 11.0 |
task.Term.cutTerm() | 28.0 | 8.0 | 9.0 | 10.0 |
Total | 236.0 | 140.0 | 182.0 | 196.0 |
Average | 3.1052631578947367 | 1.8421052631578947 | 2.3947368421052633 | 2.5789473684210527 |
真是太多表格了!這隻能怪我重構了三次(笑)。不過肉眼可見,第三次作業的複雜度和結構相對於第二次作業也有了提升。這說明我對結構的掌握也有了一定的提升畢竟重構了三次。
小結
完成第一單元的任務後,我感覺還是收穫良多。雖然沒有體會到迭代開發的樂趣,但是在經過了多次重構以及層次架構設計
二、Bug分析
公測與互測
首先列出我在三次公測與互測中的測試情況
作業次數 | 弱中測 | 強測 | 互測 |
---|---|---|---|
Homework1 | 通過 | 通過 | 0/21 |
Homework2 | 通過 | 三個點WA | 1/11 |
Homework3 | 通過 | 通過 | 4/12 |
第一次作業的完成情況最佳、第三次之、第二次再次。但得分並不能準確反映我對題目和程式設計思想的理解,我認為我在第三次作業中的收穫是最大的。不過,分數並不重要,分析總結自己的錯誤,吸取經驗教訓才是最重要的,我將舉出並分析我在測試中程式犯下的經典錯誤。
Homework1
在第一次作業中,儘管沒有被發現Bug,但是作業截止前的Debug階段還是讓我難以消受。
零次冪:忘記對零次冪表示式因子的特殊處理,發生了執行時錯誤。
正則表示式出錯:正則表示式無法判斷一些輸入情況,屬於考慮上的疏忽。但不得不承認正則表示式的切分方法很容易出錯。這也導致我在第二次作業中更換了切分的方法
Homework2
第二次作業的問題主要也是由於考慮不周。
自定義函式符號出錯(強測):自定義函式解析完畢向上返回的時候,忘記按照規範在串首判斷是否需要新增符號,導致有時正負解析錯誤。
零次冪(強測、互測):表示式因子出現零次冪時,直接返回1,忘記根據其符號判斷是否需要返回-1
Homework3:
第三次作業的問題在互測被發現了。
變號出錯(互測):一般計算-(a+b)
這樣的式子時,要先計算內部,再反轉符號;而我的程式先反轉了內部的符號,再進行計算。這是一個比較大錯誤,我也很驚訝這個錯誤沒在強測中被測出來。
求和函式sum(互測):本次作業中,求和函式可能是這種形式:
$$sum(i,s,e,i**2)
$$
在正常的輸入輸出當中,形如2**3
這種資料是非法的;但是根據求和函式的定義,上面這種情況卻是可能出現的!但我的程式並沒有對這種"合法的非法情況"進行考慮,故而發生了執行時錯誤。修復這個Bug,只需要在替換所有的i時套上括號即可。
小結:
總體上來講,我做的還是比我的預期好很多的。儘管很多分數都不應該丟掉,但是對於程式設計能力較弱的我來說,還是應該一步一步來。本次的Bug有很多都邊界情況發生了問題;也有一些邏輯上的問題。很多師兄建議我學習一下自動化測試的方法,我現在也開始深感其必要性,畢竟程式設計師要對自己的程式碼負責
Hack
介紹完我的Bug,也要介紹一下我"幫別人Debug"的思路。其實我並不是一個熱衷於Hack別人的人,但是這個階段還是比較有意思的;活躍度也關乎成績。我Hack別人的主要思路大致如下:
-
邊界。我犯的錯誤,別人也可能反。在給自己的程式Debug過程中,會使用零次冪、0+0、超長整數等邊界情況進行測試
-
針對優化進行的攻擊。有的同學會進行一些追求極致效能的優化,這會使得他們在一些簡單情境下犯錯。
-
針對解析方法進行的攻擊。尤其是正則解析法,這種解析方法很容易出錯。
-
共享資料。一些同學提供的測試點也很有參考價值
由於不會自動化測試,我的Hack並不算猛烈。但不能因為要Hack而去學習測試,學習測試永遠是為了自我提高
三、總結分析
1.設計體會
由於最初的架構不佳,幾乎每一次作業我都進行了一次重構。不過,雖然程式碼在不斷重構,設計思想卻是不斷迭代的。從最初的只能解析一層的、計算和解析糅合在一起的程式,到最後的能夠進行多層解析、耦合度低的程式,這期間設計的思想是經過了很大的完善和修補的;為了順利編寫程式碼,我用於設計而寫的手稿也有幾張草稿紙之多,儘自己所能規範了每一處的細節處理方法。唯一可惜的就是重構佔去了我很多的時間,導致我沒能夠進行很多的優化,這算是第一單元的一些遺憾了。不過還是在第一單元的設計過程中收穫良多,"領會精神"永遠是更重要的。
2.本階段OO課程的心得體會
首先一個字:難!
到目前為止的OO作業,包括pre、第一單元、上機訓練,都給我一種很強的壓迫感。作業截止時間的緊迫、程式量的巨大、對Java語言的陌生、設計思路的新穎,對我來說都是很大的阻力。聽吳際老師說,以後會將pre課程放入暑期學期單獨開課,我私以為這是很好的做法,畢竟不是人人都有很好的程式設計基礎,或者說是幾天速成的自學能力,或者說是願意在寒假預習。有Java基礎的同學,對各種資料結構的使用應當是信手拈來。當然,想要解決難題,也不僅僅要靠基礎,還需要學會自己搜尋資源、積極與同學討論交流設計思想,這都是解決困難的很好的辦法。我希望未來的OO課程在不失去難度的同時,能夠讓更多人更好地上手。
另外,助教團隊的高度負責精神也讓我很是感動。精心設計的題目和指導書、面對突發情況的高效解決、對同學疑問的耐心解答等,都是每位同學有目共睹的。北航六系許多優秀的課程也都是在強大的助教團隊下打磨而成。我感到很幸運,能在如此優秀的助教團隊的陪伴下學習這門課程。