“來,我們來說說所謂的模塊是怎么回事。”老C喝了一口茶,又開始和小P聊起來,“你看,我們的程序按照邏輯可以分成幾個部分,分別是apple
game這個游戲,child
queue這個游戲的內容以及queue這個抽象的數據結構。在這里我們以名詞為中心,將對這些名詞的操作分別提煉為函數,放到不同的文件里。這樣我們就
可以認為有了三個邏輯單元……”老C一邊說,一邊在白板上畫了三個框框,又用線條將它們連接在一起。
“看,這三個模塊有聯系,apple game與child queue有關系,而child queue與apple game
和queue都有聯系。這三個模塊接口的調用就表現出這種聯系關系,比如apple game的PlayGame()函數就調用了child
queue的
QueMoveToNextChild()
函數,而child
queue內部又包含了queue這個抽象的數據結構。這是一種新的思考問題的方式,這種思考問題的方法強調以數據為中心,而不是以操作為中心;這種思維
方法與我們熟悉的結構化編程方法有些不同。”看到小P有些摸不到頭腦的樣子,老C決定打個比方,“就像我們的電腦,CPU,GPU和南北橋芯片各自獨立,
提供給外部一些接口,但是芯片內部是如何處理數據的卻被封裝在內部,我們需要的就是根據一定的規格將它們放到合適的地方就可以了,這樣就可以簡單的大規模
生產了,只要你可以讀懂說明書,你自己就可以組裝電腦;但是收音機就不是這樣——雖然它的技術復雜度遠比電腦小——如果你要去處理沒有被封裝過的模擬電
路,那你首先得本科畢業。”
“唔……”小P點點頭。
“還有一個好處是復用,如果我們可以將queue這個模塊提煉出來——使得它保持合理的接口——那么它就可以被用到其它地方,只要這個地方需要隊列這樣一
個數據結構。”老C撓撓頭,“但是在這里我們想要提煉這個queue數據結構還是有些困難的,因為在這里它不是一個典型的queue,或者說它沒有表現出
queue的特質。”
“為什么呢?”小P問道。
“因為沒有體現出FIFO的需求……queue這個東西還是用在需要first in first out的場合更合適些。”老C答道。
“哦,那么在這里應當使用什么數據結構呢?”小P疑惑道。
“根據我的經驗,此處使用linked list會好一些。”老C回答,“因為在這個游戲里更多的是體現在某處插入和刪除數據……要不我們改寫一下我們的代碼,好讓你有個更清楚的認識?”
“好啊,怎么更改呢?”小P問道。
“我們把具體需要的動作用函數來表達,這樣你就可以更清楚的看到我們需要什么樣的數據結構了。”老C說道,“同樣我們還是需要偽代碼來幫忙。我們先試著不
要陷入為主的使用queue作為child
queue的實現,先用偽代碼將空白的地方添上,看看我們到底需要什么樣的數據結構。”說著老C開始更改代碼。
applegame.c:
#include "applegame.h"
#include "mydebug.h"
static void QueInitQueue (QUEUE* childQueue);
static int QueIsChildExisted (QUEUE* childQueue);
static void QueKickOutChild (QUEUE* childQueue);
static void QueMoveToNextChild (QUEUE* childQueue);
static int QueFindRemainedChild (QUEUE* childQueue);
void InitAppleGame(APPLE_GAME* game)
{
MY_DEBUG("Init the apple game.\n");
game->currCountNum_ = 0;
game->kickOutNum_ = KICK_OUT_NUM;
game->childrenRemained_ = CHILDREN_NUM;
QueInitQueue(&(game->childrenQueue_));
}
int IsGameOver(APPLE_GAME* game)
{
MY_DEBUG_1("The children remained %d\n", game->childrenRemained_);
return (1 == game->childrenRemained_);
}
void PlayGame(APPLE_GAME* game)
{
MY_DEBUG("Play game...\n");
/* If the current child is existed in the queue, count on, then check if she will be kicked out. */
if (QueIsChildExisted(&(game->childrenQueue_)))
{
/* Count on. */
game->currCountNum_++;
/* If the child counts kicked out number, then she is kicked out. */
if (game->currCountNum_ == game->kickOutNum_)
{
QueKickOutChild(&(game->childrenQueue_));
game->childrenRemained_--;
game->currCountNum_ = 0;
MY_DEBUG_1("The child kicked out is %d\n", game->childrenQueue_.queue_[game->childrenQueue_.index_].seatNum_);
}
}
QueMoveToNextChild(&(game->childrenQueue_));
}
int LastChildSeatNum(APPLE_GAME* game)
{
int seatNum;
MY_DEBUG("Searching last child's seat number\n");
seatNum = QueFindRemainedChild(&(game->childrenQueue_));
return seatNum;
}
/************************************************************************/
/* Local functions */
/************************************************************************/
static void QueInitQueue(QUEUE* childQueue)
{
/* Insert all children into the game. */
int i;
childQueue->size_ = CHILDREN_NUM;
childQueue->index_ = 0;
for (i = 0; i < childQueue->size_; ++i)
{
childQueue->queue_[i].seatNum_ = i + 1;
childQueue->queue_[i].existeState_ = EXISTED;
}
}
static int QueIsChildExisted(QUEUE* childQueue)
{
/* Is the child existed in the game? */
return (EXISTED == childQueue->queue_[childQueue->index_].existeState_);
}
static void QueKickOutChild(QUEUE* childQueue)
{
/* Remove the child from the game. */
childQueue->queue_[childQueue->index_].existeState_ = ABSENT;
}
static void QueMoveToNextChild(QUEUE* childQueue)
{
/* Go to the next child. */
childQueue->index_++;
childQueue->index_ %= childQueue->size_;
}
static int QueFindRemainedChild(QUEUE* childQueue)
{
/* Find the last child remained. */
int i;
for (i = 0; i < childQueue->size_; ++i)
{
if (EXISTED == childQueue->queue_[i].existeState_)
{
break;
}
}
return childQueue->queue_[i].seatNum_;
}
“看來我們更加需要一個便于在任意地方插入與刪除元素的數據結構,而不是便于在頭和尾部插入與刪除元素的數據結構。”老C道,“首先我們了解了需求,現在我們看如何將這些需求組織成為我們代碼的單元模塊。”老C搓搓手,“這樣吧,我來寫,你來看。”說著他開始改寫applegame.c,applegame.h。
applegame.h:
#if !defined(APPLE_GAME_H_)
#define APPLE_GAME_H_
#include "list.h"
#define CHILDREN_NUM 20U
#define KICK_OUT_NUM 7U
typedef struct tagAPPLE_GAME
{
int currCountNum_;
int kickOutNum_;
LIST childrenList_;
}APPLE_GAME;
extern void InitAppleGame (APPLE_GAME* game);
extern int IsGameOver (APPLE_GAME* game);
extern void PlayGame (APPLE_GAME* game);
extern int LastChildSeatNum (APPLE_GAME* game);
#endif /* APPLE_GAME_H_ */
------------------------------------------------------(華麗的分割線)
applegame.c:
#include "applegame.h"
#include "mydebug.h"
#include <assert.h>
#include <stdlib.h>
static void ChListInitChildList (LIST* childList);
static void ChListKickOutChild (LIST* childList);
static void ChListMoveToNextChild (LIST* childList);
static int ChListFindRemainedChild (LIST* childList);
void InitAppleGame(APPLE_GAME* game)
{
MY_DEBUG("Init the apple game.\n");
game->kickOutNum_ = KICK_OUT_NUM;
game->currCountNum_ = 0;
ChListInitChildList(&(game->childrenList_));
}
int IsGameOver(APPLE_GAME* game)
{
int childRemained;
childRemained = ListSize(&(game->childrenList_));
MY_DEBUG_1("The children remained %d\n", childRemained);
return 1 == childRemained;
}
void PlayGame(APPLE_GAME* game)
{
MY_DEBUG("Play game...\n");
++game->currCountNum_;
if (game->kickOutNum_ == game->currCountNum_)
{
/* When kick out child, the index automatically points to the next child. */
ChListKickOutChild(&(game->childrenList_));
game->currCountNum_ = 0;
}
else
{
ChListMoveToNextChild(&(game->childrenList_));
}
}
int LastChildSeatNum(APPLE_GAME* game)
{
int seatNum;
MY_DEBUG("Searching last child's seat number\n");
seatNum = ChListFindRemainedChild(&(game->childrenList_));
return seatNum;
}
/************************************************************************/
/* Local functions */
/************************************************************************/
static void ChListInitChildList( LIST* childList )
{
/* Insert all children into the game. */
int i;
LIST_NODE* child;
/* Make sure that the game at least has one child */
assert(0 != CHILDREN_NUM);
ListInitList(childList);
for (i = 0; i < CHILDREN_NUM; ++i)
{
child = (LIST_NODE*)malloc(sizeof(LIST_NODE));
child->content_.seatNum_ = i + 1;
/* Attach a new node to the end of the list. */
ListPushBack(childList, child);
}
childList->index_ = ListBegin(childList);
}
static void ChListKickOutChild( LIST* childList )
{
MY_DEBUG_1("The child kicked out is %d\n", childList->index_->content_.seatNum_);
/* Remove a node the index pointing to. */
childList->index_ = ListErase(childList, childList->index_);
}
static void ChListMoveToNextChild( LIST* childList )
{
/* Index goes to the next node. */
ListForward(childList);
}
static int ChListFindRemainedChild( LIST* childList )
{
LIST_NODE* iter;
int seatNum;
/* Get the first node of the list. */
iter = ListBegin(childList);
seatNum = iter->content_.seatNum_;
/* Destroy the list. */
ListClear(childList);
return seatNum;
}
“
看,我們現在把linked list數據結構從apple
game的數據聲明中拿出,并認為它應當出現在list.h中。然后根據我們的使用環境,在child list模塊中提出對抽象的linked
list的具體要求,也就是我們在編寫操作child
list時希望list數據結構所具有的接口。”老C指著代碼向小P解釋,“比如我們在編寫child list的初始化函數ChListInitChildList()時,我們希望list模塊提供相應的初始化函數ListInitList(),因為作為使用者,我們無需知道也不可能知道list模塊是怎么樣初始化的。其它的接口也一樣,它們都是根據具體的應用環境需求提出來的。”老C揉揉發酸的手指,“現在我們已經有了具體的對list模塊的需求,就可以根據這些要求對linked list這個模塊進行編碼了……”
“等等,”小P插嘴道,“
ChListInitChildList()函數中的assert()是什么意思?”
“哦,這是一種編程習慣,用于查找程序中違反編程約定的地方。一般的程序在運行的時候有各種各樣的不變性條件,分為前置不變性,運行不變性和后置不變性條
件,assert()用于檢測這些條件是否滿足我們編程的約定。因為我們的linked list不能出現為0的情況,且linked
list的大小實在程序中用宏定義出來的,所以在初始化的時候我使用斷言確保初始化程序可以在正常的條件下進行。但是如果linked
list是由外部輸入,比如鍵盤輸入得到,那么我們最好使用一個if()判斷來處理用戶輸入的異常情況……總之看你認為這些反常的情況是出現在程序內部還
是外部——這些我以后還會談到的。關于斷言,你可以在網上搜索一下。”老C解釋。
“好的……又是一個需要經驗積累的東西嗎?”小P問。
“呵呵,可以這樣說,”老C點點頭,“你先大概看看代碼的結構,不必糾纏于細節,我們再來看看linked list的具體實現。”說完老C新建了list.h和list.c兩個文件,然后又開始扣扣扣扣的打字了。
list.h:
#if !defined(LIST_H_)
#define LIST_H_
typedef int SEAT_NUM;
typedef enum tagEXISTE_STATE { ABSENT, EXISTED } EXISTE_STATE;
typedef struct tagCHILD
{
SEAT_NUM seatNum_;
}CHILD;
typedef CHILD LIST_CONTENT;
struct tagLIST_NODE
{
struct tagLIST_NODE* prev_;
struct tagLIST_NODE* next_;
LIST_CONTENT content_;
};
typedef struct tagLIST_NODE LIST_NODE;
typedef struct tagLIST
{
int size_;
LIST_NODE* index_;
LIST_NODE* list_;
}LIST;
extern void ListInitList (LIST* list);
extern int ListSize (LIST* list);
extern LIST_NODE* ListBegin (LIST* list);
extern LIST_NODE* ListEnd (LIST* list);
extern void ListInsert (LIST* list, LIST_NODE* iter, LIST_NODE* newNode);
extern LIST_NODE* ListErase (LIST* list, LIST_NODE* iter);
extern void ListClear (LIST* list);
extern void ListPushBack (LIST* list, LIST_NODE* newNode);
extern void ListPopFront (LIST* list);
extern LIST_NODE* ListForward (LIST* list);
#endif /* LIST_H_ */
------------------------------------------------------(華麗的分割線)
list.c:
#include "list.h"
#include <stdlib.h>
#include <string.h>
/* Create the list head. */
void ListInitList(LIST* list)
{
LIST_NODE* head;
head = (LIST_NODE*)malloc(sizeof(LIST_NODE));
head->next_ = head;
head->prev_ = head;
list->size_ = 0;
list->list_ = head;
list->index_ = list->list_;
}
/* Return the size of the list. (Does not include list head.) */
int ListSize(LIST* list)
{
return list->size_;
}
/* Return the first node of the list. */
LIST_NODE* ListBegin(LIST* list)
{
return list->list_->next_;
}
/* Return the node after the last node of the list */
LIST_NODE* ListEnd(LIST* list)
{
return list->list_;
}
/* Insert a new node before the specified list node. */
void ListInsert( LIST* list, LIST_NODE* iter, LIST_NODE* newNode )
{
list = list;
newNode->next_ = iter;
newNode->prev_ = iter->prev_;
newNode->prev_->next_ = newNode;
iter->prev_ = newNode;
++list->size_;
}
/* Remove a list node specified. */
LIST_NODE* ListErase(LIST* list, LIST_NODE* iter)
{
LIST_NODE* nextNode;
list = list;
nextNode = iter->next_;
if (ListEnd(list) == nextNode)
{
nextNode = ListBegin(list);
}
iter->prev_->next_ = iter->next_;
iter->next_->prev_ = iter->prev_;
memset(iter, 0, sizeof(LIST_NODE));
free(iter);
--list->size_;
return nextNode;
}
/* Destroy all nodes of the list, including the head. */
void ListClear(LIST* list)
{
while (ListSize(list))
{
ListPopFront(list);
}
memset(list->list_, 0, sizeof(LIST_NODE));
free(list->list_);
}
/* Attach a new node to the end of the list. */
void ListPushBack( LIST* list, LIST_NODE* newNode )
{
ListInsert(list, ListEnd(list), newNode);
}
/* Remove the fist node of the list. */
void ListPopFront(LIST* list)
{
if (ListSize(list))
{
ListErase(list, ListBegin(list));
}
}
/* Move the list index to the next node. If reaches the one after the last node,
the index is moved to the first node. */
LIST_NODE* ListForward(LIST* list)
{
list->index_ = list->index_->next_;
if (ListEnd(list) == list->index_)
{
list->index_ = ListBegin(list);
}
return list->index_;
}
“在這里我們實現了一個雙向鏈表,因為這樣更容易添加和刪除元素;這個雙向鏈表有一個頭指針,在list結構體里邊叫list_,這樣在刪除和添加元素的
時候比較好處理;注意在這里我們使用了左閉右開定義域,就是說使用了[first,
last)的形式定義鏈表的定義域,這樣在計算長度和循環迭代上都很方便,關于這方面的內容我們以后還會討論到……”老C撓
撓頭,接著說道,“注意在刪除內存前將其內容清0是很好的習慣,這樣在數據敗壞時程序會迅速崩潰,從而減少大量的調試時間。”他又看了看代碼,“list
=
list這樣奇怪的用法只是為了去除編譯警告——認真的去除所有編譯警告是也是比較好的習慣——為什么要在函數形參設置多余的參數?……為了統一,這樣可
以減少一些記憶力上的負擔……喔,iter是iterator的簡寫……”老C給小P簡單的講解了一下。
“等等,我再仔細看看……”小P開始在屏幕上翻來翻去,滾動鼠標的滾動軸,“嗯,這下我有些明白了,apple game使用了child
list作為實現,apple game的函數——就是main.c中的函數——調用了child list的接口作為實現的一部分,而child
list的接口就是apple game中的static函數……然后child list又使用linked
list這個模塊的接口作為其實現的一部分……嗯,層次結構就是這樣……”
“沒錯,我們將功能分配到各個模塊中,最后得到了一個和具體需求——也就是apple game——獨立的模塊,就是linked
list,這個模塊完全不知道apple game的任何信息,這樣——的確很有趣——我們實現的linked
list有可能被用在別處。”老C接著補充道。
“等等等等,”小P叫住老C,“我還得再看看代碼,熟悉熟悉,學習學習……”他又看了幾遍代碼,重點看了看linked
list的實現,“不錯,只要我們將list.h和list.c加入到其他項目,并修改LIST_CONTENT這個類型的定義,的確可以將這些代碼用在
其他地方……”
“沒錯,由于我們模塊的粒度……什么叫粒度?就是規模……由于我們模塊的規模足夠細致,這樣我們有一部分的代碼就可以被復用,而且這些代碼是經過我們這個項目測試過的,完全可以用在其他地方以減少開發和測試的工作量。”老C習慣性的總結道。
“但是……等等……好像list模塊最多只能用一次,因為沒有辦法在一個工程中定義兩個LIST_CONTENT類型……”小P突然注意到什么,很是興奮的分析道。
“呵呵,這個是下一步的工作了,你先看看這一版的實現是否正確……”老C開始有些佩服小P的觀察力了。
“嗯,我來看看程序是否工作正常……”小P修改了
applegame.h中 CHILDREN_NUM 和 KICK_OUT_NUM 宏的定義,觀察了調試信息的輸出,“嗯,我認為沒有什么問題。”他點頭說道。
“注意值為1的邊界情況和0的非正常情況。”老C提醒。
“知道了,”小P應道,又嘗試了一些邊界情況和非正常情況,“嗯,程序的執行沒有什么問題,在0值的情況下會出現runtime錯誤。”
“好啦,我們的C編碼活動告一段落啦。”老C說完將所有文件拷貝到AppleGame_V1.02,然后將AppleGame_V1.02命名為AppleGame_V2.0。“我們已經進行了簡單的開發活動,讓我們來總結總結……”
“是么?有哪些需要總結的地方?”小P摸摸頭,問道。
“首先是思想和原理性的東西,其次是方法上的東西。”老C道。
“?槑”小P有些莫名其妙。
“呵呵,我來說你來寫吧……把白板擦干凈……”老C揉揉手指,決定偷懶一下。
“好!”
老C接過彩筆,在白板中間從上到下畫了一道線,左邊寫上思想,右邊寫上方法。“你先寫寫思想上的東西吧,”他喝了一口水,“思想是最重要的,我們需要通過
學習語言來學習思想——只要學會了編程的思想,那么你再學習其他任何語言都會很快——要深入語言去學習,而不是只是使用語言。首先我們的第一個經驗是,以
數據為中心思考問題,而不是以活動為中心思考問題。”
“嗯,好像沒有什么問題,如果我們以數據為中心思考問題,那么總會抽象出一些變化較少的,相對穩定的數據,將對數據的操作與數據捆綁到一個代碼單元中,這樣就可以有限度的復用已經開發的代碼……”小P若有所思。
“呵呵,這只是一個好處,還有一些其他的好處,需要你在以后的編程中體會。”老C笑笑。這樣白板的左邊出現了第一個和第二個經驗的總結。
思想:
1. 以數據為中心思考問題的解決之道。
2. 將對同類數據的操作放在相同的編譯單元中。
“唔,如果我們使用這種方法去解決問題,是不是就是面向對象了呢?”小P突然想到了什么,問老C。
“哦,還不是。因為面向對象的三大特性,封裝、繼承和多態,我們只使用了封裝這個特性,所以不能叫面向對象的,只能叫基于對象的。”老C答道,“所以我們第三個總結,就是將對數據的操作,如非必要,盡量隱藏起來,而不要暴露給其他編譯單元或者用戶。”
“好哩。”小P又飛快的在下面加上了第三條經驗。
思想:
1. 以數據為中心思考問題的解決之道。
2. 將對同類數據的操作放在相同的編譯單元中。
3. 信息隱藏,而不是暴露,以將問題的規模控制在可理解的范圍。
“還有什么?”小P問。
“這三條已經可以了,夠你體會一陣子了。貪多嚼不爛,我們就先總結這么多吧。”老C無奈道,“編程的思想是很難學習的,需要經驗來體會——不要妄圖一次學
會所有的東西,學習任何東西都是螺旋形上升的,編程也一樣……就連我們的開發過程也一樣……好啦,我們再來總結總結方法吧。”
“那么我就寫在右邊了?”小P問。
“是啊,當然了。”老C囧道,“我們的方法你也看到了,其實也就是那么幾條。首先要從大方面著手,將問題分解為幾個基本的步驟,然后用偽代碼寫出解決問題的思路。”
“哦,是這樣的。”小P迫不及待的在白板左面寫下第一條關于方法的總結。
“不,不是這樣,”老C阻止小P,“無所謂自上向下和自下向上,因為在開發過程中這兩個活動是并發的并且始終貫穿于開發活動當中,甚至有人說設計要自上向下,實現要自下向上。”
“那么應當怎么總結方法呢?”小P郁悶道。
“首先著眼于整體,而不是細節。”老C得意的說道。
“好,我不反對。”于是小P將白板右面的文字進行了更改。
“然后呢?”小P問。
“然后使用偽代碼,寫出問題的解決之道。”老P回答,“然后再根據偽代碼,寫出相應的數據定義以及需要解決此問題的對數據的操作,并寫出測試代碼,讓我們的框架可以迅速編譯通過并運行。”
“好,那么我就這樣總結。”小P在白板上涂涂抹抹。
方法:
1. 首先關注整體,而不是細節。
2. 先考慮測試,更先于編碼。
3. 先用偽代碼編程,以體現程序的意圖,再用具體代碼完善之。
4. 迅速構建可編譯通過的,并且可執行的框架程序,使用測試代碼測試之。
“如果我們做到以上幾點,我們可以迅速擁有一個正確的可以運行的框架,以后寫的代碼都可以在此框架下進行測試,避免了后期進行大規模代碼集成的風險。”老C道,“這些等你編碼規模變大以后就會有一些體會了。”
“是啊,現在我們已經有了框架了,然后怎么辦呢?”小P問道。
“因為我們已經將問題分解為幾個部分了,對每一個部分反復運用我們以上總結的四個經驗,這樣我們就得到了更細小的模塊,遞歸下去,直到每個模塊的規模都在我們的掌控之中,我們就解決問題了。”老C回答到。
“是啊是啊,這個很像分而治之的思想。”小P道。
“沒錯,你總結的很到位,這就是分而治之思想的一個運用而已。”老C開始有些佩服小P的總結能力和聯想能力了,“還有一些方法需要總結,比如說要在代碼環
境中提取對其他模塊接口的需求,而不要自底向上的猜測某一模塊的接口應當是什么樣子。我們在實現linked
list模塊就是用這種方法做的,我不管linked
list究竟有多少接口,我只管在applegame.c的函數中實現算法,在其中自然而然的就會有對linked
list接口函數的需求。根據這些需求,我們先寫出.h文件,然后拋開雜念專心致志的在其對應的.c文件中實現這些外部對linked
list接口的需要,而模塊外面沒有表現出來的一些更加具體的操作,自然被隱藏起來成為linked
list內部的static函數。不過在我們的代碼中,還沒有這樣的被隱藏起來的static函數而已,但如果我們繼續深入的優化linked
list模塊,這些函數遲早會出現的。”
“嗯,那么我們可以這么總結。”小P回應道。
方法:
1. 首先關注整體,而不是細節。
2. 先考慮測試,更先于編碼。
3. 先用偽代碼編程,以體現程序的意圖,再用具體代碼完善之。
4. 迅速構建可編譯通過的,并且可執行的框架程序,使用測試代碼測試之。
5. 將以上方法反復應用于子模塊,直到問題被解決。
6. 在上下文環境中提取對其他模塊的需求。
7. 先寫.h文件,后寫.c文件。
“這樣就可以了吧。”小P問。
“嗯,還差一些,你有沒有觀察到我們的程序經歷了好幾個版本?而每一個版本都比上一個版本稍微好上一點點?”老C問道。
“的確是這樣,那么這點應當如何總結呢?”小P問。
“嗯,就是先快速實現一個可行的解法,然后放在實際需求環境中考察這個解法,根據實際需求對解法的反饋,不斷修正此程序。”老C回答,“簡單的說就是迭代的開發,哲學一些就是螺旋形上升的開發。”
“我還是喜歡更通俗一些的說法。”小P在白板上又增加了一條。
方法:
1. 首先關注整體,而不是細節。
2. 先考慮測試,更先于編碼。
3. 先用偽代碼編程,以體現程序的意圖,再用具體代碼完善之。
4. 迅速構建可編譯通過的,并且可執行的框架程序,使用測試代碼測試之。
5. 將以上方法反復應用于子模塊,直到問題被解決。
6. 在上下文環境中提取對其他模塊的需求。
7. 先寫.h文件,后寫.c文件。
8. 先快速實現一個可行的解法,再根據反饋信息優化之。
“呵呵,其實前面幾步我們是在做架構設計,也就是概要設計,后面幾步是在進行詳細設計及編碼,一般很少有一次就搞定的事情,比較好的解決之道總是這樣反復
來上幾次才找到的——除非你所在的團隊對問題的領域特別熟悉,對需求了解相當透徹。”老C總結,“好啦,時候也不早了,我們回去睡覺吧,過幾天我們來看看
用C++如何解決這個問題。”
“好吧,你先回去吧,我把白板上的東西抄回去,再對照著我們寫過的代碼仔細琢磨琢磨。”小P道。
“哈哈,算了算了,明天再搞吧,今天已經晚了。勞逸結合才是正道,不如你今晚和我再打打魔獸吧……”老C笑道。
“……也好……看我再給你支幾招……”小P笑道。
“呵呵,那就趕快回去吧,我的手有些癢癢了……”老C拉起小P,飛快的走出教研室。
(想知道C++版本的實現么?)