6

试图向朋友解释语义版本控制的重要性时,我遇到了以下困境。

假设我们有 library libfoo, version1.2.3公开了以下功能:

def foo(x, y):
    """
    Compute the sum of the operands.
    :param x: The first argument.
    :param y: The second argument.
    :returns: The sum of `x` and `y`.
    """
    return x + y

现在假设此函数及其文档更改为:

def foo(a, b):
    """
    Compute the sum of the operands.
    :param a: The first argument.
    :param b: The second argument.
    :returns: The sum of `a` and `b`.
    """
    return a + b

我的第一印象是说下一个版本将是1.2.4,因为公共界面没有改变。例如,像这样调用函数的人根本不会注意到变化:

foo(3, 4)

但再想一想,这很可能是一个API 中断,因为 Python 允许通过参数名称来指定参数。如果有人像这样调用我的函数:

foo(y=4, x=3)

这将不再适用于 version 1.2.4,从而破坏了语义版本控制合同。

另一方面,这样的变化似乎很小,以至于我对将版本增加到2.0.0.

总而言之,这是否构成 API 中断?在这种情况下,下一个版本号应该是什么?

4

2 回答 2

5

简短回答:是的,我认为这构成 API 中断,因此可能会增加主要版本号。但请注意以下警告。


当您公开一个公共/外部 API 时,您承担了额外的“注意义务”来仔细考虑对接口的更改。例如,这包括推迟潜在的改进以避免破坏向后兼容性*。在您维护 API 时,应非常仔细地考虑任何会合法地影响使用您的接口的代码**的更改。

语义版本控制的规范是明确的:

如果将任何向后不兼容的更改引入公共 API,则必须增加主要版本 X (Xyz | X > 0)。

正如您在问题中所确定的那样,更改参数的名称会导致通过关键字传递参数的代码向后不兼容。

但是,与其说这个更改应该增加主版本,我会得出结论,不应该进行更改,或者至少不应该孤立地进行更改 - 这是一个太小的更改,无法证明可能破坏现有有效代码的主要增量是合理的包含。除了以下情况:

  1. 它是一些更大的重要变化的一部分;或者
  2. 您的示例中未显示的更改有一个非常好的理由(一些显示停止的错误或其他依赖它的功能);

我会完全推迟改变。最好放慢速度并确保您继续满足您的语义版本控制合同,只有在有令人信服的理由时才进行此类更改。


Python 3.8开始,您可以指定仅位置参数,而在 PEP中正是指出了这种问题 作为理由的一部分(强调我的):

如果 API 的调用者开始使用关键字参数,则库作者无法重命名参数,因为这将是一项重大更改

在这种情况下,如果原始定义是:

def foo(x, y, /):

那么重命名参数不会是一个重大变化。


*由于我们在 Python 标记中,请考虑整数除法,尽管BDFL 承认它是一个错误,但直到今天仍保留在 2.x 版本中。

**我说“合法地”是为了排除未按照官方文档使用它的代码,例如通过访问按约定私有的属性——他们应该期望有时会感到不便!因此,如果您预测到了这种变化,并且明确指定只应使用位置参数,那么这种变化是可以的,但这将是一个奇怪的选择。

于 2015-01-27T22:27:45.650 回答
-1

这种类型的更改可能会在发布规模上分为许多不同的领域。

重大变化(从 1.x 增加到 2.x)

这违反了您的 API 合同,并有资格作为重大更改。然而,对此的巨大警告是,这是否是唯一的变化。如果是这样,我不会将此作为主要版本更改。另一方面,如果这也是破坏您的 API 合同的众多更改之一,我相信主版本增量是合理的。

微小变化(从 1.2 增加到 1.3)

借用 Python文档

次要版本号 [is] 递增以减少惊天动地的变化。

对我来说,这是一个很小的变化。正如您所说,如果用户没有命名他们的参数,他们甚至不会注意到发生了变化。

微变化(从 1.2.3 增加到 1.2.4)

这是您的错误修复级别的更改。如果您因为它是一个错误而更改foo(x, y)foo(a, b),那么此修复证明了微点增量是合理的。


我对此类更改的看法是使其成为次要更改。这当然不是“惊天动地”的变化,但它确实可能会迫使最终用户更改代码。我不会将其归类为重大更改,因为它仅在函数调用上更改参数名称,否则其行为方式完全相同,返回完全相同的数据,并将完全相同的数据作为参数,但只是将它们应用于不同的名称函数内。

于 2015-01-27T22:21:32.187 回答