Note préliminaire : Lors de leur dernière mise à jour, ces notes ont été révisées en utilisant R version 4.0.3 et le package testthat
version 3.0.2. Pour d’autres versions, les informations peuvent différer.
Ces notes présentent comment tester et déboguer nos fonctions en R. Nous allons également y apprendre à contrôler les messages d’erreur et d’avertissement produits par nos fonctions. Cependant, avant d’entrer dans le vif du sujet, revenons sur les étapes conseillées de développement de fonctions.
Rappelons-nous que l’objectif numéro 1 des bonnes pratiques de programmation en R est de développer du code qui produit les résultats escomptés. Voici quelques conseils pour atteindre cet objectif.
Les étapes de développement de fonction conseillées dans les notes sur les fonctions R peuvent être complétées comme suit :
Les étapes 5 et 6 ont été ajoutées à celles déjà présentées. L’étape 5 sert à s’assurer de rencontrer l’objectif de produire les résultats escomptés. L’étape 6, celle du débogage, est nécessaire lorsque quelque chose cloche dans le comportement de la fonction. Les sous-étapes du débogage nous mène, espérons le, à régler les anomalies.
La démarche de travail recommandée ici a aussi pour but d’aider à atteindre le deuxième objectif des bonnes pratiques : développer du code facile à maintenir. Au fil du temps, il n’est pas rare d’avoir besoin de modifier une fonction que nous avons créée. La modification peut avoir pour but de :
Quand vient le temps de modifier une fonction, notre travail est facilité si celle-ci a été bien documentée et testée. Avant d’apporter des changements au code, il est encore recommandé de bien planifier le travail (donc de réfléchir avant de programmer). Après avoir modifié la fonction, il faut mettre à jour la documentation et les tests au besoin. Exécuter ces tests de nouveau nous permet de nous assurer que les modifications apportées n’ont pas altéré d’anciens comportements de la fonction qui ne doivent pas changer.
Créons ensemble une fonction qui calcule la distance de Manhattan entre deux points.
Planification (étape 1) :
Développement du corps de la fonction (étape 2) :
# mini-données test
<- c(0, 0)
pt1 <- c(1, 1)
pt2
# Code le plus simple qui me vient en tête
abs(pt1[1] - pt2[1]) + abs(pt1[2] - pt2[2])
## [1] 2
# Il faudrait que ça fonctionne peu importe la dimension de l'espace dans lequel mes
# points sont représentés (donc peu importe la longueur des vecteurs pt1 et pt2)
sum(abs(pt1 - pt2))
## [1] 2
Création de la fonction à partir du programme développé (étape 3) :
<- function(point1, point2) {
dist_manhattan sum(abs(point1 - point2))
}
Documentation de la fonction (étape 4) :
Le plus simple est de fournir des informations en commentaires avant la définition de la fonction ou au début du corps de celle-ci. Mentionnons ici ce que la fonction fait, quels arguments elle accepte en entrée et ce qu’elle retourne en sortie.
# Calcule la distance de Manhattan entre deux points
# Arguments :
# - point1 : Un vecteur numerique des coordonnees du premier point.
# - point2 : Un vecteur numerique des coordonnees du deuxieme point.
# Sortie : La distance de Manhattan entre point1 et point2 (une seule valeur).
<- function(point1, point2) {
dist_manhattan sum(abs(point1 - point2))
}
Nous allons revenir sur ce point dans le cours sur la création de packages, car toute fonction d’un package doit avoir une fiche d’aide. Nous verrons donc une façon plus formelle de documenter des fonctions.
Test et débogage (étapes 5 et 6) :
Nous allons faire les tests et le débogage après avoir vu la théorie à ce sujet dans les sections suivantes.
Une bonne pratique dans l’organisation de notre code est de placer les définitions de nos fonctions dans un ou des fichiers dédiés (donc contenant uniquement ces définitions). Ainsi, les instructions comportant des appels à nos fonctions ne sont pas dans le même programme R que les définitions des fonctions.
Cependant, afin de pouvoir utiliser nos fonctions, elles doivent être présentes dans un des environnements du chemin de recherche de R. Nous pourrions les mettre dans un package que nous créons et charger ce package. Plus simplement, nous pourrions soumettre le code définissant les fonctions dans la console R afin de créer les fonctions dans notre environnement de travail. C’est la façon de faire utilisée dans le cours jusqu’à maintenant.
Si nous avons placé les définitions de fonctions dans un fichier à part, il est facile de soumettre d’un coup tout le code contenu dans le fichier en une seule commande : un appel à la fonction source
. Par exemple, si nos fonctions sont définies dans le fichier mes_fonctions.R
du répertoire C:\coursR
, la commande suivante :
source("C:/coursR/mes_fonctions.R")
évalue toutes les instructions contenues dans mes_fonctions.R
. Les objets créés par ces instructions sont stockés par défaut dans l’environnement de travail.
Appeler la fonction source
est donc similaire à sélectionner tout le code contenu dans un fichier et le soumettre dans la console. Cependant, les deux façons de faire ne sont pas identiques. Avec source
, dès que le code dans le fichier comporte au moins une erreur de syntaxe, aucune ligne de code du fichier n’est soumise. Aussi, seuls les appels spécifiques à la fonction print
provoquent des impressions, alors qu’une instruction contenant seulement le nom d’un objet ne génère aucune impression. Mais la plus grande différence entre les deux approches est que soumettre une commande source
est plus efficace en terme de temps de travail que de sélectionner des lignes de code dans un script R, puis de soumettre toutes ces lignes. Avec source
, le script R contenant les définitions des fonctions n’a même pas besoin d’être ouvert.
Ainsi, pour compléter la bonne pratique de placer les définitions de nos fonctions dans des fichiers distincts, il est recommandé d’inclure un appel à la fonction source
au début d’un programme R utilisant des fonctions définies dans un autre fichier, afin de soumettre le contenu de ce fichier pour avoir accès aux fonctions qui y sont définies. Ces appels à la fonction source
devraient accompagner les chargements des packages utilisés dans le programme.
Tester ses fonctions consiste à appeler les fonctions en donnant en entrée des valeurs d’arguments pour lesquelles nous savons quel résultat devrait être obtenu.
Les objectifs sont d’obtenir les résultats escomptés, mais aussi de générer des erreurs et avertissement en temps opportun.
Afin de vérifier si une fonction retourne le résultat escompté, il faut la tester dans toutes sortes de situations.
Testons la fonction dist_manhattan
avec d’autres points que ceux utilisés pour développer la fonction.
dist_manhattan(point1 = c(0, -5), point2 = c(0, -15))
## [1] 10
Résultat attendu selon un calcul à la main : 10 = résultat obtenu (nous passons le test avec succès).
dist_manhattan(point1 = c(0, 0, 0, 0, 0), point2 = c(1, 1, 1, 1, 1))
## [1] 5
Résultat attendu selon un calcul à la main : 5 = résultat obtenu (nous passons le test avec succès).
dist
qui implémente le même calcul que notre fonction dist_manhattan
:dist_manhattan(point1 = c(0, 0), point2 = c(1, 1))
## [1] 2
dist(rbind(c(0, 0), c(1, 1)), method = "manhattan")
## 1
## 2 2
dist_manhattan(c(0, 0), c(1, 1)) ==
dist(rbind(c(0, 0), c(1, 1)), method = "manhattan")[1]
## [1] TRUE
dist_manhattan(point1 = c(0, -5), point2 = c(0, -15))
## [1] 10
dist(rbind(c(0, -5), c(0, -15)), method = "manhattan")
## 1
## 2 10
dist_manhattan(c(0, -5), c(0, -15)) ==
dist(rbind(c(0, -5), c(0, -15)), method = "manhattan")[1]
## [1] TRUE
dist_manhattan(point1 = c(0, 0, 0, 0, 0), point2 = c(1, 1, 1, 1, 1))
## [1] 5
dist(rbind(c(0, 0, 0, 0, 0), c(1, 1, 1, 1, 1)), method = "manhattan")
## 1
## 2 5
dist_manhattan(c(0, 0, 0, 0, 0), c(1, 1, 1, 1, 1)) ==
dist(rbind(c(0, 0, 0, 0, 0), c(1, 1, 1, 1, 1)), method = "manhattan")[1]
## [1] TRUE
Nous obtenons les mêmes distances.
Si nous n’avions pas obtenu les résultats escomptés, il aurait fallu apporter des correctifs à notre fonction.
Les tests visent aussi à vérifier si une fonction réagit correctement aux exceptions. Qu’est-ce qu’une exception?
Une exception est une situation anormale ou exceptionnelle qui requiert un traitement spécial (souvent l’arrêt de la fonction).
Des exemples d’exceptions sont :
Lors de la rencontre d’exceptions, les fonctions R réagissent en générant des erreurs ou des avertissements.
Les erreurs et avertissements sont appelés conditions en R.
En plus des erreurs et avertissements, R comporte un troisième type de condition : les messages.
Les différents types de conditions sont définis ainsi :
Notons que les messages associés à une condition R, peu importe son type, sont parfois traduits de façon automatique en fonction de la langue de notre système d’exploitation.
Testons si notre fonction dist_manhattan
gère correctement quelques exceptions.
dist_manhattan
des points de dimensions différentes, que se passe-t-il?dist_manhattan(point1 = c(-1, 0), point2 = c(1, 2, 3))
## Warning in point1 - point2: longer object length is not a multiple of shorter
## object length
## [1] 8
Nous pourrions préférer que la fonction retourne une erreur plutôt qu’un avertissement. Nous y reviendrons plus loin.
dist_manhattan
des arguments non numériques, que se passe-t-il?dist_manhattan(point1 = c("a", "b"), point2 = c("c", "d"))
## Error in point1 - point2: non-numeric argument to binary operator
L’exécution de la fonction s’arrête et le message d’erreur affiché est informatif. Un message informatif aide l’utilisateur à comprendre ce qu’il a fait incorrectement. Un message non informatif ne guide pas suffisamment l’utilisateur dans la modification de son appel de la fonction afin de ne plus avoir d’erreur.
dist_manhattan(point1 = rbind(c(0, 0), c(1, 0)), point2 = rbind(c(3, 2), c(2, 3)))
## [1] 9
Ce résultat peut être surprenant pour quelqu’un qui pensait obtenir plus d’une distance, par exemple une entre la ligne i de point1
et la ligne i de point2
pour tout i = 1, …, nrow(point1)
. Nous pourrions envisager de produire un message d’avertissement (nous verrons comment faire plus loin).
Le package testthat
offre des fonctions facilitant l’écriture, l’organisation et l’exécution automatique de tests unitaires en R. Voici quelques fonctions du package :
fonctions d’écriture (un appel à une de ces fonctions = un test unitaire) :
expect_equal
: pour tester la quasi égalité entre des valeurs (différences inférieures à une certaine tolérance ignorées), à l’image de ce que fait all.equal
(mais expect_equal
donne plus d’information que all.equal
en cas de différences);expect_error
: pour tester la génération d’une erreur;expect_warning
: pour tester la génération d’un avertissement;testthat
propose plusieurs fonctions de type expect_*
, voir https://testthat.r-lib.org/reference/index.html#section-expectations);fonction d’organisation : test_that
;
fonctions d’exécution : test_file
, test_package
, etc.
L’utilisation de ce package n’est pas décrite en détail ici, mais un court exemple est présenté pour illustrer son utilisation. Écrire ses tests avec testthat
demande un certain investissement en temps, mais une fois cette étape terminée, il est facile de lancer ses tests à plusieurs reprises en cours de travail.
Voici quelques exemples qui reprennent des tests effectués précédemment sur la fonction dist_manhattan
.
library(testthat)
test_that("nous reproduisons un calcul à la main", {
expect_equal(dist_manhattan(point1 = c(0, -5), point2 = c(0, -15)), 10)
})
## Test passed
test_that("nous obtenons le même résultat que la fonction dist", {
expect_equal(
dist_manhattan(point1 = c(0, 0), point2 = c(1, 1)),
as.vector(dist(rbind(c(0, 0), c(1, 1)), method = "manhattan"))
) })
## Test passed
test_that("des vecteurs de dimensions différentes génèrent une erreur", {
expect_error(dist_manhattan(point1 = c(-1, 0), point2 = c(1, 2, 3)))
})
## -- Warning (<text>:2:3): des vecteurs de dimensions différentes génèrent une erreur --
## longer object length is not a multiple of shorter object length
## Backtrace:
## 1. testthat::expect_error(...)
## 6. global::dist_manhattan(point1 = c(-1, 0), point2 = c(1, 2, 3))
##
## -- Failure (<text>:2:3): des vecteurs de dimensions différentes génèrent une erreur --
## `dist_manhattan(point1 = c(-1, 0), point2 = c(1, 2, 3))` did not throw an error.
test_that("des matrices génèrent un avertissement", {
<- rbind(c(0, 0), c(1, 0))
mat1 <- rbind(c(3, 2), c(2, 3))
mat2 expect_warning(dist_manhattan(point1 = mat1, point2 = mat2))
})
## -- Failure (<text>:4:3): des matrices génèrent un avertissement ----------------
## `dist_manhattan(point1 = mat1, point2 = mat2)` did not produce any warnings.
Nous allons apporter plus loin des changements à dist_manhattan
afin de passer avec succès tous ces tests. Pour l’instant, nous passons avec succès les 2 premiers, mais échouons les deux derniers.
Le débogage est un processus méthodique pour trouver et régler les bogues dans un programme informatique, soit les anomalies de fonctionnement du programme.
Si nos tests ont révélé des résultats inattendus ou des exceptions mal gérées, un débogage est de mise. La première étape du débogage est de repérer le bout de code responsable du bogue et de comprendre pourquoi le bogue est rencontré. Ensuite, il faut modifier le code pour corriger le problème.
Les outils présentés pour accomplir la première étape du débogage peuvent aussi servir à comprendre une condition obtenue lors de l’utilisation d’une fonction programmée par quelqu’un d’autre.
Lorsque nous obtenons une erreur en appelant une fonction, le message d’erreur explique parfois suffisamment clairement pourquoi la fonction ne peut pas retourner de résultats.
Exemple : argument fourni dans un mauvais format
aggregate(x = iris$Sepal.Length, by = iris$Species, FUN = min)
## Error in aggregate.data.frame(as.data.frame(x), ...): 'by' must be a list
Dans cet exemple, le message d’erreur nous aide à comprendre que nous avons mal utilisé la fonction et nous met sur une piste pour modifier notre appel à la fonction.
Dans d’autres cas, les messages d’erreur ou d’avertissement ne sont pas très informatifs. Dans un tel cas, la documentation de la fonction peut parfois nous aider. Une autre option est d’utiliser des outils de débogage pour comprendre la nature de l’exception rencontrée et comment utiliser correctement la fonction.
Lorsque nous croyons avoir découvert un bogue dans du code que nous n’avons pas développé nous même, il est bien de contacter le mainteneur du code pour lui en faire part. Il faut par contre d’abord s’assurer d’utiliser la dernière version du code. Le mainteneur des fonctions de base de R est le R Core Team. La page web https://www.r-project.org/bugs.html explique comment faire part de bogues potentiels à cette équipe. Pour signaler un bogue dans un package R, il suffit de contacter son mainteneur par courriel. Toute documentation de package contient l’adresse courriel de son mainteneur. Si le package est développé en utilisant un service web public d’hébergement et de gestion de versions, tel que GitHub, la meilleure façon de rapporter un bogue est de créer un nouvel issue.
Faire part d’un bogue potentiel à un mainteneur présente des avantages pour tous. L’utilisateur arrive souvent ainsi à régler le problème qu’il rencontre et le mainteneur a l’opportunité d’améliorer son code en corrigeant des bogues ou en identifiant les aspects moins compris de son code ou de sa documentation.
Supposons que nous développons une fonction qui calcule des moyennes. Si l’argument donné en entrée est un vecteur, la fonction doit calculer une seule moyenne, celle des observations dans le vecteur. Si l’argument donné en entrée a plus d’une dimension, la fonction colMeans
doit être appelée. Pour une matrice en entrée, nous obtiendrions donc la moyenne des observations dans chaque colonne.
<- function(x) {
mean_2 if (is.null(dim(x))) {
colMeans(x)
else {
} mean(x)
} }
# Tentatives d'utilisation de la fonction
mean_2(matrix(1:4, nrow = 2, ncol = 2))
## [1] 2.5
mean_2(1:4)
## Error in colMeans(x): 'x' must be an array of at least two dimensions
La fonction ne fait pas ce que nous voulions. Déboguons-la.
traceback
La première chose à faire en cas d’erreur rencontrée est de tenter de comprendre le message d’erreur affiché. La fonction traceback
peut apporter plus d’informations concernant la provenance de l’erreur.
traceback()
## 3: stop("'x' must be an array of at least two dimensions")
## 2: colMeans(x) at #3
## 1: mean_2(1:4)
Cette fonction retourne la séquence des appels de fonctions qui a mené à l’erreur. Nous apprenons ici que l’erreur a été générée par la fonction stop
, dans un appel à la fonction colMeans
, à l’intérieur de l’appel à mean_2
. En fait, ici, le message d’erreur nous avait déjà informés que l’erreur provenait d’un appel à colMeans
. Pourquoi la fonction colMeans
est-elle appelée alors que la valeur de x
fournie en entrée est un vecteur?
browser
La fonction browser
permet d’interrompre l’exécution d’une fonction, de donner accès à l’environnement d’exécution de la fonction et d’exécuter le corps de la fonction une instruction à la fois. Pour ce faire, il suffit d’insérer l’instruction
browser()
dans le corps de la fonction, à l’endroit où nous souhaitons interrompre l’exécution. Ensuite, il faut soumettre de nouveau la définition de la fonction. Le prochain appel à cette fonction sera interrompu lorsque l’instruction browser()
sera rencontré.
La commande
browser()
peut être appelée un point d’arrêt (breakpoint), pour réutiliser un terme usuel en débogage informatique.
Lorsque l’outil d’inspection de code ouvert par la fonction browser
est actif, le symbole d’invite de commandes (prompt) dans la console devient > Browse[d]
au lieu de >
. Ici, d
représente la profondeur de la séquence d’appels de fonctions. Les mots-clés suivants sont alors compris (voir help(browser)
pour la liste complète des mots-clés) :
n
: pour exécuter la prochaine commande,c
: pour exécuter jusqu’au prochain point d’arrêt (ex. une autre commande browser()
),Q
: pour sortir de l’outil d’inspection de code et retourner au mode R interactif usuel.Il est aussi possible de soumettre n’importe quelle commande R dans l’outil d’inspection de code. Par contre, si un objet porte le nom d’un des mots-clés, nous ne pouvons plus taper directement son nom dans la console pour l’afficher. Il faut passer par une commande telle que print(n)
.
L’environnement intégré de développement RStudio offre des fonctionnalités facilitant grandement l’utilisation de la fonction browser
. Lorsque la fonction browser
est appelée, RStudio :
browser
a été appelé et souligne en jaune le prochain bout de code à être soumis dans l’exécution pas à pas,browser
a été appelé à partir de la sous-fenêtre Environment,n
, c
, Q
, etc.browser
dans le corps d’une fonction
Il ne faut pas oublier d’aller retirer la commande et de soumettre de nouveau le code source de la fonction lorsque le débogage est terminé. Ainsi, l’outil d’inspection de code ne sera plus ouvert à chaque fois que la fonction est appelée.
trace
, comme suit :trace(mean_2, tracer = browser)
L’argument at
permet de spécifier à quel endroit dans le code la commande browser()
doit être insérée. Par défaut elle est mise dans la première ligne. La commande browser()
est ensuite retirée avec la fonction untrace
, comme suit :
untrace(mean_2)
debug
, comme suit :debug(mean_2)
La commande browser()
est alors insérée dans la première ligne du corps de la fonction mean_2
. La commande browser()
est ensuite retirée avec la fonction undebug
, comme suit :
undebug(mean_2)
error
Il est possible du faire du débogage post mortem en R. Ce type de débogage consiste à tenter de trouver la cause d’une erreur après que l’exécution de la fonction ait été interrompue. La fonction traceback
est donc en fait un outil de débogage post mortem, mais pas très puissant.
Si nous donnons comme valeur à l’option globale nommée error
la fonction recover
comme suit
options(error = recover)
R donne accès à l’environnement d’exécution de toute fonction dans laquelle une erreur est générée. Par exemple, essayons de soumettre la commande
mean_2(1:4) # fonction non soumise ici, à essayer dans une session R
R nous demande alors d’identifier l’environnement que nous souhaitons inspecter : celui de l’exécution de mean_2
ou celui de l’exécution de colMeans
(car l’erreur a été rencontrée dans un appel à colMeans
, qui a eu lieu dans un appel à mean_2
). Après avoir fait notre choix, nous pouvons visualiser les objets dans l’environnement d’exécution choisi.
Pour remettre l’option error
à sa valeur par défaut, il faut soumettre le code suivant :
options(error = NULL)
Dans l’exemple de la fonction mean_2
, vous l’avez déjà trouvé, l’erreur est simplement que ce n’est pas la bonne branche du if
qui est sélectionné selon la nature de x
.
Correction :
<- function(x) {
mean_2 if (!is.null(dim(x))) { # ajout d'une négation ici
colMeans(x)
else {
} mean(x)
} }
# Tentatives d'utilisation de la fonction
mean_2(matrix(1:4, nrow = 2, ncol = 2))
## [1] 1.5 3.5
mean_2(1:4)
## [1] 2.5
print
et cat
Les fonctions print
et cat
s’avère aussi être des outils de débogage très simples en R. Ces fonctions permettent d’imprimer une trace des calculs effectués dans la fonction.
Intéressons-nous au cas particulier d’une boucle qui est arrêtée à cause d’une erreur. Il est alors informatif de savoir quelle itération de la boucle est problématique.
Voici un exemple de fonction qui sert à inverser une série de matrices fournies en entrée.
<- function(...) {
inverses <- list(...)
matrices <- vector(mode = "list", length = length(matrices))
inverses for (i in 1:length(matrices)) {
<- solve(matrices[[i]])
inverses[[i]]
}return(inverses)
}
# Tentative d'utilisation de la fonction
inverses(
a = matrix(1:4, nrow = 2, ncol = 2),
b = matrix(c(1, 0, -2, 0, 1, 2, -1, -2, -2), nrow = 3, ncol = 3),
c = matrix(c(1, 3, 2, 6, 4, 2, 3, 5, 6) , nrow = 3, ncol = 3)
)
## Error in solve.default(matrices[[i]]) :
## Lapack routine dgesv: system is exactly singular: U[3,3] = 0
Il est possible d’obtenir de l’information concernant l’itération problématique avec du débogage post mortem utilisant l’option error
. Une autre possibilité serait de faire imprimer une trace temporaire des calculs à chaque itération.
<- function(...) {
inverses <- list(...)
matrices <- vector(mode = "list", length = length(matrices))
inverses for (i in 1:length(matrices)) {
cat("itération", i, "\n")
# ou
# print(i)
<- solve(matrices[[i]])
inverses[[i]]
}return(inverses)
}
# Tentative d'utilisation de la fonction
inverses(
a = matrix(1:4, nrow = 2, ncol = 2),
b = matrix(c(1, 0, -2, 0, 1, 2, -1, -2, -2), nrow = 3, ncol = 3),
c = matrix(c(1, 3, 2, 6, 4, 2, 3, 5, 6) , nrow = 3, ncol = 3)
)
## itération 1
## itération 2
## Error in solve.default(matrices[[i]]) :
## Lapack routine dgesv: system is exactly singular: U[3,3] = 0
Nous savons maintenant que l’erreur est causée par la deuxième matrice fournie en entrée, soit la matrice b
.
Une fois le problème compris et réglé (ce qui sera fait plus loin pour cet exemple), nous souhaitons la plupart du temps retirer les appels à la fonction print
ou cat
de la fonction.
Nous avons parfois besoin que nos fonctions génèrent des erreurs et des avertissements, notamment :
Il vaut mieux arrêter l’exécution de la fonction si les arguments fournis en entrée sont incorrects et que le comportement de la fonction n’est pas approprié (mauvais calcul ou message d’erreur non informatif).
stop
, stopifnot
, match.arg
(vue dans les notes sur la création de fonctions en R),warning
.Remarque : Pour la tâche spécifique de valider les valeurs fournies en argument, le package checkmate
propose plusieurs fonctions rendant la tâche plus facile au développeur, par exemple les fonctions checkCount
, checkScalar
, checkIntegerish
, etc. Nous ne verrons cependant pas ce package ici.
Faisons générer une erreur à notre fonction dist_manhattan
lorsqu’elle reçoit en entrée deux vecteurs qui ne sont pas de mêmes longueurs.
<- function(point1, point2) {
dist_manhattan if (length(point1) != length(point2)) {
stop("'point1' and 'point2' must have the same length")
}return(sum(abs(point1 - point2)))
}
dist_manhattan(c(-1, 0), c(1, 2, 3))
## Error in dist_manhattan(c(-1, 0), c(1, 2, 3)) :
## 'point1' and 'point2' must have the same length
ou encore
<- function(point1, point2) {
dist_manhattan stopifnot(length(point1) == length(point2))
return(sum(abs(point1 - point2)))
}
dist_manhattan(c(-1, 0), c(1, 2, 3))
## Error in dist_manhattan(c(-1, 0), c(1, 2, 3)): length(point1) == length(point2) is not TRUE
Faisons générer un avertissement à notre fonction dist_manhattan
si les arguments point1
et point2
sont de dimension supérieure à 1.
<- function(point1, point2) {
dist_manhattan if (length(point1) != length(point2)) {
stop("'point1' and 'point2' must have the same length")
}if (!is.null(dim(point1)) || !is.null(dim(point2))) {
warning("'point1' and 'point2' are treated as dimension 1 vectors")
}return(sum(abs(point1 - point2)))
}
dist_manhattan(rbind(c(0, 0), c(1, 0)), rbind(c(3, 2), c(2, 3)))
## Warning in dist_manhattan(rbind(c(0, 0), c(1, 0)), rbind(c(3, 2), c(2, 3))):
## 'point1' and 'point2' are treated as dimension 1 vectors
## [1] 9
Nos tests ne devraient maintenant plus échouer.
test_that("nous reproduisons un calcul à la main", {
expect_equal(dist_manhattan(point1 = c(0, -5), point2 = c(0, -15)), 10)
})
## Test passed
test_that("nous obtenons le même résultat que la fonction dist", {
expect_equal(
dist_manhattan(point1 = c(0, 0), point2 = c(1, 1)),
as.vector(dist(rbind(c(0, 0), c(1, 1)), method = "manhattan"))
) })
## Test passed
test_that("des vecteurs de dimensions différentes génèrent une erreur", {
expect_error(dist_manhattan(point1 = c(-1, 0), point2 = c(1, 2, 3)))
})
## Test passed
test_that("des matrices génèrent un avertissement", {
<- rbind(c(0, 0), c(1, 0))
mat1 <- rbind(c(3, 2), c(2, 3))
mat2 expect_warning(dist_manhattan(point1 = mat1, point2 = mat2))
})
## Test passed
C’est bien le cas.
Il est possible d’attraper des erreurs et de les manipuler avec la fonction try
. Cette fonction permet entre autres d’éviter l’arrêt d’une boucle lorsqu’une erreur est rencontrée pour une certaine itération.
Mentionnons que la fonction try
est en fait une fonction enveloppe de la fonction tryCatch
, qui est un peu plus compliquée à utiliser, mais qui est plus flexible. Le tidyverse
offre aussi des fonctions permettant de manipuler des conditions : les fonctions safely
, possibly
et quietly
du packagepurrr
.
Nous allons ici seulement illustré comment utiliser try
.
Rappelons que l’exécution de notre fonction inverses
est arrêtée dès qu’elle rencontre une matrice non inversible :
inverses(
a = matrix(1:4, nrow = 2, ncol = 2),
b = matrix(c(1, 0, -2, 0, 1, 2, -1, -2, -2), nrow = 3, ncol = 3),
c = matrix(c(1, 3, 2, 6, 4, 2, 3, 5, 6) , nrow = 3, ncol = 3)
)
## Error in solve.default(matrices[[i]]) :
## Lapack routine dgesv: system is exactly singular: U[3,3] = 0
Il serait plutôt souhaitable que le calcul soit fait pour toutes les matrices, en sautant celles non inversibles.
<- function(...) {
inverses <- list(...)
matrices <- vector(mode = "list", length = length(matrices))
inverses for (i in 1:length(matrices)) {
<- try(solve(matrices[[i]]), silent = TRUE)
tentative if (inherits(tentative, "try-error")) {
# Si la commande a généré une erreur, retourner une matrice de NA
<- matrix(NA, nrow = nrow(matrices[[i]]), ncol = ncol(matrices[[i]]))
inverses[[i]] else {
} # Sinon, retourner la matrice inversée
<- tentative
inverses[[i]]
}
}return(inverses)
}
inverses(
a = matrix(1:4, nrow = 2, ncol = 2),
b = matrix(c(1, 0, -2, 0, 1, 2, -1, -2, -2), nrow = 3, ncol = 3),
c = matrix(c(1, 3, 2, 6, 4, 2, 3, 5, 6) , nrow = 3, ncol = 3)
)
## [[1]]
## [,1] [,2]
## [1,] -2 1.5
## [2,] 1 -0.5
##
## [[2]]
## [,1] [,2] [,3]
## [1,] NA NA NA
## [2,] NA NA NA
## [3,] NA NA NA
##
## [[3]]
## [,1] [,2] [,3]
## [1,] -0.35 0.75 -0.45
## [2,] 0.20 0.00 -0.10
## [3,] 0.05 -0.25 0.35
Il faut donner comme premier argument à la fonction try
une expression. Dans l’exemple précédent, il s’agissait d’une seule instruction. Il aurait aussi pu s’agir d’une série d’instructions, entre accolades. L’argument silent = TRUE
a signifié à try
de ne pas afficher de messages.
L’objet retourné par try
est l’objet retourné par l’expression fournie en premier argument si aucune erreur n’est rencontrée. Sinon, il s’agit d’un objet de classe "try-error"
contenant le message d’erreur. L’instruction inherits(tentative, "try-error")
retourne TRUE
si l’objet tentative
possède la classe "try-error"
, FALSE
sinon. Rappelons que l’utilisation de la fonction inherits
est l’outil recommandé pour tester l’appartenance d’un objet à une classe.
warn
warn
prend une valeur négative : tous les avertissements sont ignorés,warn
prend la valeur 0 (option par défaut) : les avertissements sont affichés à la fin de l’exécution de la fonction,warn
prend la valeur 1 : les avertissements sont affichés au fur et à mesure qu’ils surviennent,warn
prend la valeur 2 : tous les avertissements sont transformés en erreurs;suppressWarnings
: permet d’ignorer les avertissements générés par des instructions R spécifiques.# Exemple d'utilisation de la fonction suppressWarnings
suppressWarnings(dist_manhattan(rbind(c(0, 0), c(1, 0)), rbind(c(3, 2), c(2, 3))))
## [1] 9
mes_fonctions.R
;source("chemin/mes_fonctions.R")
.Appeler les fonctions en donnant en entrée des valeurs d’arguments pour lesquelles nous savons quel résultat nous devrions obtenir.
Objectifs :
Exception = situation anormale ou particulière qui requiert un traitement spécial
Des exemples d’exceptions sont :
Réaction à des exceptions : erreurs ou avertissements générés.
Différents types de conditions en R :
Fonctions pour l’écriture, l’organisation et l’exécution automatique de tests unitaires en R :
expect_equal
, expect_error
, expect_warning
, etc.;test_that
;test_file
, test_package
, etc.Outils de débogage en R :
traceback()
: retourne la séquence des appels de fonctions provoquant une erreurbrowser
(seule, avec trace
et untrace
ou avec debug
et undebug
) : permet
error = recover
: débogage post mortemprint
et cat
: imprime une trace des calculsProduire des erreurs et des avertissements
stop
, stopifnot
, match.arg
;warning
.Manipuler des erreurs et des avertissements
try
;warn
;suppressWarnings
.Tests :
Débogage :