Narzędzia użytkownika

Narzędzia witryny


ewdrstyle

Dobre praktyki tworzenia kodu

Zasady formatowania kodu R w projekcie EWD

  • Zasadniczo trzymamy się zasad z Google's R Style Guide
  • Z następującymi wyjątkami:
    1. jako operatora przypisania wartości używamy '=' a nie '<-';
    2. schematy nadawania nazw:
      1. nazwa_funkcji(),
      2. nazwaZmiennej, nazwaObiektu,
      3. nazwa_kolumny_w_ramce_danych;
    3. można wyrównywać (wcinać) kod tabulacjami, byle nie mieszać w jednym pliku z wyrównywaniem spacjami;
    4. każda tworzona funkcja - nawet jeśli chwilowo nie jest zamykana w żadnym pakiecie - powinna być dokumentowana w notacji roxygenowej (p. http://adv-r.had.co.nz/Documenting-functions.html).
  • Oraz uzupełnieniami:
    1. Przy wykonywaniu zapytań do bazy trzymamy się wytycznych.
    2. Piszemy funkcje, które nie sięgają do zmiennych, które nie zostały im przekazane jako parametry.
      • Dotyczy to również funkcji anonimowych!
        wektor = c('a', 'b', c')
        # Źle
        wynik = apply(1:3, function(x){ wektor[x] })
        # Dobrze
        wynik = apply(1:3, function(x, y){ y[x] }, wektor)
        # Też dobrze, działa równie szybko
        wynik = c()
        for(i in 1:3){
          wynik[i] = wektor[i]
        }
      • W przypadku wykorzystania funkcji takich jak subset() czy summarise() używamy funkcji get() do oznaczenia, że chcemy wykorzystać kolumnę z obrabianego data frame'a, a nie obiekt z zewnętrznego (wobec tego data frame'a) środowiska.
        dane = data.frame(a = rep(1:2, 5), b = 1:10)
        # Źle
        agr = ddply(dane, ~a, summarise, sr = mean(b))
        podzb = subset(dane, a == 1)
        # Dobrze
        agr = ddply(dane, ~a, summarise, sr = mean(get("b")))
        podzb = subset(dane, get("a") == 1)
    3. Ciało funkcji anonimowych zawsze otaczamy nawiasami klamrowymi.
      # Źle
      apply(tablica, 1, funtion(x) max(x) - min(x))
      # Dobrze
      apply(tablica, 1, function(x){ max(x) - min(x) })
    4. Unikamy tworzenia nadmiernie długich linii kodu:
      # Źle
      sapply(lista, function(x){ apply(x, c(1, 2), function(y){ rowSums(y) / sum(y) }) })
      # Dobrze
      sapply(lista, function(x){ 
        apply(x, c(1, 2), function(y){ 
          rowSums(y) / sum(y)
        }) 
      })
      
      # Źle
      zupelnie_prosta_funkcja(lista[[1]]$kolumna[filtr], apply(dane, c(1, 2), function(x){ mean(x) - sd(x) }))
      # Dobrze
      arg1 = lista[[1]]$kolumna[filtr]
      arg2 = apply(dane, c(1, 2), function(x){ 
        mean(x) - sd(x) 
      })
      zupelnie_prosta_funkcja(arg1, arg2)
      
      # Źle
      zlozona_funkcja(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14)
      # Dobrze
      zlozona_funkcja(
        arg1, arg2, arg3, arg4, arg5, arg6, arg7,
        arg8, arg9, arg10, arg11, arg12, arg13, arg14
      )

      W szczególności:

      • łamiemy do nowego wiersza ciała wszelkich nietrywialnych funkcji anonimowych (tzn. zawierających coś więcej niż prostą operację arytmetyczną lub pojedyncze wywołanie funkcji, której parametrem wywołania nie jest żadna inna funkcja);
      • łamiemy do nowego wiersza argumenty wywołania funkcji, jeśli jest ich zbyt wiele;
      • przy korzystaniu z magrittr-owego operatora %>% (stosowanego w szczególności przy korzystaniu z pakietu dplyr) łamiemy linię po każdym jego zastosowaniu, chyba że w danym ciągu poleceń jest on wykorzystywany tylko raz (łączy ze sobą dokładnie dwie operacje; z tym że wtedy należy też stosować się do reguły o długości linii);
        # Źle
        dane = select(dane, kolumna1, kolumna2) %>% filter(kolumna2 > 0) %>% summarise(koluma3 = kolumna1 / kolumna2)
        # Dobrze
        dane = select(dane, kolumna1, kolumna2) %>%
          filter(kolumna2 > 0) %>%
          summarise(koluma3 = kolumna1 / kolumna2)
      • za regułę kciuka przyjmujemy, że nadmiernie długa linia to linia o więcej niż 80 znakach.

Weryfikacja poprawności argumentów funkcji (run-time testing)

Ogólne zasady weryfikacji argumentów funkcji

  • Generalnie przyjmujemy zasadę, że funkcje powinny weryfikować poprawność przekazywanych do nich argumentów. W przypadku tworzenia pakietów zasada ta powinna być bezwzględnie przestrzegana w odniesieniu do funkcji, które są eksportowane.
  • Jeżeli w funkcji implementowana jest opcjonalna konwersja typów argumentów (np. jeśli argument powinien być wektorem liczb całkowitych, ale jest wektorem logicznym, można skonwertować go na wektor liczb całkowitych), to dokonanie konwersji typów powinno wiązać się z wygenerowanie ostrzeżenia widocznego dla użytkownika.
  • Minimalny zakres weryfikacji obejmuje:
    • Zgodność typów (w przypadku argumentów o bardziej złożonej strukturze, ale posiadających przypisane klasy - zgodność klas).
    • Zakres dozwolonych wartości - jeśli dotyczy.
    • Długość (liczba elementów) - jeśli dotyczy (odnosi się to głównie do parametrów sterujących procesem, które z założenia mają być skalarami).
  • Ewentualne rozszerzanie zakresu weryfikacji ponad wyżej wymienione (typowo bardziej szczegółowe sprawdzanie struktury złożonych argumentów) należy rozważać w odniesieniu do:
    • Skutków niewykrycia niezgodności.
    • Szacowanego prawdopodobieństwa wystąpienia niezgodności.
    • Złożoności kodu weryfikującego w stosunku do złożoności całej funkcji.
    • Obciążenia obliczeniowego związanego z weryfikacją.

Metody weryfikacji argumentów funkcji

Spośród opisanych niżej metod preferowane jest wykorzystanie funkcji pakietu assertive w rozsądnym połączeniu z użyciem warunków do obsługi bardziej skomplikowanych sytuacji (w szczególności ostrzeżeń i konwersji typów, jeśli funkcja takowe wykonuje).

Przewodnik po funkcjach pakietu assertive.

Stosowanie warunków

  • Zalety:
    • Elastyczne.
    • Umożliwia zwracanie czytelnych komunikatów o błędach i/lub ostrzeżeń.
  • Wady:
    • Wymaga pisania bardzo dużo kodu, co jest uciążliwe i często czyni kod nieczytelnym.
  • Przykład:
    moja_funkcja = function(x) {
      if (any(is.na(x)) {
        stop("Argument 'x' nie może zawierać braków danych!")
      } else {
        return(x)
      }
    }

Stosowanie funkcji stopifnot()

  • Zalety:
    • W typowych, niezbyt złożonych sytuacjach, krótki i czytelny kod.
  • Wady:
    • Niezbyt przyjazne użytkownikowi komunikaty o błędach.
    • Obsługa wyłącznie błędów (ale ostrzeżeń już nie).
  • Przykład:
    moja_funkcja = function(x) {
      stopifnot(all(!is.na(x))
      return(x)
    }

Stosowanie funkcji pakietu assertive

Przewodnik po funkcjach pakietu assertive.

  • Zalety:
    • Krótki i czytelny kod w szerokim zakresie zastosowań (nieco szerszym niż stopifnot()).
      • Wiele warunków i formatów, z którymi można sprawdzać zgodność.
      • Automatyzacja najbardziej typowych kombinacji warunków w pojedynczych funkcjach.
    • Przyjazne użytkownikowi komunikaty o błędach.
  • Wady:
    • Nic nie będzie równie elastyczne, jak używanie if.
    • Wymaga trochę czasu, żeby zorientować się w mnogości funkcji udostępnianych przez pakiet.
  • Przykład:
    moja_funkcja = function(x) {
      assert_all_are_not_na(x)
      return(x)
    }
ewdrstyle.txt · ostatnio zmienione: 2016/10/03 21:16 przez t.zoltak