Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
ewdrstyle [2015/03/23 12:20] t.zoltak |
ewdrstyle [2016/10/03 21:16] (aktualna) t.zoltak [Zasady formatowania kodu R w projekcie EWD] |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
+ | ======Dobre praktyki tworzenia kodu====== | ||
+ | |||
=====Zasady formatowania kodu R w projekcie EWD===== | =====Zasady formatowania kodu R w projekcie EWD===== | ||
- | * Zasadniczo trzymamy się zasad z [[https://google-styleguide.googlecode.com/svn/trunk/Rguide.xml|Google's R Style Guide]] | + | * Zasadniczo trzymamy się zasad z [[https://google.github.io/styleguide/Rguide.xml|Google's R Style Guide]] |
* Z następującymi wyjątkami: | * Z następującymi wyjątkami: | ||
- jako operatora przypisania wartości używamy '=' a nie '%%<-%%'; | - jako operatora przypisania wartości używamy '=' a nie '%%<-%%'; | ||
Linia 11: | Linia 13: | ||
- 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]]). | - 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: | * Oraz uzupełnieniami: | ||
- | - 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). | ||
- Przy wykonywaniu zapytań do bazy trzymamy się [[ewdsql|wytycznych]]. | - Przy wykonywaniu zapytań do bazy trzymamy się [[ewdsql|wytycznych]]. | ||
- Piszemy funkcje, które nie sięgają do zmiennych, które nie zostały im przekazane jako parametry. | - Piszemy funkcje, które nie sięgają do zmiennych, które nie zostały im przekazane jako parametry. | ||
- | * Dotyczy to również funkcji anonimowych! <code>wektor = c('a', 'b', c') | + | * Dotyczy to również funkcji anonimowych!<code> |
+ | wektor = c('a', 'b', c') | ||
# Źle | # Źle | ||
wynik = apply(1:3, function(x){ wektor[x] }) | wynik = apply(1:3, function(x){ wektor[x] }) | ||
Linia 24: | Linia 26: | ||
wynik[i] = wektor[i] | wynik[i] = wektor[i] | ||
}</code> | }</code> | ||
- | * 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. <code>dane = data.frame(a = rep(1:2, 5), b = 1:10) | + | * 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.<code> |
+ | dane = data.frame(a = rep(1:2, 5), b = 1:10) | ||
# Źle | # Źle | ||
agr = ddply(dane, ~a, summarise, sr = mean(b)) | agr = ddply(dane, ~a, summarise, sr = mean(b)) | ||
Linia 31: | Linia 34: | ||
agr = ddply(dane, ~a, summarise, sr = mean(get("b"))) | agr = ddply(dane, ~a, summarise, sr = mean(get("b"))) | ||
podzb = subset(dane, get("a") == 1)</code> | podzb = subset(dane, get("a") == 1)</code> | ||
+ | - Ciało funkcji anonimowych zawsze otaczamy nawiasami klamrowymi.<code> | ||
+ | # Źle | ||
apply(tablica, 1, funtion(x) max(x) - min(x)) | apply(tablica, 1, funtion(x) max(x) - min(x)) | ||
# Dobrze | # Dobrze | ||
Linia 51: | Linia 56: | ||
}) | }) | ||
zupelnie_prosta_funkcja(arg1, arg2) | zupelnie_prosta_funkcja(arg1, arg2) | ||
- | |||
# Źle | # Źle | ||
Linia 63: | Linia 67: | ||
* ł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 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; | * ł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);<code> | ||
+ | # Ź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) | ||
+ | </code> | ||
* za regułę kciuka przyjmujemy, że nadmiernie długa linia to linia o więcej niż 80 znakach. | * 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). | ||
+ | |||
+ | [[pakietassert|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:**<code>moja_funkcja = function(x) { | ||
+ | if (any(is.na(x)) { | ||
+ | stop("Argument 'x' nie może zawierać braków danych!") | ||
+ | } else { | ||
+ | return(x) | ||
+ | } | ||
+ | }</code> | ||
+ | |||
+ | ===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:**<code>moja_funkcja = function(x) { | ||
+ | stopifnot(all(!is.na(x)) | ||
+ | return(x) | ||
+ | }</code> | ||
+ | |||
+ | ===Stosowanie funkcji pakietu assertive=== | ||
+ | |||
+ | [[pakietassert|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:**<code>moja_funkcja = function(x) { | ||
+ | assert_all_are_not_na(x) | ||
+ | return(x) | ||
+ | }</code> |