系统程序员成长计划-Don’t Repeat Yourself(DRY)(下)
2,234 views| 2008-11-20| 李先静| 系统程序员成长计划| | 9 条评论转载时请注明出处和作者联系方式
文章出处:http://www.limodev.cn/blog
作者联系方式:李先静 <xianjimli@gmail.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里面实现是不应该的。为了能实现这些功能,我们提供一种满足这些需求的机制就好了。热心肠是好的,但一定不能违背原则,否则就费力不讨好了。
本节的示例请到这里下载。
系统程序员成长计划 Share
Comments
Tags
Recent Posts
Most Viewed
- 系统程序员成长计划写作提纲 - 19,604 views
- Android IPC机制详解 - 6,277 views
- 系统程序员成长计划-走近专业程序员(上) - 6,253 views
- 系统程序员成长计划-写得又快又好的秘诀(一) - 5,390 views
- 系统程序员成长计划-背景知识 - 5,070 views
- i++循环与i–循环的执行效率 - 4,712 views
- 系统程序员成长计划-Write once, run anywhere(WORA)(上) - 4,700 views
- 系统程序员成长计划-走近专业程序员(下) - 4,254 views
- Linux下的调试工具 - 4,017 views
- Advanced Linux Sound Architecture (ALSA) 研究笔记 - 4,017 views
- 系统程序员成长计划-序 - 3,985 views
- 系统程序员成长计划-写得又快又好的秘诀(三) - 3,929 views
- 中国人与自由软件文化研究(搞笑版) - 3,735 views
- Android中的MessageQueue,Handler,Looper和Thread - 3,686 views
- 答复:我不会OOO,仍然可以XXX - 3,658 views
Categories
- Android (28)
- Broncho-A1-Hack (6)
- DirectFB (7)
- FTK(嵌入式GUI) (24)
- GTK+ (29)
- KVM hack notes (8)
- Linux Mobile (65)
- Management (5)
- Mozilla (9)
- Open Source (5)
- Programming (34)
- Tools (9)
- Uncategorized (23)
- Win32 (3)
- X Windows (31)
- 沉思录 (29)
- 系统程序员成长计划 (67)
Blogroll
gallery
Linux guru
推荐网站
Recent Comments
- Dig on 嵌入式GUI FTK设计与实现-事件源(FtkSource)
- 用心生活每一天 » GNU gprof: linux profiling tools 使用 on gcc profiling的工作原理
- JavaScript for: i++ vs i–-传播、沟通、分享-一直“有你” on i++循环与i–循环的执行效率
- Frankly Law on 嵌入式GUI FTK介绍(11)-交叉编译
- tracing on Linux下的调试工具
- ndljsn on FTK移植指南(初稿)
- tracing on 爬塘朗山
- tracing on GTK+(基于DirectFB)的字体处理
- Kely on 系统程序员成长计划写作提纲
- tracing on 爬塘朗山



November 20th, 2008
Joey.Huang
November 20th, 2008
连续了看了几天,还是决定再冒出来赞一个。作者的表达功力确实雄厚。有志于学习编程的朋友跟着作者来学,可以学到很多东西。
Dig
November 21st, 2008
“求和和求最大值不是dlist应该提供的功能,放在dlist里面实现是不应该的”
在这样的东西里面,是不是可以提供一个dump (Ex: dlist_dump) 函数,在调试时候方便把整个链表打印出来?
nihao
November 21st, 2008
连续看了很多您的大作,很过瘾,如果能专门出一本书系统讲解这些,我相信会有很多向我这样的大学生购买的!毕竟好多知识是大学里学不到的,感谢您的经验分享,让我少走了很多的弯路。
如果您出书,我第一个购买,并帮助宣传!!
admin
November 21st, 2008
谢谢大家关注和支持。
to Dig: 你可以看前一章的dlist_print实现。
The linux mobile development » Blog Archive » 系统程序员成长计划写作提纲
May 25th, 2009
[...] once, run anywhere(WORA)(上)(下) 1.4 拥抱变化(上)(下) 1.5 Don’t Repeat Yourself(DRY) (上)(下) 1.6 你的数据放在哪里(上)(下) 第2章 写得又快又好的秘诀 完成 2.1 [...]
zsm
August 6th, 2009
需要修改下
static DListRet sum_cb(void* ctx, void* data)
{
long long* result = ctx;
*result += (int)data; //*result += *(int*)data;
return DLIST_RET_OK;
}
admin
August 8th, 2009
谢谢。不用改,void* data其实是int data,因为里面存放的是整数。
steven
April 2nd, 2010
ctx应该作为回调函数的第一个参数? 我没有看明白, 哪位能帮解释一下?
李先静
April 7th, 2010
thiz是面向对象函数的第一个参数,它通常也就是函数ctx。这样就可以与这类函数保持一致。