軟工網(wǎng)絡(luò)結(jié)對編程作業(yè)
軟工網(wǎng)絡(luò)結(jié)對編程作業(yè)
0. 團隊成員
| 姓名 | 學(xué)號 | 博客園首頁 | 碼云主頁 |
|---|---|---|---|
| 孫志威 | 20152112307 | Agt | Eurekaaa |
| 孫慧君 | 201521123098 | 野原澤君 | 野原澤君 |
1. 需求分析:針對現(xiàn)有代碼的改進分析,新開發(fā)功能的分析。
1. 題目需求:
- 原題要求:
- 寫一個能自動生成小學(xué)四則運算題目的命令行 “軟件”:
- 除了整數(shù)以外,還要支持真分數(shù)的四則運算,真分數(shù)的運算,例如:1/6 + 1/8 = 7/24;
- 運算符為 +, ?, ×, ÷;
- 并且要求能處理用戶的輸入,并判斷對錯,打分統(tǒng)計正確率
- 要求能處理用戶輸入的真分數(shù), 如 1/2, 5/12 等;
- 使用 -n 參數(shù)控制生成題目的個數(shù),例如執(zhí)行下面命令將生成10個題目 :Myapp.exe -n 10。
- 把這個程序做成GUI:
- 記錄用戶的對錯總數(shù),程序退出再啟動的時候,能把以前的對錯數(shù)量保存并在此基礎(chǔ)上增量計算;
- 有計時功能,能顯示用戶開始答題后的消耗時間;
- 界面支持中文簡體/中文繁體/英語,用戶可以選擇一種。
- 兩個任務(wù):
- 把計算模塊提取出來,單獨創(chuàng)建一個類。
- 針對提取出來的計算類的接口函數(shù)做單元測試。
- 寫一個能自動生成小學(xué)四則運算題目的命令行 “軟件”:
- 功能改進與擴展:
- 增加括號操作符;
- 減少重復(fù)題目;
- 增加一個運算符(乘方):用符號 ^ 表示乘方,例如:4 ^ 2=16;
- 回歸測試:在開發(fā)新功能時避免損壞舊的功能,以確保新的功能不與原有功能沖突,在確認修改的功能正確之后再嵌入代碼;
- 效能分析。
2. 代碼分析
- 獲取代碼
- 引用老師提供的代碼為: 碼云地址
- Clone該項目
![]()
- 分析結(jié)構(gòu)
-
類之間關(guān)系
比較碼云的鏈接中給出的代碼只有兩個類:
TrivialCalculator 和 TrivialCalculatorTest -
類圖
![]()
-
邏輯分析:該項目一共只有4個函數(shù),分別為
- gcd 求出最大公約數(shù)
- cal 實現(xiàn)兩個數(shù)字的加減乘除計算
- zfcal 實現(xiàn)兩個分數(shù)的加減乘除
- calinput 處理輸入的字符串
基本上都是邏輯清晰的分支結(jié)構(gòu),所以沒有“邏輯泥球”
-
測試用例
![]()
![]()
測試用例完整覆蓋了所有函數(shù)的各個分支
-
分析現(xiàn)有代碼需要改進的地方:
- 沒有實現(xiàn)真分數(shù)和整數(shù)的同時存在:使用隨機實現(xiàn)真分數(shù)和整數(shù)的可能同時出現(xiàn)。
- 沒有實現(xiàn)表達式中數(shù)字個數(shù)的隨機:利用宏定義統(tǒng)一設(shè)置表達式數(shù)字個數(shù)的最多個數(shù),從2到設(shè)置的最多位數(shù)隨機取一個數(shù),決定表達式的數(shù)字長度。
【表達式的生成利用循環(huán)“隨機符號+隨機數(shù)字”,最終去除第一位符號來得到】 - 碼云里的代碼不能運行;
- GUI界面有待改善;
- 代碼可讀性不強,缺少相應(yīng)的注釋。
- 新開功能的分析:
- 添加括號:
- 表達式中數(shù)字個數(shù)大于2時,可隨機添加括號;
- 左括號的添加位置可以為表達式前,或各個運算符的后面(除了最后一個運算符),右括號的添加位置可以為表達式的最后,或與左括號相隔一個符號開始到表達式最后的每個運算符的左邊。
- 減少重復(fù)題目:
- 將表達式分解為多個“符號+數(shù)字”組合的數(shù)組,第一位數(shù)字前符號為“+”;
- 對組合數(shù)組進行排序,并放入二叉樹,左子節(jié)點為該父節(jié)點組合在數(shù)組中的下一位,右子節(jié)點為在數(shù)組中擁有相同上幾位組合的同位的不同組合;
- 每生成一個表達式就在該二叉樹內(nèi)檢索插入,已存在則剔除重新生成。(直說好難理解,下面會用圖解釋)
- 添加乘方:
- 為了減小使用者和計算機內(nèi)部的計算量,將最多只添加一個二次冪或三次冪;
- 遍歷表達式字符串,得到運算符的位置,并在這些位置和表達式的末尾中隨機抽取一個位置,在其右邊添加"^2"或 "^3"。
-
2. 程序設(shè)計:針對新開發(fā)功能做設(shè)計,建議使用思維導(dǎo)圖。
-
程序總體設(shè)計
![]()
-
所使用的二叉樹算法介紹
![]()

-
程序主要部分介紹(C++,C++/QT,Python,C#)
- 生成表達式(C++):采用隨機產(chǎn)生字符拼接成字符串的方式生成
- 加工表達式(C++)
- 對生成的表達式進行合法性檢查
- 在合理位置添加括號、 冪運算等操作
- 將表達式分割為以“符號+數(shù)字”的更小元素
- 將表達式以“元素”為單元進行統(tǒng)一排序
- 避免表達式廣義重復(fù)(C++)
- 構(gòu)建了一個二叉樹結(jié)構(gòu)用來解析排序后的表達式是否重復(fù)
- 二叉樹將添加并維護存在過的所有表達式
- 解析表達式(python)
- 使用C++調(diào)用python接口
- 使用python的eval函數(shù)動態(tài)解析表達式
所以就不用正常的中綴-后綴 + 棧解析的做法了(py大法好)
- 前端顯示(C++/QT)
- 用QT簡單的做了個顯示表達式、能檢測輸入框答案是否正確的GroupBox
- 然后運行時在MainWindows里new幾個Box就醬
- 單元測試部分(C#)
- 單元測試用的是MS測試框架
- 測試項目不需要與正常代碼放在同一項目下,‘引用’功能和測試資源管理器都十分好用
-
各新增功能演示
-
支持括號
![]()
-
表達式不重復(fù)
表達式重復(fù)的幾率為0,具體算法見二叉樹算法處 -
![]()
![]()
-
乘方運算
![]()
-
-
測試分析結(jié)果
-
回歸測試:
測試方法大綱
![]()
部分測試用例
![]()
-
代碼覆蓋率測試
![]()
-
效能分析
![]()
-
消耗最大模塊
- 發(fā)現(xiàn)占用絕大部分時間的都不是用戶的函數(shù)
基本都是系統(tǒng)調(diào)用 - 但是在用戶代碼中有一個 Evaluator::initPython方法被調(diào)用了124次,實際上這里是可以優(yōu)化的
- 優(yōu)化建議:將Evaluator的一些需要全局經(jīng)常調(diào)用的方法設(shè)置為static的類方法,一旦init后就不需要再調(diào)用了,可以大大減少該函數(shù)的調(diào)用次數(shù)
- 發(fā)現(xiàn)占用絕大部分時間的都不是用戶的函數(shù)
-
3. 代碼展示:展示每個功能的核心代碼。
-
字符串生成類
class ExpGenerator { public: ExpGenerator(); ~ExpGenerator(); int getGcd(int x, int y); string generateRawExp(); vector<int> findOperator(string exp); string addBracket(string exp); string addPower(string exp); vector<string> apartExp(string exp); vector<string> sortExp(vector<string> items); vector<string> generate(int amount, vector<double>&results); };- 生成字符串
string ExpGenerator::generateRawExp() { //產(chǎn)生初始表達式 string exp = ""; char ch[5] = { '+','-','*','~','^' }; int maxNum; if (NUMBER < 2) { //表達式位數(shù)小于2則報錯 exp = "ERROR!"; return exp; } else if (NUMBER == 2) maxNum = 2; else maxNum = rand() % NUMBER + 2; for (int i = 0; i < maxNum; i++) { //以(符號+數(shù)字)*maxNum形成第一位為符號的“偽表達式” //隨機生成符號 char c = ch[rand() % 4]; exp.push_back(c); //隨機生成數(shù)字 stringstream ss; string s; int flag = rand() % 10; if (flag != 0) //生成整數(shù)(90%) ss << rand() % MAX + 1; else { //生成分數(shù)(10%) int mumNum = rand() % MAX + 2; int sonNum = rand() % (mumNum - 1) + 1; sonNum /= getGcd(mumNum, sonNum); mumNum /= getGcd(mumNum, sonNum); ss << sonNum << "/" << mumNum; } ss >> s; exp += s; } //截去第一位符號,生成初始表達式 exp = exp.substr(1, exp.length()); ////cout << exp << endl; return exp; } - 拆分字符串并排序
vector<string>ExpGenerator::apartExp(string exp) { //遍歷初始表達式,以“符號+數(shù)字”的結(jié)構(gòu)分割返回 vector<string> items; int begin = 0; int end; unsigned int i; string str; exp = "+" + exp; char c; for (i = 0; i < exp.length(); i++) { c = exp.at(i); if ((c == '+' || c == '-' || c == '*' || c == '~') && (i != 0)) { end = i; str = exp.substr(begin, end - begin); items.push_back(str); begin = i; } } str = exp.substr(begin, exp.length() - begin); items.push_back(str); return items; } vector<string> ExpGenerator::sortExp(vector<string> items) { //將分割后的表達式項進行排序,用于二叉樹的形成 sort(items.begin(), items.end()); return items; }
- 生成字符串
-
二叉樹相關(guān)
class UniqueBinaryTree { public: UniqueBinaryTree(); ~UniqueBinaryTree(); // check if the tree contains the expression // if contains return true // else return false // and add the unique new nodes to the tree bool add(vector<string>expressionVec, double answer); // clear the tree but not destoryed it void clear(); // get amount of the nodes int nodeAmount(); // get amount of all the resultsNodes int resultsAmount(); // amount of both result node and the mormal node int amount(); // Debug: show contents to the console void showLayer(); private: // add the result node to the last node (levelNode) // return true if new result added // which represent that the new expression // has the same expression(without brackets) but // different answers bool mountAnswerToNode(PNode levelNode, double answer); int countAnswerNode(PNode levelNode); void clearAnswerNode(PNode levelNode); Node * head = NULL; };- add函數(shù)
bool UniqueBinaryTree::add(vector<string> expressionVec , double answer) { // treat empty string as true/contains and ignore it if (expressionVec.empty()) return true; int length = expressionVec.size(); PNode curNode = this->head->right; // compare each node from the first PNode fatherNode = this->head; string currentStr; int index = 0; // the current index of stringVector bool isContained = false; while (curNode != NULL) { currentStr = expressionVec.at(index); // record the father node to support the insertion later fatherNode = curNode; if (curNode->data == currentStr) { curNode = curNode->left; // jump to the next string index++; // break when the whole expression finish if (index >= length) break; } else { curNode = curNode->right; } } //if not then it's an new expression // mount it to the tree // begin from the last part they're same // the current node is the last node matches part of the new expression bool first = true; // if it reached the end of the expression in the last loop // will skip it while (index < length) { string curStr = expressionVec.at(index); PNode newNode = new Node(); if (first) { // mount the first newNode as the rightChild fatherNode->right = newNode; first = false; } else { fatherNode->left = newNode; } newNode->data = curStr; newNode->left = NULL; newNode->right = NULL; newNode->answer = NULL; fatherNode = newNode; index++; } // now the new expression has been added to the tree // add the answer of the expression as well // and check if the answer is inside the tree // if it's inside it return false as well PNode lastNode = fatherNode; bool newAnswerAdded = this->mountAnswerToNode(lastNode,answer); // new answer node is unique , and been added to the tree return !newAnswerAdded; }
- add函數(shù)
-
具體代碼見碼云Agt-TrivialCalculator
4. 程序運行:程序運行及每個功能的使用截圖。
- 以下圖片分別演示了
- 隨機生成字符串
- 表達式的加減乘除
- 表達式的括號運算符以及冪運算
- 用戶的輸入和結(jié)果正確反饋
- 表達式的不重復(fù)
- 表達式的正確解析
- etc...
![]()
5. 結(jié)對編程記錄

-
碼云記錄
![]()
-
編碼規(guī)范文檔
![]()
-
PSP表格
PSP2.1 個人開發(fā)流程 預(yù)估耗費時間(分鐘) 實際耗費時間(分鐘) Planning 計劃 20 30 Estimate 明確需求和其他相關(guān)因素,估計每個階段的時間成本 10 30 Development 開發(fā) 413 940 Analysis 需求分析 (包括學(xué)習(xí)新技術(shù)) 20 60 Design Spec 生成設(shè)計文檔 60 60 Design Review 設(shè)計復(fù)審 60 30 Coding Standard 代碼規(guī)范 3 10 Design 具體設(shè)計 30 60 Coding 具體編碼 120 400 Code Review 代碼復(fù)審 60 200 Test 測試(自我測試,修改代碼,提交修改) 60 120 Reporting 報告 60 120 · 測試報告 10 20 · 計算工作量 30 30 · 并提出過程改進計劃 60 60
6. 小結(jié)感受:
-
結(jié)對編程真的能夠帶來1+1>2的效果嗎?
關(guān)于結(jié)對編程的1+1是否大于2,我還不是很確定,原因如下。 1. 在結(jié)對編程的過程中,同一臺電腦,一個人敲代碼,另一個負責復(fù)審,無可厚非會浪費掉一個人敲代碼的時間,但與此同時,又節(jié)省了復(fù)審時間,這兩者之間孰輕孰重我還無法準確比較; 2. 結(jié)對編程的兩個人是不可能實力相當?shù)?,于是就會有一部分時間是“隊友教我做作業(yè)”的狀態(tài)OvO,在這里對我(沒錯,我弱)的編程能力的提高是有所幫助的。這個時候相比于讓隊友直接上手,我們更加樂意于“隊友教我做作業(yè)”,但是這個時候“教學(xué)時間”會花很多時間=-=; 3. 我在編程時,偶爾會邏輯混亂,腦子漿糊,我可以冷靜下來和我的隊友分析一波,理清思路再繼續(xù)編寫,由此完成我們的項目,我覺得在這里結(jié)對編程相比于一個人完成代碼是有所優(yōu)勢的; 4. 最后一點給結(jié)對編程的優(yōu)點,在編程時,兩人可以發(fā)揮各自的優(yōu)勢,促進項目的完成,就比如:數(shù)據(jù)結(jié)構(gòu)的規(guī)劃是我想出來的,但我實際構(gòu)建數(shù)據(jù)結(jié)構(gòu)會弱一點,那么我的隊友就負責完成這一個部分,我負責其他部分。 -
通過這次結(jié)對編程,請談?wù)勀愕母惺芎腕w會。
至于感受: 1. 學(xué)好英語很重要=-=。英語學(xué)不好,命名只能用拼音全拼,非常丑! 2. 一個項目的好的設(shè)計可能不是在開始敲代碼之前就完成的,可能是編寫時突然的一個靈感來完善的。在之前,我還沒注意到老師說可以用二叉樹,數(shù)據(jù)結(jié)構(gòu)完全是我們自己分析出來的。起初決定用單純的樹,當時覺得“嗯,這個結(jié)構(gòu)好像不錯”,后來發(fā)現(xiàn)多個度的樹是很難用代碼實現(xiàn)的(OK FINE);接著聯(lián)想到了樹轉(zhuǎn)化為二叉樹,用左孩子右兄弟的方式存儲,“妙哇,節(jié)省空間又很快”;但是發(fā)覺答案的存儲又成了一個問題,再次改進運用了指針。 3. 其實,很多時候項目本身不難,如果仔細分析一波,分成一個個小塊去實現(xiàn),編寫好項目是很容易的,只是你不愿意去認真而已。 4. 做項目的時候合理控制一下時間。一方面,屁股都坐不住,就不要妄想做好一個項目;另一方面,沉迷代碼,面對顯示屏太久人也會受不了的,同時也會影響工作效率。
以上就是該博客的全部內(nèi)容,喜歡請關(guān)注+轉(zhuǎn)發(fā)+收藏+硬幣,我們下次再見。
↑皮這一下很開心hhhhh。


















浙公網(wǎng)安備 33010602011771號