C++软件开发模块化的研究(3)

camelyi 2005-05-23 02:38:48
<3> 依赖和版本管理

依赖是一件可怕的事情吗?

我个人认为依赖关系是软件开发过程彻底崩溃的源泉之一.有一次我企图借用另一同事开发的一个窗口控件.首先我从他那获得了.h和.cpp文件,(假设我得到的是.h和.lib,其实结果也会差不多).此时我发现.h文件中包含了另外几个.h文件.我不得不再次拷贝,而且一些.h文件必须结合.cpp组件才能编译连接.如此循环,最后带来超过100个源代码文件.我试图从中剥离我所需要的控件,但是发现非常困难,这些文件之间紧密耦合,互相依赖.取出其中任何一个有用的部件都非常困难.

实际上这样的代码基本上是无用的代码.也许在一个项目中,它已经起到了它应该起的作用.但是当我们再次需要这样的功能,或者仅仅想做一些功能上的改进的时候,我们不得不重新做起,忘记它们.因为有太多的依赖关系使那些代码绑定在一起.无法分离.

当一个中小规模的软件开发项目不断升级膨胀,而且不加任何管理的情况下,最终产生的大量代码都是如此,最后开发过程崩溃只是个时间问题,主要看产品产生之前崩溃(开发失败),还是产品产生之后(幸运成功,但是谁都不想继续折磨自己了).

1.必须严厉禁止模块之间的循环依赖.

这是很多专著中的共识.实际上在c++开发中这是完全可能的.A依赖B,B依赖C,C依赖A这种情况,导致A,B,C三个模块没有任何一个可以单独使用,实际上模块划分已经失去了意义.

2.不要让模块接口的间接依赖干扰你的开发.

开发过程中,如果始终只要关心直接依赖的模块的头文件,工作会轻松许多.当然二进制的间接依赖是不可以避免的.也就是说,如果我的工程依赖模块A(a.h,a.lib),而模块A依赖模块B,那么,我仅仅需要

#include "a.h"

则轻松搞定.当然连接的时候,我还是需要b.lib.这是二进制间接依赖.而接口方面则没有间接依赖的问题.

当然,倘若我的工程中确实使用了B模块,则情况应该是这样:

#include "a.h"
#include "b.h"

根据前两节所论述的一些原则,a.h和b.h中都不含有另外的#include指定,所以不会引入更多依赖.

下面我们用箭头表示依赖关系. A->B,则表示A依赖B.

每个模块都可能会不断升级,而不论它在依赖树的哪个节点上.假设有这样一个依赖树:

A->B->C-+->E
|
+->D->F

A本来一直运行良好.但是树中某个模块升级之后,A运行出错.此时A最需要的是恢复原来版本的模块.头文件的影响甚小(仅仅涉及模块B),主要是二进制依赖.可以在开发A的时候,对所有CVS服务器上的lib打上标记.这样失败的时候,我能把以前运行良好的lib再check out出来.

这个bug将有B来修改(如果A模块开发者提交B模块的Bug报告),但是一个依赖模块具体版本图将提交给B模块开发者作为参考:

无bug状态:

A->B(1-0-1)->C(1-2-2)-+->E(1-2-4)
|
+->D(2-1-1)->F(3-2-4)

出错状态:

A->B(1-0-1)->C(1-2-4)-+->E(1-2-4)
|
+->D(2-1-2)->F(3-2-4)


对B模块的开发者来说,是非常好的参考,显然问题在于C和D的版本升级.如果B开发者能提出模块C的bug报告,那么问题将由C开发者解决.反之,B必须自己解决.

以上的管理方式如果要良好运作,至少应该有以下几个条件:

1.有良好的方式避免模块开发者之间互相推诿责任,拖延bug的解决,并必须使他们需要的沟通减少到最少.否则招来的是没完没了的会议和讨论,沟通的时间挤占开发的时间.

2.能方便的取得和管理依赖树上每个模块的版本.这是一个纯技术的问题.

合理的分工可以极大的减少需要的沟通.首先我们确定同一个模块只由一人(或者管理上视为一人的一组人)开发维护,但是绝不是一人只能开发一个模块.相反,我主张模块不要太大(巨大的模块带来的问题总是比小模块多),因此一人开发多个模块是正常的和必然的.考虑上边的树:

A->B->C-+->E
|
+->D->F

A与B由一人开发是合理的.此人只需要直接依赖模块C.C和E由一人开发也是合理的,此人仅仅需要直接依赖D.同理D和F可以由一人开发,此人仅仅需要向C的开发者负责.

如果是两人划分,A与B一人开发,C,E,D,F一人开发不错.或者ABCE/DF的划分也不错.

反之,AC/BE这样的划分,就非常让人难堪了.此二人有相互依赖的关系,导致更多的沟通.

1.让开发者和开发者之间的关系尽量保持单线单向负责.

2.把让开发者之间的负责关系减少到最少.

3.关键模块和与之依赖的无关紧要的旁支交给同一个人开发.

如下图:

A->B->C-+->E-+
| |
+->D-+->F

所谓的关键模块是和与之相关的模块多的模块,无关紧要的旁支则是与之有关系的模块少的模块.现在统计一下各个模块的"关键性":

A : 1
B : 2
C : 3
E : 2
D : 2
F : 1

以上数字实际上是一个节点的相邻节点数而已.

F和A可以说是旁支,仅仅和一个模块有关系.而C是关键模块.C应该和A,B交给同一个人开发,或者至少应该附加B或者E.

有专门的软件可以解决"方便的取得和管理依赖树上每个模块的版本"的问题.

...全文
187 2 打赏 收藏 转发到动态 举报
写回复
用AI写文章
2 条回复
切换为时间正序
请发表友善的回复…
发表回复
mis98ZB 2005-05-24
  • 打赏
  • 举报
回复
我觉得在lib提供的头文件中不含有#include基本上是不可能的。
如果强行这么做,那么在去除依赖的同时,必定会发生重复。
解耦与去除重复,就像鱼和熊掌一样……
如果为了同时避免耦合与重复,而把太多的东西放在一起,那么又产生了内聚的问题……
mis98ZB 2005-05-24
  • 打赏
  • 举报
回复
同意楼主关于任务分配的观点。
把耦合度高的工作交给一个工作单元(可能是一个人,也可能是一个小组),降低对工作单元之间交流的依赖,这是任务分配的常识。
【※请注意我是说减小对交流的依赖,不是说忽视交流。】

64,633

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧