項目 | 內容 |
---|---|
所屬課程 | 2019春季計算機學院軟件工程(任健)(北京航空航天大學) |
做業要求 | 這裏 |
課程目標 | 提高本身的編程水平,拿一個合適的分數 |
這個做業在哪一個具體方面幫助我實現目標 | 學習結對編程 |
binggge/longestWordChain 共有兩個分支,master分支爲命令行程序和UI界面,是最終提交分支,UI分支爲單獨的用戶界面程序。c++
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 15 | 30 |
· Estimate | · 估計這個任務須要多少時間 | 15 | 30 |
Development | 開發 | ~1400 | 1475 |
· Analysis | · 需求分析 (包括學習新技術) | 60 | 120 |
· Design Spec | · 生成設計文檔 | 120 | 90 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 60 | 30 |
· Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 30 | 15 |
· Design | · 具體設計 | 50 | 30 |
· Coding | · 具體編碼 | ~360 | 600 |
· Code Review | · 代碼複審 | 120 | 90 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 480 | 500 |
Reporting | 報告 | 255 | 195 |
· Test Report | · 測試報告 | 120 | 120 |
· Size Measurement | · 計算工做量 | 15 | 15 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 120 | 60 |
合計 | ~1800 | 1700 |
在編程過程當中,對於每一個類隱藏一些其特有的屬性,防止它們被其餘類或方法修改。使用信息隱藏不只有助於保證一些信息的安全性,也有助於維護程序的健壯性。參考git
在咱們的程序中,咱們對外界不須要關心的數據都進行了隱藏,如圖的邊,節點等。其餘類,如讀入,GUI等不能對這些數據隨意訪問操做,只能經過規定的接口進行,這樣咱們的數據修改都是可控的。程序員
外界應該只關心模塊的接口而不是其具體實現。這樣在修改模塊內部邏輯提高運行效率時能夠不須要改變接口。在咱們的計算程序中,對外界開放的接口只有題目中要求的兩個,其餘方法都是私有的。用戶只須要關心傳入傳出數據的合法性,對於咱們計算模塊的具體實現不須要關心。github
在計算機運算和系統設計中,一個鬆耦合的系統中的每個組件對其餘獨立組件的定義所知甚少或一無所知。子範圍包括類、接口、數據和服務之間的耦合。鬆耦合是緊耦合的對立面。
參考 模塊之間聯繫越密切,表示他們之間的耦合度越高。耦合度高的程序難以維護,又容易有更多的隱藏bug。所以鬆耦合時十分必要的。算法
咱們在完成代碼編寫後,與其餘組同窗互換了GUI和DLL,另外一個組是週二的白世豪(16061167),宋卓洋(16061170)組。因爲生成dll的教程相似,並無出現什麼問題,對於無異常狀況能夠正常運行,以下圖所示:編程
其中Core.dll是咱們生成的dll,DLL1.dll是另一個組的dll,通過測試,加載兩個dll時GUI的行爲一致。 安全
整個程序的邏輯大體以下圖所示:微信
由於GUI程序是不會涉及到普通的參數正確性的(只有關於指定頭尾的字母是否正確),因此 出於解耦合的須要,咱們並無將處理參數的程序集成在Core類之中,而是獨立出來,在main函數中判斷參數的正確性。Core類中確保接受到的是正確的單詞組char *words[]
和其餘合適的參數。咱們暴露在外的只有兩個接口。函數
int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop); int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
其餘的部分以private
成員的形式,供內部使用。工具
而後每一個接口中對單詞的處理也須要分紅兩種。
\\沒有-r選項,檢測有環時直接報錯,無環時以寬度優先搜索的形式(不會陷入死循環)找到最長單詞鏈; \\有-r選項,不須要檢測是否有環,直接以深度優先搜索的形式搜索最長單詞鏈; \\僞代碼以下 if(!enable_loop) { roundtest();//有環時拋出錯誤 createMap();//創建bfs的地圖 BFS();//寬度優先搜索 getBFSResult();//處於節省時間和空間的考慮,上一步寬度優先搜索並無保存路徑,而是得出了最長的長度,而後根據長度倒推出路徑。 } else { createMap();//創建dfs的地圖 DFS();//深度優先搜素 getDFSResult();//推導最終路徑,緣由同上 }
基礎的函數大體就是這些,兩種類型chain_word
和chain_char
的各有一套。而後咱們還引入了get_tails()
就是在掃描邊的時候,排除不可能的首尾選項。大體原理就是當不成環的時候,出度爲0的點纔多是尾字母,不然不多是最長鏈。這樣咱們能夠剪枝,減小搜索時的複雜度。
我使用了自動化工具生成類圖與調用關係,參考工具Github,生成的調用關係以下: 考慮到自動生成的類圖可能有些難懂,我又手畫了一個簡單的類圖供參考。
總體上說,咱們採起了BFS解決沒有-r的狀況,使用DFS解決有-r的狀況。
首先是沒有-r的部分。因爲算法在設計之初就已經考慮到了一些優化,咱們重複執行同一條指令50次,取得更好的採樣效果。如圖所示,整個算法的耗時最長是在建圖與刪重邊。程序在輸入8000個單詞時運算時間在1s之內。所以沒有作進一步改進,這裏介紹一下完成的優化。 咱們主要完成了如下幾個優化:
在含有-r的部分,考慮到這是一個NP問題,並且要求的是準確解,不是近似解,咱們採用回溯法進行深度搜索。程序輸入90個單詞,摘自Wikipedia的一個隨機網頁,運行時間3分04秒。
能夠發現,程序在DFS上耗費了大量的時間。咱們對於這種模式依然作了一些優化,咱們同BFS同樣減小了可能的起點,由於起點的出度>=入度,終點的入度>=出度,也有必定效果。
此外,咱們還在總體上對程序進行了優化。考慮處處理器支持AVX指令集,咱們在編譯選項中選擇了支持生成AVX2指令的編譯選項,取得了不錯的效果,對於一組較複雜的測試用例,運行時間從2分58秒減小到2分03秒,提高接近1/3。
契約式設計如同它的名字同樣,講究一種契約精神,提供一個接口,只接受合規的輸入,保證輸出合規。對於錯誤輸入,能夠不進行容錯處理,而是經過拋出異常等形式。
根據維基百科的介紹,契約式設計一般包含:
- 可接受和不可接受的值或類型,以及它們的含義
- 返回的值或類型,以及它們的含義
- 可能出現的錯誤以及異常狀況的值和類型,以及它們的含義
- 反作用
- 先驗條件
- 後驗條件
- 不變條件
- (不太常見)性能上的保證,如所用的時間和空間
它的優勢十分明顯,首先它下降了代碼編寫的複雜程度,由於程序員不須要對錯誤輸入進行處理容錯。第二它簡化了測試,由於其明確了測試的範圍與內容。第三它使整個程序更健壯,在保證每一個模塊都遵照其本身的契約與整理邏輯正確時,能夠論證總體的正確性。
它也有一些缺點。首先,全部程序員寫的代碼的行爲必須遵照契約,不然契約就是無效的,這也是契約式設計的重要前提。第二,每段代碼都必須通過論證,確保它遵照契約,工做量較大。
在咱們的結對做業中,咱們首先明確了每一個人主要負責的部分,對於有交集的函數等明確了行爲,至關於進行了契約式設計。同時咱們明確了採起拋出異常到上層的行爲處理異常而不是進行容錯。我認爲明確這種總體上的規範的行爲就是一種契約式設計。
單元測試總體覆蓋率91%。未覆蓋到的部分主要是拋出異常的if語句。
計算模塊暴露的接口共有兩個,分別爲
咱們對其進行了單元測試。測試的流程是測試程序加載一組測試數據,放入char * words[]中,與一些參數一塊兒傳入函數,獲得返回的result後加載標答並逐個比較,以下面的代碼片斷所示:
TEST_METHOD(TestMethod1) { Core * core = new Core(); // do sth to update words core->gen_chain_word(words1, len, result1, 0, 0, false); // get true value into realAnswer Assert::AreEqual(51, length_of_result1); for (int i = 0; i < length_of_result1; i++) { Assert::AreEqual(strcmp(result1[i], realAnswer[i]), 0); } }
對於正常數據,咱們使用隨機生成+對拍的方式檢驗。 對於異常數據,咱們構造瞭如下幾個特殊狀況:
其中對於有環的狀況,咱們還分了如下幾種狀況:
如下爲單元測試展現,因爲太長默認收起。
注意,請依次運行每一個單元測試,不要一塊兒運行,防止出錯。
TEST_METHOD(TestMethod1) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words1.txt"); core->gen_chain_word(words1, len, result1, 0, 0, false); int len2 = readFile1("../WordChainUnitTesr/solution1.txt"); Assert::AreEqual(51, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words1.txt, solution1.txt能夠在Github上找到。
TEST_METHOD(TestMethod2) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words1_1.txt"); core->gen_chain_char(words1, len, result1, 0, 0, false); int len2 = readFile1("../WordChainUnitTesr/solution1_1.txt"); Assert::AreEqual(51, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words1_1.txt, solution1_1.txt能夠在Github上找到。
TEST_METHOD(TestMethod3) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words2.txt"); core->gen_chain_word(words1, len, result1, 0, 0, true); int len2 = readFile1("../WordChainUnitTesr/solution2.txt"); Assert::AreEqual(4, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words2.txt, solution2.txt能夠在Github上找到。
TEST_METHOD(TestMethod4) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words2_1.txt"); core->gen_chain_char(words1, len, result1, 0, 0, true); int len2 = readFile1("../WordChainUnitTesr/solution2_1.txt"); Assert::AreEqual(3, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words2_1.txt, solution2_1.txt能夠在Github上找到。
TEST_METHOD(TestMethod5) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words3.txt"); core->gen_chain_word(words1, len, result1, 'e', 0, false); int len2 = readFile1("../WordChainUnitTesr/solution3.txt"); Assert::AreEqual(4, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words3.txt, solution3.txt能夠在Github上找到。
TEST_METHOD(TestMethod6) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words4.txt"); core->gen_chain_word(words1, len, result1, 0, 'e', false); int len2 = readFile1("../WordChainUnitTesr/solution4.txt"); Assert::AreEqual(4, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words4.txt, solution4.txt能夠在Github上找到。
TEST_METHOD(TestMethod7) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words5.txt"); core->gen_chain_word(words1, len, result1, 'c', 'e', false); int len2 = readFile1("../WordChainUnitTesr/solution5.txt"); Assert::AreEqual(2, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words5.txt, solution5.txt能夠在Github上找到。
TEST_METHOD(TestMethod8) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words6.txt"); core->gen_chain_char(words1, len, result1, 'e', 'e', true); int len2 = readFile1("../WordChainUnitTesr/solution6.txt"); Assert::AreEqual(3, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } }其中 words6.txt, solution6.txt能夠在Github上找到。
TEST_METHOD(TestMethod9) { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words7.txt"); core->gen_chain_word(words1, len, result1, 0, 0, false); int len2 = readFile1("../WordChainUnitTesr/solution7.txt"); Assert::AreEqual(13, len2); for (int i = 0; i < len2; i++) { Assert::AreEqual(strcmp(result1[i], words1[i]), 0); } delete core; }其中 words7.txt, solution7.txt能夠在Github上找到。
TEST_METHOD(TestMethod10) { try { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words8.txt"); core->gen_chain_word(words1, len, result1, 0, 0, false); //RAISE EXCEPTION "Find a loop but no -r" } catch (const char* msg) { Assert::AreEqual(strcmp(msg, "Find a loop but no -r"), 0); } }其中 words8.txt能夠在Github上找到。
TEST_METHOD(TestMethod11) { try { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words9.txt"); core->gen_chain_word(words1, len, result1, 0, 0, false); //RAISE EXCEPTION "Find a loop but no -r" } catch (const char* msg) { Assert::AreEqual(strcmp(msg, "Find a loop but no -r"), 0); } }其中 words9.txt能夠在Github上找到。
TEST_METHOD(TestMethod12) { try { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words10.txt"); core->gen_chain_word(words1, len, result1, 0, 0, false); //RAISE EXCEPTION "Find a loop but no -r" } catch (const char* msg) { Assert::AreEqual(strcmp(msg, "No chains found"), 0); } }其中 words10.txt能夠在Github上找到。
TEST_METHOD(TestMethod13) { try { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words11.txt"); core->gen_chain_word(words1, len, result1, 'e', 0, false); //RAISE EXCEPTION "Find a loop but no -r" } catch (const char* msg) { Assert::AreEqual(strcmp(msg, "No chains found"), 0); } }其中 words11.txt能夠在Github上找到。
TEST_METHOD(TestMethod14) { try { Core * core = new Core(); int len = readFile1("../WordChainUnitTesr/words12.txt"); core->gen_chain_word(words1, len, result1, 0,'e', false); //RAISE EXCEPTION "Find a loop but no -r" } catch (const char* msg) { Assert::AreEqual(strcmp(msg, "No chains found"), 0); } }其中 words12.txt能夠在Github上找到。
TEST_METHOD(TestMethod15) { try { Core * core = new Core(); words1[0] = ""; core->gen_chain_word(words1, 0, result1, 0, 'e', false); //RAISE EXCEPTION "Find a loop but no -r" } catch (const char* msg) { Assert::AreEqual(strcmp(msg, "Input file is empty"), 0); } }其中 words0.txt是空白文件。
Wordlist.exe -w in.txt -c in.txt "duplicated read file" Wordlist.exe -w -r "missing arguments" Wordlist.exe -w aaa.xyz(does not exist) "file not exist" Wordlist.exe -w in.txt -h a -h b "duplicated -h" Wordlist.exe -w in.txt -h ab "wrong -h" Wordlist.exe -w in.txt -t a -t b "duplicated -t" Wordlist.exe -w in.txt -t ab "wrong -t" Wordlist.exe -w in.txt -r -r "duplicated -r" Wordlist.exe -r "no input file"; Wordlist.exe -x "undefined error" Wordlist.exe -w in.doc "wrong format, not *.txt"左邊爲指令,右邊爲報錯信息
計算模塊共計有三種異常,異常分別是:
測試數據見上一節
計算模塊經過拋出異常的方式處理異常,向上一級拋出一個字符串,內容是具體的異常信息。
咱們使用QT繪製界面,經過加載DLL調用接口。在網上查詢了教程後,咱們發現設計界面模塊不是很難,主要流程以下:
畫出界面上按鈕,文本框等的位置
針對按鈕編寫不一樣的運行邏輯
針對報錯彈出報錯窗口
在實現過程當中,咱們參考了這篇教程。
因爲時間等緣由,咱們並無對界面進行過多的美化,以體現功能爲主。界面主要代碼以下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include
#include
#include
#include
#include
#include
typedef int(*gen_chain_word)(char * words[], int len, char * result[], char head, char tail, bool enable_loop); typedef int(*gen_chain_char)(char * words[], int len, char * result[], char head, char tail, bool enable_loop);
MainWindowMainWindow(QWidget *parent) : QMainWindow(parent), ui(new UiMainWindow) { ui->setupUi(this); drawUI(this); connect(this->pushButton,SIGNAL(clicked()),this,SLOT(fileOpen())); connect(this->pushButton_2,SIGNAL(clicked()),this,SLOT(generate())); connect(this->pushButton_3,SIGNAL(clicked()),this,SLOT(fileSave()));
} void MainWindowfileOpen(){ QString filePath= QFileDialoggetOpenFileName(this,tr("file"),"",tr("TXT(.txt)")); if (filePath.isEmpty()){ return; }else{ QFile fin(filePath); if (fin.open(QIODeviceReadOnly | QIODeviceText)){ while (!fin.atEnd()){ QByteArray line = fin.readLine(); QString strin(line); this->textEdit->insertPlainText(strin); } } fin.close(); } } void MainWindow::generate(){ bool allFlag=true; QLibrary lib("dll1.dll"); gen_chain_word get_chain_word=(gen_chain_word)lib.resolve("gen_chain_word"); gen_chain_word get_chain_char=(gen_chain_char)lib.resolve("gen_chain_char"); bool A_w,A_c,A_r,A_h,A_t; A_w=this->checkBox->isChecked()?true:false; A_c=this->checkBox_2->isChecked()?true:false; A_r=this->checkBox_3->isChecked()?true:false; A_h=this->checkBox_4->isChecked()?true:false; A_t=this->checkBox_5->isChecked()?true:false; QString textin=this->textEdit->toPlainText(); qDebug() << textin; char ch; QByteArray ba = textin.toLatin1(); ch=ba.data(); char ** words=new char*[10000]; char ** result=new char*[10000]; int len=0; int chLen=strlen(ch); int currPos=0; while (currPos<chLen){ if (isalpha(ch[currPos]) != 0) { char * wordBuff = new char[1000]; int wordBuffPos = 0; //printf("%c",currentChar); while (isalpha(ch[currPos]) != 0) { wordBuff[wordBuffPos] = tolower(ch[currPos]); wordBuffPos++; currPos++; } wordBuff[wordBuffPos] = '\0'; words[len] = wordBuff; len++; } else { currPos++; } } qDebug()<<ch; qDebug()<<len; qDebug()<<words[len-1]; char head=0,tail=0; if (A_t){ QString tmp=this->plainTextEdit_2->toPlainText(); char* tt; QByteArray ba = tmp.toLatin1(); tt=ba.data(); if (strlen(tt)!=1){ allFlag=false; QMessageBoxcritical(0 , "ERROR" , "More than one char or no char got in -h or -t", QMessageBoxOk | QMessageBoxDefault ,0 , 0 ); }else } if (A_h){ QString tmp=this->plainTextEdit->toPlainText(); char* tt; QByteArray ba = tmp.toLatin1(); tt=ba.data(); if (strlen(tt)!=1){ allFlag=false; QMessageBoxcritical(0 , "ERROR" , "More than one char or no char got in -h or -t", QMessageBoxOk | QMessageBoxDefault ,0 , 0 );
} void MainWindowfileSave(){ QString filePath= QFileDialoggetSaveFileName(this,tr("file"),"",tr("TXT(*.txt)")); if (filePath.isEmpty()){ return; }else{ QFile fout(filePath); if (fout.open(QIODeviceWriteOnly | QIODeviceText)){ QString textin=this->textEdit_2->toPlainText(); QTextStream out(&fout); out << textin << endl; } fout.close(); } } MainWindow::~MainWindow() { delete ui; }
}else{
head=tt[0];
}
}
if (A_w && A_c){
allFlag=false;
QMessageBox::critical(0 , "ERROR" , "Cannot choose both -w and -c", QMessageBox::Ok | QMessageBox::Default ,0 , 0 );
}
if ((!A_w)&&(!A_c)){
allFlag=false;
QMessageBox::critical(0 , "ERROR" , "Must choose -w or -c", QMessageBox::Ok | QMessageBox::Default ,0 , 0 );
}
qDebug()<<"!!!!!!";
qDebug()<<(int)tail;
qDebug()<<(int)head;
if (allFlag){
try {
if (A_c) get_chain_char(words,len,result,head,tail,A_r);
if (A_w) get_chain_word(words,len,result,head,tail,A_r);
} catch (const char* msg) {
QMessageBox::critical(0 , "ERROR" , msg, QMessageBox::Ok | QMessageBox::Default ,0 , 0 );
}catch(...){
QMessageBox::critical(0 , "ERROR" , "ERROR", QMessageBox::Ok | QMessageBox::Default ,0 , 0 );
}
qDebug()<<"!!!!!!";
int pp=0;
while (result[pp]!=NULL){
qDebug(result[pp]);
this->textEdit_2->insertPlainText(result[pp]);
this->textEdit_2->insertPlainText("\n");
pp++;
}
}
界面模塊能夠在GitHub上的UI分支裏看到
界面模塊經過加載DLL的方式與計算模塊對接,關鍵代碼爲:
typedef int(*gen_chain_word)(char * words[], int len, char * result[], char head, char tail, bool enable_loop); typedef int(*gen_chain_char)(char * words[], int len, char * result[], char head, char tail, bool enable_loop); QLibrary lib("Core.dll"); gen_chain_word get_chain_word=(gen_chain_word)lib.resolve("gen_chain_word"); gen_chain_word get_chain_char=(gen_chain_char)lib.resolve("gen_chain_char");
經過上面的代碼即實現了功能對接。
第1,2行是聲明要調用的函數的指針,類型
第三行是加載要使用的dll
第4,5行是定義dll的接口,在dll中找到接口,進行調用準備
GUI中還對部分異常輸入作了處理,代碼與命令行的相似。對於文件讀入輸出部分咱們使用QT自帶的庫進行讀寫。
結對過程總體來講十分順利,交流順暢。我和隊友在本次做業以前並不認識,是經過在羣裏發佈結對信息結對的。在結對過程當中,咱們很快的就進入了工做的狀態,效率要比預想中高,而在完成做業的過程當中,對於大多數問題都很快的達成了共識,思路上也比較接近。
在實際進行過結對編程後,我以爲結對編程的優缺點都十分明顯,主要優勢有如下幾點:
兩人合做,思路更廣。兩我的能夠隨時地交流本身的想法,對於項目初期規劃問題的解決方向十分有幫助。
更容易避免手誤致使的BUG。在編程過程當中不可避免地出現打錯字母,或者利用自動補全時補全了錯誤的內容。結對編程時另外一我的就能夠及時發現這個問題,避免這類bug。
相互學習,共同提升。兩我的能夠從對方身上學到本身不足的地方,共同提升知識水平。
結對編程也有一些缺點,如:
對時間要求高。我和個人隊友來自不一樣的系,所以找到一個合適的都有空的時間十分困難。
效率較多人分開編程低。儘管結對編程能夠有效地下降代碼中的BUG,減小DEBUG時間,可是我以爲分開編程效率仍是更高一些。
本身和隊友的優缺點
優勢 | 缺點 | |
---|---|---|
本身 | 比較肝,能一口氣寫好久 能在學習後掌握新知識 能積極溝通,解決問題 |
知識水平比較低,沒有找到一個完美解決100單詞的算法 對C++不熟悉,開始的效率較低 |
隊友 | 實力很強,解決問題的邏輯清晰 善於溝通,能互相理解 負責任 |
稍微有點趕ddl,不過這是我的習慣不一樣,其實在ddl以前作完都是合理的 |