如何解决语义版本控制:更改*应该*通过库函数分配的非不透明结构
我的 C 库,在 1.0.0 版本中,定义了一个结构体和一些用于分配和使用结构体的函数:
typedef struct { int x; int y; } MyStruct;
MyStruct *allocate( int,int );
void destroy( MyStruct* );
void print( MyStruct* );
用户不应该自己分配结构,也不应该按值复制它。这是与问题 Semantic versioning: minor or major change? 的主要区别。例如,一个程序应该像这样使用它:
void f(){
MyStruct *ms = allocate(0,0);
ms->x += 1;
print(ms);
destroy(ms);
}
typedef struct { int x; int y; int z; } MyStruct;
新结构体比旧结构体占用更多内存:如果程序尝试直接分配 MyStruct
实例或按值复制它,如果它链接的库版本不同于它是用它构建的。
然而,这不是程序使用 MyStruct
的方式:只要它们遵循文档,一切正常。但是代码中没有任何内容可以阻止他们滥用结构。
我正在使用语义版本控制来对我的库进行版本控制。在上述情况下,我应该增加次要版本(以向后兼容的方式添加功能)还是主要版本(不兼容的 API 更改)?
解决方法
您对公开可见的结构进行了重大更改,该结构在 C 中不向后兼容早期版本。除了结构大小的变化之外,您自己的示例还说明了为什么这是一个重大变化:
void f(){
MyStruct *ms = allocate(0,0);
ms->x += 1;
print(ms); // Indicates you are aware of external dependencies.
destroy(ms);
}
您正在公开一个数据结构,该数据结构可能由您的客户合并到某种类型的数据集中。您可能对如何使用您的库有一些想法,但我可以向您保证,您的客户总是会给您带来惊喜。
鉴于您发布的代码,以及您声明的意图说明如何使用您的库,我认为您需要重新设计您的 API,这样 MyStruct
对用户来说是完全不透明的并且永远不会改变大小(客户经常缓存这样的东西)。从标准库中借用一个页面,使用句柄。
typedef int MyHandle;
MyHandle allocate(int x,int y);
void destroy(MyHandle h);
void print(MyHandle h);
句柄可以由您的内部代码进行范围检查,然后用作结构或结构指针表的索引,或者它可以是二叉树的关键。关键是您可以随心所欲地做任何事情,而不会中断您的 API。
如果您希望 x y
位可见,请使用可扩展结构:
typedef struct {
int x;
int y;
void* reserved; // For internal use only!
} MyStruct;
MyStruct.reserved 字段应始终为 NULL,直到您需要它供内部使用。请记住,一旦您公开这样的数据字段,您的客户如何使用它们就完全不受您的控制了。当您以这种方式公开结构时,您就是在对客户做出承诺。
关于 getter 和 setter。
// Using the MyHandle type described earlier:
typedef struct _My_XY {
int x;
int y;
} My_XY;
My_XY GetXY(MyHandle h);
问题解决了。
我要补充一点,因为添加 z
字段无论如何都是一个重大更改,而且您似乎觉得需要扩展您的产品的功能,这将是彻底重写 API 的好时机。但是,如果您必须在不破坏客户群的情况下扩展它,您可以使用以下内容在内部隐藏 z
数据点:
// Public API
typedef struct { int x; int y; } MyStruct;
MyStruct *allocate( int,int );
void destroy( MyStruct* );
void print( MyStruct* );
// Implementation
typedef struct _XY_Node {
MyStruct* xy;
int z;
struct _XY_Node *pNext;
} XY_Node;
XY_Node root = null;
XY_Node* AddNode(int x,int y,int z) {...}
XY_Node* RemoveNode(int x,int z) {...}
XY_Node* FindNode(int x,int z) {...}
int DeriveZ(int x,int y) {...}
MyStruct *allocate(int x,int y)
{
return AddNode(x,y,DeriveZ(x,y)).xy;
}
// etc...
您可以使用哈希表或某种形式的二叉树,而不是列表。关键是,您不必为了扩展 API 而破坏它。您可以将其作为 1.y.z 系列的最终版本来执行,然后彻底修改您的 API,从而获得更清晰的客户体验和更高效的实施。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。