Ecriture de code R robuste: espaces de noms, masquage et utilisation de l’opérateur `::`

Version courte

Pour ceux qui ne veulent pas lire mon “cas”, c’est l’essence:

  1. Quelle est la manière recommandée de minimiser les chances que de nouveaux paquets brisent le code existant, c.-à-d. De rendre le code que vous écrivez aussi robuste que possible ?
  2. Quelle est la manière recommandée d’utiliser au mieux le mécanisme de l’ espace de noms lorsque

    a) utiliser uniquement des paquets consortingbués (par exemple dans un projet d’parsing R)?

    b) en ce qui concerne le développement de ses propres paquets?

  3. Comment éviter les conflits avec les classes formelles (principalement les classes de référence dans mon cas), car il n’existe même pas de mécanisme d’espace de nommage comparable à :: pour les classes (AFAIU)?


Le fonctionnement de l’univers R

C’est quelque chose qui me tracasse dans la tête depuis environ deux ans, mais je n’ai pas l’impression d’avoir trouvé une solution satisfaisante. De plus, je sens que ça empire.

Nous voyons un nombre croissant de paquets sur CRAN , github , R-Forge et autres, ce qui est tout simplement génial.

Dans un tel environnement décentralisé, il est naturel que la base de code qui constitue R (disons que la base R et la consortingbution R , par simplicité) s’écartent d’un état idéal en matière de robustesse: les gens suivent des conventions différentes, il y a S3, S4 , Classes de référence S4, etc. Les choses ne peuvent pas être “alignées” comme elles le seraient si une ” instance de compensation centrale ” imposait des conventions. C’est bon.

Le problème

Compte tenu de ce qui précède, il peut être très difficile d’utiliser R pour écrire du code robuste. Tout ce dont vous avez besoin ne sera pas dans la base R. Pour certains projets, vous finirez par charger plusieurs paquets consortingbués.

IMHO, le plus gros problème à cet égard est la manière dont le concept d’espace de noms est utilisé dans R: R permet d’écrire simplement le nom d’une fonction / méthode donnée sans exiger explicitement son espace de nommage ( foo vs. namespace::foo ) .

Donc, par souci de simplicité, c’est ce que tout le monde fait. Mais de cette façon, les conflits de noms, le code cassé et la nécessité de réécrire / refactoriser votre code ne sont qu’une question de temps (ou de nombre de paquets différents chargés).

Au mieux, vous saurez quelles fonctions existantes sont masquées / surchargées par un nouveau paquet ajouté. Au pire, vous n’aurez aucune idée tant que votre code ne sera pas brisé.

Quelques exemples:

  • essayez de charger RMySQL et RSQLite en même temps, ils ne vont pas très bien
  • RMongo va également écraser certaines fonctions de RMySQL
  • prévisions masque beaucoup de choses en ce qui concerne les fonctions liées à ARIMA
  • R.utils masque même la base::parse routine

(Je ne peux pas me rappeler quelles fonctions en particulier causaient les problèmes, mais je suis prêt à le rechercher s’il y a un intérêt)

Étonnamment, cela ne semble pas déranger beaucoup de programmeurs. J’ai essayé de susciter de l’intérêt à deux resockets chez R-Devel , sans succès.

Les inconvénients de l’opérateur ::

  1. Dominick Samperi a souligné que l’utilisation de l’opérateur :: .
  2. Lorsque vous développez votre propre paquet, vous ne pouvez même pas utiliser l’opérateur :: dans votre propre code car votre code n’est pas encore un paquet réel et il n’y a donc pas encore d’espace de noms. Donc, je devrais d’abord m’en tenir à la méthode foo , construire, tester et revenir à tout changer à namespace::foo . Pas vraiment.

Solutions possibles pour éviter ces problèmes

  1. Réaffectez chaque fonction de chaque paquetage à une variable qui suit certaines conventions de nommage, par exemple namespace..foo afin d’éviter les inefficacités associées à namespace::foo (je l’ai souligné une fois ici ). Avantages: ça marche. Inconvénients: c’est maladroit et vous doublez la mémoire utilisée.
  2. Simulez un espace de noms lors du développement de votre package. AFAIU, ce n’est pas vraiment possible, au moins on me l’a dit à l’époque .
  3. Rendre obligatoire l’ utilisation de namespace::foo . IMHO, ce serait la meilleure chose à faire. Bien sûr, nous perdrions de la simplicité, mais là encore, l’univers R n’est plus simple (du moins, ce n’est pas aussi simple qu’au début des années 2000).

Et qu’en est-il des classes (formelles)?

Outre les aspects décrits ci-dessus, :: way fonctionne assez bien pour les fonctions / méthodes. Mais qu’en est-il des définitions de classe?

Prenez le paquet timeDate avec sa class timeDate . Disons qu’un autre paquet vient avec un class timeDate . Je ne vois pas comment je pourrais explicitement déclarer que j’aimerais une nouvelle instance de class timeDate dans l’un des deux packages.

Quelque chose comme ça ne fonctionnera pas:

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

Cela peut poser un énorme problème car de plus en plus de personnes adoptent un style OOP pour leurs packages R, ce qui entraîne de nombreuses définitions de classes. S’il existe un moyen de traiter explicitement l’espace de nommage d’une définition de classe, j’apprécierais beaucoup un pointeur!

Conclusion

Même si cela a été un peu long, j’espère que j’ai été en mesure de souligner le problème / la question fondamentale et que je peux sensibiliser davantage ici.

Je pense que les devtools et les mvbutils ont des approches qui mériteraient d’ être diffusées , mais je suis sûr qu’il y a plus à dire.

GRANDE question.

Validation

Écrire du code R robuste, stable et prêt pour la production est difficile. Vous avez dit: “Étonnamment, cela ne semble pas déranger beaucoup de programmeurs”. C’est parce que la plupart des programmeurs R n’écrivent pas de code de production . Ils effectuent des tâches académiques / de recherche ponctuelles. Je remettrais sérieusement en question les compétences de tout codeur qui prétend que R est facile à mettre en production. En dehors de mon mécanisme de recherche / de recherche auquel vous avez déjà associé, j’ai également écrit un article sur les dangers de l’ avertissement . Les suggestions aideront à réduire la complexité de votre code de production.

Conseils pour écrire du code R robuste / de production

  1. Évitez les packages qui utilisent Depends et favorisez les packages qui utilisent les importations. Un paquet avec des dépendances empilées uniquement dans les importations est complètement sûr à utiliser. Si vous devez absolument utiliser un package qui utilise Depends, envoyez un courrier électronique à l’auteur immédiatement après avoir appelé install.packages() .

Voici ce que je dis aux auteurs: “Bonjour auteur, je suis un fan du paquet XYZ. Je voudrais faire une demande. Pouvez-vous déplacer ABC et DEF de Depends to Imports dans la prochaine mise à jour? Je ne peux pas append votre paquet à Imaging de NAMESPACE pour chaque paquet, le message général de R Core est que les paquets doivent essayer d’être de “bons citoyens”. Si je dois charger un paquet Depends, cela ajoute une charge importante: Je dois vérifier les conflits chaque fois que je prends une dépendance sur un nouveau paquet.Avec Imports, le paquet est sans effets secondaires.Je comprends que vous pouvez casser les paquets d’autres personnes en faisant cela.Je pense que c’est la bonne chose à faire pour démontrer un engagement envers les importations et à long terme, cela aidera les gens à produire un code R plus robuste. ”

  1. Utilisez importFrom. N’ajoutez pas un package complet à Imports, ajoutez uniquement les fonctions spécifiques dont vous avez besoin. J’accomplis ceci avec la documentation de la fonction Roxygen2 et roxygenize () qui génère automatiquement le fichier NAMESPACE. De cette façon, vous pouvez importer deux packages qui ont des conflits où les conflits ne sont pas dans les fonctions que vous devez réellement utiliser. Est-ce que c’est fastidieux? Seulement jusqu’à ce que cela devienne une habitude. L’avantage: vous pouvez rapidement identifier toutes vos dépendances tierces. Cela aide avec …

  2. Ne mettez pas à jour les packages à l’aveuglette. Lisez le changelog ligne par ligne et examinez comment les mises à jour affecteront la stabilité de votre propre package. La plupart du temps, les mises à jour ne touchent pas les fonctions que vous utilisez réellement.

  3. Évitez les classes S4. Je fais quelques mouvements de la main ici. Je trouve S4 complexe et il faut assez de puissance cérébrale pour gérer le mécanisme de recherche / recherche du côté fonctionnel de R. Avez-vous vraiment besoin de cette fonctionnalité OO? Gestion de l’état = gestion de la complexité – laissez cela pour Python ou Java =)

  4. Ecrire des tests unitaires. Utilisez le package testthat.

  5. Chaque fois que vous créez / testez votre paquet, parsingz le résultat et recherchez NOTE, INFO, WARNING. En outre, scannez physiquement avec vos propres yeux. Il y a une partie de l’étape de construction qui note les conflits, mais qui ne lui associe pas un WARN, etc.

  6. Ajoutez des assertions et des invariants juste après un appel à un package tiers. En d’autres termes, ne faites pas entièrement confiance à ce que quelqu’un d’autre vous donne. Sondez un peu le résultat et stop() si le résultat est inattendu. Vous n’êtes pas obligé de devenir fou – choisissez une ou deux affirmations qui impliquent des résultats valides / fiables.

Je pense qu’il y a plus mais c’est devenu de la mémoire musculaire maintenant =) J’augmenterai si d’autres viennent à moi.

Ma prise de vue:

Résumé: La flexibilité vient avec un prix. Je suis prêt à payer ce prix.

1) Je n’utilise tout simplement pas les paquets qui causent ce genre de problèmes. Si j’ai vraiment besoin d’une fonction de ce paquet dans mes propres paquets, j’utilise le importFrom() dans mon fichier NAMESPACE . En tout cas, si j’ai des problèmes avec un paquet, je contacte l’auteur du paquet. Le problème est à leurs côtés, pas R.

2) Je n’utilise jamais :: dans mon propre code. En exportant uniquement les fonctions nécessaires à l’utilisateur de mon paquet, je peux conserver mes propres fonctions dans NAMESPACE sans rencontrer de conflits. Les fonctions qui ne sont pas exscopes ne masqueront pas non plus les fonctions portant le même nom, ce qui représente une double victoire.

Un bon guide sur la façon dont les environnements, les espaces de noms et les goûts fonctionnent exactement ici: http://blog.obeautifulcode.com/R/How-R-Searches-And-Finds-Stuff/

Ceci est certainement un must pour tout le monde écrit des paquets et les goûts. Après avoir lu ceci, vous réaliserez que l’utilisation de :: dans votre code de package n’est pas nécessaire.