zoukankan      html  css  js  c++  java
  • 系统程序员成长计划Don’t Repeat Yourself(DRY)(下)

    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    实现这两个函数并不是件难事,但真正写好的人并不多。初学者通常的做法有两种:

    1.各写一个独立的函数。dlist_find_max用来找出最大值,dlist_sum用来求和。这种做法和前面写dlist_print时所犯的错误一样,会造成重复的代码,让dlist的实现随着应用环境的变化而变化。

    2.采用回调函数法。细心的初学者会发现,这两个函数的实现与dlist_print的实现很类似,无非是print那行代码要换成别的功能。能想 到这一点很好,不过在真正动手时,发现每个回调函数都要保存一些中间数据。大部分人选择了用全局变量来保存,这可以实现要求的功能,但违背了禁用全局变量 原则。

    这两个函数没有什么实用价值,但是通过它们我们可以学习几点:

    1.不要编写重复的代码

    按传统的方法写出dlist_find_max之后,每个人都知道这个函数与dlist_print很类似,在写出dlist_sum之后,那种感 觉就更明显了。在这个时候,不应该停下来,而是要想办法把这些重复的代码抽出来。即使因为经验所限,也要极力去想思考和查资料。

    写重复的代码很简单,甚至凭本能都可以写出来。但要想成为优秀的程序员,你一定要克服自己的惰情,因为重复的代码造成很多问题:

    重复的代码更容易出错。在写类似代码的时候,几乎所有人(包括我)都会选择Copy&Paste的方法,这种方法很容易犯一些细节上的错误,如果某个地方修改不完整,那就留下了”不定时”的炸弹,说不定什么时候会暴露出来。

    重复的代码经不起变化。无论是修改BUG,还是增加新特性,往往你要修改很多地方,如果忘掉其中之一,你同样得为此付出代价。请记住古惑仔的话,出来混迟早是要还的。大师们说过,在软件中欠下的BUG,你会为此还得更多。

    去除重复代码往往不是件简单的事情,需要更多思考和更多精力,不过事实证明这是最值得的投资。在这里,我们要怎么抽取这些重复的代码呢?

    这三个函数无非是要遍历双向链表并做一些事情,遍历双向链表我们可以提供一个dlist_foreach函数,至于要做什么,这是千变万化的行为,可以通过回调函数让调用者去做。

    2.任何回调函数都要有上下文

    大部分初学者都选择了回调函数法,不过都无一例外的选择了用全局变量来保存中间数据,这里我不想再强调全局变量的坏处了,记性不好的读者可以看看前面的内容。我们要说的是,在这种情况下,如何避免使用全局变量。

    很简单,给回调函数传递额外的参数就行了。这个参数我们称为回调函数的上下文,变量名用ctx(context的缩写)。要在这个上下文中存放什么东西呢?那得根据具体的回调函数而定,为了能保存任何数据类型,我们选择void*表示这个上下文。

    下面我们看看怎么实现这个dlist_foreach:

    DListRet dlist_foreach(DList* thiz, DListVisitFunc visit, void* ctx)
    {
    DListRet ret = DLIST_RET_OK;
    DListNode* iter = thiz->first;

    while(iter != NULL && ret != DLIST_RET_STOP)
    {
    ret = visit(ctx, iter->data);

    iter = iter->next;
    }

    return ret;
    }

    visit是回调函数,ctx就是我们说的上下文。要特别强调的一点是,ctx应该作为回调函数的第一个参数。为什么呢?在前面我们讲过的面向对象 的函数命名规则中,我们以thiz作为函数的第一个参数,而thiz通常也就是函数的上下文。如果在这里恰好ctx==thiz,就不需要因为参数顺序不 同而做转换了。

    实现求和的回调函数:

    static DListRet sum_cb(void* ctx, void* data)
    {
    long long* result = ctx;
    *result += (int)data;

    return DLIST_RET_OK;
    }

    调用foreach:
    long long sum = 0;
    dlist_foreach(thiz, sum_cb, &sum);

    是不是很简单?以后在使用回调函数时,记得多加一个ctx参数,即使暂时用不着,留着方便以后扩展。好了,请读者用类似的方法实现查找最大值的功能吧。

    3.只做份内的事

    我见到不少任劳任怨的程序员,别人让他做什么他就做什么,不管是不是份内的事,不管是上司要求的还是同事要求的,都来者不拒。别人说需要一个XXX 功能的函数,他就写一个函数在他的模块里,日积月累后,他的模块变得乱七八糟的,成了大杂烩。我亲眼见过在系统设置和桌面两个模块里,提供很多毫不相干的 函数,这些函数造成不必要的耦合和复杂度。

    在这里也是一样的,求和和求最大值不是dlist应该提供的功能,放在dlist里面实现是不应该的。为了能实现这些功能,我们提供一种满足这些需求的机制就好了。热心肠是好的,但一定不能违背原则,否则就费力不讨好了。

    本节的示例请到这里下载


    欢迎到Linux mobile development上交流




  • 相关阅读:
    OSX安装nginx和rtmp模块(rtmp直播服务器搭建)
    用runtime来重写Coder和deCode方法 归档解档的时候使用
    Homebrew安装卸载
    Cannot create a new pixel buffer adaptor with an asset writer input that has already started writing'
    OSX下面用ffmpeg抓取桌面以及摄像头推流进行直播
    让nginx支持HLS
    iOS 字典转json字符串
    iOS 七牛多张图片上传
    iOS9UICollectionView自定义布局modifying attributes returned by UICollectionViewFlowLayout without copying them
    Xcode6 iOS7模拟器和Xcode7 iOS8模拟器离线下载
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167591.html
Copyright © 2011-2022 走看看