微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

语义版本控制:更改*应该*通过库函数分配的非不透明结构

如何解决语义版本控制:更改*应该*通过库函数分配的非不透明结构

我的 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 举报,一经查实,本站将立刻删除。