当加载`library(packagename)`时,如何最好地向用户提醒软件包文档的使用方法?最佳实践是什么?

8

我正在编写一个R包,并花费大量时间在文档vignettes上。我想知道是否应该添加类似于

.onAttach <- function( libname , pkgname ){
   packageStartupMessage("use `browseVignettes('packagename')` to see vignettes")
}

当用户调用时,立即显示的内容。
library(packagename)

什么是向用户通知软件包vignette的最佳方式?还是默认用户会在没有明确通知的情况下寻找它们?

6
长期使用 R 的用户可能会觉得这很烦人。而初学者可能有不同的看法,所以我认为这真的取决于软件包、它的目的和目标受众。 - baptiste
4
@baptiste 我同意。我想知道是否聪明/可能仅在前十个 library(packagename) 加载时打印一些内容? - Anthony Damico
更新了我的答案,包括一种“计算”软件包加载次数的方法。 - coatless
我喜欢data.table使用的包启动策略,详情请见https://github.com/Rdatatable/data.table/blob/d9cf539b0fda2197b3a1d3bd5a0b5180b036200f/R/onAttach.R - Anthony Damico
2个回答

7

小标题:Vignettes的普及

对于日常使用R语言的用户,以下几点需要注意:

  1. 在CRAN软件包页面上可以找到vignettes(如果作者有这个功能,则说明软件包质量很高!)
  2. 添加到软件包二进制文件中,并通过browseVignettes(package = "pkgname")在vignette帮助部分中显示出来 [正如你所指出的]
  3. 嵌入到R软件包源代码中
  4. 可以将vignette中的嵌入式代码作为单独的脚本演示在/demos中使用demo()
  5. 最重要的一点:用户根本不知道vignettes是什么。

因此,如果您在软件包文档上花费了相当多的时间,那么您可能至少应该在启动时指出存在这样一个功能。

利用.onAttach()中的packageStartupMessage()使用户受益。

在软件包加载时,控制台相对清晰,用户一定会看到红色文本,因为通常情况下普通的蓝色R文本与它形成了较高的对比度(假设没有使用crayon)。但是,在某些情况下,向用户提醒vignettes的存在并没有意义。

  1. 如果会话是非交互式的(例如bash脚本)。
  2. 如果用户是R专家
  3. 如果用户已经长时间使用您的软件包,导致用户忽略消息。

因此,不良实现的packageStartupMessage()会产生负面影响。

因此,我建议在以下四种不同情况下引入启动消息。

  1. (所有情况) 通过查询interactive()来查看是否有人类参与。如果有,则继续:
  2. (标准情况) 准备至少3个不同的启动消息列表,并每次随机选择一个进行显示;
  3. (正常情况) 随机生成一个数字以确定是否应该显示启动消息;
  4. (专家情况) 统计软件包加载次数,在x次软件包加载后,停止启动消息,直到用户重新安装该软件包。

接下来,我将介绍两种解决方案,它们遵循(1)-(3)的原则,以及仅使用(1)和(4)的简单解决方案。

随机选择软件包提示

在此版本中,我们仅寻求检查人类是否存在,提供一个跳过添加软件包加载消息的方法,并随机选择一个提示来显示。除了base作用域之外,只需要一个依赖项,即stats::runif命令,以生成介于[0,1]之间的概率。

#' @importFrom stats runif
.onAttach <- function(...) {

  # If interactive, hide message
  # o.w. check against rng seed.
  if (!interactive() || stats::runif(1) > 0.5){
     return()
  }

  # Create a list of helpful tips
  pkg_hints = c(
    "Check for updates and report bugs at https://cran.r-project.org/web/packages/pkgname/.",
    "Use `suppressPackageStartupMessages()` to remove package startup messages.",
    "To see the user guides use `browseVignettes('pkgname')`"
  )

  # Randomly pick one hint
  startup_hint = sample(pkg_hints, 1)

  # Display hint
  packageStartupMessage(paste(strwrap(startup_hint), collapse = "\n"))
}

计算包的加载次数

为了使用一个计数器,我们利用保存到包的安装目录而不是"工作"目录/用户空间的能力。这样有一些与之相关的问题,我会简要提一下:

  1. 用户可能只有查看而不是修改system库的权限(用户库可以)。
    • 因此,他们将始终根据此方法收到软件包提示,因为他们无法增加计数器。
  2. 如果软件包在system库中,则可能共享计数器。
    • 这将导致所有提示很快被使用完。
  3. 错误日志可能会增加。

当然,您可以通过R_USERHOME 环境变量在用户空间内创建自己的文件夹来消除这些问题。(此处留给读者做练习,提示:使用Sys.getenv()dir.create()。)

无论如何,这个函数的好处之一是,在以后的时间里,您可能会在包内包含一个"发送软件包使用统计信息"的函数。这将实际上给出一个相当准确的“电话回家”统计信息,而不是当前的RStudio CRAN镜像软件包下载信息。但我跑题了。

为了使此方法起作用,我们需要在最初提供给CRAN的软件包上做更多的准备工作。具体来说,我们可能应该通过以下方式预安装计数器:

# Set working directory to package
setwd("package_dir")

# Create the inst directory if it does not exist
if(!dir.exists("inst")){
  dir.create("inst")
}

# Create a counter variable
pkg_count = 0

# Save it into a .rda file
save(pkg_count, file="inst/counter.rda")

现在,开始介绍.onAttach()计数器的实现!

.onAttach <- function(...){
  if (!interactive()) return()

  # Get the install directory location of the counter
  path_count = system.file("counter.rda", package = "pkgname")

  # Suppress messages by default
  displayMsg = FALSE

  # Are we able to load the counter?
  a = tryCatch(load(path_count), error = function(e){"error"}, warning = function(e){"warning"})

  # Set error variable
  count_error = a %in% c("error","warning")

  # Check if the count is loaded
  if(!count_error){

    # Check if load count is low... 
    if(pkg_count < 10){
      pkg_count = pkg_count + 1

      # Try to save out
      try(save(pkg_count, file = path_count), silent=TRUE)
      displayMsg = T
    }
  }

  # Display message (can be suppressed with suppressPackageStartupMessages)
  if(displayMsg){
    packageStartupMessage("use `browseVignettes('packagename')` to see vignettes")
  }
}

最后一点想法

请记住,如果您有任何软件包依赖关系(例如DESCRIPTION中的Depend:),它们可能会有自己的启动消息,在您的软件包中编写的消息之前显示。


在这种方式下,写入磁盘时您需要小心一点,因为用户可能没有该目录的写入权限,而且我认为这违反了 CRAN 的政策。 - hadley
@Coatless 我认为这非常清晰明了:“包不应该写入用户的主文件空间,也不应该在R会话的临时目录之外的任何文件系统位置上写入(或者在安装期间指向TMPDIR的位置上写入:此类用法应该被清理掉)。” - hadley
@hadley 我同意。然而,正是之后的技术性问题让我说“可能”:“如果软件包从用户那里获得确认,交互式会话中可能允许有限的例外情况。” 如果在第一次运行时提示用户批准具有“衰减”提示结构的选项,并将其保存在 options() 中,则可以批准反例。无论如何,正如我之前所说,包含反例更多地是为了满足最初帖子的评论。我更喜欢第一个例子,并检查警告用户是否安装了最新的软件包。 - coatless
@AnthonyDamico 我不确定是否有一种方法来检测这个问题,因为通过namespaceImport()进行包加载的方式。请参考:https://github.com/wch/r-source/blob/6241c60301a91a709aaf08a2f906f943963f4378/src/library/base/R/namespace.R - coatless
@Coatless 抱歉,你是怎么触发那个的? - Anthony Damico
显示剩余9条评论

6
我提供相反的意见(StackOverflow允许意见吗?),即启动消息广告小品并不有用。现在越来越多的软件包具有广泛的依赖关系,然后消息出现在上下文之外(我为什么在连接Bar时会得到关于Foo包的指导?),被埋在其他软件包的启动消息中,与警告或错误混淆,或者压倒性地覆盖其他部分生成的可能有价值的信息(例如关于掩码符号的信息)。此外,Bioconductor 软件包的小品非常普遍,用户很快就会了解它们的价值(还有一些非Bioconductor软件包中可用的惊人小品的价值),因此用户对小品的存在和价值可能不像描述的那样陌生。
每一行代码都可能存在错误,因此编写复杂的代码以有条件地显示关于小品文的信息并不是程序员时间的有价值的使用方式,也不利于无缝用户体验,尤其是当成功执行代码是使用包的必要条件时!在@Coatless的解决方案中,仅为了说明这一点,除了担心写入已安装包位置之外,我还会对使用T而不是TRUEload()/save()而不是readRDS()/saveRDS(),以及捕获错误或警告但未向用户通信的非特定方式提出异议,通过stats::runif()引入额外的包依赖项,以及启动消息中可能使用的markdown符号。相反,在描述:字段和各个帮助页面中引用小品文。

只要你在 onAttachonLoad 中完成它,就可以解决依赖问题。 - hadley
1
“广告小插曲”上的评论让我想起了@DavidRobinson的这篇博客文章 - C8H10N4O2

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