項目 | 內容 |
---|---|
本次做業所屬課程 | 北航2019軟件工程 |
本次做業要求 | 要求詳情 |
我在本課程的目標 | 提高工程化思想與能力 |
本次做業的幫助 | 初次體驗結對編程,瞭解開發流程 |
https://github.com/PaParaZz1/longest-word-chainc++
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 20 | 15 |
· Estimate | · 估計這個任務須要多少時間 | 20 | 15 |
Development | 開發 | 2050 | 2955 |
· Analysis | · 需求分析 (包括學習新技術) | 240 | 360 |
· Design Spec | · 生成設計文檔 | 25 | 20 |
· Design Review | · 設計複審 (和同事審覈設計文檔) | 15 | 15 |
··Coding Standard | · 代碼規範 (爲目前的開發制定合適的規範) | 10 | 10 |
· Design | · 具體設計 | 60 | 90 |
· Coding | · 具體編碼 | 1440 | 1800 |
· Code Review | · 代碼複審 | 20 | 180 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 240 | 480 |
Reporting | 報告 | 80 | 110 |
· Test Report | · 測試報告 | 60 | 90 |
· Size Measurement | · 計算工做量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 過後總結, 並提出過程改進計劃 | 10 | 10 |
合計 | 2150 | 3080 |
咱們在設計計算模塊時使用了自定義的對外接口,接口形式以下git
c++ se_errcode Calculate(const string& input_text, string& output_text, LongestWordChainType& longest_type, const char& head, const char& tail, bool enable_circle, WordChainError& handle_error);
對於該接口的說明以下:github
LongestWordChainType
進行描述,head和tail表示單詞鏈的首尾字母,enable_circle表示是否容許輸入中含有環,這樣因此的需求選項均可以被集中在一個接口中,代碼的複用性很是高,且很簡潔。char*
數組,增長了程序的魯棒性。關於計算邏輯的實現,咱們認爲這裏主要的兩部分是數據結構和算法,接下來咱們會對這兩方面分開進行說明。算法
數據結構方面:編程
WordMapElement
類去描述它,對於首字母和尾字母相同的邊,存儲在這一個元素之中,並按照單詞含有的字母數量降序進行排列存儲。而首字母,尾字母根據問題去建立結點,使用特殊的「邊」元素,構建出咱們所用的數據結構。unordered_map<char, unordered_map<char, WordMapElement> >
這樣的c++容器結構去存儲,避免了定長數組帶來的維護性差,可擴展性差的問題,同時又具備直接根據key-value來訪問元素的方便性。算法方面:數組
SearchInterface
,定義了公共的接口方法Search
和LookUp
,任何搜索算法只要繼承接口類重寫這兩個方法便可,其內部的算法優化邏輯將封裝在方法內部,與外部的邏輯無關,這樣能夠很方便的添加優化算法,同時保證架構設計的完整性。另外一獨特之處:數據結構
Design by contract (DbC), also known as contract programming, programming by contract and design-by-contract programming, is an approach for designing software. It prescribes that software designers should define formal, precise and verifiable interface specifications for software components, which extend the ordinary definition of abstract data types with preconditions, postconditions and invariants. These specifications are referred to as "contracts", in accordance with a conceptual metaphor with the conditions and obligations of business contracts.
引用自wikipedia架構
TEST_METHOD(Test_ExtractWord) { //TEST ExtractWord WordChainError error1; string input_text1 = "_this is a!@#$test of0extract!word...... "; vector<string> input_buffer1; vector<string> result1 = { "this","is","a","test","of","extract","word" }; ExtractWord(input_text1, input_buffer1,error1); Assert::AreEqual(result1.size(), input_buffer1.size()); for (int i = 0; i < result1.size(); i++) { Assert::AreEqual(result1[i], input_buffer1[i]); } WordChainError error2; string input_text2 = "_this___is___another======test of extract[][][]word. "; vector<string> input_buffer2; vector<string> result2 = { "this","is","another","test","of","extract","word" }; ExtractWord(input_text2, input_buffer2,error2); Assert::AreEqual(result2.size(), input_buffer2.size()); for (int i = 0; i < result2.size(); i++) { Assert::AreEqual(result2[i], input_buffer2[i]); } WordChainError error3; string input_text3 = "_[][][]...."; vector<string> input_buffer3; vector<string> result3 = { }; ExtractWord(input_text3, input_buffer3,error3); Assert::AreEqual(result3.size(), input_buffer3.size()); for (int i = 0; i < result3.size(); i++) { Assert::AreEqual(result3[i], input_buffer3[i]); } }
TEST_METHOD(Test_Class_Word) { //TEST Class_Word Word test1 = Word("a"); Assert::AreEqual(test1.GetHead(), 'a'); Assert::AreEqual(test1.GetTail(), 'a'); Assert::AreEqual(test1.GetWord(), string("a")); Assert::AreEqual(test1.GetKey(), string("aa")); Word test2 = Word("phycho"); Assert::AreEqual(test2.GetHead(), 'p'); Assert::AreEqual(test2.GetTail(), 'o'); Assert::AreEqual(test2.GetWord(), string("phycho")); Assert::AreEqual(test2.GetKey(), string("po")); }
TEST_METHOD(Test_Class_DistanceElement_Method) { //TEST Class_DistanceElement_Method: SetDistance/GetDistance/SetWordChain/CopyWordBuffer/ToString LongestWordChainType type1 = letter_longest; DistanceElement testElement1 = DistanceElement(type1); Assert::AreEqual(testElement1.GetDistance(), 0); vector<string> input1 = { "a","test","of","it" }; vector<string> output1; testElement1.SetWordChain(input1); testElement1.CopyWordBuffer(output1); for (int i = 0; i < input1.size(); i++) { Assert::AreEqual(output1[i], input1[i]); } testElement1.SetDistance(6); Assert::AreEqual(testElement1.GetDistance(), 6); Assert::AreEqual(testElement1.ToString(), string("a-test-of-it")); LongestWordChainType type2 = word_longest; DistanceElement testElement2 = DistanceElement(type2); Assert::AreEqual(testElement2.GetDistance(), 0); vector<string> input2 = { "another","test","of","it" }; vector<string> output2; testElement2.SetWordChain(input2); testElement2.CopyWordBuffer(output2); for (int i = 0; i < input2.size(); i++) { Assert::AreEqual(output2[i], input2[i]); } testElement2.SetDistance(2); Assert::AreEqual(testElement2.GetDistance(), 2); Assert::AreEqual(testElement2.ToString(), string("another-test-of-it")); }
TEST_METHOD(Test_Calculate) { //Test Calculate: include CalculateLongestChain/ChainSearch WordChainError error; string input_text ="Algebra))Apple 123Zoo Elephant Under Fox_Dog-Moon Leaf`;;Trick Pseudopseudohypoparathyroidism"; string output_text1 = ""; LongestWordChainType type1 = word_longest; Calculate(input_text, output_text1, type1, NO_ASSIGN_HEAD, NO_ASSIGN_TAIL, false,error); string result1 = "algebra\napple\nelephant\ntrick\n"; Assert::AreEqual(result1, output_text1); string output_text2 = ""; LongestWordChainType type2 = letter_longest; Calculate(input_text, output_text2, type2, NO_ASSIGN_HEAD, NO_ASSIGN_TAIL, false,error); string result2 = "pseudopseudohypoparathyroidism\nmoon\n"; Assert::AreEqual(result2, output_text2); string input_text_ring = "Algebra))Apple aaaaa 123Zoo Elephant Under Fox_Dog-Moon Leaf`;;Trick Pseudopseudohypoparathyroidism"; string output_text3 = ""; string result3 = "algebra\naaaaa\napple\nelephant\ntrick\n"; LongestWordChainType type3 = word_longest; Calculate(input_text_ring, output_text3, type3, NO_ASSIGN_HEAD, NO_ASSIGN_TAIL, true, error); Assert::AreEqual(result3, output_text3); }
其中重複單詞異常會在命令行輸出,可是不會影響程序的進行,計算模式異常和命令行參數異常均爲在對命令行進行解析時發生的異常,咱們並無單獨爲其寫一個方法,因此難以在單元測試中驗證,僅會在命令行輸出錯誤信息。app
其他四種異常均在單元測試中進行了驗證。框架
輸入文件異常,對應找不到輸入文件的場景等:
std::ifstream in("notexist.txt"); std::stringstream buffer1; WordChainError error3; if (!in.is_open()) { char buffer1[MAX_BUFFER_SIZE]; sprintf(buffer1, "Error Type: can't open input file\n"); string error_content(buffer1); int error_code = SE_ERROR_OPENING_INPUT_FILE; error3.AppendInfo(error_code, error_content); } string errortext3 = error3.ToString(); Assert::AreEqual(errortext3, string("Error Type: can't open input file\nError Content: Error Type: can't open input file\n"));
std::stringstream buffer2; std::ofstream out("close.txt"); WordChainError error4; out.close(); if (!out.is_open()) { char buffer2[MAX_BUFFER_SIZE]; sprintf(buffer2, "Error Type: can't open output file\n"); string error_content(buffer2); int error_code = SE_ERROR_OPENING_OUTPUT_FILE; error4.AppendInfo(error_code, error_content); } string errortext4 = error4.ToString(); Assert::AreEqual(errortext4, string("Error Type: can't open output file\nError Content: Error Type: can't open output file\n"));
WordChainError error1; string input_text1 = "Algebra))Apple aaaaa 123Zoo Elephant Under Fox_Dog-Moon Leaf`;;Trick Pseudopseudohypoparathyroidism"; string output_text1 = ""; string errortext1; LongestWordChainType type1 = word_longest; Calculate(input_text1, output_text1, type1, NO_ASSIGN_HEAD, NO_ASSIGN_TAIL,false, error1); errortext1 = error1.ToString(); Assert::AreEqual(errortext1,string("Error Type: input has circle but not enable circle\nError Content: Error Type: input has circle but not enable circle\n"));
WordChainError error2; string input_text2 = "Algebra Zoo"; string output_text2 = ""; string errortext2; LongestWordChainType type2 = word_longest; Calculate(input_text2, output_text2, type2, 'i', NO_ASSIGN_TAIL, false, error2); errortext2 = error2.ToString(); Assert::AreEqual(errortext2, string("Error Type: no available word chain\nError Content: no available word chain for head(i) and tail(0)\n"));
界面模塊咱們使用了Qt的庫進行了設計。編碼上仍然是c++語言,ui設計上使用了Qt Creator進行設計。
以上的需求能夠大概代表咱們的用戶界面須要至少五個參數選擇的交互按鈕,兩個界面,其中一個負責寫入文本,一個負責顯示正確結果和錯誤信息。另外須要四個按鈕,分別對應導入文本,運行程序,導出結果和顯示使用說明。
明確了以上需求以後,咱們在Qt Creator中設計了大概的用戶界面(macOS下):
其中使用radiobutton選擇兩種計算方式,checkbox選擇是否容許單詞環,下拉框選擇是否有開頭和結尾字母的要求,這些設計都是爲了方便用戶的使用。
如下爲部分用戶界面的代碼:
//按鈕觸發事件(引入文件以及顯示幫助信息) void MainWindow::on_pushButton_import_clicked() { QString fileName=QFileDialog::getOpenFileName(this,tr("Choose File"),"",tr("text(*.txt)")); QFile file(fileName); if(!file.open(QFile::ReadOnly|QFile::Text)){ QString errMsg="error when import file"; ui->outputArea->setText(errMsg); return; } QTextStream in(&file); ui->inputArea->clear(); ui->inputArea->setText(in.readAll()); } void MainWindow::on_pushButton_help_clicked() { dialog = new Dialog(this); dialog->setModal(false); QString helpMsg="test help"; dialog->ui->textBrowser->setPlainText(helpMsg); dialog->show(); }
void MainWindow::on_pushButton_run_clicked() { int para=ui->radioButton_w->isChecked()?1:2; bool ring=ui->checkBox_loop->isChecked(); string content = ui->inputArea->toPlainText().toStdString(); char head, tail; if (ui->comboBox_h->currentIndex() == 0) { head = '\0'; } else { head = 'a' + ui->comboBox_h->currentIndex() - 1; } if (ui->comboBox_t->currentIndex() == 0) { tail = '\0'; } else { tail = 'a' + ui->comboBox_t->currentIndex() - 1; } if(content.size()==0){ QString errMsg="empty input!"; ui->outputArea->setPlainText(errMsg); } else{ //call corresponding function string output; LongestWordChainType type; se_errcode code; QString s = "fin"; WordChainError error; if (para == 1) { type = word_longest; code=Calculate(content, output,type,head,tail,ring,error); } else { type = letter_longest; code=Calculate(content, output, type, head, tail, ring,error); } if (code == SE_OK) { QString result = QString::fromStdString(output); ui->outputArea->setPlainText(result); } else { string result = error.ToString(); QString error= QString::fromStdString(result); ui->outputArea->setPlainText(error); } } //cout<<"onclick_run"<<endl; }
功能運行結果以下:
結對編程的優勢在於首先在代碼質量上,取決於水平較高的那一位,而且兩我的一塊兒工做,能夠提升各自的效率和工做能力,實力較差的那一方還能夠從另外一方學習到新的知識和技能,明確編碼規範,以提高本身的工程化思想。分工合做同時也可以讓每一個人專一於本身的職責。而且在編碼過程當中,兩我的會產生更多的思惟碰撞和交流,有利於項目的推動和維護。結對編程的缺點在於因爲代碼是兩我的各自寫的,因此在風格和方法上會有差別,每一個人的理解上也會存在誤差,須要及時的交流。另外假若其中一方不能保證足夠的合做時間,無疑會拖慢項目的進度。
結對過程當中我同伴的優勢是:一、代碼十分規範,風格良好,使我學到了不少。二、在項目開始的時候很快的明確了數據類型和接口定義,爲項目推動打好了基礎。三、編寫代碼效率很高,而且還能及時解答我不瞭解或不熟悉的代碼問題。缺點因爲同伴日常很忙,是在一塊兒編程的時間比較少。個人優勢在於:一、因爲新建工程,單元測試,gui方面都是我主要負責,碰到了不少配環境,引入文件的問題,遇到困難可以快速的查詢解決方法而且解決。二、按時完成要求的開發任務和測試任務。三、努力學習了命名規範和代碼風格,爭取統一。缺點在於編碼能力不強,對c++語言仍是不熟悉,須要很認真的閱讀代碼和查詢資料才能看明白每一個函數的用處。