第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一)

     “今天早上阳光明媚啊,”老C在教研室门口深呼吸。

     “嗯,东花园显得很漂亮啊……”小P应声道,两人走过东花园,来到教研室,准备妥当后,坐在小P桌前。

     “好,我们今天先不评论你的代码内容,而是重新来写这个代码。”老C不好意思说小P的代码太烂以至于无法评审,所以决定另起炉灶,“然后我们拿新写的这个版本与原来的版本做比较。”

     “也好。”小P同意。

     “这样,我们两个来一起写这个代码,这样更快一些。”老C道。

     “好啊好啊。”

     两人新建了一个名叫AppleGame的工程,然后老C添加了一个main.c的文件,“本来在这里应当使用配置管理工具的,但是因为简单起见,我们就土 法炼钢,采用拷贝的方法记录版本吧。”老C一边说一边在硬盘上建立了一个新目录,起名为AppleGame_V0.01,“名字也随便起了,不用使用 VBD等等复杂的规范,但是你千万注意这只是例子,以后千万不要随便学啊。”老C解释道,“至于什么配置管理和版本命名规范,我们以后再说。”

     “槑……”小P有些晕,下意识的回答道。

     “我们的做法在一般面试、笔试的时候可千万不要用,完全是大炮打蚊子……在这里出现只是为了演示,然而如果你熟悉了这样的开发过程,再反过来使用更直接的方法应付面试和笔试还是比较容易的,”老C又补充道,“大炮一响,黄金万两……有时打打大炮也是有价值的,起码熟悉了开炮过程,这样以后用大炮打黄金就驾轻就熟了……”

     “……”小P决定无视老C的自言自语,心想人上了年纪就是有些罗嗦。

     “好,第一个任务,写一个main函数。”

     “这个简单。”小P应道。在文件上敲下几行代码。


void main()
{
}

     “唉,这样是有问题滴……”老C说到。

     “什么问题?”

     “规范……”老C回答,“我们应当这样写。”老C开始更改小P的代码。


int main()

{

    return 0;

}

int main(int argc, char* argv[])

{

    return 0;

}


     “我们两个中间选一个,因为这个程序没有命令支持,所以就第一个吧。”老C说到,“这个是C99的新标准,我们还是按照标准来吧。”老C补充道,“你可以 在学校图书馆查查《ISO/IEC 9899 : 1999》这个文档,里面说的很清楚。如果我们还使用原来的格式,可能在代码兼容性上会出问题,我们写出的代码就不能在所有编译器上编译通过……”

     “是么?这么复杂……”小P有些疑惑,“好吧,那么就这样吧。”

     “那么现在说说你解决这道题的思路吧。”老C问。

     “嗯,先设计一个循环队列,每个队列中的元素表示一个小朋友,1表示在对列中,0表示他不在对列中,然后开始按照规则玩游戏,直到剩下最后一个小朋友,然后在队列中找出这个剩下的元素,打印它的下标……就是座位号码啦……”

     “嗯,思路还算清晰,”老C评论,“我们姑且不论选择循环队列是否合适,就先按照这个思路来做,等到后面再评审更改吧。”然后他在文件中添加如下的语句。


int main()
{
    /* Initialize the queue. */

    /* Play the game, until the last one is found. */

    /* Search the last one's seat number.  */

    return 0;
}

     “好了,我们的第一版程序完成了。”老C拍拍手。
     “完了?”小P有些不敢相信。
     “是啊,”老C确定的说,“编程不只是写代码,代码 != 程序!当你开始进行思考的时候,就开始进行程序设计了,代码不过是程序的表达方式。如果人类的语言可以在计算机上执行,你刚才说的话,就是代码。是不是这样?”
     “嗯,有些道理……”
     “编译!好了,我们的第一版程序没有什么问题!”老C说完,将main.c文件拷贝到AppleGame_V0.01文件夹下面,然后又新建了一个AppleGame_V0.02的文件夹。
     “下来我们需要一个调试宏,”老P说到,“本来可以使用IDE为我们准备的debug和release编译选项,但是这里我们先不用,为了明白背后的道理,我们完全自己打造一个先。关于debug和release,我们以后再说。”
     “哦……”小P点点头。
     老C在main.c的开头写下如下代码。

#include <stdio.h>

#define PRINT_DEBUG_INFO


#if defined(PRINT_DEBUG_INFO)
#define MY_DEBUG(str)            printf(str)
#define MY_DEBUG_1(str, par)    printf(str, par)
#else
#define MY_DEBUG(str)
#define MY_DEBUG_1(str, par)
#endif // PRINT_DEBUG_INFO

     “这几个宏用于在调试的时候输出一些中间信息,”老C解释道,“如果我们想输出调试信息,只需要#define PRINT_DEBUG_INFO就可以了,否则就注释掉这个宏。这只是一些小技巧而已,没有什么神秘的。”
     “是吗?嗯,我看看……”小P琢磨着代码。
     “下来进行一些实质性的,”老C接着说,“但是之前我们要了解一个规则,用问题域的词汇去编程,而不是解决域。”
     “槑,什么叫问题域?解决域?”小P不解。
     “我写你看好了。”老C说道,然后在main.c文件中接下来的部分写下如下内容。

//////////////////////////////////////////////////////////////////////////
//
#define CHILDREN_NUM    20U

typedef int SEAT_NUM;
typedef enum tagEXIST_STATE { ABSENT, EXISTED } EXIST_STATE;
typedef struct tagCHILD
{
    SEAT_NUM       seatNum_;
    EXIST_STATE    existState_;
}CHILD;

#define QUEUE_LENGTH    CHILDREN_NUM
typedef CHILD QUEUE_CONTENT;
typedef struct tagQUEUE
{
    int size_;
    int index_;
    QUEUE_CONTENT queue_[QUEUE_LENGTH];
}QUEUE;

typedef struct tagAPPLE_GAME
{
    int currCountNum_;
    int childrenRemained_;
    QUEUE childrenQueue_;
}APPLE_GAME;

void InitAppleGame (APPLE_GAME* game);
int IsGameOver (APPLE_GAME* game);
void PlayGame (APPLE_GAME*  game);
int LastChildSeatNum (APPLE_GAME* game);



//////////////////////////////////////////////////////////////////////////
//
int main()
{
    APPLE_GAME theGame;
    int num;
    
    /* Initialize the game. */
    InitAppleGame(&theGame);

    /* Play the game, until the last child is found. */
    while (!IsGameOver(&theGame))
    {
        PlayGame(&theGame);
    }

    /* Search the last child's seat number.  */
    num = LastChildSeatNum(&theGame);

    printf("The last child's seat number is %d.\n", num);

    return 0;
}



//////////////////////////////////////////////////////////////////////////
//
void InitAppleGame(APPLE_GAME* game)
{
    MY_DEBUG("Init the apple game.\n");
}

int IsGameOver(APPLE_GAME* game)
{
    static int n = -1;
    
    MY_DEBUG("Only one child?\n");
    ++n;

    return n;
}

void PlayGame(APPLE_GAME* game)
{
    MY_DEBUG("Play game...\n");
}

int LastChildSeatNum(APPLE_GAME* game)
{
    int n = 1;
    MY_DEBUG("Searching last child's seat number\n");

    return n;
}

     “喏,就是这个意思,尽量用现实生活的语言对需要解决的问题进行描述,并将关系相近的变量用结构体放在一起。”老C说,“然后将对这些名词的操作写成可以 用现实生活语言表达的函数,并将结构体作为函数的入口参数传入函数中。”老C咽了一口唾沫,“咳咳,你再比较比较我们这两版的注释有什么变化?”
     “叫我看看……”小P开始比较代码,“哦,在这个版本你用 game 代替了 queue,用 child 代替了 one, 但是有什么实质区别?”小P有些不解。
     “嗯,这个是一个用问题域词汇编程而不是解决域词汇编程的例子,最大的优点是意图明确,容易理解,代码可读性强;另外一个好处是相对稳定——比如用 game 代替 queue——其一,评审代码的人可能会不明白这个queue是做什么的,为什么和下面的初始化函数格格不入,从而造成你频繁的回答大量的沟通性的问题, 这将大大影响你生活的稳定性和质量;其二,如果我们将来——我是说如果——使用list数据结构来替换queue,避免了还要更改注释的风险——代码更新 而注释陈旧,正是我们在进行项目开发时一个特别特别特别的n次幂严重的问题……而使用问题域的词汇,只要需求不发生变更,则我们就不需要修改什么而导致一 些……代码人格上的分裂……”
     “哦,我再消化消化……”小P开始看代码,“下面这些函数的实现是什么意思?”
     “嗯,是测试,”老C说,“我们先不看具体函数,先看看main()函数的主要结构。”

int main()
{
    APPLE_GAME theGame;
    int num;
    
    /* Initialize the game. */
    InitAppleGame(&theGame);

    /* Play the game, until the last child is found. */
    while (!IsGameOver(&theGame))
    {
        PlayGame(&theGame);
    }

    /* Search the last child's seat number.  */
    num = LastChildSeatNum(&theGame);

    printf("The last child's seat number is %d.\n", num);

    return 0;
}

     “我们用实际的代码完善刚才的注释——刚才的注释其实就是伪代码的一部分——然后在框架函数中加入测试代码,检验我们的算法是否可行。”老C解释道,“现在我们的算法一目了然,你看看是否是用问题域的词汇表达算法更清晰一些呢?比一些a,b,c之类,或者其类似的解决域内的名字更好理解吧?”
     “哦,我再看看……”小P答道,“我要消化一下……”
     “嗯,”等小P抬起头,老C补充道,“接下来一个重要的规则是先构思如何测试,更先于编码!”
     “稍等,”可能被新的信息灌输的有些头晕,“这个是什么意思?”小P有些反应不过来。
     “就是说,在编码之前,我们要先想好如何测试我们即将要编写出来的代码。我们的代码是否易于被测试,关系到我们代码质量的生命!”老C解释道,“如果你一 开始就考虑到这些问题,并留有充分的余地,那么在做代码自测和测试人员测试时,会节省组织内部大量的精力……算了,这些也是要靠编写代码的规模积累起来的 经验,你以后会慢慢的明白的。但是,无论如何你在编写代码的时候要保持足够的意识,要不断提醒自己,我所写的代码易于测试吗?”
     “好,我记住了。”小P说。
     “呵呵,其实经历了一些挫折你才会真正明白——不过就算建立了概念也不错。”老C笑道,“现在你编译并运行一下代码吧,观察一下屏幕输出的信息……”
     “好,”小P看了看文件底部的函数实现,然后又看了看屏幕输出信息,“哦,算法的脉络这样看就比较清楚了,果然我脑海中就是这么想的,不过现在更具体,也好追踪了。”
     “O.K.!我们第二版的程序又有了!”
     “这么快?为什么?”小P不解道。
     “我们验证了算法,证实算法框架运行与设计——就是你脑海中的步骤——是一致的,这样当然ok了!”老C一边说一边将main.c拷贝到AppleGame_V0.02,并且又新建了一个AppleGame_V0.03目录。

(请等待V0.03版本)

posted on 2009-01-23 03:36 Anderson 阅读(1749) 评论(6)  编辑 收藏 引用

评论

# re: 第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一)[未登录] 2009-01-23 12:40 Len

看来更新的速度很快呀,又要结册出版了  回复  更多评论   

# re: 第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一)[未登录] 2009-01-23 16:45 ypp

非常不错,原创作品,极力支持  回复  更多评论   

# re: 第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一) 2009-01-23 17:22 winsty

现在用纯C开发的机会太少了吧
个人觉得某些规范在OOP下并不合适  回复  更多评论   

# re: 第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一) 2009-01-23 23:55 imnobody

真的很棒,i like  回复  更多评论   

# re: 第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一) 2009-02-04 00:24 zoujer

此章学到很多东西,期待更精彩的~~~~~  回复  更多评论   

# re: 第一桶 从C到C++ 第六碗 陈老C演迭代开发 潘小P学渐进编程(之一) 2009-02-04 09:03 tmhlcwp

之前在"C++博客"上有看到过同风格的博文,但现在找不到了,有谁知道的告诉下哈,谢谢了  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(6)

随笔档案(21)

文章档案(1)

搜索

最新评论

阅读排行榜

评论排行榜