语义版本控制对参数名称更改意味着啥?

Posted

技术标签:

【中文标题】语义版本控制对参数名称更改意味着啥?【英文标题】:What does semantic versioning imply about parameter name changes?语义版本控制对参数名称更改意味着什么? 【发布时间】:2015-03-26 16:21:31 【问题描述】:

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

假设我们有库libfoo,版本1.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)

这将不再适用于版本 1.2.4,违反语义版本控制合同。

另一方面,这样的变化似乎很小,以至于我会因为将版本增加到2.0.0而感到难过。

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

【问题讨论】:

换个角度想一想——鉴于您正在尝试维护版本控制合同,您是否会自行更改(可能会破坏)接口?或者您会推迟更改,直到您有足够的更改一起(或其他一些真正很好的理由)来证明新的整数版本是合理的? @jonrsharpe:当然。但问题更多是关于“一个人可以在代码库中安全地做到这一点,还是他/她必须假设这是一个 API 中断?”正如您所说的那样“他/她应该将此类更改推迟到下一个版本吗?” 对你来说很小。我经常使用关键字参数,即使它们不是必需的(更明确地说,特别是当有 3 个以上参数时),所以这样的机会可能会破坏 使用您的库编写的代码。对我来说,这完全是一个向后不兼容的更改,就像将 def f(x, y) 更改为 def f(y, x) 一样。 如果您要维护一个公共 API,我会说您应该考虑对接口进行任何更改可能会影响任何代码(在合理范围内 - 访问按约定私有属性的代码有时会带来不便!)是 API 更改,因此很重要。如果您公开公开此接口,则您有额外的“注意义务”以避免任何不必要的更改(除非另有说明 - 如果您明确声明 “不要为此功能使用关键字” 那么可能没问题,但这似乎有点奇怪)。 @jonrsharpe 这一切都说得通。我想我只需要从别人那里读到同样的结论。谢谢 !请随意添加答案,以便我给予您适当的信任。 【参考方案1】:

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


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

specification for semantic versioning 是明确的:

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

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

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

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

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


从Python 3.8,您可以指定位置-only 参数,而正是这种问题在in the PEP 被调用 作为理由的一部分(强调我的):

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

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

def foo(x, y, /):

然后重命名参数不会是一个重大更改。


* 当我们在 Python 标记中,考虑整数除法,尽管 being acknowledged as a mistake by the BDFL,直到今天仍然在 2.x 版本中。

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

【讨论】:

【参考方案2】:

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

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

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

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

借用 Python documentation:

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

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

微变化(从 1.2.3 增加到 1.2.4)

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


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

【讨论】:

这个不同版本组件的定义很有趣,但它似乎在某种程度上与“语义版本控制”的定义相矛盾。我当然同意根据您给出的定义,我的更改是次要的,但我的问题更多是关于了解它在语义版本控制领域中的资格。

以上是关于语义版本控制对参数名称更改意味着啥?的主要内容,如果未能解决你的问题,请参考以下文章

语义版本控制 (Semver) - 如何对向后兼容的大型功能更新进行 semver

数据库模式更改是不是应该增加语义版本控制中的主要版本?

语义版本控制和依赖更改

语义版本控制 - 向后兼容性和版本 0.x.y

HTTP Restful 语义版本控制

在 Gradle 中对依赖项使用语义版本控制