代码复用在软件开发中存在两个层次。第一个层次是,在设计一个新的软件功能或是开发一个新的项目时,复用已存在的软件模块,这种复用或许称之为设计复用更好。另一个层次是,程序员在开发一个软件模块时,模块的内部应尽可能地复用。从编程习惯的角度来看,这里指的是后者。
现在假设存在一个双向链表(Double-Linked List, DLL)的一个模块,如果这个模块在开发的过程中,已经存在了两个函数,分别是dll_push_tail()和dll_pop_head(),这两个函数的作用分别是将一个新的节点加入到链表的尾部以及从链表中删除并返回头节点。其代码实现如下所示。
以下为引用的内容:
dll.c 00088: void dll_push_tail (dll_t *_p_dll, dll_node_t *_p_node) 00089: { 00090: if (0 ==_p_dll->tail_) { 00091: _p_dll->head_ = _p_dll->tail_ = _p_node; 00092: _p_node->next_ = _p_node->prev_ = 0; 00093: } 00094: else { 00095: dll_node_t *p_tail = _p_dll->tail_; 00096: 00097: p_tail->next_ = _p_node; 00098: _p_node->prev_ = p_tail; 00099: _p_node->next_ = 0; 00100: _p_dll->tail_ = _p_node; 00101: } 00102: 00103: _p_dll->count_ ++; 00104: } 00105: 00106: dll_node_t *dll_pop_head (dll_t *_p_dll) 00107: { 00108: dll_node_t *p_node = _p_dll->head_; 00109: 00110: if (p_node != 0) { 00111: _p_dll->count_--; 00112: _p_dll->head_ = p_node->next_; 00113: if (0 ==_p_dll->head_) { 00114: _p_dll->tail_ = 0; 00115: } 00116: else { 00117: p_node->next_->prev_ = 0; 00118: } 00119: } 00120: 00121: return p_node; 00122: } |
如果此时需要增加一个新的链表操作函数dll_merge(),用于合并两个链表。则这个函数的实现可能如图2所示。其思路也很简单,就是从_p_src链表中将一个个的节点取出并放到_p_dest链表的尾部。
以下为引用的内容:
dll.c 00165: void dll_merge (dll_t *_p_dest, dll_t *_p_src) 00166: { 00167: dll_node_t *p_node = _p_src->head_; 00168: 00169: while (0 != p_node) { 00170: if (0 ==_p_dest->tail_) { 00171: _p_dest->head_ = _p_dest->tail_ = p_node; 00172: _p_dest->next_ = _p_dest->prev_ = 0; 00173: } 00174: else { 00175: dll_node_t *p_tail = _p_dest->tail_; 00176: 00177: p_tail->next_ = _p_dest; 00178: _p_dest->prev_ = p_tail; 00179: _p_dest->next_ = 0; 00180: _p_dest->tail_ = _p_dest; 00181: } 00182: 00183: _p_dest->count_ ++; 00184: p_node = p_node->next_; 00185: } 00186: 00187: _p_src->count_ = 0; 00188: _p_src->head_ = 0; 00189: _p_src->tail_ = 0; 00190: } |
有问题吗?从功能性的角度来说没有问题,但是从可维护性方面来看,这一实现并不好,取而代之的更好实现是通过代码复用的方式,如下所示。
以下为引用的内容:
dll.c 00175: void dll_merge (dll_t *_p_dest, dll_t *_p_src) 00176: { 00177: dll_node_t *p_node = dll_pop_head (_p_src); 00178: 00179: while (0 != p_node) { 00180: dll_push_tail (_p_dest, p_node); 00181: p_node = dll_pop_head (_p_src); 00182: } 00183: } |
显然,采用代码复用的方式,其可读性更好,也更容易维护。在实现一个软件模块时,应当考虑从所需实现的功能中抽取出一些公共的基本函数(比如,这里谈到的dll_pop_head()和dll_push_tail()),且这些函数所实现的功能是正交的(即功能没有重叠)。接下来,其它的功能(比如这里谈到的dll_merge())可以考虑采用搭积木的方式,通过运用那些最基本的函数去实现。
需要注意的是,采用复用方式实现的dll_merge()引入了函数调用,而函数的调用因为存在参数的传递可能会带来一定的处理器开销,其开销的大小与处理器的处理能力有关。但是,对于现代的大多处理器来说,这种开销都是很小的,且小到可以几乎忽略不计。另外,如果要去除函数调用所带来的开销,可以考虑采用inline的方式。拿这里的dll_merge()的实现为例,如果dll_push_tail()和dll_pop_head()被定义为inline的话,则dll_merge()中调用这两个函数就完全不存在函数调用的开销了。