從C、DS、計組一路折磨過來, 幾乎都在採用過程化、函數式的編程思想。初接觸面向對象的項目開發,通過了三週的對多項式求導問題的迭代開發,經歷了設計、coding、測評環節,算是對面向對象有了必定的認識,這個過程總結了一些經驗,在這裏但願和你們一塊兒share,歡迎你們給我提意見。java
1、關於代碼架構python
一、第一次做業正則表達式
主要設置了3個classshell
PolyComputer做爲主類,進行I/O操做,正則表達式匹配,項的提取,合併同類型,排序這些操做express
PolyTerm表示每一項,包含項的基本特徵係數和指數,constructor(處理每一個String項,提取係數和冪),以及獲取和修改係數和指數,轉化爲String這些Methods.編程
PolyDerivation繼承PolyTerm,修改構造器,繼承PolyTerm的Methods.架構
總體思路分析:框架
一、在PolyComputer類中,使用正則表達式匹配每一項,並提取項之間的符號。(一項一項匹配能夠有效防止爆棧問題,固然還可使用獨佔模式進行匹配來避免,我會在後面的做業dom
使用到,這裏推薦一個學習連接https://blog.csdn.net/weixin_42516949/article/details/80858913)模塊化
二、在PolyComputer類中,把提取出的符號和項進行合併,因爲這裏存在正負性的問題,我是單獨設置了一個Method去合併,其實在提取過程當中邊提取邊合併也不失爲一種好方法
(我的甚至更推崇)。
三、提取出的每個String類的terms傳給PolyDerivation(繼承PolyTerm)的生成器,在super(term)中採用正則表達式提取係數和指數,而後根據求導法則,進行求導。
四、合併同類項、排序(無關緊要,能夠考慮在排序過程當中增長一種量化方法即儘可能選取正數項爲開頭,提高性能)、輸出
2、第二次做業
第二次做業的總體架構跟第一次比沒有太大的變化,主要就是修改了提取各個項的方法(引入了由乘號鏈接因子的項的狀況,須要修改正則表達式,而在這裏爲了保險起見,避免爆棧
我採用了獨佔模式的正則表達式匹配),對於每一個項,因爲均可以抽象爲(x_coeff,x_deg,sin_deg,cos_deg)的形式,所以在PolyTerm類中增長了對sin,cos的指數的提取,修改
PolyDerivation的生成器,在繼承PolyTerm的基礎上轉化爲更復雜的鏈式求導法則。
反思:
一、應該增設factor類,求導方法類,使架構更OO,也便於後續開發。
二、優化只停留在合併同類項和對sin(x)^2+cos(x)^2=1的簡單優化(二次項優化),目前打算修改成啓發式化簡(在討論課上已經詳細介紹過,也是python的sympy庫的優化方法)的方法,提高性能。
三、第三次做業
從類圖能夠看出來,這很不OO,這是一次比較難受的迭代開發,我主要增設了求導方法和求導(遞歸)兩個類,主類的方法也進行了大量的修改(主要體如今開頭就進行了
非法字符的識別,去空白符等操做,把原先留給後面的提取和求導的複雜度提早,爲相對此次比較複雜的遞歸求導開闢一條相對平坦的路,提升求導過程的開發體驗)
本次的架構還有大量須要改進的地方,這裏並不推薦,將主要闡述我所採用的遞歸求導方法。
我沒有采用主流的遞歸降低的詞法分析方法。
而是採用了層層拆解的方法
一、拆項(split)
全部的expression均可以以+/-爲分隔拆解成爲term,運用加法求導法則
term = split(str,1); if (term.size() > 1) { return method.plusRules(term); }
全部的term均可以以*爲分隔拆解爲factor,運用乘法求導法則
term = split(str,2);
因而,我創造了
public String plusRules(ArrayList<String> term) {} public String multRules(ArrayList<String> term, int i,String strIn) {} public ArrayList<String> split(String str, int mode) {}
這裏須要用到棧(java自帶棧,教程請移步https://www.jb51.net/article/130374.htm)其用於括號匹配,保證split的正確性,是個很是好用的手段。
二、對於不可拆項的預處理
去括號
因爲多層嵌套以及表達式因子的存在,爲了後續求導的便捷我單獨寫了一個去括號的Method,作一個化簡
public String strip(String strIn) { Stack<Character> stack = new Stack<>(); .... }
三、對於不可拆項的求導
主要分紅三類
(1)知足第二次做業可求導的部分
public String matchBasic(String str) {} public String multForBasic(ArrayList<String> term, int i) {}
(2)帶冪次方的部分
public String matchPower(String str) {} public String multForPowerRules(ArrayList<String> term, int i) {}
(3)帶sin、cos的嵌套因子
public int matchTrig(String str,int mode) {} public String multForSin(ArrayList<String> term, int i) {} public String multForCos(ArrayList<String> term, int i) {}
因而,在類DerivationMethod 和 Derivation中部署好相應的Method以後,遞歸的框架也造成了
//...... while (!str.equals(strip(str))) { str = strip(str); } //...... term = split(str,1); if (term.size() > 1) { return method.plusRules(term); } //...... term = split(str,2); //...... for (i = 0; i < term.size(); i++) { if (!matchPower(term.get(i)).equals("")) { returnString = returnString + method.multForPowerRules(term,i); } else if (matchTrig(term.get(i),1) != -1) { returnString = returnString + method.multForSin(term,i); } else if (matchTrig(term.get(i),2) != -1) { returnString = returnString + method.multForCos(term,i); } else if (!matchBasic(term.get(i)).equals("") || term.size() > 1) { returnString = returnString + method.multForBasic(term,i); } else { //WF } } return returnString;
這就是我搭建的遞歸框架,不得不感嘆函數式編程深刻人心,也但願你們能對個人方法提出寶貴的意見
2、自我BUG總結
三次測評,公測中共被hack一次,互測共被發現兩個bug
第一次公測中被發現的bug出如今類PoltTerm的toString()法中,是典型的手抖黨bug。
else if (this.coeff.equals(new BigInteger("-1"))) { output = '-' + "*x^" + String.valueOf(this.deg);
看了這段代碼不由驚歎-*x是什麼鬼????這是什麼神仙bug,顯然這個bug很是之好改,刪除*就完事了
第一次互測中除了被人發現了上述的bug,萬萬沒想到還有一個驚天的bug
某匿名朋友向我扔了一個1+,而後我死了
後來我發現,這個方法來自於個人提取項和符號的過程,一項一項匹配的方法有一個致命的須要判斷的地方,就是提取出的項數和符號數萬萬不能相等,因而我作出了這樣的修改
if (terms.size() == ops.size()) { System.out.println("WRONG FORMAT!"); System.exit(0); }
第一次做業以被hack的面目全非了結了
第二次,
我總結了第一次做業的教訓,好比嘗試構造覆蓋性樣例,沒有自動化的瘋狂測試,沒有作單元模塊測試
因而,裝備上了自動化評測機、翻看了無數遍指導書、作了分塊測試後的第二次公測、互測我終於無缺完好得活着了
第三次,事情又有那麼一些不簡單
這一次的錯誤來源於我對指導書的理解的誤差
指導書中有這麼一句話",例如1926^0817
是不合法的,由於冪函數的自變量只能爲x
。",因而我天真的覺得相似於
sin(sin(sin((-+3+-8*-1))^9))^47的樣例是絕對不合法的,所以他們沒有自變量x,因而我在互測中果真被hack了。
可是其實這個冪是在三角函數上的。指導書理解誤差的慘痛經歷。
反思
從我本身課下debug的過程以及測試中被發現的bug來看,設計結構跟debug的難易程度以及bug的數量是息息相關的。
***第一次的bug除了測試方法的鍋,還有一個重要緣由就是Method的長度,設計中設計了比較長的Method(如今看來30行左右的體驗是最好的)容易致使邏輯錯誤,思惟混亂,而後就手抖了
***本身測試過程(尤爲是第三次),因爲一些重要的類,例如Dervation類過長,遞歸過程當中的異常狀況常常出現,而且難以定位。並且Derivation中的split方法也比較長,較多的if-else
嚴重影響了本身的體驗。
***類的拆分很重要,前兩次我都直接定義了Term類,而忽略了Factor類,致使了Term類複雜度太高(尤爲是正則表達式)出錯的狀況不在少數。
***對WF作單獨檢查真的過重要!!!!!!爲了有效防範過程當中重複不斷的WF,寫一個Method或者甚至Class來檢查格式會讓設計過程當中集中精力在功能,體驗更佳。
3、測試方法分析
這三次多項式求導是第一次對WF有了這麼深的認識,也是第一次進行如此高強度的測試(尤爲是互測和強測環節)。從第一次在強測環節遭到hack後的思考總結,以及討論區的各個帖子,逐漸摸索出一些比較有效的測試方法。
先從傻瓜測試講起:第一次測試,沒有進行模塊化的功能測試,本身構造的樣例測試不全面,想到什麼樣例測什麼,一切都有點糟糕。(讓我第一次公測崩掉一個case的罪魁禍首!!
後來,事情終於逐漸有了起色。
Method1: 模塊化測試
對設計的每一個class,每一個method,在設計過程當中,完成每一部分後進行測試,尤爲是分支語句各個部分 都要有樣例覆蓋(第一次在這裏吃大虧了!!)。
Method2: out of bounds檢查
衆所周知,設計過程當中不免使用到charAt,substring等語句,極容易碰到OUT OF BOUNDS的報錯(尤爲 是一不當心字符串被化簡成空串的狀況!!),爲了有效規避這類常見錯誤,我的採起了設計以後搜索charAt ,sustring等關鍵字,逐條檢查。
Method3: 無比快樂的自動化測試
這裏採用我比較熟悉的python。
一、隨機生成測試case
Xeger
Library to generate random strings from regular expressions
在寫自動化測試的case的時候,採用Xger庫根據正則表達式隨機生成測試數據,測試效率將會大大提升
(java也可使用Xeger,進行隨機生成,具體見buaa_oo討論區大佬的分享)
to install,type: pip install xeger to use,type:
from xeger import Xeger x = Xeger(limit=const) #const can be changed x.xeger("your regular expression")
注:
*前兩次做業能夠直接複用java中的正則表達式,設置好limit(能夠採用random庫生成),直接生成case
def creat_factor(): def creat_term(): #creat_factor() * creat_factor() def creat_expr(): #creat_term() +creat_term()
*第三次做業加入嵌套因子和表達式因子以後,在creat_factor()中作微調引入creat_expr() 以及增長random函數設置嵌套層數。
*關於WF的測試:random函數對生成的結果作隨機刪除
二、打包程序,I/O操做
os庫
os 模塊提供了很是豐富的方法用來處理文件和目錄。這裏咱們藉助os指導python進行文件路徑的訪問。
subprocess
subprocess包中定義有數個建立子進程的函數,這些函數分別以不一樣的方式建立子進程,因此咱們能夠根據須要來從中選取一個使用。另外subprocess還提供了一些管理標準流(standard stream)和管道(pipe)的工具,從而在進程間使用文本通訊。在這裏咱們藉助subprocess講信息輸入給java,獲取java輸出。
to use,type: import os import subprocess def get_output(path,java_main,input): os.chdir(path) #改變工做目錄到java文件的目錄 #class Popen(args, bufsize=0, executable=None, stdin=None, #stdout=None, stderr=None, preexec_fn=None, #close_fds=False, #shell=False, cwd=None, env=None, universal_newlines=False, #startupinfo=None, creationflags=0) out, err = popen.communicate(input=bytes(input.encode('utf-8'))) #get stdout, stderr os.chdir('..') #返回上級目錄 return str(out.decode())
注:
*調用cmd對.java進行編譯
to use,type: cd java_src javac *.java cd ..
三、合法性驗證
因爲不太會用python比對運行結果,所以我採用了更加簡便的matlab進行結果比對
(也能夠給python引入malb庫,可是博主在安裝過程當中碰到了嚴重的編碼問題(可能仍是比較菜),所以還在嘗試,稍後成功後會做出補充)
利用python把輸入,輸出存到文件中,利用matlab讀取文件,把輸入用matlab求導後,與輸出作比對(這裏我採用了給x賦 [-100,100]區間內的值,比對計算結果的方法)。
to use,type:
syms x
%fopen() while feof(fp)~=1 %fgetl() %diff() for i = -100:100: %compare
四、多看指導書!!!手動搭樣例!!!!
我的認爲,自動化測試幾乎能夠覆蓋性得測試,可是爲了防止指導書理解誤差等帶來的bug,仍是多看幾遍指導書,動手再搭一些樣例(尤爲是指導書的特殊狀況)比較穩妥。
4、互測策略
互測策略主要和上述本身的測試方法接近
主要進行黑盒測試:
一、記錄本身的存在bug的樣例,便於後續用來hack別人(親測至關有效
我會把全部的樣例存在txt文件中,後續直接在文件中調出,輸入程序
就像這樣:
二、打包
既然進行黑盒測試,不如打包好,用python直接輸入、輸出,方法與上述自動化測試方法中同樣,是提升效率的重要方法
三、評測機瘋狂測試
用本身已經搭建好的評測機,瘋狂自動化測試,我的經驗基本上能夠覆蓋掉全部bug,體驗極佳
輔助白盒測試:
我會選擇性調用幾份代碼學習順便找bug,着重關注if-else語句邏輯,正則表達式的正確性(對發現WF極有幫助),輸出邏輯等
5、基於度量的關於架構的深入反思和重構思路
這三次做業(尤爲是最後一次)的代碼結構是我本次算是最不滿意的地方
所以,我選擇單獨開一個部分對本次架構作深入的反思。
對三次的代碼的class和method分別作了複雜度分析
第一次做業:
主類和Term類設計存在問題
主類PolyComputer的frontDeal、getTermsAndOps的ev(G)偏高
frontDeal和getTermsAndOps中都對WF的狀況過量判斷,好的架構應該傾向於單獨的Method中作統一判斷,或者使用簡介的 正則表達式一次判斷。
PolyTerm類中存在的問題主要集中在toString(),其中過多的if-else給代碼維護帶來了極大額困難,應該從新設計一個Method作簡化輸出的工做,便於維 護。
第二次做業:
除了和第一次有相似的問題以外(其中簡化過程的問題與第一次的問題不謀而合),類的設計的問題更爲突出。直接跳到Term類,而不設置Factor類, 無疑不適合當下已經比較複雜的表達式了,正則表達式過長,Term類承擔太重負擔,沒有設置單獨求導接口帶來的代碼複雜度高、極其不容易維護以
及複用性差等問題凸顯。
一個好的架構應該更傾向於求導方法的接口化、甚至設置單獨求導類或方法(由於求導法則只會愈來愈多愈來愈複雜,應該考慮後續不斷增長的需求)
設置有層次的Factor和Term,並採用繼承和多態來組織。
第三次做業:
我對第三次的構造方法只是採起了對第二次簡單的沿用封裝和新的求導規則的從新構建(由於前面架構實在不適合第三次了),新的Derivation和 DerivationMethod的構造問題
這麼多飄紅,驚了
可見此次的不管是遞歸求導仍是求導方法的構建都是比較失敗的
(1)本次更凸顯Factor的重要地位,以及各類Fator、Term間千奇百怪的求導規則,嵌套因子和表達式因子的新加入就更須要Factor來承擔起家庭的重任,而我尋求的層層拆解最後抽象成三類的求導準則無疑太複雜了,大概也撐不起下一次的迭代開發了,有牽一髮而動全身的風險。應該採用層層拆解後把部分任務分擔給各類小類來減輕Derivation的工做壓力。
(2)設置求導方法接口已經刻不容緩。expr-factor,term-term,factor-factor各類間做用的求導方法和接口值得被開發。