https://github.com/tilmto/TILMTO/tree/master/Arithmeticjava
本次結對編程做業分爲如下兩種類型Core(計算核心)和UI(用戶界面),咱們組是UI組, 負責把core組生成的四則運算表達式展示在用戶界面上。android
1.對Core各屬性參數(生成題目的數量,操做數的數量,題目及答案中的數值的範圍……)進行設置;git
2.調用Core模塊獲得題目和運算結果,顯示題目,接受輸入,並能判斷答案是否正確;github
3.增長「倒計時」功能,每一個題目必須在20秒內完成,不然,得0分並進入下一題;express
4.增長「錯題記錄」功能,對於答錯的題,將其保存下來,當下次進行「複習」時,增大錯題在練習題中的機率;編程
5.增長」歷史紀錄「功能,把用戶作題的成績紀錄下來並能夠展示歷史紀錄。設計模式
Statuapi |
Stagesapp |
預估耗時/hdom |
實際耗時/h |
Accept |
——需求分析 |
0.5 |
0.5 |
Accept |
——技術學習 |
6 |
4 |
Accept |
——倒計時功能 |
2 |
1 |
Accept |
——錯題讀入功能 |
2 |
3 |
Accept |
——錯題保存功能 |
2 |
1 |
Accept |
——歷史記錄功能 |
2 |
1.5 |
Accept |
——UI-core對接 |
3 |
6 |
Accept |
—— 測試 |
2 |
1 |
Accept |
——博客撰寫 |
2 |
2.5 |
Accept |
——合計 |
21.5 |
20.5 |
PB15000175 傅泳淦: 需求分析、技術學習、功能設計、所有代碼編寫、博客撰寫;
PB15061308 張軍: 技術學習、輔助提議、與core組通聯、軟件使用說明書撰寫。
結對編程是軟件開發人員必須掌握的開發流程,其最大的優勢在於極大地促進了開發人員人間的信息交流,省去了以往溝通上耗費的時間,而且讓多樣化的思惟下降了代碼的錯誤率,提高了可靠性。
此次做業與上次做業相比,工做量大了許多,除了組內兩人結對編程外,UI組與Core組也要彼此交流對接,共同開發一套軟件。因此,此次的做業與上次的做業相比,多了許多交流合做與信息溝通的成分。這一點在你們以往的編程經歷中從未出現過的,然而信息交流在真正的團隊開發中是十分重要的,其重要性毫不亞於我的碼代碼的能力,缺乏了必要的信息溝通會嚴重耽誤整個項目的進展,相信這一點也是老師和助教們但願咱們從此次經歷中體會到的。
咱們組的此次結對編程過程與其餘組比較不一樣,由於咱們兩人的編程能力相差較大,咱們的結對關係相似於軟件企業中師傅帶徒弟的模式,我(傅泳淦 PB15000175)承擔了所有的功能設計與代碼編寫工做,張軍同窗充當徒弟的角色在一旁學習使用QT與C++的使用。在給他講解的過程當中我也順勢理清了思路,相信他也有所收穫。在這個過程當中我發現要想對本身的代碼有個透徹的瞭解,就要拉我的給他講本身的代碼是怎麼回事,講清楚了也就知道代碼有沒有問題了。
此次做業過程當中鄧老師有句話讓我映象很深入:標準是要搶的。之前我本身報過經濟學類課程,其中很重要的一個問題就是標準,先搶到標準制定權的人每每能搶得先機。軟件開發中也是這個道理,先發出sdk接口的組,只要質量基本使人滿意,便頗有機會壟斷整個行業。像此次最早發core的幾個組,不少UI組的參數輸入界面就是根據這些core定製的,可見其搶佔了大片市場。
軟件名稱爲Arithmetic,爲小學生提供自定製算術訓練。
主頁面以下:
上方有菜單欄和工具欄以及相應快捷鍵以供選擇,用戶能夠利用他們生成隨機算式、回顧錯題、保存錯題、查看歷史成績以及退出應用。每當鼠標移動到功能位置,下方狀態欄會給出相應使用提示。
主界面的四個功能與菜單欄相應功能對應,用法詳見菜單欄介紹。
菜單欄function下有五個選項,分別是:
當鼠標停留在某一個功能上時,狀態欄會給出這個功能的使用方式。例如,當鼠標停在 Generate 功能時,下方狀態欄會有 Generate new exercises randomly 的提示。
在工具欄中,咱們又將這五項功能列了出來,使得用戶能夠經過移動工具欄停靠位置得到更好的體驗。
Exercise/Generate:點擊主界面的 Exercise 或菜單欄/工具欄的 Generate 可產生隨機算式,首先會彈出算式設置窗口,如圖:
參數含義:
Exercise Num:生成的練習題數量,默認爲10;
Max Operator Num:運算符最大的數量,默認爲5;
Range of Numbers:算式中的最大數,需輸入大於20的數,默認1000;
Precision of Decimals:結果保留小數的精度,默認爲2;
Has Fraction:能否出現帶分數,默認爲否;
Has Decimal:能否出現小數,默認爲否;
Has Multiply/Divide:能否出現乘除號,默認爲是;
Has Power:能否出現乘方,默認爲否。
設置完成後點擊random generate生成隨機算式。
生成題目界面如圖所示:
每道題有20s的答題時間,時間一過文本框將沒法輸入。
回答完畢點擊 Show Answers或者所有時間用完,系統進行批改並打分:
此時能夠點擊 Save 按鈕,選擇保存路徑新建一個文件以保存錯題。
Review:點擊主界面或菜單欄/工具欄的review 功能,可讀入 Save的錯題進行復習,從新訓練並再次打分。
History:點擊主界面或菜單欄/工具欄的History功能,可查看作題記錄:
Close:點擊主界面的Quit或菜單欄/工具欄的close便可退出程序。
首先是打開程序的主頁面,直接用UI設計便可(拖動控件),而後人爲添加幾個槽函數:
void MainWindow::on_pushButton_clicked() { settingWindow(); } void MainWindow::on_pushButton_2_clicked() { readXML(); } void MainWindow::on_pushButton_3_clicked() { history(); } void MainWindow::on_pushButton_4_clicked() { this->close(); }
在作接下來的步驟前先把須要的資源文件添加進工程,我使用的是八大行星的貼圖,來做爲每一個功能的圖標。
接下來實現菜單欄、工具欄、狀態欄,用 QMainWindow 中的函數能夠建立三個欄目,同時爲菜單欄、工具欄添加動做,即要求的功能,產生隨機算式、複習錯題、保存錯題、查看歷史成績、退出應用,分別對應 generateAction 、 readXMLAction 、writeXMLAction、 historyAction 、 closeAction 幾個動做對應,最後用 connect 將每一個動做與其對應槽函數鏈接。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); setWindowTitle(tr("Arithmetic")); resize(500,500); QAction *generateAction=new QAction(QIcon(":/images/neptune"),tr("Generate"),this); generateAction->setStatusTip(tr("Generate new exercises randomly")); generateAction->setShortcut(tr("ctrl+g")); QAction *readXMLAction = new QAction(QIcon(":/images/jupiter.png"), tr("Review"), this); readXMLAction->setStatusTip(tr("Review the wrong exercises")); readXMLAction->setShortcut(tr("ctrl+r")); QAction *writeXMLAction = new QAction(QIcon(":/images/venus.png"), tr("Save"), this); writeXMLAction->setStatusTip(tr("Save the wrong exercises")); writeXMLAction->setShortcut(tr("ctrl+w")); QAction *historyAction = new QAction(QIcon(":/images/mars.png"), tr("History"), this); historyAction->setStatusTip(tr("Show history")); historyAction->setShortcut(tr("ctrl+h")); QAction *closeAction=new QAction(QIcon(":/images/mercury"),tr("Close"),this); closeAction->setStatusTip(tr("Close the application")); closeAction->setShortcut(tr("esc")); QMenu *menu = menuBar()->addMenu(tr("Function")); menu->addAction(generateAction); menu->addAction(readXMLAction); menu->addAction(writeXMLAction); menu->addAction(historyAction); menu->addAction(closeAction); QToolBar *toolBar = addToolBar(tr("Function")); toolBar->addAction(generateAction); toolBar->addAction(readXMLAction); toolBar->addAction(writeXMLAction); toolBar->addAction(historyAction); toolBar->addAction(closeAction); statusBar()->showMessage(tr("Ready")); connect(generateAction,&QAction::triggered,this,&MainWindow::settingWindow); connect(readXMLAction,&QAction::triggered,this,&MainWindow::readXML); connect(writeXMLAction,&QAction::triggered,this,&MainWindow::writeXML); connect(historyAction,&QAction::triggered,this,&MainWindow::history); connect(closeAction,&QAction::triggered,this,&MainWindow::close); }
以上內容效果以下:
下面實現每一個具體的功能。
settingWindow 函數實現的是爲設置隨機算式的格式,針對使用的core定製參數輸入界面,要求輸入的參數如使用手冊中所示,再也不重複,重點在與如何實現佈局。這次我使用的方式是,將每一個 QLabel 和 QLineEdit 放入一個 QHBoxLayout ,再將全部 QHBoxLayput 放入一個統一的 QVBoxLayout ,如此實現較工整的佈局。
void MainWindow::settingWindow() { wSet=new QWidget; vlayout=new QVBoxLayout; labelExerciseNum=new QLabel("Exercise Num"); editExerciseNum=new QLineEdit; editExerciseNum->setPlaceholderText("10"); hlayout[0]=new QHBoxLayout; hlayout[0]->addWidget(labelExerciseNum); hlayout[0]->addWidget(editExerciseNum); vlayout->addItem(hlayout[0]); labelMaxOperator=new QLabel("Max Operator Num"); editMaxOperator=new QLineEdit; editMaxOperator->setPlaceholderText("5"); hlayout[1]=new QHBoxLayout; hlayout[1]->addWidget(labelMaxOperator); hlayout[1]->addWidget(editMaxOperator); vlayout->addItem(hlayout[1]); labelMaxRange=new QLabel("Range of Numbers"); editMaxRange=new QLineEdit; editMaxRange->setPlaceholderText("1000"); hlayout[2]=new QHBoxLayout; hlayout[2]->addWidget(labelMaxRange); hlayout[2]->addWidget(editMaxRange); vlayout->addItem(hlayout[2]); labelPrecision=new QLabel("Precision of Decimals"); editPrecision=new QLineEdit; editPrecision->setPlaceholderText("2"); hlayout[3]=new QHBoxLayout; hlayout[3]->addWidget(labelPrecision); hlayout[3]->addWidget(editPrecision); vlayout->addItem(hlayout[3]); labelFraction=new QLabel("Has Fraction"); radioFraction=new QRadioButton("Fraction"); radioFraction->setChecked(false); radioFraction->setAutoExclusive(false); hlayout[4]=new QHBoxLayout; hlayout[4]->addWidget(labelFraction); hlayout[4]->addWidget(radioFraction); vlayout->addItem(hlayout[4]); labelDecimal=new QLabel("Has Decimal"); radioDecimal=new QRadioButton("Decimal"); radioDecimal->setChecked(false); radioDecimal->setAutoExclusive(false); hlayout[5]=new QHBoxLayout; hlayout[5]->addWidget(labelDecimal); hlayout[5]->addWidget(radioDecimal); vlayout->addItem(hlayout[5]); labelMuldiv=new QLabel("Has Multiply/Divide"); radioMuldiv=new QRadioButton("* /"); radioMuldiv->setChecked(true); radioMuldiv->setAutoExclusive(false); hlayout[6]=new QHBoxLayout; hlayout[6]->addWidget(labelMuldiv); hlayout[6]->addWidget(radioMuldiv); vlayout->addItem(hlayout[6]); labelPower=new QLabel("Has Power"); radioPower=new QRadioButton("^"); radioPower->setChecked(false); radioPower->setAutoExclusive(false); hlayout[7]=new QHBoxLayout; hlayout[7]->addWidget(labelPower); hlayout[7]->addWidget(radioPower); vlayout->addItem(hlayout[7]); buttonGenerate=new QPushButton("Random Generate"); vlayout->addWidget(buttonGenerate); wSet->setLayout(vlayout); wSet->show(); connect(buttonGenerate,&QPushButton::clicked,this,&MainWindow::randomGenerate); }
效果如圖:
randomGenerate 接收參數,作範圍檢查,如有效則調用core函數,將產生的算式與答案保存,並馬上調用 showExpression 函數展現在界面上:
void MainWindow::randomGenerate() { expression.clear(); answer.clear(); coreExp=new string(); coreAns=new string(); int exerciseNum=editExerciseNum->text().toInt(); if(exerciseNum==0) exerciseNum=10; else if(exerciseNum>20) { QMessageBox::warning(this,tr("Exercise Num"),tr("Please input exercise num <= 20")); return; } int maxOperator=editMaxOperator->text().toInt(); if(maxOperator==0) maxOperator=5; int maxRange=editMaxRange->text().toInt(); if(maxRange==0) maxRange=1000; else if(maxRange<20) { QMessageBox::warning(this,tr("Max Range"),tr("Please input max range >= 20")); return; } int precision=editPrecision->text().toInt(); if(precision==0) precision=2; int fraction=radioFraction->isChecked()?1:0; int decimal=radioDecimal->isChecked()?1:0; int muldiv=radioMuldiv->isChecked()?1:0; int power=radioPower->isChecked()?1:0; set_setting(maxOperator,maxRange,precision,fraction,decimal,muldiv,power); for(int i=0;i<exerciseNum;i++) { generate(coreExp,coreAns); expression.push_back(QString::fromStdString(*coreExp)); answer.push_back(QString::fromStdString(*coreAns)); } wSet->hide(); showExpression(); }
showExpression 函數與 settingWindow 中的佈局方式相似,每一行 hlayout[i] 包括算式文本框 labelExp[i] 、用戶輸入答案文本框 edit[i] 、正確答案文本框 labelAns[i] 、正確/錯誤標籤 labelConsq[i] ,將產生的全部算式及相關信息排成一列。同時在函數觸發時使用 QTimer 類進行定時,每當定時時間(20s)到達,觸發一次 forbidWrite 函數,依次禁止用戶輸入文本框的輸入。
void MainWindow::showExpression() { wDisp=new QWidget; this->setCentralWidget(wDisp); vlayout=new QVBoxLayout; vlayout->addWidget(labelHelp); for(int i=0;i<expression.size();i++) { hlayout[i]=new QHBoxLayout; labelExp[i]=new QLineEdit(expression[i]); labelExp[i]->setReadOnly(true); labelAns[i]=new QLineEdit; labelAns[i]->setReadOnly(true); labelAns[i]->setPlaceholderText("Click to show the answer"); labelConsq[i]=new QLabel; edit[i]=new QLineEdit; edit[i]->setPlaceholderText("Input your answer"); hlayout[i]->addWidget(labelExp[i]); hlayout[i]->addWidget(edit[i]); hlayout[i]->addWidget(labelAns[i]); hlayout[i]->addWidget(labelConsq[i]); vlayout->addItem(hlayout[i]); } buttonAnswer=new QPushButton(tr("Show Answers")); labelGrade=new QLabel(" Grade: "); QHBoxLayout *consq=new QHBoxLayout; consq->addWidget(buttonAnswer); consq->addWidget(labelGrade); vlayout->addItem(consq); wDisp->setLayout(vlayout); connect(buttonAnswer,&QPushButton::clicked,this,&MainWindow::showAnswer); myTimer=new QTimer(); myTimer->setInterval(20000); connect(myTimer,&QTimer::timeout,this,&MainWindow::forbidWrite); myTimer->start(); }
void MainWindow::forbidWrite() { if(nextForbid<expression.size()-1) { edit[nextForbid]->setReadOnly(true); nextForbid++; } else showAnswer(); }
效果如圖:
當全部題目的計時總時間用完,或者用戶點擊 Show Answers 按鈕觸發 showAnswer 函數,定時器中止工做並禁用全部用戶輸入文本框和 Show Answer 按鈕,併爲每一行的算式驗證用戶答案與正確答案,將正確答案顯示在正確答案文本框 labelAns[i] 裏,同時將正確與否顯示在 labelConsq[i] 裏。根據正確題目與題目總數的比例,爲用戶計算出這次成績,顯示在 labelGrade 裏。
void MainWindow::showAnswer() { int count=0; int i; int grade; buttonAnswer->setEnabled(false); myTimer->stop(); nextForbid=0; for(i=0;i<answer.size();i++) edit[i]->setReadOnly(true); for(i=0;i<answer.size();i++) { labelAns[i]->setText(answer[i]); if(QString::compare(edit[i]->text(),labelAns[i]->text())==0) { labelConsq[i]->setText("Correct"); count++; } else labelConsq[i]->setText("Wrong"); } grade=(double)count/answer.size()*100; labelGrade->setText(QStringLiteral(" Grade: ")+QString("%1").arg(grade)); updateHistory(count); }
同時,也要在歷史記錄中保存這次練習的結果,因此在 showAnswer 中須要更新歷史記錄,即以讀寫方式打開當前目錄下的 history.txt 文件,並將指針移動到文末添加這次練習的結果。
void MainWindow::updateHistory(int count) { QString path="history.txt"; QFile file(path); if(!file.open(QIODevice::ReadWrite | QIODevice::Text)) { QMessageBox::warning(this,tr("File"),tr("Cannot Update History.")); return; } QTextStream os(&file); os.seek(file.size()); os<< count << " / " << answer.size() << " Grade: " << int((double)count/answer.size()*100) << "\n"; file.close(); }
效果如圖:
若要查看歷史記錄,則要使用 history 函數,實現方式就是讀取上面提到的 history.txt 文件並將內容輸出到界面:
void MainWindow::history() { QString path="history.txt"; QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("File"), tr("No History")); return; } QTextStream is(&file); editHistory=new QTextEdit; editHistory->setText(is.readAll()); file.close(); editHistory->show(); }
效果如圖:
最後實現錯題保存功能與讀入功能。這裏考慮了MVP模式,即模型與視圖分離,且數據具備較好的可移植性,特意使用了 xml 格式來記錄和讀入錯題。Qt提供了完善的 xml 處理函數,即 QXmlStreamReader 和QXmlStreamWriter ,分別能夠實現對 xml 文件的讀出和寫入。用這樣一套api和相關文件操做能夠輕鬆實現 xml 文件的各類處理,如果從此軟件移植到網頁端或移動端,只需調整控制器代碼,便可將歷史數據復如今新的視圖上。
void MainWindow::writeXML() { if(expression.size()==0) { QMessageBox::warning(this,tr("File"),tr("No exercises.")); return; } QString path = QFileDialog::getSaveFileName(this,tr("Save File"),"/",tr("XML Files(*.xml)")); if(!path.isEmpty()) { QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("File"),tr("Cannot Save File.")); return ; } QXmlStreamWriter writer(&file); writer.setAutoFormatting(true); writer.writeStartDocument(); writer.writeStartElement("exercise"); for(int i=0;i<expression.size();i++) { if(QString::compare(labelConsq[i]->text(),QStringLiteral("Wrong"))==0) { writer.writeTextElement("expression",expression[i]); writer.writeTextElement("answer",answer[i]); } } writer.writeEndElement(); writer.writeEndDocument(); file.close(); } else { QMessageBox::warning(this, tr("Path"),tr("You did not save any file.")); return; } }
void MainWindow::readXML() { QString path = QFileDialog::getOpenFileName(this,tr("Open File"),"/",tr("XML Files(*.xml)")); if(!path.isEmpty()) { QFile file(path); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { QMessageBox::warning(this, tr("File"),tr("Cannot Open File.")); return ; } expression.clear(); answer.clear(); QXmlStreamReader reader; reader.setDevice(&file); while (!reader.atEnd()) { QXmlStreamReader::TokenType type = reader.readNext(); if (type == QXmlStreamReader::StartElement) { QString elementName=reader.name().toString(); if(QString::compare(elementName,QStringLiteral("expression"))==0) expression.push_back(reader.readElementText()); if(QString::compare(elementName,QStringLiteral("answer"))==0) answer.push_back(reader.readElementText()); } } if (reader.hasError()) { QMessageBox::warning(this, tr("XML"),reader.errorString()); } file.close(); if(expression.size()!=answer.size()) { QMessageBox::warning(this, tr("XML"),tr("Wrong XML Format.")); return; } showExpression(); } else { QMessageBox::warning(this, tr("Path"),tr("You did not select any file.")); return; }
以上就是所有的UI核心功能實現。
由於之前有一些Qt經驗,因此此次沒遇到什麼卡着過不去的bug,但總歸遇到了一些小問題:
我以爲走上工做崗位後對部分任務採起結對編程是必要的。
首先,剛剛進入崗位時,對相關技術還不夠熟悉,此時須要一個代碼方面的老師來指導具體的編寫技巧,由駕駛員指導領航員駕駛的技巧。同時,做爲領航員雖然代碼編寫經驗不足,可是能夠用本身在其餘方面的經驗和邏輯思惟能力,幫助駕駛員檢查代碼中的漏洞,提供多樣化的思惟方式。
其次,就算不是企業中的新手,當遇到邏輯十分複雜的項目,也很適合結對編程。兩個結對者首先討論出可行的設計方案,當駕駛員行駛時,領航員時刻監控着邏輯網的搭建,一旦出現問題馬上反饋給駕駛員,同時領航員在行進過程當中也時刻思考着邏輯上的簡化與更好的解決方案,時刻能對代碼進行優化。當邏輯最複雜的一段路行駛完後,兩個結對者能夠分道揚鑣,繼續進一步實現不一樣的功能模塊,因爲他們對基礎模塊的來歷都十分清晰,無需過多的交流,他們馬上都能馬上穩定地再次出發。
最後,結對也是個互相學習的過程,技術隨着時代一直在變,結對是一個很好地彼此學習交流的機會,可以促進良性競爭。
首先必須給鄧老師和助教們點贊,與其餘老師助教相比大家的工做量要大不少,鄧老師是我見過水羣最多的老師,可見各位老師助教都很負責。
要說建議的話,我但願從此的做業可以彈性更大一點,或者說要求更寬鬆一點。鄧老師但願以軟件企業的要求來給咱們更真切的體驗與更豐富的經驗,可是畢竟咱們大部分人的志向都不是軟件開發人員,咱們各有各的方向,這點是與其餘學校軟件工程班不一樣的。有時候做業的要求有點矯枉過正,班裏有不少大三的學生忙着申請暑研,你們也都有繁忙的其餘課程,做業的要求與ddl若是太死會給咱們很大壓力。若是是由於要熟悉企業的管理方式、得到更多的經驗,卻丟掉了課內其餘課程的許多內容,可能有點得不償失。因此,我但願做業的彈性可以大一些。
從此要在每次的我的做業、結對做業及課後做業中吸收經驗,改完每一個bug或是看完每套準則後,都要帶着思考這些經驗怎麼能用於個人團隊項目,或者說個人團隊項目在哪可能出現這樣的bug或者用到這樣的準則,每次積累一點經驗,逐步改善開發流程。同時,要將每次的我的做業的方向往團隊項目上靠,好比個人團隊項目是android開發,那我從此的讀書筆記方向就能夠是java設計模式,多借鑑別人的經驗比本身一點點摸索要快得多。