0Pricing
R Academy · Lekcja

Pisanie odpornych funkcji za pomocą stop() i warning()

Zgłaszaj niestandardowe warunki z poziomu własnych funkcji.

Pisanie odpornych funkcji za pomocą stop() i warning() to bezpłatna lekcja R Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej R Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs R Academy zawiera 4 lekcji w sumie.

Dlaczego walidacja danych wejściowych ma znaczenie

Funkcje defensywne sprawdzają dane wejściowe i jawnie zgłaszają błędy z informacyjnymi komunikatami, zamiast po cichu zwracać nieprawidłowe wyniki. Funkcję, która odrzuca nieprawidłowe dane na wejściu, znacznie łatwiej debugować niż funkcję, która propaguje błędne dane przez cały potok.

# Without validation: wrong result, no error
bad_mean <- function(x) sum(x) / length(x)
bad_mean('hello')   # no error, just NA

# With validation: clear error
good_mean <- function(x) {
  if (!is.numeric(x)) stop('x must be numeric')
  sum(x) / length(x)
}
tryCatch(good_mean('hello'), error = function(e) cat(e$message, '\n'))

Informacyjne komunikaty błędów

Ogólne błędy, takie jak stop('bad input'), utrudniają debugowanie. Należy używać paste0() lub sprintf() wewnątrz stop(), aby zawrzeć w komunikacie rzeczywistą wartość i oczekiwany typ. Oszczędza to czas podczas śledzenia błędów w potokach.

check_numeric <- function(x, arg_name = 'x') {
  if (!is.numeric(x)) {
    stop(sprintf(
      "'%s' must be numeric, but got class '%s'",
      arg_name, class(x)
    ))
  }
}
tryCatch(check_numeric('abc', 'score'),
  error = function(e) cat(e$message, '\n'))

Sprawdzanie długości i zakresu

Typowe kontrole obejmują sprawdzenie, czy wektor nie jest pusty (length(x) == 0), czy skalar mieści się w określonym zakresie oraz czy ramka danych ma oczekiwane kolumny. Każda kontrola powinna mieć własny, dopasowany komunikat błędu.

validate_prob <- function(p) {
  if (!is.numeric(p)) stop(paste0('p must be numeric, got: ', class(p)))
  if (length(p) != 1) stop(paste0('p must be length 1, got: ', length(p)))
  if (p < 0 || p > 1) stop(paste0('p must be in [0,1], got: ', p))
  p
}
cat(validate_prob(0.7), '\n')
tryCatch(validate_prob(1.5), error = function(e) cat(e$message, '\n'))

Używanie warning() w przypadku problemów niekrytycznych

Funkcji warning() należy używać, gdy funkcja nadal może zwrócić sensowny wynik, ale wywołujący powinien wiedzieć, że wydarzyło się coś nietypowego — na przykład nastąpiła konwersja wartości, użycie wartości domyślnej lub wykrycie danych na granicy dopuszczalnego zakresu.

clamp <- function(x, lo, hi) {
  if (any(x < lo)) warning(paste0(sum(x < lo), ' value(s) below lo, clamped'))
  if (any(x > hi)) warning(paste0(sum(x > hi), ' value(s) above hi, clamped'))
  pmax(lo, pmin(hi, x))
}
result <- clamp(c(2, -5, 7, 100, 4), 0, 10)
cat('Result:', result, '\n')

call. = FALSE w stop()

Domyślnie stop() dodaje przed komunikatem prefiks Error in funcname(...):. Należy przekazać call. = FALSE, aby ukryć ten prefiks, gdy kontekst wywołania jest oczywisty lub gdy funkcja jest narzędziem przeznaczonym dla użytkownika.

# With call. = TRUE (default): shows calling function
f1 <- function(x) stop('bad input')
tryCatch(f1(1), error = function(e) cat(conditionMessage(e), '\n'))

# With call. = FALSE: cleaner message
f2 <- function(x) stop('bad input', call. = FALSE)
tryCatch(f2(1), error = function(e) cat(conditionMessage(e), '\n'))

Niestandardowe klasy warunków dla stop()

Niestandardową klasę błędu można utworzyć za pomocą structure(), aby wywołujący mógł przechwytywać tylko błędy danej funkcji, a nie wszystkie błędy. Jest to profesjonalny sposób tworzenia błędów na poziomie pakietu.

value_error <- function(msg, call = sys.call(-1)) {
  structure(
    class = c('value_error', 'error', 'condition'),
    list(message = msg, call = call)
  )
}
check_positive <- function(x) {
  if (x <= 0) stop(value_error(paste0('Expected positive, got: ', x)))
  x
}
tryCatch(check_positive(-3),
  value_error = function(e) cat('ValueError:', e$message, '\n'),
  error        = function(e) cat('Other error:', e$message, '\n'))

Sprawdzanie kolumn ramki danych

Gdy funkcja oczekuje ramki danych, przed użyciem kolumn należy sprawdzić, czy wymagane kolumny istnieją. Do sprawdzania nazw kolumn należy użyć %in%, a do wskazania brakujących kolumn — setdiff().

require_cols <- function(df, cols) {
  missing <- setdiff(cols, names(df))
  if (length(missing) > 0) {
    stop(paste0('Missing columns: ', paste(missing, collapse = ', ')))
  }
  invisible(df)
}
df <- data.frame(x = 1:3, y = 4:6)
tryCatch(
  require_cols(df, c('x', 'z', 'w')),
  error = function(e) cat(e$message, '\n')
)

stopifnot() do zwięzłych asercji

stopifnot(condition1, condition2, ...) zgłasza błąd, jeśli którykolwiek warunek ma wartość FALSE. Jest to zwięzły sposób sprawdzania warunków wstępnych na początku funkcji bez pisania osobnych bloków if (!...) stop(...).

compute_area <- function(width, height) {
  stopifnot(
    is.numeric(width),
    is.numeric(height),
    width > 0,
    height > 0
  )
  width * height
}
cat(compute_area(5, 3), '\n')
tryCatch(compute_area(-1, 3), error = function(e) cat(e$message, '\n'))

Nazwane stopifnot() dla lepszych komunikatów

W R 4.0+ nazwane wyrażenia w stopifnot() zastępują automatycznie wygenerowany komunikat własnym opisem. Należy użyć tekstu wyrażenia jako nazwy, aby uzyskać informacyjne komunikaty o niepowodzeniu.

validate_age <- function(age) {
  stopifnot(
    'age must be numeric'    = is.numeric(age),
    'age must be positive'   = age > 0,
    'age must be under 150'  = age < 150
  )
  invisible(age)
}
tryCatch(validate_age(-5), error = function(e) cat(e$message, '\n'))
tryCatch(validate_age('x'), error = function(e) cat(e$message, '\n'))

Łączenie walidacji z logiką biznesową

Dobrze zaprojektowana funkcja oddziela walidację od logiki. Najpierw należy przeprowadzić walidację, a następnie wykonać operację. Ułatwia to czytanie funkcji i zapewnia wykrywanie błędów przed rozpoczęciem obliczeń.

discount_price <- function(price, pct) {
  if (!is.numeric(price) || price <= 0)
    stop(paste0('price must be positive numeric, got: ', price))
  if (!is.numeric(pct) || pct < 0 || pct > 100)
    stop(paste0('pct must be in [0,100], got: ', pct))
  if (pct > 50) warning('discount > 50% is unusual')
  price * (1 - pct / 100)
}
cat(discount_price(100, 20), '\n')
cat(suppressWarnings(discount_price(100, 60)), '\n')

tryCatch wewnątrz funkcji narzędziowych

Czasami funkcja narzędziowa powinna wewnętrznie obsługiwać błędy i zwracać wartość zastępczą zamiast je propagować. Należy opakować główną logikę w tryCatch() i w przypadku niepowodzenia zwrócić uzgodniony znacznik, taki jak NA lub NULL.

safe_log <- function(x) {
  tryCatch({
    if (!is.numeric(x)) stop('not numeric')
    if (x <= 0) stop('must be positive')
    log(x)
  }, error = function(e) {
    warning(paste0('safe_log failed for x=', x, ': ', e$message))
    NA_real_
  })
}
results <- sapply(list(10, -1, 'a', 100), safe_log)
cat(results, '\n')

Szybkie sprawdzenie

Co robi call. = FALSE przekazane do stop()?

Solidne funkcje: najważniejsze informacje

Najważniejsze informacje dotyczące pisania solidnych funkcji:

  • Waliduj dane wejściowe na początku; jawnie zgłaszaj błędy za pomocą informacyjnych komunikatów stop()
  • Umieszczaj rzeczywistą wartość w komunikatach błędów: paste0('Expected numeric, got: ', class(x))
  • Używaj warning() w przypadku problemów niekrytycznych, gdy obliczenia mogą być kontynuowane
  • call. = FALSE ukrywa prefiks funkcji wywołującej w komunikatach błędów
  • stopifnot() służy do zwięzłych asercji, a nazwana postać do własnych komunikatów (R 4.0+)
  • Niestandardowe klasy warunków umożliwiają wywołującym wybiórcze przechwytywanie błędów
robust_divide <- function(x, y) {
  stopifnot('x must be numeric' = is.numeric(x),
            'y must be numeric' = is.numeric(y))
  if (y == 0) stop('Division by zero', call. = FALSE)
  if (abs(y) < 1e-10) warning('y is very small; result may be inaccurate')
  x / y
}
cat(robust_divide(10, 2), '\n')
tryCatch(robust_divide(10, 0), error = function(e) cat(e$message, '\n'))

Często zadawane pytania

Czy lekcja „Pisanie odpornych funkcji za pomocą stop() i warning()” jest bezpłatna?

Tak — pełny tekst „Pisanie odpornych funkcji za pomocą stop() i warning()” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu R Academy, przejdź na CoddyKit PRO. Kurs R Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Pisanie odpornych funkcji za pomocą stop() i warning()”?

Zgłaszaj niestandardowe warunki z poziomu własnych funkcji. Ćwiczysz R Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć R Academy?

Nie wymagamy żadnego doświadczenia. R Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Pisanie odpornych funkcji za pomocą stop() i warning()”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji R Academy?

Tak. Każda lekcja R Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Błędy, ostrzeżenia i komunikaty w R
  2. tryCatch() do obsługi błędów
  3. withCallingHandlers() i restarty
  4. Pisanie odpornych funkcji za pomocą stop() i warning()
← Powrót do R Academy