编写健壮的R代码:命名空间,屏蔽和使用`::`运算符
简洁版本
对于那些不想通读我的“案例”的人来说,这就是本质:
- 什么是推荐的方法,尽量减less新包打破现有代码的机会,即使你写的代码尽可能健壮 ?
-
在什么时候最好地使用命名空间机制是推荐的方法
a)只使用贡献的软件包(比如在一些R分析项目中)?
b)关于开发自己的软件包?
-
如何避免与正式类 (主要是参考类在我的情况下)的冲突,因为甚至没有一个类似于
::
for classes(AFAIU)的命名空间机制?
R宇宙的工作方式
这是在我脑海中徘徊了两年的东西,但我不觉得我已经达到了令人满意的解决scheme。 另外我觉得情况越来越糟。
我们看到在CRAN , github , R-Forge等等上的软件包数量不断增加,这真是太棒了。
在这样一个分散的环境中,组成R的代码库(为了简单起见,这就是基础R和贡献R )的代码基础在鲁棒性方面将偏离理想状态:人们遵循不同的惯例,有S3,S4 ,“S4参考课程”等等。如果有一个强制执行的“ 中央清算实例 ”,事情就不可能像“alignment”一样。 没关系。
问题
鉴于上述情况,使用R编写健壮的代码可能非常困难。 并非所有你需要的都在R基础上。对于某些项目,你最终会加载相当多的贡献包。
恕我直言,在这方面最大的问题是命名空间概念在R中使用的方式:R允许简单地写一个特定的函数/方法的名称,而不明确要求它的名称空间(即foo
与namespace::foo
) 。
所以为了简单起见,这就是大家正在做的事情。 但是这样一来,名称冲突,破坏代码以及重写/重构代码的需要只是时间问题(或者加载的不同包的数量)。
充其量,你会知道哪些现有的function被新添加的软件包掩盖/重载。 最糟糕的是,你将不知道,直到你的代码中断。
几个例子:
- 尝试加载RMySQL和RSQLite的时候,他们不太一样
- RMongo也会覆盖RMySQL的某些function
- 预测掩盖了许多与ARIMA相关的function方面的内容
- R.utils甚至掩盖了
base::parse
例程
(我不记得哪些function特别是导致问题,但如果有兴趣,我愿意再看一遍)
令人惊讶的是,这似乎并没有打扰那里的很多程序员。 我试图在r-devel几次提高兴趣,没有任何意义。
使用::
运算符的缺点
- 正如Dominick Samperi 指出的那样,使用
::
运算符可能会严重影响效率。 - 在开发你自己的包的时候,你甚至不能在自己的代码中使用
::
运算符,因为你的代码还没有真正的包,因此也没有名称空间。 所以我将不得不一直坚持foo
方式,build立,testing,然后回去改变一切到namespace::foo
。 不是真的。
避免这些问题的可能解决scheme
- 将每个包中的每个函数重新分配给一个遵循特定命名约定的variables,例如
namespace..foo
,以避免与namespace::foo
(我在此概述一次)相关的低效率。 优点:它的工作原理。 缺点:笨拙,你使用的内存翻倍。 - 在开发包的时候模拟一个命名空间。 AFAIU,这是不可能的,至less我当时被告知 。
- 强制使用
namespace::foo
。 恕我直言,这将是最好的事情。 当然,我们会失去一些简单性,但是R宇宙再也不是那么简单了(至less不像在00年初那么简单)。
那么(正式的)课程呢?
除了上面描述的方面外, ::
方法对于函数/方法来说工作得很好。 但是类定义呢?
把package timeDate和它的class timeDate
。 说另一个包,也有一个类timeDate
。 我不明白我怎么可以明确说明,我想从两个包中的任何一个新的类timeDate
实例。
像这样的东西不会工作:
new(timeDate::timeDate) new("timeDate::timeDate") new("timeDate", ns="timeDate")
这可能是一个巨大的问题,因为越来越多的人转换到R包的OOP风格,导致大量的类定义。 如果有一种方法可以明确地定义类定义的名称空间,我将非常感激一个指针!
结论
尽pipe这有点冗长,但我希望能够指出核心问题和问题,我可以在这里提高意识。
我认为devtools和mvbutils确实有一些可能值得传播的方法,但我相信还有更多要说的。
伟大的问题。
validation
编写健壮,稳定,可用于生产的R代码非常困难。 你说:“令人惊讶的是,这似乎并没有打扰很多程序员”。 这是因为大多数R程序员不写代码。 他们正在执行一次性的学术/研究任务。 我认真地质疑任何编码者的技能,他们认为R很容易投入生产。 除了我已经链接到的search/查找机制之外,我还写了一篇关于警告的危险的文章。 这些build议将有助于降低生产代码的复杂性。
编写健壮/生产R代码的技巧
- 避免使用取决于和偏好使用Imports的包的包。 仅依赖于Imports的包是完全安全的。 如果您绝对必须使用一个使用Depends的软件包,请在致电
install.packages()
后立即通过电子邮件发送作者。
下面是我告诉作者的一句话:“Hi Author,我是XYZ包的粉丝,我想提出一个请求,你可以在下一次更新中将ABC和DEF从Depends移动到Imports吗?我自己的软件包的Imports直到发生这种情况,使用R 2.14对每个软件包实施NAMESPACE,R Core的一般信息是软件包应该是“好公民”,如果我必须加载一个Depends软件包,我每次依赖一个新的包都必须检查是否有冲突,使用Imports,这个包没有任何副作用,我明白你可能会破坏别人的包,我认为它是正确的展示对import的承诺,并从长远来看,这将有助于人们生产更强大的R代码。“
-
使用importFrom。 不要将整个包添加到Imports,只添加您需要的那些特定function。 我用Roxygen2函数文档和roxygenize()来完成这个工作,它会自动生成NAMESPACE文件。 通过这种方式,您可以导入两个冲突不在您实际需要使用的函数中的有冲突的程序包。 这是乏味的吗? 直到它成为一种习惯。 好处:您可以快速识别所有第三方依赖关系。 这有助于…
-
不要盲目升级软件包。 逐行阅读更新日志,并考虑更新会如何影响您自己软件包的稳定性。 大多数情况下,更新不会触及您实际使用的function。
-
避免S4class。 我在这里做一些手势。 我发现S4是复杂的,它需要足够的智力来处理R的function方面的search/查找机制。您是否真的需要这些OOfunction? pipe理状态=pipe理复杂性 – 保留为Python或Java =)
-
编写unit testing。 使用testthat包。
-
每当你R CMDbuild立/testing你的包时,parsing输出并寻找NOTE,INFO,WARNING。 另外,用自己的眼睛进行身体扫描。 有一部分构build步骤logging冲突,但不附加WARN等。
-
调用第三方软件包之后立即添加断言和不variables。 换句话说,不要完全相信别人给你的东西。 稍微探测一下结果,如果结果是意外的,
stop()
。 你不必疯狂 – 挑选一个或两个暗示有效/高信度结果的断言。
我认为还有更多,但现在已经成为肌肉记忆=)如果更多的话,我会增加。
我承担:
总结:灵活性带有一个价格。 我愿意支付这个价格。
1)我根本不使用导致这种问题的软件包。 如果真的,真的需要从我自己的软件包中的函数,我在我的NAMESPACE
文件中使用importFrom()
。 无论如何,如果我有一个软件包的麻烦,我联系软件包的作者。 问题是在他们身边,而不是R的。
2)我从不在自己的代码中使用::
。 通过只导出我的包的用户所需要的function,我可以在NAMESPACE中保留自己的function而不会遇到冲突。 没有导出的函数也不会隐藏同名的函数,所以这是一个双赢。
一个很好的指导如何确切的环境,名称空间和喜欢你在这里find工作: http : //obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/
这绝对是每个人编写软件包和类似软件的必读书籍。 读完这个之后,你会意识到在你的包代码中使用::
是没有必要的。