一个C实现的DLL, 导出了大量的函数供C#写的主程序调用. 也有少数几个供另外的独立应用程序和第三方调用, 当整个团队忙着开发主程序时, 为了一个新功能修改了其中一个导出函数的原型: 在函数参数列表最后新添加了两个参数. 编译器的原型检查保证了主程序中调用该函数的地方都提供了正确参数个数和顺序, 每个人都把第三方独立应用程序的支持忘记了, 直到参加展会的最后阶段, 才开始忙着测试各种可能要用到的东西, 这时候才发现第三方独立程序调用这个DLL时程序行为就不正确了, 具体怎么个不正确法这里不说, 程序出了错误, 往往是现象离原因太远, 太不沾边. 这里顺着描述很容易理解整件事情, 从整个事情的时间顺序上来看, 跨时很长, 多处误猜.
怎样可以避免这样的Bug?
DRY(Don't repeat yourself)
我们的第三方独立应用程序是这样引用该导出函数的:
1. 在第三方程序中写出 该函数的原型.
typedef void (*exported_func)(type1 arg1, type2 arg2);
如何知道这个原型的? 同一个公司内部, 程序员之间的沟通, 再具体的情形就是一个人在办公室隔间里吼一嗓子, 说谁知道某某功能代码在哪, 有人回应, 指出CVS上的位置, 或者直接共享给他一段代码, 此人据此写出该函数的原型.
问题就出在这种沟通上, 这种类型的沟通和协作只能保证沟通发生的那个时间点是有效的, 随着时间的推移, 这个函数在主程序中演进了, 它需要更多的参数, 而它演进的时候, 当初回应那一嗓子的人也许已经不在公司, 也许不再负责这块程序, 没有人/没有一种机制/更没有一种制度保证这种改变发生时, 受这一改变发生影响的人都能得到通知.
2. 通过Product code找到主程序的安装路径, 从而找到DLL的绝对路径名
3. LoadLibrary, GetProcAddress
将返回的地址赋值给1中定义的函数指针的一个实例.
4. 使用者进行调用.
DLL对导出函数的约定只是函数名, 没有函数原型, 它并没有一个象编译器一样的东西来保证你调用跟原型声明是匹配的.
解决办法: 第三方程序中不再手工写出该函数的原型. 主程序提供该函数原型. 并用一个独立的, 不牵涉其它头文件的 .h 文件来存放, 这样其它程序可以轻松地include该头文件.
好处是每次第三方程序编译时, 编译器会自动做一致性检查, 当整个系统变得不一致时, 编译器一定会及时通知, 而依赖人与人之间沟通的通知则永远是不可靠的.
DRY: Don't repeat yourself. 非常非常重要的原则. 一旦你把一个东西复制成多于一份保存, 总有一天, 其中一份发生改变而其它copy则完全不知情. 实际的软件系统中往往充斥着这样的重复.
阅读(1391) | 评论(0) | 转发(0) |