1. GitHub地址
https://github.com/swearitagain/wordlist
2. 項目預估開發時間&實際開發時間
| PSP2.1 | Personal Software Process Stages | 預估耗時(分鍾) | 實際耗時(分鍾) |
| Planning | 計划 | 10 | 20 |
| · Estimate | · 估計這個任務需要多少時間 | 10 | 20 |
| Development | 開發 | 870 | 1470 |
| · Analysis | · 需求分析 (包括學習新技術) | 60 | 120 |
| · Design Spec | · 生成設計文檔 | 40 | 40 |
| · Design Review | · 設計復審 (和同事審核設計文檔) | 30 | 30 |
| · Coding Standard | · 代碼規范 (為目前的開發制定合適的規范) | 20 | 20 |
| · Design | · 具體設計 | 60 | 100 |
| · Coding | · 具體編碼 | 400 | 1000 |
| · Code Review | · 代碼復審 | 60 | 40 |
| · Test | · 測試(自我測試,修改代碼,提交修改) | 120 | 120 |
| Reporting | 報告 | 70 | 120 |
| · Test Report | · 測試報告 | 20 | 30 |
| · Size Measurement | · 計算工作量 | 20 | 30 |
| · Postmortem & Process Improvement Plan | · 事后總結, 並提出過程改進計划 | 30 | 60 |
| 合計 | 950 | 1610 |
3. 接口設計
Information Hiding,Interface Design, Loose Coupling
Information Hiding
首先參考wikipedia定義:
information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed
就是把數據封裝起來,防止變化的部分對於原有數據的破壞,在結對編程中就是面向對象的實現,比如把輸入輸出封裝為input_output類,將讀入的數據暴露一個input函數,對外返回的是vector<string>。
Interface Desgin
接口按照https://edu.cnblogs.com/campus/buaa/BUAA_SE_2019_LJ/homework/2638 設計
static int gen_chain(char* words[], int len, char* result[]);
static int gen_chain_word(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
static int gen_chain_char(char* words[], int len, char* result[], char head, char tail, bool enable_loop);
Loose Coupling
松耦合是指在編程的時候讓一個部分盡可能少地依賴其他部分的組件,這樣就算因為需求更改而重寫以前的函數,也能避免對其他沒有變化的部分造成影響。
松耦合在本次結對編程項目中主要體現在對於函數以及.cpp的封裝上,保證每個模塊的功能獨立性,比如input_output.cpp只是對於輸入輸出的處理,calculate只是對於計算的處理。
4. 計算模塊接口部分的性能改進
首先定義了一個接口類作為基類,暴露出調用者需要的方法,其中get_result()函數返回計算模塊計算的結果:
class calculateInterface
{
public:
virtual ~calculateInterface();
virtual vector<string> *get_result()=0;
};
其次設計了calculate子類來完成最基本的計算功能:
這個類實現了接口類中定義的get_result方法,並新增了一個構造方法,以及一些私有的成員和方法。構造方法傳入單詞文本,以及-c參數。
私有方法中chain_find_next方法作用為找到當前單詞結點的所有能夠成鏈的下一個單詞,而check_current_chain判斷當前鏈是否是找到的最大鏈。
class calculate :
public calculateInterface
{
public:
calculate(vector<string> words, bool more_letter);
~calculate();
vector<string> *get_result() override;
protected:
vector<word_node> word_map[ALPHA_COUNT];
bool has_circle = false;
bool more_letter;
int longest_letter_count = 0;
int current_letter_count = 0;
vector<string> longest_word_chain;
vector<string> current_word_chain;
virtual bool chain_find_next(word_node prev_node);
virtual void check_current_chain();
};
再然后設計了specified_calculate類,該類繼承calculate類,支持了指定鏈首尾字母的功能。
該類重寫了calculate類的get_result方法和check_current_chain方法,保留使用了父類的chain_find_next方法。
class specified_calculate :
public calculate
{
public:
//構造函數四個參數:
//1. 字符串數組,由所有單詞構成
//2. 布爾變量,是否按照字母最多計算單詞鏈
//3. 整型,指定首字母,-1為不指定,0-26對應26個字母
//4. 整型,指定尾字母,-1為不指定,0-26對應26個字母
specified_calculate(vector<string> words, bool more_letter, int assigned_initail, int assigned_tail);
~specified_calculate();
vector<string> *get_result() override;
void check_current_chain() override;
protected:
int assigned_initial;
int assigned_tail;
};
最后設計了circle_calculate類,該類繼承specified_calculate類,支持了允許單詞文本中隱含單詞環功能。
該類重寫了specified_calculate類的check_current_chain方法,保留使用了父類的其他所有方法。
class circle_calculate :
public specified_calculate
{
public:
//構造函數五個參數:
//1. 字符串數組,由所有單詞構成
//2. 布爾變量,是否按照字母最多計算單詞鏈
//3. 整型,指定首字母,-1為不指定,0-26對應26個字母
//4. 整型,指定尾字母,-1為不指定,0-26對應26個字母
//5. 布爾類型,是否允許文本隱含單詞環
circle_calculate(vector<string> words, bool more_letter,
int assigned_initail, int assigned_tail, int circle);
~circle_calculate();
bool chain_find_next(word_node prev_node) override;
protected:
bool circle;
};
類之間的關系可以參考下文的UML圖。
5. UML圖

6. 性能分析
我們對項目進行了性能分析,發現項目的性能瓶頸在於遞歸迭代的深度過大。因此我們優化了遞歸函數的結構,減少了其不必要的操作,性能稍有提升。

7. Design by Contract, Code Contract
最開始不是很了解Design By Contract的概念,在Wikipedia上得到的標准定義如下:
//Design by contract
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.
//code contract
The contracts take the form of pre-conditions, post-conditions, and object invariants. Contracts act as checked documentation of your external and internal APIs. The contracts are used to improve testing via runtime checking, enable static contract verification, and documentation generation.
Design By Contract也就是契約式設計,Code Contract規定了接口的數據類型,接口執行之前的條件(precondition)和接口執行之后的條件(postcondition)。
我認為這種編程方式的優點:
- 保證在滿足前提條件的情況下,代碼才會按照特定的方式執行,同時反面就是如果不滿足特定條件,代碼不會執行,等同於過濾了錯誤輸入。
- 不用在接口中驗證輸入是否滿足條件,保證了功能的純潔。
缺點:
- 增加編碼的復雜度,對於迭代快的開發感覺不適合。
項目中如何使用:
- 通過提前規定接口文檔的方式,一定程度上替代了契約式編程。
8. 計算模塊部分單元測試展示
由於在單元測試中不能使用命令行輸入,所以只需要設計函數的輸入,鑒定所需要的輸出即可,測試案例如下:
TEST_METHOD(test_gen_chain_w) {
char *result[4];
char *words[4] = { "END", "OF", "THE", "WORLD" };
Assert::AreEqual(2, gen_chain(words, 4, result));
}
測試覆蓋率時使用OpenCppCoverage-0.9.6.1 VS插件進行,將單元測試模塊遷移到main函數中測試之后,測試覆蓋率圖如下:

9. 計算模塊部分異常處理說明
| 異常類型 | 設計目標 |
|---|---|
| 對於傳入的單詞文本為空時的報錯 | |
| 單詞文本隱含單詞環 | 對於沒有-r參數時出現隱含單詞環的報錯 |
| 首尾字母約束不合法 | 對於單詞首/尾字母指定不合法的報錯 |
| 文本中某個單詞為空 | 對於某個單詞為空時的報錯 |
| 文本中某單詞首為非字母 | 對於首字母為非字母情況的報錯 |
| 文本中某單詞尾為非字母 | 對於尾字母為非字母情況的報錯 |
關於命令行輸入的異常如下:
| 拋出異常 | 說明 |
|---|---|
| -w param repeat | w參數重復 |
| -c param repeat | c參數重復 |
| -h param repeat | h參數重復 |
| -t param repeat | t參數重復 |
| -r param repeat | r參數重復 |
| invalid param | 無效參數 |
| 非法輸入:文件不存在 | 非法輸入:文件不存在 |
10. 界面模塊的詳細設計過程
本次只實現了命令行模塊。
在結對編程項目中構建了一個input_output類,專門處理從文本的輸入和將結果輸出到文本。
首先是從命令行的輸入,核心模塊是處理命令行的輸入:
while (i < in.size()) {
if (in.at(i) == '-') {
i++; //get next char
char cur = in.at(i);
if (cur == 'w') {
if (is_w) {
throw exception("-w param repeat");
}
is_w = true;
}
else if (cur == 'c') {
if (is_c) {
throw exception("-c param repeat");
}
is_c = true;
}
else if (cur == 'h') {
if (is_h != 0) {
throw exception("-h param repeat");
}
i+=2; //get the blank char
is_h = in.at(i);
}
else if (cur == 't') {
if (is_t != 0) {
throw exception("-t param repeat");
}
i += 2; //get the blank char
is_t = in.at(i);
}
else if (cur == 'r') {
if (is_r) {
throw exception("-r param repeat");
}
is_r = true;
}
else {
throw exception("invalid param");
}
}
else if (in.at(i) != ' ') { //read the absolute path of input file
break;
}
i++;
}
其中對於不符合規定的部分使用異常拋出,在main函數中接受異常。
11. 界面模塊與計算模塊的對接
本次只實現了命令行模塊。
根據解耦合的思想,設計了一個專門的input_output
class input_output
{
public:
input_output();
~input_output();
vector<string> input();
void output(vector<string> words);
vector<string> words;
bool is_w; //word-按單詞數量統計
bool is_c; //count-按字母數量統計
char is_h; //head-指定首字母
char is_t; //tail-指定尾字母
bool is_r; //round-是否成環
string in_path; //輸入文件路徑
string out_path; //輸出文件路徑
string err_msg; //錯誤日志
};
12. 結對的過程
首先,在拿到題目后,我們迅速閱讀了項目的整個要求。在對項目的整體輪廓有大致的了解后,我們開始討論分析了項目的結構。僅僅浮於口上的討論是不夠的,也不利於后續實現。因此我們草擬了一個文檔初稿來規定了具體分工、接口設計、代碼規范等技術細節問題。
在具體分工方面,雖然是結對編程,但我們的工作仍有不同的側重。根據分工,我主要負責計算核心模塊的開發和異常處理,隊友主要負責測試工作和界面模塊開發。
在接口設計方面,我們遵照項目要求的接口設計,計算核心模塊和界面模塊都遵照項目要求中的三個接口進行設計。
在代碼技術規范方面,我們采用《百度C++編程規范》中的要求和建議,作為我們的代碼設計規范。
在前期的預備工作准備完畢后,我們開始上手工作。首先設計了約定的接口並作出簡單的測試,之后我負責開發核心計算模塊,隊友則負責編寫界面模塊和測試用例。由於事先約定清晰,我們分別完成首個版本計算模塊和交互模塊后就立即展開了對接,沒有多余的消耗。
此后我進行了幾輪迭代,完善了計算模塊的所有功能;隊友跟進單元測試和回歸測試,保證了計算模塊的正確性。最后我們作了性能分析等后續工作,完成項目。

13. 結對編程解析
優點:能夠使代碼處於一種一直在被復審的狀態,程序員不斷審核對方的代碼,可以提高編碼質量以及及時發現問題[大概率上]。
缺點:對於迭代快的項目開發,人力資源可能會很緊張, 需要團隊成員獨自開發自己的模塊,結對編程對時間的總體利用率很可能不高。
結對編程最終的效果如何無非就是取決於1. 兩個人的編程水平 2. 兩個人合作的效率。至於博客要求至少列出每個人三個優點和一個缺點,我覺得沒啥可寫的。如果兩個人都不鴿對面,並且盡可能推進項目的進展,對於結對編程的目的來說,就夠了。
