1、測試的url地址及Coding.net地址javascript
可測試的url地址:http://39.105.6.214/myWeb_war/css
Coding.net源碼倉庫地址:https://git.coding.net/wanghz499/2016012032partnerWork.githtml
命令行測試command.java類(用到本身作的core.jar):前端
(注:請使用win+R命令提示符,不要使用PowerShell)
1.打開命令行進入src文件夾:
cd C:\Users\wangh\Desktop\2016012032partnerWork\src
(注意根據本身放的位置調整路徑,下同)
2.編譯command.java和core.jar:
javac -cp C:\Users\wangh\Desktop\2016012032partnerWork\web\WEB-INF\lib\core.jar -encoding utf-8 C:\Users\wangh\Desktop\2016012032partnerWork\src\Command.java
(-cp表示路徑,core.jar包放在web的WEB-INF的lib文件夾下)
3.運行command.java和core.jar,並輸入參數:
java -cp ..\web\WEB-INF\lib\core.jar; Command -n 10 -m 1 100 -o 5html5運行成功,result.txt生成在與src同級的目錄下。java
2、估計項目開發時間react
已記錄在結尾的PSP表格,此處不重複顯示。git
3、設計接口原則
web
在設計接口以前,咱們應該明白接口是作什麼的,它有什麼設計原則,怎樣才能設計出好的接口。因而我去知乎上瀏覽了相關內容,有幾點是我印象比較深入的:算法
1.職責單一化,不要想着作件大事,提供各類通用性,儘可能接口拆細,交給調用方本身去組合。
這句話代表一個接口就是作一件事的,所以咱們在設計接口的時候要明確接口的做用,且做用不能普遍,應該清晰而單一。
2.業務的獨立性,儘可能對外屏蔽你的業務細節,同時也對調用者更加友好。
接口是提供給用戶調用的,所以最好將接口的信息隱藏,而用戶沒必要知道接口內的具體代碼實現。而在本次結對項目中,咱們的接口用戶就是本身,儘管如此,咱們也應該將它的具體實現隱藏起來,所以咱們最終會將接口作成一個core.jar包。
3.拼寫要準確,接口函數一旦發佈就不能改了。函數最好是動賓結構doSomething,如openFile、setName。站在使用者的角度去思考,API設計也要講究用戶體驗。
這些是接口命名的規範,接口的命名要能一眼就看出它的做用,這樣會有更好的用戶體驗。而且在設計接口時,要從使用者的角度出發,若是這個接口讓用戶以爲使用起來很複雜,那麼這就是個失敗的接口了。
4、計算模塊接口的設計與實現過程
在設計接口前,我先分析了下這個接口的做用:產生必定數量的符合條件的式子。所以這個接口須要接收關於數量和條件限制的參數,分別是如下6個參數:題目數量n,數值下界downBound,數值上界upBound,最大運算符數largeOperatorCount,是否有乘除hasMulDiv,是否有括號hasBracket。
首先,這個接口裏共有2個類:Creat類和Calculator類,前者做用是產生規定數量的式子,後者的做用是判斷某條式子是否符合條件。在Create類中,專門設計了一個方法generate()來接收並判斷上述6個參數正確性,隨後generate()方法再經過for循環調用n次createProblem()方法,createProblem()每次產生一條式子,在產生式子時createProblem()方法會調用Caculator類的algorithm()方法來預先計算這條式子的答案,因爲algorithm()方法是用的調度場算法和後綴表達式求值,所以能夠判斷當前式子在計算過程當中是否會產生小數、負數等,以此來達到篩選符合條件的式子的做用。這個接口最終返回的是一個裝滿符合條件式子的字符串數組。最後再考慮到接口信息隱藏的特性,將這兩個類封裝成了一個core.jar包,而後就能夠直接調用了。core.jar內部結構示意圖:
實現的關鍵在於generate()接收的6個參數在以上各個方法之間的傳遞,如generate()調用createProblem()時會將6個參數都傳過去,隨後createProblem()根據參數進行條件判斷產生相應的式子;在createProblem()調用algorithm()時,會將數值下界downBound和上界upBound傳過去以篩選計算過程和最終結果都在數值範圍內的式子。
5、計算模塊接口部分的性能改進
起初個人計算模塊只是稍微改了一下第一次我的做業的代碼,多接收了幾個參數而已。後來發現當限制的數值範圍比較小的時候,控制檯報棧溢出異常。我去網上搜才發現是遞歸調用過分,線程已滿致使程序崩潰。後來我檢查個人代碼的確好幾處都用了遞歸,好比當前生成的式子不知足條件時就再遞歸調用生成式子的createProblem方法,再如運算符下標數組只要所有同樣(即式子的運算符全同樣)我也會再遞歸調用index()方法從新生成一個下標數組,總之多處用到了遞歸。當數值範圍比較小時,生成的式子大可能是不知足條件的,因而會頻頻遞歸產生新式子,當運算符個數比較少時,下標數組也很容易同樣,會頻繁遞歸調用index()方法,最終致使程序跑不了。
在知道是遞歸調用過分的緣由後,根據報錯信息我得知index()方法是程序中消耗最大的函數,因而我修改了index()方法,使其不使用遞歸。修改思路:當下標數組的前n-1個都同樣時,第n個必定與前n個不同,這樣就保證了下標數組至少有2個不一樣,即保證了一條式子至少有2中運算符。通過此次棧溢出異常後,我明白了遞歸要慎用,雖然它簡單,但它及可能會拖慢程序速度或使程序崩潰,我也是第一次意識到代碼性能分析的重要性。下面是效能分析的圖。
6、
Command類測試代碼:
import org.junit.Before; import org.junit.Test; public class CommandTest { @Before public void setUp() throws Exception { Command command = new Command(); } @Test public void main() throws Exception { String[] args = {"-n","10","-m","1","999","-o","9","-c","-b"}; Command.main(args); String[] args1 = {"-n","10","-m","1","999","-o","9"}; Command.main(args1); String[] args2 = {"-n","10","-m","1","999"}; Command.main(args2); String[] args3 = {"啦啦啦"}; Command.main(args3); String[] args4 = {"-n","10","-m","110","999","-o","9"}; Command.main(args4); String[] args5 = {"-n","10","-m","1","99999","-o","9"}; Command.main(args5); String[] args6 = {"-n","10","-m","99","60","-o","9"}; Command.main(args6); String[] args7 = {"-n","0","-m","99","60","-o","9"}; Command.main(args7); String[] args8 = {"-n","a","-m","99","60","-o","9"}; Command.main(args8); String[] args9 = {"-n"}; Command.main(args9); String[] args10 = {"-n","3","-m","60","-o","9"}; Command.main(args10); String[] args11 = {"-n","3","-m"}; Command.main(args11); String[] args12 = {"-o","-2","-m"}; Command.main(args12); String[] args13 = {"-o"}; Command.main(args13); String[] args14 = {"-o","b"}; Command.main(args14); String[] args15 = {"-m","1","999","-o","9"}; Command.main(args15); String[] args16 = {"-n","10","-m","1","999","-o","9","-b"}; Command.main(args16); String[] args17 = {"-n","10","-o","9","-c","-b"}; Command.main(args17); } }
import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class CreateTest { private Create create; @Before public void setUp() throws Exception { create = new Create(); } @Test public void generate() throws Exception { create.generate(10,10,100,3,true,true); create.generate(10,100,10,12,true,true); create.generate(10,10,1000,12,true,true); create.generate(10,1,10000,8,true,true); create.generate(10,101,1000,8,true,true); create.generate(0,1,1000,8,true,true); create.generate(10,1,1000,8,true,true); create.generate(10,1,1000,8,false,true); create.generate(10,1,1000,8,true,false); create.generate(10,1,1000,8,false,false); } @Test public void createProblem() throws Exception { create.generate(10,1,1000,8,true,true); create.generate(10,1,1000,8,false,true); create.generate(10,1,1000,8,true,false); create.generate(10,1,1000,8,false,false); } @Test public void index() throws Exception { create.index(3,4); } }
7、計算模塊部分異常處理說明
計算模塊共需接收的參數有6個,異常都圍繞這6個參數展開。
1.關於題目數量n的參數異常有3種:數量範圍越界、未輸入題目數量和非法字符輸入。
case "-n": { try { n = Integer.parseInt(args[i + 1]); if (n < 1 || n > 10000) { System.out.println("對不起,題目數量只能是1-10000!"); return; //結束運行 } } catch (ArrayIndexOutOfBoundsException e) { //未輸入數字時args[i+1]會數組越界 System.out.println("未輸入題目數量!"); return; }catch (NumberFormatException e) { //輸入非數字字符等 System.out.println("對不起,題目數量只容許輸入1-10000的數字!"); return; //結束運行 } break; }
單元測試樣例:
@Test public void main() throws Exception { String[] args7 = {"-n","0","-m","99","60","-o","9"}; Command.main(args7); String[] args8 = {"-n","a","-m","99","60","-o","9"}; Command.main(args8); String[] args9 = {"-n"}; }
2.關於數值上下界的異常有4種:數值下界大於上界、數值上下界沒有在相應規定的範圍、未輸入數值上下界、非法字符輸入。
case "-m": { try { downBound = Integer.parseInt(args[i + 1]); upBound = Integer.parseInt(args[i + 2]); if (downBound >= upBound) { System.out.println("數值下界不能大於等於上界!"); return; } else if (downBound < 1 || downBound > 100 || upBound < 50 || upBound > 1000) { System.out.println("數值下界只能是1-100,數值上界只能是50-1000!"); return; } } catch (ArrayIndexOutOfBoundsException e) { System.out.println("未分別輸入數值上下界!"); return; }catch (NumberFormatException e) { System.out.println("對不起,數值上下界只容許數字格式!"); return; } break; }
單元測試樣例:
@Test public void main() throws Exception { String[] args6 = {"-n","10","-m","99","60","-o","9"}; Command.main(args6); String[] args11 = {"-n","3","-m"}; Command.main(args11); String[] args4 = {"-n","10","-m","110","999","-o","9"}; Command.main(args4); String[] args18 = {"-n","10","-m","hhh","kkk","-o","9"}; Command.main(args18);
3.關於最大運算符數量的異常3種:不在規定範圍、未輸入、非法輸入
case "-o": { try { largeOperatorCount = Integer.parseInt(args[i + 1]); if (largeOperatorCount < 1 || largeOperatorCount > 10) { System.out.println("對不起,運算符數量只能是1-10!"); return; } } catch (ArrayIndexOutOfBoundsException e) { System.out.println("未輸入最大運算符數量!"); return; }catch (NumberFormatException e) { System.out.println("對不起,運算符數量只容許輸入1-10的數字!"); return; } break; }
單元測試樣例:
@Test public void main() throws Exception { String[] args12 = {"-o","-2","-m"}; Command.main(args12); String[] args13 = {"-o"}; Command.main(args13); String[] args14 = {"-o","b"}; Command.main(args14); }
4.什麼相關的參數都沒輸,只輸非法字符等:
if (n == 0 && downBound == 0 && upBound == 0) { System.out.println("參數格式有誤!"); return; } else if (n == 0) { System.out.println("未輸入題目數量!"); return; } else if (downBound == 0 || upBound == 0) { System.out.println("未分別輸入數值上下界!"); return; }
單元測試樣例:
@Test public void main() throws Exception { String[] args3 = {"啦啦啦"}; Command.main(args3); }
8、界面模塊的詳細設計過程
2.註冊頁面:有判斷用戶名已存在、兩次密碼輸入不一致的功能。
3.出題大廳界面:用戶在此能夠設定出題條件。文本框採用了html5的number類型,保證用戶只能輸入限定範圍的數字。
題目數量:<input placeholder="1-10000" type="number" min="1" max="10000" name="n"/> 數值範圍:<input placeholder="1-100" type="number" min="1" max="100" name="downBound"> - <input placeholder="1-100" type="number" min="50" max="1000" name="upBound">
4.點擊開始出題,會跳到result.txt文件下載頁面:
5.點擊下載:
6.下載完後,點擊開始作題,進入答題界面:此頁面有計時器。
7.答完題後,點擊提交,進入成績反饋界面:會顯示答對多少題,答錯多少題,所用時長,以及錯題卡片(包含錯題與正確答案),可供孩子鞏固複習。
8.隨後點擊查看歷史答題記錄,進入歷史答題界面:能夠看到剛剛的答題已經呈如今歷史列表裏。此頁面會按時間前後順序展現以往的答題記錄,並會顯示歷史最佳成績,還可顯示該練習的類型是系統產生仍是用戶本身上傳的。
9.點擊其中一條答題記錄,查看該次練習的詳情。點擊橙色下拉按鈕可查看具體答題記錄(作對和作錯的題都顯示)。再點擊按鈕可收起,此效果由javascript實現。
<script language="javascript" type="text/javascript"> function aaa(){ if(document.getElementById("contentTable").style.display == 'none') {document.getElementById("contentTable").style.display='';} else {document.getElementById("contentTable").style.display='none';} } </script>
10.點擊側欄「上傳題目」,用戶可上傳本身的題目進行答題:這裏有個文件格式限制,若上傳正確格式文件,會出現一個開始答題按鈕;若上傳錯誤格式的文件開始答題的按鈕是不會出現的。此效果是經過javascript識別後臺傳來el表達式的值實現的。
<script type="text/javascript"> var msg = "${msg}"; if(msg=='題目上傳成功!'){ document.getElementById('begin').style.display="inline-block"; }else { document.getElementById('begin').style.display="none"; } </script>
11.點擊開始答題就會進入答題界面,題目內容是用戶本身上傳的題。界面與上述答題界面同樣就不展現啦。
12.修改密碼界面:要求輸入原密碼與新密碼。有原密碼輸入錯誤判斷,有必定安全性。
13.點擊退出帳號,退回登陸頁面並顯示退出成功。
9、界面模塊與計算模塊的對接
界面模塊與計算模塊的對接即jsp與servlet的對接。因爲計算模塊已經封裝好了,servlet只需調用計算模塊的Create類產生題目,將答案存於另外一個數組中,並將題目置於ArrayList中利用request.setAttribute()傳給jsp,在jsp中利用jstl標籤<c:foreach>循環列出。用戶答完題後經過Form表單將答案傳給servlet,servlet用一個數組接收用戶答案,再將用戶答案數組與正確答案數組對比,便可判斷對錯。
相關代碼以下:
/** * 生成題目 * @param request * @param response * @throws ServletException * @throws IOException */ private void create(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException { int n = Integer.parseInt(request.getParameter("n")); int downBound = Integer.parseInt(request.getParameter("downBound")); int upBound = Integer.parseInt(request.getParameter("upBound")); int MulDiv = Integer.parseInt(request.getParameter("hasMulDiv")); int Bracket = Integer.parseInt(request.getParameter("hasBracket")); int largeOperatorCount = Integer.parseInt(request.getParameter("largeOperatorCount")); RequestDispatcher rd; if(downBound>=upBound){ request.setAttribute("msg","數值下界不能大於上界!"); rd = request.getRequestDispatcher(WebContents.MAIN); rd.forward(request,response); } boolean hasBracket = false; boolean hasMulDiv = false; if(Bracket==1){ hasBracket=true; } if(MulDiv==1){ hasMulDiv=true; } //將生成的result.txt放在web/file下 Create create = new Create(); String[] result = create.generate(n,downBound,upBound,largeOperatorCount,hasMulDiv,hasBracket); //將題目(不含答案)存入session String[] question = new String[n]; for(int i=0;i<n;i++){ int index = result[i].indexOf("="); question[i]=result[i].substring(0,index); } request.getSession().setAttribute("question",question); //將正確答案存入session String[] realAnswer = new String[n]; for(int i=0;i<n;i++){ int index = result[i].indexOf("="); realAnswer[i]=result[i].substring(index+1); } request.getSession().setAttribute("realAnswer",realAnswer); MakeFile2 makeFile2 = new MakeFile2(); String path = request.getSession().getServletContext().getRealPath("file")+"/"; File f = new File(path); //文件夾也是個文件 if(!f.exists()){ f.mkdirs(); //若是該文件夾不存在,則建立該文件夾 } String fileName = UUID.randomUUID()+"result.txt"; File file = makeFile2.creatFile(result,path+fileName); //將該練習存入數據庫 String relativePath = "../../../file/"+fileName; User user = (User) request.getSession().getAttribute("user"); int userId = user.getId(); String creatType = "系統產生"; PracticeDao practiceDao = new PracticeDaoImpl(); Practice practice = new Practice(relativePath,creatType,userId); practiceDao.insertPractice(practice); Practice practice1 = practiceDao.selectPracticeByPath(relativePath); request.getSession().setAttribute("practiceId",practice1.getId()); request.setAttribute("filepath",path+fileName); request.getSession().setAttribute("file",file); request.setAttribute("n",n); rd = request.getRequestDispatcher(WebContents.DOWNLOAD); rd.forward(request,response); } /** * 開始答題(返回題目內容) * @param request * @param response * @throws ServletException * @throws IOException */ private void begin(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException { File file = (File)request.getSession().getAttribute("file"); int n = Integer.parseInt(request.getParameter("n")); ReadFile readFile = new ReadFile(); List<Problem> list= readFile.getFileContent(file,n); request.setAttribute("list",list); //將讀的文件內容賦給list,給前臺遍歷 request.setAttribute("n",n); RequestDispatcher rd; rd = request.getRequestDispatcher(WebContents.BEGIN); rd.forward(request,response); } /** * 檢查用戶答案 * @param request * @param response * @throws ServletException * @throws IOException */ private void check(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException { String timelong = request.getParameter("timelong"); System.out.println(timelong); int n = Integer.parseInt(request.getParameter("n")); String[] userAnswer = new String[n]; for(int i=0;i<n;i++){ userAnswer[i] = request.getParameter("s"+i); //得到用戶答案 } String[] question = (String[]) request.getSession().getAttribute("question"); String[] userContent = new String[n]; for(int i=0;i<n;i++){ userContent[i]=question[i]+"="+userAnswer[i]; } String[] realAnswer = (String[])request.getSession().getAttribute("realAnswer"); int correctCount=0; int wrongCount=0; List<FeedBack> feedBackList = new ArrayList<>(); //答錯的題+正確答案 for(int i=0;i<n;i++){ if(!userAnswer[i].equals(realAnswer[i])){ wrongCount++; String a = userContent[i]; String b = realAnswer[i]; FeedBack feedBack = new FeedBack(a,b); feedBackList.add(feedBack); }else correctCount++; } String[] wrongProblem = new String[feedBackList.size()]; String[] correctAnswer = new String[feedBackList.size()]; for(int i=0;i<feedBackList.size();i++){ wrongProblem[i] = feedBackList.get(i).getWrongUserContent(); correctAnswer[i] = feedBackList.get(i).getCorrectAnswer(); } //將這次練習信息存入數據庫 int practiceId = (int)request.getSession().getAttribute("practiceId"); Practice practice = new Practice(practiceId,Arrays.toString(wrongProblem),Arrays.toString(correctAnswer),Arrays.toString(userContent),correctCount,wrongCount,timelong); PracticeDao practiceDao = new PracticeDaoImpl(); practiceDao.updatePractice(practice); request.setAttribute("correctCount",correctCount); request.setAttribute("wrongCount",wrongCount); request.setAttribute("timelong",timelong); request.setAttribute("feedBackList",feedBackList); RequestDispatcher rd; rd = request.getRequestDispatcher(WebContents.FEEDBACK); rd.forward(request,response); }
因爲實現了用戶功能,因此這次項目用到了數據庫,數據庫中有2張表——用戶表和練習題表。
整個項目結構以下:
其中entity是實體,dao層負責數據庫的鏈接實現數據的增刪改查,servlet負責業務邏輯處理,sql封裝了全部用到的sql語句,util是servlet須要用到的的一些工具類,test放着全部的測試類(包括增刪改查方法測試、計算模塊Create類測試、命令行測試),web的WEB-INF下放着jar包和jsp等。
10、
11、
優勢:我以爲結對編程的優勢在於2我的會互相督促,加快項目進度,並且由於都不想拖後腿,因此態度會很積極。當遇到困難時,兩我的共同面對會使心理壓力小不少,會下降對bug和其餘技術困難的畏懼。因爲結對編程時每一行代碼都由兩我的思考過,所以出錯和修改代碼的概率會小不少,從而提升編程效率。最重要的是,結對編程可以加深兩我的的友誼,以爲對方就是與本身共患難的戰友。
缺點:結對編程最大的缺點就是兩我的的工做量不一致,極可能會出現一人奮鬥、一人打醬油的局面。若是在不少小問題上沒溝通好兩人容易發生矛盾。
我對鄧旭的評價:優勢是態度很積極,並且很謙虛,會傾聽個人建議,老是給人帶來正能量,會鼓勵支持我。缺點就是此次去參加比賽,沒能和我一塊兒敲大部分的代碼,不過這也沒辦法呀,省運會比較重要,我很理解~總之此次與鄧旭一塊兒的結對項目仍是比較愉快的!
SP2.1 |
任務內容 |
計劃共完成須要的時間(h) |
實際完成須要的時間(h) |
Planning |
計劃 |
74.5 |
108.5 |
· Estimate |
· 估計這個任務須要多少時間,並規劃大體工做步驟 |
1 |
1 |
Development |
開發 |
67.5 |
98.5 |
· Analysis |
· 需求分析 (包括學習新技術) |
3 |
5 |
· Design Review |
· 代碼設計 |
4 |
5 |
· Coding Standard |
· 代碼規範 (爲目前的開發制定合適的規範) |
0.5 |
0.5 |
· Design |
· 具體設計 |
3 |
5 |
· Coding |
· 在計算模塊花費的時間 在UI模塊花費的時間 在後臺處理模塊花費的時間 |
15 10 30 |
20 10 40 |
· Code Review |
· 代碼複審 |
2 |
3 |
· Test |
· 測試(自我測試,修改代碼,提交修改) |
2 |
10 |
Reporting |
報告 |
6 |
9 |
· Test Report |
· 測試報告 |
5 |
8 |
· Size Measurement |
· 計算工做量 |
0.5 |
0.5 |
· Postmortem & Process Improvement Plan |
· 過後總結, 並提出過程改進計劃 |
0.5 |
0.5 |