“來(lái),我們來(lái)說(shuō)說(shuō)所謂的模塊是怎么回事。”老C喝了一口茶,又開始和小P聊起來(lái),“你看,我們的程序按照邏輯可以分成幾個(gè)部分,分別是apple
game這個(gè)游戲,child
queue這個(gè)游戲的內(nèi)容以及queue這個(gè)抽象的數(shù)據(jù)結(jié)構(gòu)。在這里我們以名詞為中心,將對(duì)這些名詞的操作分別提煉為函數(shù),放到不同的文件里。這樣我們就
可以認(rèn)為有了三個(gè)邏輯單元……”老C一邊說(shuō),一邊在白板上畫了三個(gè)框框,又用線條將它們連接在一起。
“看,這三個(gè)模塊有聯(lián)系,apple game與child queue有關(guān)系,而child queue與apple game
和queue都有聯(lián)系。這三個(gè)模塊接口的調(diào)用就表現(xiàn)出這種聯(lián)系關(guān)系,比如apple game的PlayGame()函數(shù)就調(diào)用了child
queue的
QueMoveToNextChild()
函數(shù),而child
queue內(nèi)部又包含了queue這個(gè)抽象的數(shù)據(jù)結(jié)構(gòu)。這是一種新的思考問(wèn)題的方式,這種思考問(wèn)題的方法強(qiáng)調(diào)以數(shù)據(jù)為中心,而不是以操作為中心;這種思維
方法與我們熟悉的結(jié)構(gòu)化編程方法有些不同。”看到小P有些摸不到頭腦的樣子,老C決定打個(gè)比方,“就像我們的電腦,CPU,GPU和南北橋芯片各自獨(dú)立,
提供給外部一些接口,但是芯片內(nèi)部是如何處理數(shù)據(jù)的卻被封裝在內(nèi)部,我們需要的就是根據(jù)一定的規(guī)格將它們放到合適的地方就可以了,這樣就可以簡(jiǎn)單的大規(guī)模
生產(chǎn)了,只要你可以讀懂說(shuō)明書,你自己就可以組裝電腦;但是收音機(jī)就不是這樣——雖然它的技術(shù)復(fù)雜度遠(yuǎn)比電腦小——如果你要去處理沒有被封裝過(guò)的模擬電
路,那你首先得本科畢業(yè)。”
“唔……”小P點(diǎn)點(diǎn)頭。
“還有一個(gè)好處是復(fù)用,如果我們可以將queue這個(gè)模塊提煉出來(lái)——使得它保持合理的接口——那么它就可以被用到其它地方,只要這個(gè)地方需要隊(duì)列這樣一
個(gè)數(shù)據(jù)結(jié)構(gòu)。”老C撓撓頭,“但是在這里我們想要提煉這個(gè)queue數(shù)據(jù)結(jié)構(gòu)還是有些困難的,因?yàn)樵谶@里它不是一個(gè)典型的queue,或者說(shuō)它沒有表現(xiàn)出
queue的特質(zhì)。”
“為什么呢?”小P問(wèn)道。
“因?yàn)闆]有體現(xiàn)出FIFO的需求……queue這個(gè)東西還是用在需要first in first out的場(chǎng)合更合適些。”老C答道。
“哦,那么在這里應(yīng)當(dāng)使用什么數(shù)據(jù)結(jié)構(gòu)呢?”小P疑惑道。
“根據(jù)我的經(jīng)驗(yàn),此處使用linked list會(huì)好一些。”老C回答,“因?yàn)樵谶@個(gè)游戲里更多的是體現(xiàn)在某處插入和刪除數(shù)據(jù)……要不我們改寫一下我們的代碼,好讓你有個(gè)更清楚的認(rèn)識(shí)?”
“好啊,怎么更改呢?”小P問(wèn)道。
“我們把具體需要的動(dòng)作用函數(shù)來(lái)表達(dá),這樣你就可以更清楚的看到我們需要什么樣的數(shù)據(jù)結(jié)構(gòu)了。”老C說(shuō)道,“同樣我們還是需要偽代碼來(lái)幫忙。我們先試著不
要陷入為主的使用queue作為child
queue的實(shí)現(xiàn),先用偽代碼將空白的地方添上,看看我們到底需要什么樣的數(shù)據(jù)結(jié)構(gòu)。”說(shuō)著老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_;
}
“看來(lái)我們更加需要一個(gè)便于在任意地方插入與刪除元素的數(shù)據(jù)結(jié)構(gòu),而不是便于在頭和尾部插入與刪除元素的數(shù)據(jù)結(jié)構(gòu)。”老C道,“首先我們了解了需求,現(xiàn)在我們看如何將這些需求組織成為我們代碼的單元模塊。”老C搓搓手,“這樣吧,我來(lái)寫,你來(lái)看。”說(shuō)著他開始改寫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;
}
“
看,我們現(xiàn)在把linked list數(shù)據(jù)結(jié)構(gòu)從apple
game的數(shù)據(jù)聲明中拿出,并認(rèn)為它應(yīng)當(dāng)出現(xiàn)在list.h中。然后根據(jù)我們的使用環(huán)境,在child list模塊中提出對(duì)抽象的linked
list的具體要求,也就是我們?cè)诰帉懖僮鱟hild
list時(shí)希望list數(shù)據(jù)結(jié)構(gòu)所具有的接口。”老C指著代碼向小P解釋,“比如我們?cè)诰帉慶hild list的初始化函數(shù)ChListInitChildList()時(shí),我們希望list模塊提供相應(yīng)的初始化函數(shù)ListInitList(),因?yàn)樽鳛槭褂谜撸覀儫o(wú)需知道也不可能知道list模塊是怎么樣初始化的。其它的接口也一樣,它們都是根據(jù)具體的應(yīng)用環(huán)境需求提出來(lái)的。”老C揉揉發(fā)酸的手指,“現(xiàn)在我們已經(jīng)有了具體的對(duì)list模塊的需求,就可以根據(jù)這些要求對(duì)linked list這個(gè)模塊進(jìn)行編碼了……”
“等等,”小P插嘴道,“
ChListInitChildList()函數(shù)中的assert()是什么意思?”
“哦,這是一種編程習(xí)慣,用于查找程序中違反編程約定的地方。一般的程序在運(yùn)行的時(shí)候有各種各樣的不變性條件,分為前置不變性,運(yùn)行不變性和后置不變性條
件,assert()用于檢測(cè)這些條件是否滿足我們編程的約定。因?yàn)槲覀兊膌inked list不能出現(xiàn)為0的情況,且linked
list的大小實(shí)在程序中用宏定義出來(lái)的,所以在初始化的時(shí)候我使用斷言確保初始化程序可以在正常的條件下進(jìn)行。但是如果linked
list是由外部輸入,比如鍵盤輸入得到,那么我們最好使用一個(gè)if()判斷來(lái)處理用戶輸入的異常情況……總之看你認(rèn)為這些反常的情況是出現(xiàn)在程序內(nèi)部還
是外部——這些我以后還會(huì)談到的。關(guān)于斷言,你可以在網(wǎng)上搜索一下。”老C解釋。
“好的……又是一個(gè)需要經(jīng)驗(yàn)積累的東西嗎?”小P問(wèn)。
“呵呵,可以這樣說(shuō),”老C點(diǎn)點(diǎn)頭,“你先大概看看代碼的結(jié)構(gòu),不必糾纏于細(xì)節(jié),我們?cè)賮?lái)看看linked list的具體實(shí)現(xiàn)。”說(shuō)完老C新建了list.h和list.c兩個(gè)文件,然后又開始扣扣扣扣的打字了。
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_;
}
“在這里我們實(shí)現(xiàn)了一個(gè)雙向鏈表,因?yàn)檫@樣更容易添加和刪除元素;這個(gè)雙向鏈表有一個(gè)頭指針,在list結(jié)構(gòu)體里邊叫l(wèi)ist_,這樣在刪除和添加元素的
時(shí)候比較好處理;注意在這里我們使用了左閉右開定義域,就是說(shuō)使用了[first,
last)的形式定義鏈表的定義域,這樣在計(jì)算長(zhǎng)度和循環(huán)迭代上都很方便,關(guān)于這方面的內(nèi)容我們以后還會(huì)討論到……”老C撓
撓頭,接著說(shuō)道,“注意在刪除內(nèi)存前將其內(nèi)容清0是很好的習(xí)慣,這樣在數(shù)據(jù)敗壞時(shí)程序會(huì)迅速崩潰,從而減少大量的調(diào)試時(shí)間。”他又看了看代碼,“list
=
list這樣奇怪的用法只是為了去除編譯警告——認(rèn)真的去除所有編譯警告是也是比較好的習(xí)慣——為什么要在函數(shù)形參設(shè)置多余的參數(shù)?……為了統(tǒng)一,這樣可
以減少一些記憶力上的負(fù)擔(dān)……喔,iter是iterator的簡(jiǎn)寫……”老C給小P簡(jiǎn)單的講解了一下。
“等等,我再仔細(xì)看看……”小P開始在屏幕上翻來(lái)翻去,滾動(dòng)鼠標(biāo)的滾動(dòng)軸,“嗯,這下我有些明白了,apple game使用了child
list作為實(shí)現(xiàn),apple game的函數(shù)——就是main.c中的函數(shù)——調(diào)用了child list的接口作為實(shí)現(xiàn)的一部分,而child
list的接口就是apple game中的static函數(shù)……然后child list又使用linked
list這個(gè)模塊的接口作為其實(shí)現(xiàn)的一部分……嗯,層次結(jié)構(gòu)就是這樣……”
“沒錯(cuò),我們將功能分配到各個(gè)模塊中,最后得到了一個(gè)和具體需求——也就是apple game——獨(dú)立的模塊,就是linked
list,這個(gè)模塊完全不知道apple game的任何信息,這樣——的確很有趣——我們實(shí)現(xiàn)的linked
list有可能被用在別處。”老C接著補(bǔ)充道。
“等等等等,”小P叫住老C,“我還得再看看代碼,熟悉熟悉,學(xué)習(xí)學(xué)習(xí)……”他又看了幾遍代碼,重點(diǎn)看了看linked
list的實(shí)現(xiàn),“不錯(cuò),只要我們將list.h和list.c加入到其他項(xiàng)目,并修改LIST_CONTENT這個(gè)類型的定義,的確可以將這些代碼用在
其他地方……”
“沒錯(cuò),由于我們模塊的粒度……什么叫粒度?就是規(guī)模……由于我們模塊的規(guī)模足夠細(xì)致,這樣我們有一部分的代碼就可以被復(fù)用,而且這些代碼是經(jīng)過(guò)我們這個(gè)項(xiàng)目測(cè)試過(guò)的,完全可以用在其他地方以減少開發(fā)和測(cè)試的工作量。”老C習(xí)慣性的總結(jié)道。
“但是……等等……好像list模塊最多只能用一次,因?yàn)闆]有辦法在一個(gè)工程中定義兩個(gè)LIST_CONTENT類型……”小P突然注意到什么,很是興奮的分析道。
“呵呵,這個(gè)是下一步的工作了,你先看看這一版的實(shí)現(xiàn)是否正確……”老C開始有些佩服小P的觀察力了。
“嗯,我來(lái)看看程序是否工作正常……”小P修改了
applegame.h中 CHILDREN_NUM 和 KICK_OUT_NUM 宏的定義,觀察了調(diào)試信息的輸出,“嗯,我認(rèn)為沒有什么問(wèn)題。”他點(diǎn)頭說(shuō)道。
“注意值為1的邊界情況和0的非正常情況。”老C提醒。
“知道了,”小P應(yīng)道,又嘗試了一些邊界情況和非正常情況,“嗯,程序的執(zhí)行沒有什么問(wèn)題,在0值的情況下會(huì)出現(xiàn)runtime錯(cuò)誤。”
“好啦,我們的C編碼活動(dòng)告一段落啦。”老C說(shuō)完將所有文件拷貝到AppleGame_V1.02,然后將AppleGame_V1.02命名為AppleGame_V2.0。“我們已經(jīng)進(jìn)行了簡(jiǎn)單的開發(fā)活動(dòng),讓我們來(lái)總結(jié)總結(jié)……”
“是么?有哪些需要總結(jié)的地方?”小P摸摸頭,問(wèn)道。
“首先是思想和原理性的東西,其次是方法上的東西。”老C道。
“?槑”小P有些莫名其妙。
“呵呵,我來(lái)說(shuō)你來(lái)寫吧……把白板擦干凈……”老C揉揉手指,決定偷懶一下。
“好!”
老C接過(guò)彩筆,在白板中間從上到下畫了一道線,左邊寫上思想,右邊寫上方法。“你先寫寫思想上的東西吧,”他喝了一口水,“思想是最重要的,我們需要通過(guò)
學(xué)習(xí)語(yǔ)言來(lái)學(xué)習(xí)思想——只要學(xué)會(huì)了編程的思想,那么你再學(xué)習(xí)其他任何語(yǔ)言都會(huì)很快——要深入語(yǔ)言去學(xué)習(xí),而不是只是使用語(yǔ)言。首先我們的第一個(gè)經(jīng)驗(yàn)是,以
數(shù)據(jù)為中心思考問(wèn)題,而不是以活動(dòng)為中心思考問(wèn)題。”
“嗯,好像沒有什么問(wèn)題,如果我們以數(shù)據(jù)為中心思考問(wèn)題,那么總會(huì)抽象出一些變化較少的,相對(duì)穩(wěn)定的數(shù)據(jù),將對(duì)數(shù)據(jù)的操作與數(shù)據(jù)捆綁到一個(gè)代碼單元中,這樣就可以有限度的復(fù)用已經(jīng)開發(fā)的代碼……”小P若有所思。
“呵呵,這只是一個(gè)好處,還有一些其他的好處,需要你在以后的編程中體會(huì)。”老C笑笑。這樣白板的左邊出現(xiàn)了第一個(gè)和第二個(gè)經(jīng)驗(yàn)的總結(jié)。
思想:
1. 以數(shù)據(jù)為中心思考問(wèn)題的解決之道。
2. 將對(duì)同類數(shù)據(jù)的操作放在相同的編譯單元中。
“唔,如果我們使用這種方法去解決問(wèn)題,是不是就是面向?qū)ο罅四兀?#8221;小P突然想到了什么,問(wèn)老C。
“哦,還不是。因?yàn)槊嫦驅(qū)ο蟮娜筇匦裕庋b、繼承和多態(tài),我們只使用了封裝這個(gè)特性,所以不能叫面向?qū)ο蟮模荒芙谢趯?duì)象的。”老C答道,“所以我們第三個(gè)總結(jié),就是將對(duì)數(shù)據(jù)的操作,如非必要,盡量隱藏起來(lái),而不要暴露給其他編譯單元或者用戶。”
“好哩。”小P又飛快的在下面加上了第三條經(jīng)驗(yàn)。
思想:
1. 以數(shù)據(jù)為中心思考問(wèn)題的解決之道。
2. 將對(duì)同類數(shù)據(jù)的操作放在相同的編譯單元中。
3. 信息隱藏,而不是暴露,以將問(wèn)題的規(guī)模控制在可理解的范圍。
“還有什么?”小P問(wèn)。
“這三條已經(jīng)可以了,夠你體會(huì)一陣子了。貪多嚼不爛,我們就先總結(jié)這么多吧。”老C無(wú)奈道,“編程的思想是很難學(xué)習(xí)的,需要經(jīng)驗(yàn)來(lái)體會(huì)——不要妄圖一次學(xué)
會(huì)所有的東西,學(xué)習(xí)任何東西都是螺旋形上升的,編程也一樣……就連我們的開發(fā)過(guò)程也一樣……好啦,我們?cè)賮?lái)總結(jié)總結(jié)方法吧。”
“那么我就寫在右邊了?”小P問(wèn)。
“是啊,當(dāng)然了。”老C囧道,“我們的方法你也看到了,其實(shí)也就是那么幾條。首先要從大方面著手,將問(wèn)題分解為幾個(gè)基本的步驟,然后用偽代碼寫出解決問(wèn)題的思路。”
“哦,是這樣的。”小P迫不及待的在白板左面寫下第一條關(guān)于方法的總結(jié)。
“不,不是這樣,”老C阻止小P,“無(wú)所謂自上向下和自下向上,因?yàn)樵陂_發(fā)過(guò)程中這兩個(gè)活動(dòng)是并發(fā)的并且始終貫穿于開發(fā)活動(dòng)當(dāng)中,甚至有人說(shuō)設(shè)計(jì)要自上向下,實(shí)現(xiàn)要自下向上。”
“那么應(yīng)當(dāng)怎么總結(jié)方法呢?”小P郁悶道。
“首先著眼于整體,而不是細(xì)節(jié)。”老C得意的說(shuō)道。
“好,我不反對(duì)。”于是小P將白板右面的文字進(jìn)行了更改。
1. 首先關(guān)注整體,而不是細(xì)節(jié)。
“然后呢?”小P問(wèn)。
“然后使用偽代碼,寫出問(wèn)題的解決之道。”老P回答,“然后再根據(jù)偽代碼,寫出相應(yīng)的數(shù)據(jù)定義以及需要解決此問(wèn)題的對(duì)數(shù)據(jù)的操作,并寫出測(cè)試代碼,讓我們的框架可以迅速編譯通過(guò)并運(yùn)行。”
“好,那么我就這樣總結(jié)。”小P在白板上涂涂抹抹。
方法:
1. 首先關(guān)注整體,而不是細(xì)節(jié)。
2. 先考慮測(cè)試,更先于編碼。
3. 先用偽代碼編程,以體現(xiàn)程序的意圖,再用具體代碼完善之。
4. 迅速構(gòu)建可編譯通過(guò)的,并且可執(zhí)行的框架程序,使用測(cè)試代碼測(cè)試之。
“如果我們做到以上幾點(diǎn),我們可以迅速擁有一個(gè)正確的可以運(yùn)行的框架,以后寫的代碼都可以在此框架下進(jìn)行測(cè)試,避免了后期進(jìn)行大規(guī)模代碼集成的風(fēng)險(xiǎn)。”老C道,“這些等你編碼規(guī)模變大以后就會(huì)有一些體會(huì)了。”
“是啊,現(xiàn)在我們已經(jīng)有了框架了,然后怎么辦呢?”小P問(wèn)道。
“因?yàn)槲覀円呀?jīng)將問(wèn)題分解為幾個(gè)部分了,對(duì)每一個(gè)部分反復(fù)運(yùn)用我們以上總結(jié)的四個(gè)經(jīng)驗(yàn),這樣我們就得到了更細(xì)小的模塊,遞歸下去,直到每個(gè)模塊的規(guī)模都在我們的掌控之中,我們就解決問(wèn)題了。”老C回答到。
“是啊是啊,這個(gè)很像分而治之的思想。”小P道。
“沒錯(cuò),你總結(jié)的很到位,這就是分而治之思想的一個(gè)運(yùn)用而已。”老C開始有些佩服小P的總結(jié)能力和聯(lián)想能力了,“還有一些方法需要總結(jié),比如說(shuō)要在代碼環(huán)
境中提取對(duì)其他模塊接口的需求,而不要自底向上的猜測(cè)某一模塊的接口應(yīng)當(dāng)是什么樣子。我們?cè)趯?shí)現(xiàn)linked
list模塊就是用這種方法做的,我不管linked
list究竟有多少接口,我只管在applegame.c的函數(shù)中實(shí)現(xiàn)算法,在其中自然而然的就會(huì)有對(duì)linked
list接口函數(shù)的需求。根據(jù)這些需求,我們先寫出.h文件,然后拋開雜念專心致志的在其對(duì)應(yīng)的.c文件中實(shí)現(xiàn)這些外部對(duì)linked
list接口的需要,而模塊外面沒有表現(xiàn)出來(lái)的一些更加具體的操作,自然被隱藏起來(lái)成為linked
list內(nèi)部的static函數(shù)。不過(guò)在我們的代碼中,還沒有這樣的被隱藏起來(lái)的static函數(shù)而已,但如果我們繼續(xù)深入的優(yōu)化linked
list模塊,這些函數(shù)遲早會(huì)出現(xiàn)的。”
“嗯,那么我們可以這么總結(jié)。”小P回應(yīng)道。
方法:
1. 首先關(guān)注整體,而不是細(xì)節(jié)。
2. 先考慮測(cè)試,更先于編碼。
3. 先用偽代碼編程,以體現(xiàn)程序的意圖,再用具體代碼完善之。
4. 迅速構(gòu)建可編譯通過(guò)的,并且可執(zhí)行的框架程序,使用測(cè)試代碼測(cè)試之。
5. 將以上方法反復(fù)應(yīng)用于子模塊,直到問(wèn)題被解決。
6. 在上下文環(huán)境中提取對(duì)其他模塊的需求。
7. 先寫.h文件,后寫.c文件。
“這樣就可以了吧。”小P問(wèn)。
“嗯,還差一些,你有沒有觀察到我們的程序經(jīng)歷了好幾個(gè)版本?而每一個(gè)版本都比上一個(gè)版本稍微好上一點(diǎn)點(diǎn)?”老C問(wèn)道。
“的確是這樣,那么這點(diǎn)應(yīng)當(dāng)如何總結(jié)呢?”小P問(wèn)。
“嗯,就是先快速實(shí)現(xiàn)一個(gè)可行的解法,然后放在實(shí)際需求環(huán)境中考察這個(gè)解法,根據(jù)實(shí)際需求對(duì)解法的反饋,不斷修正此程序。”老C回答,“簡(jiǎn)單的說(shuō)就是迭代的開發(fā),哲學(xué)一些就是螺旋形上升的開發(fā)。”
“我還是喜歡更通俗一些的說(shuō)法。”小P在白板上又增加了一條。
方法:
1. 首先關(guān)注整體,而不是細(xì)節(jié)。
2. 先考慮測(cè)試,更先于編碼。
3. 先用偽代碼編程,以體現(xiàn)程序的意圖,再用具體代碼完善之。
4. 迅速構(gòu)建可編譯通過(guò)的,并且可執(zhí)行的框架程序,使用測(cè)試代碼測(cè)試之。
5. 將以上方法反復(fù)應(yīng)用于子模塊,直到問(wèn)題被解決。
6. 在上下文環(huán)境中提取對(duì)其他模塊的需求。
7. 先寫.h文件,后寫.c文件。
8. 先快速實(shí)現(xiàn)一個(gè)可行的解法,再根據(jù)反饋信息優(yōu)化之。
“呵呵,其實(shí)前面幾步我們是在做架構(gòu)設(shè)計(jì),也就是概要設(shè)計(jì),后面幾步是在進(jìn)行詳細(xì)設(shè)計(jì)及編碼,一般很少有一次就搞定的事情,比較好的解決之道總是這樣反復(fù)
來(lái)上幾次才找到的——除非你所在的團(tuán)隊(duì)對(duì)問(wèn)題的領(lǐng)域特別熟悉,對(duì)需求了解相當(dāng)透徹。”老C總結(jié),“好啦,時(shí)候也不早了,我們回去睡覺吧,過(guò)幾天我們來(lái)看看
用C++如何解決這個(gè)問(wèn)題。”
“好吧,你先回去吧,我把白板上的東西抄回去,再對(duì)照著我們寫過(guò)的代碼仔細(xì)琢磨琢磨。”小P道。
“哈哈,算了算了,明天再搞吧,今天已經(jīng)晚了。勞逸結(jié)合才是正道,不如你今晚和我再打打魔獸吧……”老C笑道。
“……也好……看我再給你支幾招……”小P笑道。
“呵呵,那就趕快回去吧,我的手有些癢癢了……”老C拉起小P,飛快的走出教研室。
(想知道C++版本的實(shí)現(xiàn)么?)