兼容性问题思考

最近思考并实践了关于兼容性方面的一些内容。兼容性是一个硕大的话题,什么是讨论的范围是什么?谈谁与谁的兼容。其次什么是兼容?兼容如何定义。

从具体的应用软件(可被用户直接使用,有人机交互的软件),到软件组件(library)都有兼容性问题。比如win2000与win98, vista与winxp,又比如gtk1.2与gtk2.0,再比如x86系列CPU,从80386开始就一直保持兼容。普遍认为,兼容是一块砖,越背越累,兼容在一定时间,一定程度内具有价值,但最终必然要被不兼容的新品替代。

那让我们来思考,以一个应用程序为例,什么是兼容?我理解,兼容就是你在之前版本所提供的功能,在之后的版本中也依然以同样的形式提供,且没有任何语义上的改变。这个看似简单的规则,却有不少陷阱。

首先,什么是功能?如何详细的定义出软件的功能。很多时候,连软件的开发者都没有想到自己的软件居然可以被这样使用。但功能就是,一旦被人察觉并被大量使用,它就是功能,不论你之前是不是称它为功能。

其次,什么是功能提供的的形式。就是人机交互组合,对于应用软件来说,可以是点击按钮(GUI),可以是执行命令(CMD)。如果之前都完成一个功能,需要点击A,B,C三个按钮,而新版本简化成为点击A,C两个按钮,这算不算兼容?

再次,什么是语义的改变?任何可以被用户觉察的改变都可以理解为语义改变。比如外部依赖。

如此严格的定义,对于应用程序来说基本没有意义,但是对于以二进制形式发布的library来说,却是有价值的。

我们知道,当今软件产业是建立在大量基础之上的精密技术,软件越来越庞大,任何一个环节的问题都有可能导致软件的崩溃和大面积错误。因此,分治法在软件行业是必须的。谈到分治,是指软件组件可以独立编写,测试,稳定和发布。可以在被组装成应用软件之后,保持独立升级。

软件组件如何才能保持兼容?首先要看我们需要的是backward还是forward兼容。其次要看我们是要在保持源码级还是二进制级的兼容。目前来说很多开源软件都可以做到backward的源码级兼容。backward是指之前提供的接口,在新版本中依然提供。源码级是指,使用新版本必须重新编译使用该组件的那部分代码(但不需要修改)。

先说说二进制和源码级的兼容之间有多少差距?是什么导致了无法达到二进制级兼容。

首先,二进制兼容必须先基于某个处理器架构。比如x86, ARM, MIPS都有自己的二进制标准。

再次,必须基于某个二进制文件标准,如ELF。

然后才是语言一级的,对于C语言来说,二进制级的兼容要求我们不能修改向用户暴露的函数及全局变量的名字,结构体定义和大小(成员顺序,成员个数,追加在是不安全的),枚举的顺序(追加在某些时候是安全的)。

比如,之前有结构体A位于向用户暴露的头文件中,

typedef struct

{

    int m1;

    int m2;

} A;

在新版本中,如果修改为

typedef struct

{

    int m1;

    int m2;

    int m3;

} A;

这就是不兼容,为什么?因为使用老版本的用户可能在他的代码中使用过"sizeof(A)"。同样,删除结构体的成员,变更成员之间的顺序统统会导致不兼容。因此,结构体一旦暴露给用户就无法再修改。

所以,要想二进制兼容,最好不要向用户暴露结构体。实在暴露了,在新版本中只能重新定义一个新版本的结构体,如结构体A2,这种手法也被一些组件使用。

再谈谈forward兼容。forward对于组件来说可能不太好理解,以应用软件word为例。如果word97可以打开word2003编写的文档,这就是forward兼容。forward是比较难的,原因在于它必须提供一个用于承载变化的不变的抽象规范,比如很多文件格式中的tag&value模式。而对于软件组件来说,最典型的forward兼容现实就是COM的QueryInterface,或者是DLL的load,它们都是根据字符串来寻找函数的入口。又如gtk的gobject使用key和value的方式来设置对象属性,一个set接口就统一了各种各样的属性设置,这也是一种forward设计。不难发现各种C语言的forward设计中都会使用到字符串这一特殊类型。

兼容性设计在基于一个基本的规范之后,并不会过多的增加软件研发成本,而兼容性的意义在于降低客户升级的成本,从而体现软件本身的价值。

猜你喜欢

转载自blog.csdn.net/ison81/article/details/5625572