1. 程式人生 > 其它 >面向物件程式設計第一單元總結

面向物件程式設計第一單元總結

一、寫在前面

摸著石頭過河“,這句話是我三次作業以來體會最深的思想,也是我在回顧三次作業時最真切的感受。OO要改變的是思維方式,這個過程是很漫長的,有很多方面都不適應,就像是要讓你過河,卻不讓你像以往一樣走橋,或者說根本沒橋,那該怎麼到達對岸呢?我認為就是要大膽探索、穩妥前進,濃縮起來就是那句老話”摸著石頭過河“。

二、程式結構

本部分主要是對我三次作業的度量分析以及對其優缺點的自我評判。在給出UML圖之前會配上一段文字簡要說明每個類的設計考慮,而巨集觀的架構考量過程和實現細節在第五部分給出。

2.1HW1

2.1.1類構建思路描述

第一次作業我除MainClass類外實現了8個類,用兩句話可以總結我的設計考慮:

  1. 採用梯度下降的解析方法(PaserLexer)對錶達式建模,整個表示式分為3層:ExprTermFactor,而Factor又包括NumberPower(冪函式)、Expr

  2. 為了計算處理的方便,表示式內部的類都實現Factor介面,從而都可以視為一個統一的抽象類,進而統一構造成多項式類Polynomial

以上兩句話分別代表了筆者第一次作業思路的兩個階段,即解析和計算,這也是我後續迭代開發的基礎。

2.1.2UML圖

 

2.1.3類複雜度圖

OCavg = Average opearation complexity(平均操作複雜度)

OCmax = Maximum operation complexity

(最大操作複雜度)

WMC = Weighted method complexity(加權方法複雜度)

 

從圖中可以看出PaserPolynomial的複雜度較高,這與我的思路有很大關係:解析部分主要由Paser實現,計算部分主要由Polynomial實現。承擔大部分工作而造成的複雜度增加似乎像是一種成功的”代價“,我會在未來繼續探索對這一問題的改進方式。

2.1.4方法複雜度圖

CogC = Cognitive complexity(認知複雜度)

ev(G) = Essential cyclomatic complexity(基本圈複雜度)

iv(G) = Design complexity

(設計複雜度)

v(G) = cyclonmatic complexity(圈複雜度)

分析圖可以看出,paserFactorPolynomial.toTerm複雜度較高,前者的客觀原因是因子本身種類較多,但也有我寫法的主觀因素(部分程式碼耦合度太高),而後者則是我定義的關於輸出的優化方法,當時並沒有考慮複雜度,在之後的迭代開發中對此段程式碼有所優化。

2.2HW2

2.2.1類構建思路描述

第二次作業加入了三角函式和自定義函式及求和函式,我在code1的基礎上進行了迭代開發。類新增了4個,包括FunctionsumTri,分別處理新加入的自定義函式、求和函式、三角函式,而對於key類,內部儲存了x的指數以及含有三角函式的HashSet,這是我第一次作業到第二次作業過渡的重要實現,在此基礎上升級Polynomial類為TriPoly類,進而可以支援三角函式的相關計算。

2.2.2UML圖

2.2.3類複雜度圖

從圖中可以看出,TriPolyPaser的複雜度依然較高,這與第一次作業的原因相同。而MainClass類複雜度較高的原因是本次作業我針對自定義函式和求和函式採用的策略是字串替換(而且寫在了MainClass中,有些欠考慮,在第三次作業中已經改為深克隆基礎上的因子替換)。key類的複雜則主要是因為三角函式的合併以及化簡(在第二次中我初步嘗試並實現了平方和優化)都與該類有密切關係。

2.2.4方法複雜度圖

經過分析,TriPoly.merge方法複雜是因為在這裡用比較粗糙的方法實現了平方和優化,公式如下(借鑑於學長的部落格):https://www.cnblogs.com/tqnwhz/p/10559893.html

paserFactor隨著因子種類的增多進一步複雜是在意料之中,而isTri方法是我為優化三角函式的負號以及一些特殊情況寫的方法,或許還有更好的實現方法。

2.3HW3

2.3.1類構建思路描述

在做第二次作業時就預感到第三次作業會出現遞迴巢狀以及三角函式內部將變為各種因子,因此在第二次時嘗試了深度克隆基礎上的因子替換以及三角函式內部支援各種因子。基於此,第三次程式碼的類與第二次類的設計完全一致,但內部實現有了很大區別。

2.3.2UML圖

2.3.3類複雜度圖

將字串替換方法改進之後,發現MainClass類的複雜度明顯降低(不過由於自定義函式建模和求和函式預處理我還是寫在了MainClass中,導致它的複雜度依然很高,這點需要改進,或許可以新增加一個類來實現)。TriPoly類因為本次作業我加入了更多優化導致其負擔更重略微增加了複雜度,其他兩類的複雜度都有下降,但仍有提升空間。

2.3.4方法複雜度圖

由於方法比較多不再展示完整複雜度圖,僅展示部分複雜度較高的方法。

其中MergefirstMergesecMerge三個方法是三角優化的主體,Tri.isExpr是三角函式括號輸出優化的方法,而雖然對於PaserFactor已經進行了優化,但依然像以往一樣比較複雜。

三、bug分析

  • 己方bug分析:筆者在三次作業的強測和互測均未出現bug。但是在自己除錯時,出現過深度克隆問題(復現方式為自定義函式多次呼叫)、HashSet容器邊遍歷邊刪除造成空指標、字串替換考慮不周、優化過度導致連鎖bug、物件型別理解不清晰等一系列問題,這些bug顯示出我在做題時對題意的把握以及java語言本身的部分特性理解並不是十分透徹,希望未來能有精進。

  • 對方bug分析:筆者在三次hack中共發現了對方四個bug,分別是:

  1. sin(0)/cos(0)未輸出,原因是處理sin中0時欠考慮。

  2. 11*x被輸出為x,原因是使用正則表示式搜尋1x並替換為x

  3. sum(i,1,2,i**2)計算錯誤,原因是處理sum函式的i時欠考慮。

  4. sin(x)**2+cos(x)**2+x輸出錯誤為x,原因是過度優化平方和。

四、hack策略

筆者在第一次hack中採用了閱讀他人程式碼並分析其中漏洞的方法,雖然從中學到了不少優秀的架構,但是這樣的方法hack效率略低,導致我第一次並沒有找到對方的bug(也有可能是第一次作業比較難出bug)。到了第二次hack,我在原方法的基礎上附加了自動生成資料以及自動評測機的方式hack,不過最終是依賴閱讀他人程式碼迅速定位的bug,評測機只是輔助作用。第三次hack,我還嘗試了討論區同學分享的單個數據同時測試房間所有同學bug的評測機http://oo.buaa.edu.cn/assignment/329/discussion/1140,並找到了部分bug。結合三次hack的經歷,我認為比較好的策略是:

  • 儘量在自己設計的過程中記錄可能出現的bug,並構造資料。

  • 閱讀他人程式碼,尋找薄弱處hack,例如高密度的正則表示式、字串替換。

  • 設計資料生成器及自動評測機,提高效率,但是應保證資料的覆蓋性。

五、架構設計體驗

本部分主要是筆者對三次作業架構的巨集觀思考和實現細節,集中了一些作業過程中的困惑與對應的解決方法。

5.1HW1思考過程

5.1.1關鍵點

  • 遞迴下降解析法:主要參考的是訓練欄目的實現方式,訓練部分的思路幫到了我很多。

  • 多項式統一處理計算:開始時我一直想不通(1+x)*(1+x)該如何計算,直到有了多項式統一處理的思路才豁然開朗,這一部分也是我應對整個第一單元的基礎。

  • HashMap儲存:有了多項式統一處理的抽象思路,具體到如何合併同類項則又考慮到使用hashmap這一容器,利用key儲存x的指數,value儲存對應的係數,這樣就輕鬆地實現了多項式乘法、加法和減法。

5.1.2其他細節

1.對符號的處理:

表示式開頭會有一個可能出現的符號,而每兩個項之間也會有正負連線符,因此在表示式內可定義儲存符號的Arraylist,並預設表示式前符號預設為正,這樣保證符號陣列和項的陣列長度一致。

項的開頭同樣會有一個可能出現的符號,但每兩個因子之間只會有乘號,因此可以只記項的開頭的符號作為項本身的符號,同樣預設預設為正。

如果構造是遞迴到因子層面仍有符號,那一定是因子本身的符號,例如:---1

按照上述處理,第一個負號作為表示式層的符號,第二個負號作為項的符號,第三個則對應1

2.對冪次的處理:

對於冪函式,類似常數構造了一個類Power來儲存,在Lexer()函式中可針對x進行特殊處理

對於表示式帶冪次的情況,在讀完右括號後,立刻讀取其後是否可能存在指數,如果存在指數,則直接將該表示式因子重複加入對應的項中。也就是把(1+x)**2直接展開成了(1+x)*(1+x)

3.多項式構造細節:

  • 如前所述,將表示式構造為多項式可以轉換為將表示式的所有項合併,由於項與項之間由正負號連線,而且每一個項也構造成了多項式,所以這一層實際只是多項式的加減法。

  • 將項構造成多項式可以轉換為將項的所有因子合併,由於因子與因子之間由乘號連線,而且每一個因子也構成了多項式,所以這一層實際只是多項式的乘法。

  • 將因子構造成多項式就是幾個基本因子(常數、冪函式)的轉化,如果是表示式因子則回到第一步。

5.2HW2思考過程

5.2.1關鍵點

  • 多項式拓展:這一點是專門為了處理三角函式而實現的,保證了我能在hw1的基礎上繼續進行迭代開發而不是大規模重構,主要是建立Key類,Key中儲存x項指數及三角函式的集合

  • 字串替換:這一點是最初對於處理自定義函式和求和函式的思路,因為成型較早,故選擇在hw2中保留,但在hw3中修改棄用。

  • 三角優化:在解析階段做了sin(0)->0sin(-1)變為-sin(1)sin(x)**4*cos(x)**2+sin(x)**2*cos(x)**4 = sin(x)**2*cos(x)**2等優化。

5.2.2其他細節

1.關於三角多項式的乘法實現:

  • 係數相乘:不變。

  • 指數相加:分為兩部分,首先是x指數相加;其次是三角函式集合的合併(同類三角函式指數相加)。

由於需要比較鍵值key,故需覆寫equals方法(hashcode方法)。

2.採用字串替換的方法需要注意形參出現的順序,特別地,形參中若有x則將x全部替換為w。

5.3HW3思考過程

5.3.1關鍵點

  • 遞迴下降解析自定義函式:解析過程為因子層面的替換,將函式表示式逐級遞迴到因子層面,對比因子與形參,若相等則替換。

  • 優化三角函式:實現了以下的優化:

//合併
sin((1)) + sin(1) 
sin((sin(x)**2+cos(x)**2)) + sin(1)
//sin(x)**2 + cos(x)**2 = 1
sin(x)**4*cos(x)**2+cos(x)**4*sin(x)**2
cos(x)**2-1
cos(x)**3-cos(x)
//取反
sin((-x-1))+sin((x+1))

5.3.2其他細節

1.深度克隆替換的四個細節:

  • 形參只可能與冪函式因子相等,而冪函式因子只可能出現在表示式因子、冪函式和三角函式中,此處相等條件應為型別相等

  • 由於形參只可能替換冪函式因子,故有可能會有指數,此時需將指數賦予實參,策略是按指數將實參加入項中多次。(此處雖然沒有進行克隆,但是由於本身帶指數的表示式因子就有相關性,似乎沒有問題)。

  • 實參應為表示式因子,目的是優化三角函式的符號時沿用之前的提到項一級的思路

  • 最終替換得到的表示式因子是有很多問題的,比如多層巢狀、sin(0)、cos(0)等等,為了簡化表示式樹層次,省去sin(0),將該表示式因子進行格式化重構(犧牲時間換取簡化)

2.三角優化的細節:三角函式內部應為表示式因子,根據內部因子輸出結果的字串判斷是否取反:

具體為判斷首字元是否為負號,若是則將hashmap中所有係數取反,並解析新的表示式因子。

六、結語及體會

首先感謝本次作業的過程中幫助我的同學和助教,今後面對更大的挑戰大家還要繼續加油。

其次是我本單元的一些反省:一是沒能很好地應用實驗課的內容到作業上來,比如對sin((1))的處理,按照我的最初的做法會得到巢狀表示式,後來我的策略是對錶達式重新解析(remake)了一遍才降低了深度,但這種方法太過簡單粗暴,而實驗課上的simplify方法則十分適合處理該問題,下次我需要更重視實驗與作業結合。二是有關資料測試的相關知識還需要像其他同學學習。

最後,在本次作業中遇到的每一個問題都很有挑戰性,我體會到了當沒有絕對好的辦法時,也要大膽嘗試,“摸著石頭過河”會比原地踏步或者跟隨他人腳步發現更多驚喜。

總之,希望我能在未來更大的挑戰前再接再厲,共勉。