在更新软件包的新版本中重命名函数时,有最佳/推荐实践要遵循吗?

43

我正在更新一个旧软件包,并缩短了一些非常长的函数名称。如何让用户知道旧函数已被弃用? 我使用 roxygen2 记录所有内容,所以我想知道是否应该使用 #' @alias ? 你有什么想法吗?


8
已弃用 - GSee
@alias 只允许用户通过 ?help 查找函数文档。最好同时定义内联和导出旧的和新的函数。some_func <- new_func_name <- function(x) 并使用 @alias 指令。如果您想要它们都在命名空间中,您需要 @export some_func new_fund_name。请参阅 ?namespace_roclet。对我来说,这听起来不像是您正在更改函数,只是更改名称 - 因此,?Deprecated 看起来并不适用。 - Brandon Bertelsen
非常感谢@BrandonBertelsen,我建议您将其发布为答案,以便其他人可以受益。 - Maiasaura
4个回答

56

即使只是缩短函数名称,我仍然建议将其视为公共API的任何更改一样重视:随着新函数的引入,对旧函数进行废弃/失效处理。

在第一阶段中,对于您想缩短名称的每个函数(我们称之为“transmute_my_carefully_crafted_data_structure_into_gold”),您保留具有该签名的函数,但将所有实际代码移动到您的新命名函数(我们称之为“alchemy”)中。

最初:

transmute_my_carefully_crafted_data_structure_into_gold <- function(lead, alpha=NULL, beta=3) {
  # TODO: figure out how to create gold
  # look like we are doing something
  Sys.sleep(10)
  return("gold")
}

新名称的首个发布版本:

transmute_my_carefully_crafted_data_structure_into_gold <- function(lead, alpha=NULL, beta=3) {
  .Deprecated("alchemy") #include a package argument, too
  alchemy(lead=lead, alpha=alpha, beta=beta)
}

alchemy <- function(lead, alpha=NULL, beta=3) {
  # TODO: figure out how to create gold
  # look like we are doing something
  Sys.sleep(10)
  return("gold")
}

为了让transmute_my_carefully_crafted_data_structure_into_gold作为alchemy的一个薄包装器起步,并增加额外的.Deprecated调用。

> transmute_my_carefully_crafted_data_structure_into_gold()
[1] "gold"
Warning message:
'transmute_my_carefully_crafted_data_structure_into_gold' is deprecated.
Use 'alchemy' instead.
See help("Deprecated") 
> alchemy()
[1] "gold"
如果你对alchemy做了更改,由于transmute_my_carefully_crafted_data_structure_into_gold只是调用前者,所以这些更改将被保留。但即使alchemy发生变化,也不要更改transmute_my_carefully_crafted_data_structure_into_gold的签名;在这种情况下,您需要尽可能地将旧参数映射到新参数。
在稍后的版本中,您可以将.Deprecated更改为.Defunct
> transmute_my_carefully_crafted_data_structure_into_gold()
Error: 'transmute_my_carefully_crafted_data_structure_into_gold' is defunct.
Use 'alchemy' instead.
See help("Defunct")

请注意,这是一个错误并会停止程序执行;它不会继续调用alchemy函数。

在一些以后的版本中,您可以完全删除此函数,但我建议将其保留为标志。

您提到使用roxygen。当您进行首次转换为弃用时,可以将@rdname更改为package-deprecated,在描述的开头添加说明已经被弃用了,并将新函数添加到“@seealso”中。当它变成无效后,将@rdname更改为package-defunct。


1
感谢您抽出时间撰写这篇精彩的回复。祝好! - Maiasaura

23

我猜“正确”的答案取决于你想要什么。从我的角度来看:

  1. Jeff和Brandon的方法存在问题,因为它们的索引将列出所有函数名称,并没有表明哪个是首选名称。此外,没有某种形式的.Deprecated调用,用户更加不可能知道应该如何调用函数。
  2. Brian的方法存在问题,因为我不清楚如何列出多个被弃用函数的流程。

接下来是我的示例。在另一个位置中,我定义了函数的“好”版本(例如alchemy、latinSquareDigram)。这里我定义了所有我想要生成弃用警告的旧“坏”版本。我采用了car包的方法,将所有使用弃用版本的函数调用改为使用...作为参数。这有助于避免一堆杂乱的@param语句。我还使用了@name和@docType指令,以便在索引中显示“yourPackageName-deprecated”。也许有人有更好的方法做到这一点?

现在,每个被弃用的函数仍然会出现在索引中,但旁边会显示“在yourPackageName包中弃用的函数”,任何对它们的调用都会产生弃用警告。要从索引中删除它们,可以去掉@aliases指令,但那样就会产生用户级别的未记录代码对象,我认为这是不好的做法。

#' Deprecated function(s) in the yourPackageName package
#' 
#' These functions are provided for compatibility with older version of
#' the yourPackageName package.  They may eventually be completely
#' removed.
#' @rdname yourPackageName-deprecated
#' @name yourPackageName-deprecated
#' @param ... Parameters to be passed to the modern version of the function
#' @docType package
#' @export  latinsquare.digram Conv3Dto2D Conv2Dto3D dist3D.l
#' @aliases latinsquare.digram Conv3Dto2D Conv2Dto3D dist3D.l
#' @section Details:
#' \tabular{rl}{
#'   \code{latinsquare.digram} \tab now a synonym for \code{\link{latinSquareDigram}}\cr
#'   \code{Conv3Dto2D} \tab now a synonym for \code{\link{conv3Dto2D}}\cr
#'   \code{Conv2Dto3D} \tab now a synonym for \code{\link{conv2Dto3D}}\cr
#'   \code{dist3D.l} \tab now a synonym for \code{\link{dist3D}}\cr
#' }
#'  
latinsquare.digram <- function(...) {
  .Deprecated("latinSquareDigram",package="yourPackageName")
  latinSquareDigram(...)
}
Conv3Dto2D <- function(...) {
  .Deprecated("conv3Dto2D",package="yourPackageName")
  conv3Dto2D(...)
}
Conv2Dto3D <- function(...) {
  .Deprecated("conv2Dto3D",package="yourPackageName")
  conv2Dto3D(...)
}
dist3D.l <- function(...) {
  .Deprecated("dist3D",package="yourPackageName")
  dist3D(...)
}
NULL

1
@mathematical.coffee:谢谢!可能是因为我在赏金获得者之后8个月才提供了我的答案。:) 我尝试了上面的建议,但结果并不令人满意。所以,当我最终得到一个让我满意的结果时,我发布了我的答案。 - russellpierce
1
我发现赏金获得者非常有帮助,但这肯定会增加它的价值。我需要额外的 roxygen2 爱。 - Paul 'Joey' McMurdie

5
我有一个问题困扰了我一段时间,一直找不到好的解决方案,后来我找到了这个。但是,以上答案对于只想要以下简单情况的人来说过于复杂:1)添加别名以便旧代码不会停止工作;2)别名必须与内置文档一起工作;3)应该使用roxygen2进行操作。
首先,添加函数的副本:
old_function_name = new_function_name

然后,在定义new_function_name()的地方,添加到roxygen2中:

#' @export new_function_name old_function_name
#' @aliases old_function_name

现在旧函数仍然可用,因为它只是新函数的复制品,文档也可以工作,因为您设置了别名。旧版本也被导出,因为它包含在@export中。


这在我看来似乎是最可靠的答案。直觉上这也是我会选择的。或许最好再加上一些关于底层正在进行的弃用周期的注释,因为我们希望在某个时间点摆脱旧的函数名。 - Garini

3
在将函数名缩短时,建议导出两个名称作为同一函数(参见@Brandon的评论)。这样可以让旧代码继续工作,同时为新用户提供更方便的选择。
我认为将某些东西标记为.Deprecated的唯一原因(参见@GSEE)是如果您计划在未来发布中基本更改功能或停止支持某些功能。如果是这种情况,可以使用.Defunct.Deprecated

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接