Narzędzia użytkownika

Narzędzia witryny


ewdrstyle

To jest stara wersja strony!


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.
  • 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 assert.

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 assert

  • 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.1435667082.txt.gz · ostatnio zmienione: 2015/06/30 14:24 przez t.zoltak