47

精简版

对于那些不想阅读我的“案例”的人来说,这就是本质:

  1. 什么是最小化新包破坏现有代码的机会的推荐方法,即使您编写的代码尽可能健壮
  2. 什么是充分利用命名空间机制的推荐方法?

    a) 只使用贡献的包(比如在一些 R 分析项目中)?

    b) 关于开发自己的软件包?

  3. 由于甚至没有与类(AFAIU)可比的命名空间机制,如何最好地避免与正式类(在我的情况下主要是参考类)相关的冲突?::


R 宇宙的运作方式

这是我两年来一直在脑海中挥之不去的事情,但我不觉得我已经找到了一个令人满意的解决方案。另外,我觉得情况越来越糟。

我们在CRANgithubR-Forge等上看到越来越多的包,这简直太棒了。

在这样一个去中心化的环境中,构成 R 的代码库(为简单起见,假设是基础 R贡献的 R)很自然地会偏离稳健性方面的理想状态:人们遵循不同的约定,有 S3、S4 、S4 参考类等。如果有一个强制执行约定的“中央清算实例”,事情就不能像它们那样“对齐” 。没关系。

问题

鉴于上述情况,使用 R 编写健壮的代码可能非常困难。并非您需要的所有东西都在 base R 中。对于某些项目,您最终会加载相当多的贡献包。

恕我直言,在这方面最大的问题是命名空间概念在 R 中的使用方式:R 允许简单地编写某个函数/方法的名称,而无需明确要求它的命名空间(即foovs. namespace::foo

所以为了简单起见,这就是每个人都在做的事情。但这样一来,名称冲突、代码损坏以及需要重写/重构代码只是时间问题(或加载的不同包的数量)。

充其量,您将知道哪些现有功能被新添加的包屏蔽/重载。在最坏的情况下,您将一无所知,直到您的代码中断。

几个例子:

  • 尝试同时加载RMySQLRSQLite,它们不太顺利
  • RMongo也会覆盖 RMySQL 的某些功能
  • 预测掩盖了很多关于 ARIMA 相关函数的东西
  • R.utils甚至掩盖了base::parse例程

(我不记得具体是哪些功能导致了问题,但如果有兴趣,我愿意再次查找)

令人惊讶的是,这似乎并没有困扰很多程序员。我试图在r-devel引起几次兴趣,但无济于事。

::使用运算符的缺点

  1. ::正如 Dominick Samperi指出的那样,在某些情况下使用运算符可能会严重影响效率。
  2. 开发自己的包时,您甚至不能::在自己的代码中使用运算符,因为您的代码还不是真正的包,因此也没有命名空间。所以我必须一开始就坚持下去foo,构建,测试,然后再把所有东西都改成namespace::foo. 并不真地。

避免这些问题的可能解决方案

  1. 将每个包中的每个函数重新分配给遵循某些命名约定的变量,例如namespace..foo,为了避免与之相关的低效率(我在这里namespace::foo概述过一次)。优点:它有效。缺点:它很笨拙,而且你使用的内存加倍。
  2. 开发包时模拟命名空间。AFAIU,这不太可能,至少我当时被告知
  3. 强制使用namespace::foo. _ 恕我直言,那将是最好的做法。当然,我们会失去一些简单性,但话说回来,R 宇宙不再简单了(至少它不像 00 年代初期那样简单)。

那么(正式)课程呢?

除了上述方面之外,::方式对于函数/方法也很有效。但是类定义呢?

将包timeDate与它的类一起使用timeDate。假设出现另一个包,它也有一个 class timeDate。我看不出我如何明确声明我想要timeDate来自两个包中的任何一个的类的新实例。

这样的事情是行不通的:

new(timeDate::timeDate)
new("timeDate::timeDate")
new("timeDate", ns="timeDate")

随着越来越多的人为他们的 R 包切换到 OOP 样式,这可能是一个大问题,从而导致大量的类定义。如果有一种方法可以显式处理类定义的命名空间,我将非常感谢一个指针!

结论

尽管这有点冗长,但我希望我能够指出核心问题/问题,并且我可以在这里提高更多认识。

我认为devtoolsmvbutils确实有一些可能值得推广的方法,但我敢肯定还有更多要说的。

4

2 回答 2

34

好问题。

验证

编写健壮、稳定且可用于生产的 R 代码很难。你说:“令人惊讶的是,这似乎并没有打扰很多程序员”。这是因为大多数 R 程序员没有编写生产代码。他们正在执行一次性的学术/研究任务。我会严重质疑任何声称 R 易于投入生产的程序员的技能。除了你已经链接到的关于搜索/查找机制的帖子之外,我还写了一篇关于警告危险的帖子。这些建议将有助于降低生产代码的复杂性。

编写健壮/生产 R 代码的技巧

  1. 避免使用 Depends 的包,而偏爱使用 Imports 的包。仅将依赖项填充到 Imports 中的包完全可以安全使用。如果您绝对必须使用使用 Depends 的软件包,请在致电后立即给作者发送电子邮件install.packages()

这是我告诉作者的话:“嗨,作者,我是 XYZ 包的粉丝。我想提出一个请求。你能在下一次更新中将 ABC 和 DEF 从 Depends 移到 Imports 吗?我无法将你的包添加到我自己的包的 Imports 直到发生这种情况。随着 R 2.14 对每个包强制执行 NAMESPACE,R Core 的一般信息是包应该尝试成为“好公民”。如果我必须加载一个 Depends 包,它会增加一个很大的负担:每次我依赖一个新包时,我都必须检查是否存在冲突。使用 Imports,该包没有副作用。我知道这样做可能会破坏其他人的包。我认为这是正确的做法展示对 Imports 的承诺,从长远来看,它将帮助人们生成更强大的 R 代码。”

  1. 使用 importFrom。不要将整个包添加到 Imports,只添加您需要的那些特定功能。我使用 Roxygen2 函数文档和自动生成 NAMESPACE 文件的 roxygenize() 来完成此操作。通过这种方式,您可以导入两个有冲突的包,其中冲突不在您实际需要使用的功能中。这很乏味吗?直到它成为一种习惯。好处:您可以快速识别所有第 3 方依赖项。这有助于...

  2. 不要盲目升级包。逐行阅读更新日志,并考虑更新将如何影响您自己的包的稳定性。大多数时候,更新不会触及您实际使用的功能。

  3. 避免 S4 课程。我正在这里挥手。我发现 S4 很复杂,它需要足够的脑力来处理 R 的功能方面的搜索/查找机制。你真的需要这些 OO 功能吗?管理状态 = 管理复杂性 - 将其留给 Python 或 Java =)

  4. 编写单元测试。使用 testthat 包。

  5. 每当您 R CMD 构建/测试您的包时,解析输出并查找 NOTE、INFO、WARNING。此外,用自己的眼睛进行物理扫描。构建步骤的一部分记录了冲突,但没有附加警告等。

  6. 在调用第 3 方包后立即添加断言和不变量。换句话说,不要完全相信别人给你的东西。稍微探查一下结果,stop()如果结果出乎意料。您不必发疯——选择一两个暗示有效/高置信度结果的断言。

我认为还有更多,但现在这已成为肌肉记忆 =) 如果有更多,我会增加。

于 2012-06-08T14:58:09.127 回答
19

我的看法:

摘要:灵活性是有代价的。我愿意付出这个代价。

1)我根本不使用会导致此类问题的软件包。如果我真的,真的需要我自己的包中的那个包中的一个函数,我会importFrom()在我的NAMESPACE文件中使用。无论如何,如果我在使用包时遇到问题,我会联系包作者。问题出在他们这边,而不是 R 那边。

2)我从不::在自己的代码中使用。通过只导出我的包的用户需要的函数,我可以将我自己的函数保留在 NAMESPACE 中而不会发生冲突。未导出的函数也不会隐藏具有相同名称的函数,所以这是双赢的。

关于环境、命名空间等如何工作的很好的指南,您可以在这里找到:http: //blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/

对于每个编写包之类的人来说,这绝对是必读的。阅读本文后,您将意识到::在您的包代码中使用是不必要的。

于 2012-06-08T11:14:35.273 回答