• 系统程序员成长计划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里面实现是不应该的。为了能实现这些功能,我们提供一种满足这些需求的机制就好了。热心肠是好的,但一定不能违背原则,否则就费力不讨好了。

    本节的示例请到这里下载

  • 相关阅读:
    官场22条潜规则,职场谁说不是呢
    pomelo使用中的常见问题
    马斯诺需求金字塔
    Mac上使用brew安装nvm来支持多版本的Nodejs
    Redis 集群解决方案 Codis
    Linux下压缩某个文件夹(文件夹打包)
    使用forever运行nodejs应用
    nodejs npm常用命令
    Mac安装Brew
    C#2.0 迭代器
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167482.html
Copyright © 2020-2023  润新知