对很多人来,嵌入式软件开发过程中 模块化(Modularization)是一个海市蜃楼、是一个书面词汇、是一个过气的时尚——模块化似乎从未真正的实现过。吹牛时人们常不屑的说:没吃过猪肉,但还没看过猪跑么?事实上,如果讨论的对象是嵌入式软件,很多人可能真的没有看过猪跑。在话题变得更像都市传说的之前,我想问一个问题:
为什么要模块化?
有经验的人会说:
为了代码复用(Code Reuse)
进一步——“为什么模块化可以实现代码复用呢?”很多人会说:
你这不是抬杠吗?明摆着的,代码做成了模块,那么别的项目就可以直接使用了,模块里的这部分代码就得到了复用。
更进一步——“代码复用又是为了什么呢?”听到这里项目经理们深吸了最后一口烟屁股,顺手丢到脚边、踩灭、起身准备离开:
代码复用可以节省开发时间,加快项目研发速度。
为了把嘴边的那句“你们慢慢聊,我还有事”噎回去,我们再问一个问题:
实际项目开发中,用模块的时候,项目的进度真的加快了么?时间真的节省了么?
项目经理们不动了,抬起到半空中的屁股慢慢的坐了下来。这次,他们的语气是认真的:
不,使用模块通常并不一定能加快项目进度。老实说,用别人的模块,程序员常常要认真理解模块的功能和代码才能在调试的时候确认问题的范围。你知道,很多时候看懂他人代码所用的时间比自己重新设计一个更长。
周围不少程序员都投来赞同的眼光,有的甚至很认真的点了点头。实际上,这里我们已经发现,在实践中,抛开用于模块化的技术不谈,使用模块实现代码复用本身往往并不能加快一个团队的开发速度——那么我们要模块化做什么?
下结论还为时尚早。从项目经理们的描述可以看出:
- 代码复用的目的或者说动机是节省开发时间
- 实际执行中,程序员因为种种原因,在使用模块时总是要花费大量时间读懂了代码才能“放心地”去使用它。
根据我们在《什么是嵌入式(上)》中的描述,程序(软件)是“程序员尝试去固化的自己的思维”;而模块(硬件)则是“业已固化的逻辑”,读懂一段程序,实际上就是要通过死的代码逻辑去反推模块构作者的思维,这是一个逆向过程,这是一个人与人之间用代码进行间接交流的过程,当逻辑本身较为复杂时,显然比将自己的思维直接翻译成程序(重新开发一个)更为困难。
通过上面的分析,很容易看出,模块化就是为了通过复用代码来加快开发速度,而正是程序员阅读要复用的代码让这一努力付之东流。由此,我们可以非常直接的得出结论:
使用模块时,必须阻止程序员阅读要复用的代码
或者换一种说法:
使用模块时,必须专注于模块的使用,而必须有意忽视模块的实现逻辑,必须要在心理上信任模块。简而言之,必须把模块视作黑盒子!
很容易发现,上面的结论是站在项目经理的视角得出的,因为项目经理关注的是项目本身,是各类资源的合理利用,是项目的进度——项目经理唯一不需要也不应该关注的是具体的技术实现细节。那么从第一线程序员的视角来看这个问题:
为什么程序员要阅读模块的代码实现呢?
笔者问过不同从业时间/经验的程序员,从过来的的角度来看,无非是以下几个原因:
- 学习目的——想知道别人是怎么实现的。很多程序员认为通过阅读别人的代码能够快速的学习他人的经验从而提升自己。 然而,从项目管理的角度来看这个问题,程序员利用业余时间阅读他人的代码来提升自己无可厚非,或者说是值得提倡的,但牺牲宝贵的项目时间来阅读模块的实现代码而不是专注于模块的使用(使用模块快速的实现项目所需的功能),这对项目本身是弊远大于利的——阅读代码带来的是程序员的能力提升,这是对团队来说的远期利好,但这一利好对项目本身的时效性却微乎其微——俗话说远水不解近渴就是这个意思。 实际上,项目经理通常要根据程序员的已有能力来分配任务,而不会寄希望于程序员通过阅读模块代码获得提升以后再来回报眼前这个火烧眉毛的项目——如果真有项目经理这么做了,那只能说,进度慢了完全不是程序员阅读模块代码的错,而是他最直接的用人问题——我也只能相信,也许他真的无人可用了。 所以结论就是:严禁工作时间以学习为目的阅读模块源代码。
- 调试目的——也许并非所有的程序员都对自己的代码质量天然的自信,但几乎所有的程序员都对别人写的代码(模块)天然的不放心——就像孔乙己一样,必须亲眼看了酒保从黄酒坛子里舀出酒来而没有掺水才放心——所以程序出了问题,必然要怀疑模块,而且甚至有很多不负责任的程序员天然的会首先怀疑模块——不是自己写的,怎么能放心——所以调试的时候必然:
- 要有源代码,否则就不会调试了
- 必然要阅读模块的代码,否则就不知道究竟这个源代码是不是对的
- 必然要读懂模块的代码,否则怎么能体“自己的程序出错完全是模块的代码写的不好”。
对于这种情况,就我个人来说,只有一条准则——不提供源代码!只提供库文件——相信我,通常面对汇编代码熟手无策的程序员会在调试的时候自动忽视模块的实现细节,专注于模块接口的输入输出行为——给什么输入,期望什么输出,实际获得什么输出——一目了然,简单直接。如果真的期望输出和观察到的实际输出不同,问题也就找到了:要么是文档没有读好,对输入输出的理解有误;要么是输入就有错;要么就是模块有问题。这绝对比读懂源代码以后再来调试要快得多!——除非这个别人写的模块需要你来维护……所以说,调试的时候
根!本!不!需!要!读!模!块!的!源!代!码!
根!本!不!需!要!读!模!块!的!源!代!码!
根!本!不!需!要!读!模!块!的!源!代!码!
以调试作为阅读模块的源代码的理由,根本就站不住脚!
- 仿制目的——这个目的没啥好说,别人把源代码给你就是个错误。请大家自觉抵制无视他人知识产权的行为。从技术上来说,因为要实现自己的版本而需要阅读他人的实现,理解他人的思维,这是一种白盒子行为,因而并不属于正常使用模块的范畴,属于普通的开发范畴。
既然在模块的使用过程中,无论是学习目的还是调试目的都不需要阅模块的源代码,那么可以明确的得出结论:程序员在使用模块的过程中完全不需要,也不应该浪费项目的时间来阅读源代码。一个团队只有做到了这一点,才能借助代码复用加快项目开发的速度。
当一个团队的项目经理理解了“阅读模块代码”对项目的巨大危害,并以制度的形式对程序员的这一行为予以了制止——移除了模块化实践的绊脚石;那么技术经理应该如何理解、设计和实践适合于当前团队和项目需求的模块化架构呢?
欲知详情,请听下回分解。