在本學期開始以前,我按照助教們所給的寒假做業指導書自學了Java語言的相關知識,瞭解了Java語言的基本語法,輸出一句「Hello World!」,掌握了基本的一些輸入輸出方法,也學習了一下如何使用正則表達式。比較順利地完成了寒假做業的三個小程序。可是,作完以後我就一直在回想,我當時寫的時候好像一直是在使用相似C語言的方法進行思考。好比,要對輸入的字符串進行格式檢查了,那就在main函數下面寫一個檢查格式的函數唄;要對正確的字符串進行處理了,那就在下面寫一個處理字符串的函數唄。甚至,我以爲將他們一古腦兒放在main函數中也是不錯的選擇,直觀,便捷。那合着咱們這門課只是一門披着Java外衣的C語言課了?當時的我爲了解決這些疑問,Google了許多關於面向對象編程的特色,查詢了不少其與面向過程編程的不一樣之處,感受也只是淺嘗輒止,聽他們說好像是對的,可是本身敘述就不明就裏。正則表達式
現在,通過了一個月的理論學習+三次課下做業,我對於面向對象編程的思想有了進一步的理解,雖然感受理解得還不是很到位,可是起碼可以感覺到與面向過程之間的巨大差異了。下面我將按部就班陳述個人學習過程,聊一聊我每一階段的心得體會。編程
首先貼出UML類圖和代碼量化分析圖。小程序
首先讓我簡單介紹一下代碼量化分析圖中的三個參數分別表明什麼含義。設計模式
ev(G)表明的是基本複雜度,主要用來衡量程序的非結構化程度。顯然,非結構化程度越高,意味着這一段代碼的維護性越差,越鬆散,給人的閱讀體驗也越差。架構
iv(G)表明的是模塊設計複雜度,主要用來衡量模塊與模塊之間的耦合程度。若是一個模塊的iv(G)越高 ,那麼就意味着它越須要其餘的模塊,與其餘模塊的相關程度也越強,維護難度越高,隔離難度也越大。函數
v(G)表明的是模塊結構的複雜度,主要用來衡量模塊內部的邏輯究竟有多複雜。通常來講,結構越複雜的模塊,帶給維護人員和測試人員的工做量也越大,產生錯誤或者遺漏的可能性也越大。學習
能夠見到,在第一次做業中,我僅僅設置了兩個類,MultinomialDifferentiate類(下文稱M類)是main函數所在的類,主要行使的功能就是得到輸出,而後傳遞給Differentiate類(下文稱D類)進行格式檢查以及求導計算。對於一個輸入進來的字符串,處理過程大體是這樣的:M類得到一行字符串輸入,調用D類中的Validation函數,判斷格式是否正確;若格式正確,那麼調用D類中的decompose函數對字符串進行肢解,從而獲得冪函數的係數以及指數,將他們分別存到D類中的coef和exp兩個Arraylist中;最後M類調用D類中的calculate函數進行計算,再經過output函數進行輸出。outJudge函數是用於化簡輸出字符串使其爲最短形式。在我看來,肢解過程,求導過程和簡化輸出過程都比較地簡單,也沒有太多的技巧可言,故在此再也不贅述。我想好好介紹一下的是我所使用的格式判斷方法。測試
我身邊的很多同窗都使用了大正則的方法,雖然此次的合法表達式的的確確可使用一個正則表達式徹底地描述,可是因爲正則表達式的特性,會致使若是輸入過長則爆棧的現象。因此我使用的方法是先將空格徹底清除,而後一小段一小段匹配的方法,簡單來講就是若是匹配到正確的一小段,那麼就將其替換成空字符串,這樣循環下去,若是最終的字符串是一個空串,那麼該字符串便合法。固然,我須要在匹配以前進行一些特判,好比所有是空格的狀況。同時還須要注意匹配的順序,應該先匹配開頭的冪函數,而後匹配其餘的冪函數,最後匹配純整數,由於不一樣的順序會致使誤匹配的狀況發生,因此應該按照格式複雜度由高到低進行匹配。關鍵部分代碼以下:優化
1 Pattern error1 = Pattern.compile("\\d[ \\t]+\\d"); 2 Pattern error2 = Pattern.compile("[-+^][ \\t]*[-+][ \\t]+[\\d]"); 3 Pattern error3 = Pattern.compile("([-+][ \\t]*){3,}"); 4 5 if (error1.matcher(str).find() || error2.matcher(str).find() || 6 error3.matcher(str).find()) { 7 return false; 8 } 9 10 if (Pattern.matches("^[ \\t]+$", str)) { 11 return false; 12 } 13 14 String strVal = str; 15 16 strVal = strVal.replaceAll("[ \\t]", ""); 17 18 String start = "^([-+]{0,2}((\\d+\\*)?x(\\^[-+]?\\d+)?|\\d+))"; 19 String end = "[-+]{1,2}(\\d+\\*)?x(\\^[-+]?\\d+)?"; 20 String pureNumber = "[-+]{1,2}\\d+"; 21 22 strVal = strVal.replaceAll(start, ""); 23 strVal = strVal.replaceAll(end, ""); 24 strVal = strVal.replaceAll(pureNumber, ""); 25 26 return strVal.equals("");
由個人代碼量化圖中能夠看到,D類中的outJudge函數三項複雜度都比較高,這主要是D類的output函數直接調用了其的緣故,就像C語言同樣。這樣的設計顯然不夠好,調試起來顯然也並不容易。spa
同理,先貼出UML類圖和代碼量化分析圖。
在第二次做業中,輸入字符串在第一次做業的基礎上加上了三角函數sin和cos的輸入,其實從整體架構上來講,複用第一次做業的結構是徹底沒有問題的。可是能夠看到,我在第二次做業中,根據老師的建議加入了InputHandler類(下稱I類)的輸入處理模塊,同時設置了一個Expression類(下稱E類)和一個Term類(下稱T類)。這個程序的執行程序大體是這樣的:先由I類接收輸入的字符串並做簡單的格式判斷,而後由main函數調用E類中的checkSyntex方法,檢驗在表達式層面可能出現的格式錯誤,好比空格的錯誤出現,好比非法字符的出現等等,這一階段還不涉及項內的格式錯誤;接着E類會刪除空格,根據加減運算符分割出一個一個的項(每個項中的可能出現的正負號已經被其餘字符所替代),而後調用T類中的checkSyntex方法,檢驗項層面可能出現的格式錯誤,若無錯誤,則將其放入一個以Term爲元素的Arraylist當中。最後,調用E類中的differentiate方法便可完成求導工做。在互測階段觀摩了其餘同窗的代碼以後,我發現許多人都是在最一開始就使用一個大正則判斷整個表達式是否符合格式。這樣的判斷當然沒有錯,可是一個須要佔據三四行的大正則的可讀性極差,而且我以爲也不太符合面向對象的思想。拿個人代碼架構舉例子,若是我在E類中就將格式審查工做作完,那麼至關於T類是一個附屬於E類的類,由於T類會默認獲得的數據都是格式正確的,可是這顯然很不利於模塊的隔離與複用,因此在E類和T類中分別判斷本身所負責的格式是否有錯,那麼這兩個模塊均可以單獨拎出來與其餘的模塊共同工做,顯然複用性就更高了。至於第二次做業的優化問題,我只完成了sin(x)^2+cos(x)^2=1的初級化簡,因此在此就不獻醜了。
因爲個人代碼結構是層層遞進式的,可是每個模塊之間我讓他們儘可能保持獨立性,因此能夠看到這一次平均的ev(G),iv(G),v(G)值都降低了,證實代碼的質量有了一些進步。可是checkSyntex函數和outJudge函數仍舊作的不夠好,由於感受這兩個模塊對其餘模塊的依賴很是地大。
同理,貼出UML類圖和代碼量化分析圖。
平心而論,第三次做業的難度和第1、第二次做業的難度不可同日而語,起碼從架構設計上來不那麼簡單了。首先觀察個人UML類圖,能夠明顯地看到整個架構是一個層層遞進的架構,而我使用的也是較爲常見的遞歸降低法。接下來我具體介紹一下整個程序的架構:Main類是main函數所在的類,InputHandler類用於處理控制檯的輸入,而後與表達式處理相關的有Expression類(下稱E類),Term類(下稱T類),Factor類(下稱F類),TriangleFunction類(下稱Tri類),PowerFunction類(下稱Pow類),Constant類(下稱Con類),AdditionDIff類(下稱Ad類),MultiplyDiff類(下稱Mu類)和NestingDiff類(下稱Ne類)。從邏輯層次上來分,此次輸入的字符串能夠分爲表達式,項和因子三個層次,因此我設計了三個類來分別對應這三個層次。同時,咱們仔細探究求導過程,能夠發現,最基本的單元都是因子的求導,因此在F類下面,我又設計了三個處理不一樣類型因子的類,真正的求導是在這三個類裏面進行計算的。你可能發現了,在這三個類裏面並無表達式因子的處理方法,那是由於我認爲表達式因子並不可以算做真正的最基本的元素,因此我會將表達式因子直接放入嵌套方法中進行計算,但如今想起來,也許多設計一個表達式因子類會使得整個結構更加地完整,易於理解。回到正題,那麼每一個因子的求導結果是如何聯合在一塊兒的呢?我設計了三個運算規則類,即Ad類,Mu類和Ne類。通過個人理解,我認爲Ad類實際上是專門爲E類所服務的,也就是E類將表達式拆分出一個一個地項,而後將這些項傳遞給Ad類,Ad類再分別調用每一個項自身的求導方法(在T類中),將返回的字符串經過加減號拼在一塊兒便可。Mu類的功能相似,調用每個因子的求導方法,而後將他們返回的結果按照乘法的求導規則拼在一塊兒便可。Ne類比較不同,我理解爲專門處理表達式因子的,即Ne類中會再次創立一個新的E類對象,再通過前述的一連串過程得到最終的結果。能夠發現,這樣的設計模式使得一種運算規則與一個層次綁定,讓結構變得更加地清晰,調試起來也更容易發現問題,定位問題,解決問題。
因爲我使用了較多的類(共11個類),因此整個架構清晰了許多,從代碼量化數據也能夠看出,三項複雜度比上兩次做業有了明顯的減小,達到了較爲理想的水平,我很欣慰我在這過程當中一直在進步,收穫頗豐。
首先,最大的體會固然是這門課主要講授的內容,面向對象編程思想。什麼是對象?對象是一個客體,具備特定屬性,可以完成特定工做。對象與C語言中的函數有什麼區別?兩者都可以完成特定的功能,可是對象更加立體,更加符合通常人的思惟方式。舉個例子,對象是一個具備獨立思考能力和獨立行動能力的健全人,而C語言中的函數則是一個根據指令完成任務的機械手臂,兩者均可以完成他們能力範圍內的特定的工做,可是他們能同樣嗎?對於機械手臂而言,咱們須要給予它數據,告訴它它須要完成什麼工做,以及一步一步究竟怎麼完成都須要預先設定好,這顯然是一種流水線的工做方式,可以提升工做效率,可是沒有變革工做方式。而對於一個健全人而言,咱們須要給予他數據,再告訴他他須要完成的任務,以及最終返回的結果是什麼樣子的就能夠了,至於他是怎麼完成的,咱們徹底不須要關心。更深刻地來講,以C語言爲表明的面向過程式的語言是按照任務完成的前後順序來進行結構設計的,而以Java爲表明的面向對象式的語言則是以數據的傳遞路徑來進行結構設計的。數據即信息,兩個模塊之間只須要按照規定傳遞正確的數據,整個任務就能夠正確地完成。這讓開發大型工程更加地輕鬆,要修改某些部分或者添加一些功能不須要改動其餘部分,只須要增長一個模塊進行數據處理便可。由此看來,大型工程的開發難度降低了,向下兼容性變好了,修改完善變簡單了,測試也變得更加舒服了,這就是我對於OOP的一些小小我的體會。
其次,我又一次感覺到了「拆分」思想的強大力量,這主要是在第三次做業中得到的。乍一看第三次做業指導書,內容繁複,條條框框不少,各類格式的可能性也很是地多,看起來根本無從下手。可是指導書也是給出了明確的邏輯分層,當我順着指導書的邏輯往下走,我發現其實質上就是表達式,項和因子三個層次組成的,每個層次中都有其自身特有的正確格式,每一種可以想到的格式錯誤也均可以明確地歸爲三個層次中的一個,由此,我只須要將三個層次的界限劃清楚,在每個層次中按照指導書要求實現對應的格式判斷就徹底解決了。求導規則的拆分也是一個很優秀的思想,從繁複的求導可能性中提取出了最爲本質的規律,而後任何字符串求導均可以輕易地歸於這些求導規則,那麼咱們在實現的時候只須要完成每個小模塊的小功能便可。在設計好以後,不到一天時間,我就完徹底全地將整個程序構建完畢並經過了課下測試,不得不感嘆「大事化小小事化了」思想的強大力量。
最後,是以測試爲導向的設計思想的理解與應用。這一設計思想是第一次研討課中彭毛小民同窗提出的(在此感謝彭毛小民同窗的啓發),我聽完後大受啓發。在設計的初期,也就是閱讀指導書的階段,咱們就要開始設計本身的測試用例了,這樣作有許多的好處。第一,咱們在設計測試用例的時候實際上是一個加深對於指導書內涵的理解過程,碰到一個模棱兩可的例子,咱們就會去從新閱讀指導書,而後強化對於規則的理解,慢慢地,咱們對於指導書的規則便了然於胸,必定程度上避免了設計架構的時候常常忽略指導書的一些要求而形成後期的反覆修改;第二,研究測試用例時咱們就會不斷斟酌咱們腦子裏那個設計架構,不斷地優化它,好比一些方法究竟該放在哪一個類中更好,數據的流向如何設置更爲合理等等;第三,這樣設計出來的測試用例更加地完整,涉及到了方方面面,從簡單的錯誤到複雜的錯誤所有涵蓋,後期進行互測時就能夠進行較好的全面覆蓋。我在第二第三次做業中都應用了這種設計理念,效果很是地喜人,在寫代碼階段更加地流暢,寫出來的代碼結構更加地清晰,在互測階段被他人輕易找到bug的概率也大大降低了。
僥倖地說,在這三次做業中,我總共只被其餘人找到了一個bug,這個bug是在第三次做業中被找到的。簡單來講就是cos函數的正則表達式匹配中,我在指數部分忘記了整數前面能夠帶符號這一特性,使得判斷出錯,進一步致使個人求導結果出錯。雖然最後我經過一個合併修復就將這個bug修復完成,可是仔細想一想,這個bug是一個很是嚴重的bug,由於我連正確的功能都沒有可以徹底實現!在自測的時候我其實發現了相似的問題,可是當時是在sin函數中發現的,因此我就改了sin函數的正則表達式判斷,原本想着連cos也要一塊兒改的,可是可能由於什麼其餘事情而最終忘記了。這個經驗告訴我,即便咱們有了比較完備的自測數據,在修改bug的時候必定要有頭有尾有始有終,咱們保不齊在何時就會走神,就會有疏忽的地方,因此必定要對本身的程序抱有必定程度的懷疑,也永遠不要驕傲地認爲本身的程序就必定沒有bug了。
再來講說我找到的別人的bug,第一第二次做業我找的bug主要都是格式方面的bug,我使用的測試方法是黑盒測試加上讀代碼的有關格式判斷的一部分,這樣的測試方法仍是比較高效的,首先利用本身的測試數據來測試他人的代碼,而後再一個一個代碼地閱讀有關格式判斷方面的錯誤,對於第二次做業還須要閱讀一下有關優化方法的一些錯誤。可是對於第三次做業,關注得更多的就是邏輯方面可能產生的錯誤,因爲代碼量過於巨大,因此我沒有閱讀他們的代碼,而是儘可能設置一些特殊結果的數據,好比最終結果是0的,或者將全部的因子都用括號括起來的這種,來探查他們是否在化簡的時候或者在輸出的時候沒有考慮徹底。我以爲須要提一下的就是若是使用string.split函數進行項的分割的話,須要注意最後若是是分割符的話,那麼split函數是不會在最後造成一個空字符串的元素的,因此須要咱們手動判斷最後是不是單獨的分割符,好比最後是一個單獨的乘號這種狀況。其餘的bug很大部分是在優化部分出的錯,由於一些沒有考慮全的緣由,輸出了空字符串或者重複輸出了加減號運算符這種,這也是我在本身編寫代碼的時候須要注意的地方。
我最大的遺憾就是第三次做業沒有可以找到很好的優化策略,作出滿意的優化效果。因爲分數導向問題,我把更多的時間放在瞭如何使架構更加合理,減小bug出現的可能性上,而對於優化輸出所作的工做確實不夠多。也許是個人架構不夠合理,因此優化的難度會很大,這也不得而知。只但願在第二單元的做業中我可以讓個人程序真正變成對用戶友好的產品,讓用的人不至於這麼的難受。
其次就是代碼複用性太低的問題。雖然說三次做業下來,個人代碼模塊性更好了,可是三次做業我重構了三次,究其緣由仍是前兩次做業的設計不夠成熟,可拓展性太差了,同時每一個模塊之間的耦合度過高,因此難以複用。但願在第二單元的做業中我可以從第一次做業開始就將可擴展性放在一個比較重要的位置上。具體的重構方法見第一部分的三次做業簡介。