Gestion des erreurs en R : déboguer, tester, valider

Faites échouer R bruyamment, déboguez vite, validez vos résultats et vérifiez le code écrit par une IA

Un script qui s’exécute sans erreur peut tout de même être faux — et désormais un chatbot écrit la moitié de votre code. Cette leçon parle de confiance : faire échouer R bruyamment et correctement (stop/warning/stopifnot/tryCatch), déboguer vite quand ça arrive (traceback/browser/recover), valider vos données et vos résultats (assertions, pointblank, testthat), et vérifier que le code écrit par une IA s’exécute réellement et est correct.

Date de publication

19 juillet 2026

Modifié

19 juillet 2026

AstucePoints clés
  • Échouez bruyamment, pas silencieusement. Un script qui se termine sans se plaindre peut tout de même être faux. Utilisez stop() pour interrompre en cas d’entrée invalide, warning() pour quelque chose de suspect, et stopifnot() pour des assertions en une ligne — un échec bruyant que vous voyez vaut mieux qu’un nombre discret auquel vous ne pouvez pas vous fier.
  • Attrapez à dessein, pas partout. tryCatch() gère un échec attendu et renvoie une valeur de repli ; withCallingHandlers() journalise et continue. Tout envelopper dans tryCatch(..., error = function(e) NULL) ne fait que masquer les bogues — laissez planter les erreurs inattendues.
  • Le débogage est le chemin rapide, pas le but. traceback() montre où ça a cassé, browser() met en pause pour que vous puissiez regarder autour, options(error = recover) vous dépose dans l’appel qui a échoué. Un copilote écrit le code ; c’est vous qui le diagnostiquez quand il est faux.
  • Validez les données et les résultats. Affirmez ce que vos entrées doivent respecter (pointblank), et testez vos fonctions d’analyse (testthat) — pas seulement les packages. Un résultat que vous ne pouvez pas vérifier est un résultat auquel vous ne pouvez pas vous fier.
  • Vérifiez le code écrit par une IA. Exécutez-le sur une entrée connue, affirmez la sortie attendue, puis soumettez-le à des cas limites. Ce contrôle — le runtime comme juge — est une compétence qu’aucun chatbot ne peut faire à votre place.

Introduction

Deux échecs vous coûtent bien plus cher qu’une trace d’appels ne le fera jamais. Le premier : un script qui s’exécute proprement, affiche un nombre plausible et est discrètement faux — un NA silencieux, une jointure qui a supprimé la moitié de vos lignes, une confusion d’unités. Le second, plus récent : une fonction qu’un assistant a écrite, qui paraît correcte, a passé votre coup d’œil, et casse sur la seule entrée que vous n’avez pas essayée.

Ni l’un ni l’autre n’est un problème de syntaxe. Les deux sont des problèmes de confiance, et cette leçon a pour but de regagner cette confiance — faire en sorte que R vous dise quand quelque chose ne va pas, et vous donner un moyen rapide de vérifier qu’un résultat (le vôtre ou celui d’une IA) est réellement correct.

Un peu de vocabulaire d’abord, car le système d’erreurs de R a des termes précis. Une condition est un signal que R émet pendant l’exécution ; les trois que vous utiliserez sont une erreur (arrête l’exécution — quelque chose ne va pas et ne peut pas continuer), un avertissement (l’exécution continue, mais quelque chose semblait anormal) et un message (simple sortie de diagnostic, pas un problème). Une assertion est une vérification qu’un fait énoncé est vrai, qui échoue bruyamment si ce n’est pas le cas. Un test unitaire est une petite vérification automatique qu’une fonction renvoie ce que vous attendez sur une entrée connue. Nous utiliserons tout cela pour construire du code sur lequel vous pouvez compter.

Voici la progression : échouer bruyamment pour que les problèmes fassent surface → déboguer efficacement quand ils surviennent → valider vos données et vos résultats pour que « ça a tourné » devienne « c’est juste » → vérifier le code écrit par une IA de la même manière. C’est exactement le jugement qu’un copilote ne peut pas vous donner : il écrit volontiers la fonction ; décider s’il faut lui faire confiance reste votre travail.

NoteComment les exemples s’exécutent

Chaque exemple est autonome — copiez n’importe quel bloc et il s’exécute tout seul. Les outils de débogage du milieu (browser(), debug(), recover()) sont interactifs : ils mettent en pause une session en cours, ils sont donc montrés sous forme d’extraits à exécuter dans votre propre console R, pas exécutés ici. Tout le reste de cette page est une sortie réelle, réellement exécutée.

Échouer bruyamment et correctement

La pire erreur est celle qui ne se produit pas. Si une fonction accepte n’importe quoi et renvoie tout de même un nombre, le bogue voyage en aval et fait surface sous forme de conclusion erronée, pas de message rouge. Le premier travail est donc de faire s’arrêter les mauvaises entrées — à la porte, avec un message qui nomme le problème.

stop(), warning(), message() — dire ce qui ne va pas

Trois signaux, trois niveaux de gravité. stop() interrompt avec une erreur (l’appelant doit la corriger), warning() laisse l’exécution continuer mais signale quelque chose de suspect, et message() affiche un simple diagnostic. Vérifiez d’emblée les hypothèses dont dépend votre fonction, puis faites-leur confiance :

celsius_to_f <- function(x) {
  if (!is.numeric(x)) {
    stop("`x` must be numeric, not ", class(x)[1])       # abort: can't continue
  }
  if (any(x < -273.15, na.rm = TRUE)) {
    warning("Some values are below absolute zero — check your data.")  # continue, but flag
  }
  x * 9 / 5 + 32
}

# A good error message names the problem AND the offending value:
tryCatch(celsius_to_f("warm"), error = function(e) conditionMessage(e))
[1] "`x` must be numeric, not character"

Le message nomme à la fois l’attente (numeric) et ce qu’il a reçu (character), de sorte que l’appelant le corrige sans ouvrir votre fonction. C’est tout l’intérêt d’une bonne erreur : elle transforme « quelque chose a cassé » en « cette chose précise a cassé, ici ».

stopifnot() — des assertions en une ligne

Écrire un if (...) stop(...) pour chaque précondition devient verbeux. stopifnot() affirme plusieurs faits à la fois et interrompt au premier qui est faux — et si vous nommez la condition, ce nom devient le message d’erreur :

total_revenue <- function(df) {
  stopifnot(
    is.data.frame(df),
    "df must have rows"        = nrow(df) > 0,
    "price must be positive"   = all(df$price > 0)
  )
  sum(df$price)
}

# A bad frame fails with the named message, not a cryptic downstream error:
tryCatch(
  total_revenue(data.frame(price = c(19.99, -5.00))),
  error = function(e) conditionMessage(e)
)
[1] "price must be positive"
# A valid frame passes the gate and computes:
total_revenue(data.frame(price = c(19.99, 5.00, 42.50)))
[1] 67.49

Des assertions comme celles-ci sont une assurance bon marché. Elles documentent ce que la fonction suppose et l’imposent, de sorte qu’une violation apparaît ici — à la cause — au lieu de trois fonctions plus loin sous la forme d’un mystérieux NA.

tryCatch() — gérer un échec attendu

Parfois un échec est attendu et vous voulez vous en remettre : un fichier peut être absent, une valeur peut ne pas s’analyser. tryCatch() exécute une expression et achemine chaque type de condition vers un gestionnaire. Ici une fonction utilitaire renvoie NA (et journalise quelle condition elle a rencontrée) au lieu de faire planter tout le script :

safe_log <- function(x) {
  tryCatch(
    log(x),
    warning = function(w) { message("warning: ", conditionMessage(w)); NA_real_ },
    error   = function(e) { message("error: ",   conditionMessage(e)); NA_real_ },
    finally = message("done checking x = ", deparse(x))
  )
}

safe_log(100)     # normal case
[1] 4.60517
safe_log(-1)      # log(-1) warns (NaN) -> handled, returns NA
[1] NA
safe_log("x")     # log("x") errors    -> handled, returns NA
[1] NA

Chaque appel est acheminé : l’entrée valide renvoie sa valeur, l’avertissement et l’erreur sont chacun attrapés et transformés en un NA propre, et finally s’exécute toujours (utile pour fermer une connexion ou une barre de progression). tryCatch est fait pour les échecs que vous avez anticipés et auxquels vous avez une réponse sensée.

withCallingHandlers() — journaliser, puis continuer

tryCatch() déroule la pile : une fois qu’un gestionnaire s’exécute, l’expression est terminée. Mais parfois vous voulez noter un avertissement et laisser le calcul continuer là où il en était — journaliser sans interrompre. C’est withCallingHandlers() plus un redémarrage :

row_means <- function(m) {
  withCallingHandlers(
    apply(m, 1, function(r) {
      if (anyNA(r)) warning("row has NA")
      mean(r, na.rm = TRUE)
    }),
    warning = function(w) {
      message("logged: ", conditionMessage(w))
      invokeRestart("muffleWarning")   # note it, then resume
    }
  )
}

m <- matrix(c(1, 2, NA, 4, 5, 6), nrow = 2, byrow = TRUE)
row_means(m)
[1] 1.5 5.0

L’avertissement sur la première ligne est journalisé, étouffé, et la boucle continue de calculer chaque ligne — vous obtenez à la fois les diagnostics et le résultat complet. Utilisez withCallingHandlers() quand un avertissement est informatif mais pas fatal ; utilisez tryCatch() quand vous voulez vous arrêter et renvoyer une valeur de repli.

NoteL’attraper, ou la laisser planter ?

L’instinct de tout envelopper dans tryCatch(..., error = function(e) NULL) est un piège — il convertit des bogues bruyants en NULL silencieux (voir Problèmes fréquents). Décidez délibérément :

flowchart TD
    A[Quelque chose peut mal tourner] --> B{Pouvez-vous raisonnablement<br/>récupérer ici ?}
    B -->|Non| C[Laissez planter —<br/>échec bruyant, corrigez la cause]
    B -->|Oui| D{Cas attendu<br/>et gérable ?}
    D -->|Oui| E[tryCatch —<br/>gérez-le, renvoyez une valeur de repli]
    D -->|Non| F[withCallingHandlers —<br/>journalisez et continuez]

Des erreurs typées avec rlang::abort()

Quand un appelant doit réagir différemment à des échecs différents, donnez une classe à vos erreurs. rlang::abort() émet une condition typée, de sorte qu’un gestionnaire peut attraper cette classe précise plutôt que de correspondre au texte du message (ce qui est fragile et casse à la traduction) :

library(rlang)

read_positive <- function(x) {
  if (!is.numeric(x))  abort("x must be numeric",     class = "invalid_type")
  if (any(x < 0))      abort("x must be non-negative", class = "negative_value")
  sqrt(x)
}

# The caller catches by CLASS, not by message string:
tryCatch(
  read_positive(-4),
  negative_value = function(e) paste("handled a negative value:", conditionMessage(e))
)
[1] "handled a negative value: x must be non-negative"

Attraper negative_value gère exactement ce cas et laisse toutes les autres erreurs se propager. Pour tout ce qui dépasse un script — un package, un pipeline partagé — les conditions typées sont la façon dont les appelants réagissent par programmation au lieu de faire du grep sur le texte des erreurs. Le chapitre « Conditions » d’Advanced R est la référence pour le système de conditions complet de R.

Déboguer efficacement quand ça casse

Un bon signalement fait que les échecs s’annoncent. Maintenant vous devez trouver la cause — vite. Voici les outils ; considérez-les comme le chemin rapide vers un correctif, pas comme la destination. (Un copilote écrira volontiers le code ; quand il se comporte mal, voici comment vous voyez pourquoi.)

traceback() — où est-ce que ça a cassé ?

Un traceback est la séquence d’appels de fonctions qui a mené à l’erreur — la pile d’appels. Après n’importe quelle erreur, traceback() l’affiche, l’appel le plus récent en premier, pour que vous voyiez le chemin exact :

outer <- function(x) inner(x)
inner <- function(x) sqrt(x)

outer("a")
#> Error in sqrt(x) : non-numeric argument to mathematical function
traceback()
#> 2: inner(x) at #1
#> 1: outer("a")

La pile vous dit que l’erreur s’est déclenchée dans inner(), appelé depuis outer() — vous savez où regarder avant de lire une seule ligne du corps. Dans RStudio, la même pile apparaît dans le panneau Traceback, et vous pouvez cliquer sur un cadre pour y accéder.

Inspecter l’objet condition

Vous n’avez pas à deviner ce qu’est une erreur — attrapez-la et regardez. Une erreur n’est qu’un objet avec un message et un ensemble de classes :

e <- tryCatch(sqrt("x"), error = function(e) e)

conditionMessage(e)   # the human-readable message
[1] "non-numeric argument to mathematical function"
class(e)              # its condition classes (match the innermost first)
[1] "simpleError" "error"       "condition"  

conditionMessage() vous donne le texte ; class() montre les classes qu’un gestionnaire tryCatch pourrait attraper. C’est ainsi que vous décidez quel nom de gestionnaire écrire — et que vous confirmez qu’une erreur typée (issue d’abort()) porte bien la classe que vous attendez.

browser(), debug() et recover() — entrer à l’intérieur

L’outil de débogage le plus efficace de tous consiste à mettre l’exécution en pause et regarder autour. Déposez un browser() dans une fonction et R s’arrête là, vous remettant une invite interactive avec les variables locales de la fonction en direct — inspectez-les, avancez d’un pas avec n, exécutez jusqu’à la fin avec c, quittez avec Q :

row_share <- function(df) {
  total <- sum(df$value)
  browser()                 # pauses here — inspect df, total, then step with n / c / Q
  df$value / total
}

Deux proches font la même chose sans modifier la fonction. debug(f) parcourt pas à pas chaque appel à f() jusqu’à ce que vous fassiez undebug(f) ; debugonce(f) le fait pour le prochain appel seulement :

debugonce(row_share)        # step through just the next call
row_share(my_data)

Et pour attraper n’importe quelle erreur de façon interactive, réglez options(error = recover) — après un plantage, R liste la pile d’appels et vous laisse descendre dans le cadre qui a échoué pour inspecter ses variables :

options(error = recover)    # after any error, choose a frame to inspect
# ... run the code that fails ...
options(error = NULL)       # restore the default when done

Pour un bogue tenace du type « pourquoi cette valeur est-elle fausse », un browser() sur la ligne suspecte — puis l’affichage de la variable fautive avec str() pour voir sa structure et son type — résout la plupart des cas en une minute. (Le chapitre « Debugging » d’Advanced R est la référence approfondie.)

Valider vos données et vos résultats

Voici le cœur du sujet. La gestion des erreurs et le débogage traitent du code qui casse. Le cas dangereux est le code qui tourne et qui est faux — et la seule défense est de vérifier ce qui doit être vrai : vos entrées avant de calculer, et vos sorties (via les fonctions qui les produisent) après.

Affirmer ce qui doit être vrai de vos données

Avant qu’une analyse ne touche un jeu de données, affirmez sa forme. La version la moins coûteuse est le stopifnot() vu plus haut ; pour un vrai jeu de données avec plusieurs règles au niveau des colonnes, pointblank est conçu pour ça. Vous créez un agent sur la table, déclarez des attentes colonne par colonne, et interrogate() les exécute toutes :

library(pointblank)

set.seed(1)
orders <- data.frame(
  id     = 1:6,
  region = c("North", "South", "East", "West", "North", "South"),
  units  = c(3, 5, 2, 8, 4, 1),
  price  = c(19.99, 5.00, 42.50, 12.00, 8.75, 99.00)
)

agent <- create_agent(tbl = orders, tbl_name = "orders") |>
  col_vals_not_null(vars(region)) |>       # region is never missing
  col_is_numeric(vars(units, price)) |>    # numeric columns really are numeric
  col_vals_gt(vars(price), value = 0) |>   # every price is positive
  col_vals_between(vars(units), left = 1, right = 20) |>  # units in a sane range
  interrogate()

# A compact pass/fail report (f_pass = fraction passing):
get_agent_report(agent, display_table = FALSE)[
  , c("i", "type", "columns", "values", "units", "n_pass", "f_pass")]
# A tibble: 5 × 7
      i type              columns values units n_pass f_pass
  <int> <chr>             <chr>   <chr>  <dbl>  <dbl>  <dbl>
1     1 col_vals_not_null region  <NA>       6      6      1
2     2 col_is_numeric    units   <NA>       1      1      1
3     3 col_is_numeric    price   <NA>       1      1      1
4     4 col_vals_gt       price   0          6      6      1
5     5 col_vals_between  units   1, 20      6      6      1
all_passed(agent)
[1] TRUE

Chaque règle passe (f_pass = 1, all_passed() vaut TRUE), donc les données gagnent leur place dans l’analyse. Maintenant regardez ce qui se passe quand ce n’est pas le cas — un prix corrompu et une région manquante :

library(pointblank)

set.seed(1)
orders <- data.frame(
  id = 1:6, region = c("North", "South", "East", "West", "North", "South"),
  units = c(3, 5, 2, 8, 4, 1), price = c(19.99, 5.00, 42.50, 12.00, 8.75, 99.00)
)
orders$price[3]  <- -42.50   # a data-entry error
orders$region[2] <- NA       # a missing key

agent <- create_agent(tbl = orders, tbl_name = "orders (with issues)") |>
  col_vals_not_null(vars(region)) |>
  col_vals_gt(vars(price), value = 0) |>
  interrogate()

get_agent_report(agent, display_table = FALSE)[
  , c("i", "type", "columns", "n_pass", "f_pass")]
# A tibble: 2 × 5
      i type              columns n_pass f_pass
  <int> <chr>             <chr>    <dbl>  <dbl>
1     1 col_vals_not_null region       5  0.833
2     2 col_vals_gt       price        5  0.833
all_passed(agent)
[1] FALSE

all_passed() vaut maintenant FALSE, et le rapport pointe quelle règle a échoué et de combien (5 lignes sur 6 passent chaque contrôle). Dans un pipeline, vous vous embrancheriez sur all_passed() — arrêter, alerter, mettre en quarantaine les mauvaises lignes — au lieu de moyenner silencieusement un prix négatif dans votre chiffre d’affaires. (Dans une session interactive, l’affichage de l’agent produit le rapport couleur complet de pointblank ; le tableau compact ci-dessus est la version qui tient sur une page statique.)

Tester vos fonctions d’analyse

Les packages sont testés ; vos fonctions d’analyse, elles, ne le sont généralement pas — et ce sont elles qui calculent vos résultats. La solution est un test unitaire : une petite vérification qu’une fonction renvoie la valeur attendue sur une entrée connue. testthat fournit les verbes d’attente. Chaque expect_*() est silencieux quand il passe et échoue bruyamment quand il échoue — donc un bloc qui s’exécute jusqu’au bout sans sortie a vérifié tout ce qu’il contient :

library(testthat)

# The analysis function we want to trust:
discount <- function(price, pct) {
  if (pct < 0 || pct > 1) stop("`pct` must be a fraction in [0, 1]")
  price * (1 - pct)
}

expect_equal(discount(100, 0.20), 80)              # a known result
expect_equal(discount(50, 0), 50)                  # a boundary
expect_error(discount(100, 1.5), "must be a fraction")  # bad input DOES abort

Rien d’affiché — les trois attentes ont tenu : le calcul est juste à un point connu et à la borne, et le garde-fou d’entrée se déclenche bien. Dans un package ou un projet, vous grouperiez les attentes liées dans un bloc test_that() et lanceriez toute la suite d’un coup (Run Tests de RStudio, ou testthat::test_file()) :

test_that("discount computes and validates its inputs", {
  expect_equal(discount(100, 0.20), 80)
  expect_equal(discount(50, 0), 50)
  expect_error(discount(100, 1.5), "must be a fraction")
})

Le but n’est pas le cérémonial — c’est qu’un résultat que personne n’a vérifié est un résultat auquel personne ne peut se fier. Une poignée d’attentes sur les fonctions qui produisent vos nombres attrape la régression avant qu’elle n’atteigne une figure de votre article.

Vérifier le code écrit par une IA

C’est la boucle moderne, et c’est là que tout ce qui précède porte ses fruits. Un assistant rédige une fonction en quelques secondes ; elle se lit de façon plausible ; vous êtes sur le point de la coller. Ne le faites pas — vérifiez-la d’abord, exactement comme vous vérifieriez la vôtre : exécutez-la sur une entrée connue, affirmez la sortie attendue, puis poussez-la sur des cas limites. Le runtime, pas la prose, est le juge.

Disons que vous avez demandé un normaliseur min-max et reçu ceci. Ça a l’air correct, comme dans un manuel :

normalize <- function(x) {
  (x - min(x)) / (max(x) - min(x))
}

# Step 1 — run it on an input you KNOW the answer to:
normalize(c(1, 2, 3))    # you expect 0.0, 0.5, 1.0
[1] 0.0 0.5 1.0

La bonne réponse sur le cas nominal — ce qui est exactement la façon dont un bogue subtil survit à la relecture. Donc étape 2 : épinglez cette attente comme un test, puis attaquez les bords qu’une IA oublie régulièrement — un vecteur constant (max - min devient-il zéro ?) et une valeur manquante :

library(testthat)

normalize <- function(x) (x - min(x)) / (max(x) - min(x))

expect_equal(normalize(c(1, 2, 3)), c(0, 0.5, 1))   # known case: passes

normalize(c(5, 5, 5))      # constant vector -> 0/0
[1] NaN NaN NaN
normalize(c(1, NA, 3))     # a single NA
[1] NA NA NA

Les voilà. Sur un vecteur constant, ça divise par zéro et renvoie NaN ; un seul NA empoisonne tout le résultat. Les deux s’exécutent sans erreur — pas d’exception, pas d’avertissement — ce qui est précisément pourquoi l’exécuter ne suffit pas et vérifier la sortie, oui. Maintenant corrigez-le et verrouillez le comportement avec des tests qui auraient attrapé les deux :

library(testthat)

normalize <- function(x) {
  rng <- range(x, na.rm = TRUE)              # ignore NA when finding the range
  if (diff(rng) == 0) return(rep(0, length(x)))  # constant vector -> all zeros
  (x - rng[1]) / diff(rng)
}

# The full contract, asserted — normal, constant, and NA input:
expect_equal(normalize(c(1, 2, 3)), c(0, 0.5, 1))
expect_equal(normalize(c(5, 5, 5)), c(0, 0, 0))
expect_equal(normalize(c(1, NA, 3)), c(0, NA, 1))

De nouveau silencieux — la fonction corrigée honore les trois cas. Voilà la boucle : générer → exécuter sur une entrée connue → affirmer → la casser sur les bords → corriger → garder les tests. Un chatbot peut écrire le premier jet, mais seul le runtime peut vous dire si le jet est correct, et vous seul pouvez décider quels bords comptent. Ce jugement ne se banalise pas — c’est toute la raison d’avoir un runtime sous vos réponses au lieu de simple prose.

🟢 Avec un agent IA

Vous avez une fonction qu’un assistant vient d’écrire et vous n’êtes pas sûr d’elle ? Collez-la et demandez à Prova « écris des tests qui vérifient ceci sur une entrée connue et ses cas limites ». Comme le runtime est juste là, vous exécutez ces tests immédiatement et voyez si la fonction tient — le contrôle qu’un chatbot sans runtime ne peut structurellement pas faire. Demander à Prova →

Problèmes fréquents

Une validation qui devrait interrompre se contente d’avertir. Si une mauvaise ligne peut corrompre votre résultat, warning() est trop doux — l’exécution continue et le déchet s’écoule en aval. Réservez warning() aux anomalies réellement récupérables ; utilisez stop() / stopifnot() (ou embranchez sur le all_passed() de pointblank) pour tout ce qui ne doit pas se poursuivre. Le test : si ceci est vrai, la réponse est-elle encore digne de confiance ? Si non, interrompez.

tryCatch() qui avale trop. tryCatch(risky(), error = function(e) NULL) transforme chaque échec — y compris le bogue que vous n’aviez pas anticipé — en un NULL silencieux, et vous passez une heure à traquer un NULL sans trace d’appels. Attrapez des conditions précises (par classe, via rlang::abort()), renvoyez une valeur de repli significative, et laissez planter les erreurs inattendues pour pouvoir les voir. Les captures larges cachent les bogues ; elles ne les corrigent pas.

Un test qui passe à vide. expect_true(length(result) >= 0) est toujours vrai — il ne teste rien. Il en va de même pour l’affirmation d’une valeur que vous avez calculée avec la fonction même que vous testez. Un vrai test nomme la réponse indépendamment (expect_equal(discount(100, 0.2), 80) — le 80 vient d’une arithmétique faite à la main, pas de discount()), et inclut au moins une entrée conçue pour échouer si la logique casse.

Faire confiance à une fonction d’IA parce qu’elle a tourné. « Elle s’est exécutée et a renvoyé un nombre » n’est pas « elle est correcte » — le normaliseur ci-dessus faisait les deux et était tout de même faux sur un vecteur constant. Exécutez toujours le code généré sur une entrée dont vous connaissez la réponse, affirmez-la, et essayez les bords (vecteur vide, NA, une valeur limite, un seul élément). Du code qui s’exécute proprement peut tout de même être discrètement, et coûteusement, faux.

Questions fréquentes

Signalez les problèmes avec stop() (interrompre), warning() (continuer mais signaler) ou message() (simple diagnostic), et vérifiez les préconditions avec stopifnot(). Pour vous remettre d’un échec attendu, enveloppez l’expression risquée dans tryCatch() et donnez-lui un gestionnaire error = (et/ou warning =) qui renvoie une valeur de repli sensée. Utilisez withCallingHandlers() quand vous voulez journaliser un avertissement et poursuivre le calcul plutôt que de le dérouler.

try() exécute une expression et, en cas d’échec, renvoie un objet spécial try-error au lieu d’arrêter le script — vous vérifiez ensuite le résultat avec inherits(x, "try-error"). tryCatch() est plus puissant : il vous laisse enregistrer des gestionnaires distincts pour les erreurs, les avertissements, les messages, et un bloc finally, et acheminer chacun vers du code différent. Utilisez try() pour un rapide « ne me laisse pas m’arrêter là-dessus » ; utilisez tryCatch() quand vous voulez gérer des conditions précises délibérément.

Après une erreur, appelez traceback() pour voir la pile d’appels qui y a mené. Pour entrer dans une fonction, insérez browser() sur la ligne suspecte (ou utilisez debug(f) / debugonce(f)) et inspectez les variables locales avec print() et str(). Pour attraper n’importe quelle erreur de façon interactive, réglez options(error = recover), qui vous dépose dans l’appel défaillant pour que vous puissiez regarder ses variables. Ceux-ci mettent en pause une session en cours, exécutez-les donc dans votre propre console R.

Utilisez le package testthat. Chaque attente — expect_equal(actual, expected), expect_error(code, "pattern"), expect_true(cond) — est silencieuse quand elle passe et échoue quand elle échoue. Groupez les attentes liées dans un bloc test_that("description", { ... }), enregistrez-les sous tests/testthat/, et lancez la suite avec testthat::test_file() ou Run Tests de RStudio. Testez les fonctions d’analyse qui calculent vos résultats, pas seulement les packages — un bon test nomme la réponse attendue indépendamment et inclut une entrée conçue pour échouer si la logique casse.

Ne présumez pas qu’il a tourné — vérifiez qu’il est correct. Exécutez-le sur une entrée dont vous connaissez déjà la réponse et confirmez que la sortie correspond (expect_equal()). Puis soumettez-le à des cas limites — un vecteur vide, un NA, une entrée constante, une valeur limite — car le code généré est généralement juste sur le cas nominal et faux aux bords. Gardez ces contrôles comme tests pour que le comportement reste vérifié. Le runtime, pas la plausibilité du code, est le juge.

Testez vos connaissances

Un assistant vous remet pct_change(), qui calcule la variation en pourcentage de old à new. Elle a l’air correcte — mais elle divise par old sans garde-fou, donc old = 0 renvoie Inf silencieusement.

pct_change <- function(old, new) {
  (new - old) / old * 100
}
pct_change(0, 5)   # returns Inf — no error, no warning

Faites deux choses : (1) réécrivez pct_change() pour qu’elle interrompe avec un message clair quand old vaut 0 ; (2) écrivez trois attentes testthat qui vérifient un résultat connu, la borne, et que la mauvaise entrée échoue maintenant.

Mettez un garde-fou en tête avec if (old == 0) stop("...") (ou stopifnot("old must be non-zero" = old != 0)). Pour les tests : expect_equal() pour un cas que vous pouvez calculer à la main (de 200 à 250, c’est +25 %), et expect_error(pct_change(0, 5), "...") pour confirmer que le garde-fou se déclenche.

library(testthat)

pct_change <- function(old, new) {
  if (old == 0) stop("`old` must be non-zero (cannot compute percent change from 0)")
  (new - old) / old * 100
}

# 1. a known result: 200 -> 250 is +25%
expect_equal(pct_change(200, 250), 25)
# 2. a boundary: no change is 0%
expect_equal(pct_change(200, 200), 0)
# 3. the bad input now aborts loudly instead of returning Inf
expect_error(pct_change(0, 5), "must be non-zero")

Les trois sont silencieux, donc le garde-fou fonctionne et l’arithmétique est juste. La fonction refuse désormais l’entrée qui produisait auparavant un Inf silencieux — un échec bruyant que vous voyez vaut mieux qu’un nombre erroné auquel vous ne pouvez pas vous fier.

Vous avez écrit une fonction qui résume un data frame, et sur des données réelles elle renvoie un nombre qui paraît trop petit. Elle s’exécute sans aucune erreur ni avertissement. Quelle est la façon la plus directe de découvrir pourquoi ?

A. Envelopper tout le corps de la fonction dans tryCatch(..., error = function(e) NULL) B. Mettre un browser() sur la ligne qui calcule le résumé et inspecter les variables avec str() C. Ajouter d’autres appels warning() et relancer

Afficher la réponse

B. Il n’y a pas d’erreur à attraper (A) ni d’avertissement à émettre (C) — le code s’exécute ; il est simplement faux. Mettre en pause sur la ligne suspecte avec browser() et examiner les objets réels (str() montre leur structure et leur type) révèle le décalage — un NA inattendu, une colonne character et non numeric, un filtre qui a supprimé des lignes. tryCatch et warning() aident avec du code qui casse, pas du code qui se comporte mal en silence.

Conclusion

R digne de confiance est une habitude, pas un package. Rendez les échecs bruyants — stop(), warning(), stopifnot() à la porte — pour que les mauvaises entrées ne puissent pas voyager. Quand quelque chose casse, tournez-vous vers traceback(), browser() et options(error = recover) pour trouver la cause vite, puis passez à autre chose ; le débogage est le chemin, pas la destination. Validez ce qui doit être vrai : affirmez vos données avec pointblank, et testez les fonctions qui produisent vos résultats avec testthat. Et traitez chaque extrait écrit par une IA comme vous traiteriez le vôtre — exécutez-le sur une entrée connue, affirmez la sortie, cassez-le sur les bords, gardez les tests. Un copilote écrit le premier jet en quelques secondes ; le jugement de lui faire confiance, ou non, est la part qui reste la vôtre — and the runtime is the judge.

Leçons connexes

Cette page vous a-t-elle été utile ?

Prouvez que vous savez le faire. Maîtrisez toute la série Les bases de R — suivez votre parcours, construisez des projets et obtenez un certificat.

Commencer gratuitement →

Passez à Pro — Prova illimité sur vos propres données et un certificat vérifiable qui atteste la compétence.

dès 15 $/mois facturé annuellement

Passer à Pro →

✓ Vous êtes Pro — continuez. The runtime is the judge.

Recevez les nouvelles leçons R & Python par e-mail

Pratique, reproductible, sans spam. Désinscription à tout moment.

Double opt-in. Nous ne partageons jamais votre e-mail.

Partager cette pageXLinkedInRedditHN
Note

Cette leçon est reproductible : chaque résultat de cette page a été produit par le code montré. Copiez n’importe quel bloc et exécutez-le localement pour le reproduire — les tests passent, les rapports de validation et les messages d’erreur sont réels, et les bogues du code IA sont exactement ceux que vous verrez. The runtime is the judge.

Réutilisation

Citation

BibTeX
@online{2026,
  author = {},
  title = {Gestion des erreurs en R : déboguer, tester, valider},
  date = {2026-07-19},
  url = {https://www.datanovia.com/learn/programming/r-foundations/robust-and-trustworthy-r},
  langid = {fr}
}
Veuillez citer ce travail comme suit :
“Gestion des erreurs en R : déboguer, tester, valider.” 2026. July 19. https://www.datanovia.com/learn/programming/r-foundations/robust-and-trustworthy-r.