INWT-Guidelines für R-Code
"It turns out that style matters in programming for the same reason that it matters in writing. It makes for better reading." Douglas Crockford in JavaScript: The Good Parts
Warum brauchen wir noch einen Styleguide?
"The reason to care about a style guide is just one thing: We want that our source code is not only interpretable by a computer but also by a human."
Sobald Sie Code nicht nur für sich selbst, sondern auch für andere schreiben, wollen Sie Ihre Leser natürlich nicht verlieren. Plötzlich müssen Sie sich Gedanken um Formatierung, Coding-Style und Verständlichkeit machen. Formatierung und teilweise auch Style können durch die Einhaltung eines Styleguides abgedeckt werden. Allein dadurch liest sich der Code aber nicht gleich wie Prosa. Wenn es einem Autor egal ist, ob sein Code lesbar ist, hilft auch ein Styleguide nicht viel. Aber wenn Sie die Lesbarkeit Ihres Codes verbessern wollen, sind Formatierung und Style gute erste Schritte.
Eine bessere Lesbarkeit bringt viele Vorteile mit sich:
- Ihre Kolleginnen und Kollegen lieben Sie und können es kaum erwarten, mit Ihrem Code zu arbeiten oder ihn weiterzuentwicklen! Naja, nicht ganz, aber es ist ein erster Schritt in diese Richtung.
- Fehler sind leichter zu finden.
- Wenn Sie Ihren eigenen Code später wieder anschauen, werden Sie sich immer seltener fragen: Wer hat das geschrieben? Und was soll das eigentlich alles?
Wenn Sie beschließen, sich an einen Styleguide zu halten, ist weniger wichtig, welchen sie konkret auswählen. Entscheidend ist vor allem, dass Sie und Ihr Team sich darauf einigen, dass der Styleguide eingehalten wird. Für R gibt es zwei beliebte Styleguides: den Style Guide von Google und den aus Advanced R von Hadley Wickham.
Wir weichen nur in einigen spezifischen Fällen von diesen Vorgaben ab. Außerdem haben wir beschlossen, dass unsere Regeln verbindlich sind: Jedes Mal, wenn Code in ein Git-Repository gepushed wird, prüft ein CI-Tool automatisch, ob es Abweichungen vom Styleguide gibt. Eine weitere Komponente ist ein Template für ein typisches Skript, das weiter unten zu finden ist. Wir haben die meisten dieser Regeln und einige weitere Ideen in einem speziellen Paket INWTUtils implementiert, das in seinem GitHub-Repository zu finden ist.
Benamung von Objekten
Im Gegensatz zu den beiden oben genannten Styleguides verwenden wir für Objektnamen im Allgemeinen lowCamelCase. Unterstriche und Punkte sind in normalen Objektnamen nicht erlaubt. Es gibt nur eine Ausnahme: in Namen für GLOBALE_VARIABLEN verwenden wir Großbuchstaben mit Unterstrichen, sodass sie auf den ersten Blick erkennbar sind. Globale Variablen werden nur in Skripten verwendet, immer zu Beginn des Skripts definiert und nicht mittendrin geändert.
Automatische Style-Prüfung
Wenn Sie noch einen Schritt weiter gehen möchten, können Sie Ihren R-Code sogar automatisch auf Einhaltung des Styleguides prüfen lassen: Das lintr-Paket enthält nützliche Funktionen zum Ausführen entsprechender Tests, welche Sie auf ein Paket oder eine einzelne Datei anwenden können. Sie können dabei genau auswählen, welche Regeln Sie testen möchten. Wir haben die folgenden ausgewählt:
- Für die Zuweisung soll <- verwendet werden, nicht =
- Nach Kommas soll ein Leerzeichen stehen, davor aber nicht
- Operatoren wie +, -, =, ... sollen von Leerzeichen umgeben sein
- Die Zeilenlänge soll eine bestimmte Anzahl von Zeichen nicht überschreiten (z. B. 80 oder 100)
- Leerzeichen sollen anstelle von Tabs verwendet werden
- Objektnamen sollen nicht zu lang sein
- Vor linken Klammern soll ein Leerzeichen stehen (außer in Funktionsaufrufen)
- Am Ende des Skripts sollen keine leeren Zeilen stehen
Wir haben auch ein paar eigene Tests geschrieben, die dann einfach in das Paket integriert werden können:
- Funktionsargumente ohne Default-Wert sollten vor Argumenten mit Default-Wert stehen
- Vermeiden Sie doppelte Leerzeichen (außer bei Einrückungen)
- Verwenden Sie setwd() oder source() nicht in Paketfunktionen - dies kann zu unerwarteten Nebeneffekten führen
- Entfernen Sie Leerzeichen am Zeilenende
- Wir wollen uns selbst dazu zwingen, keine internen Funktionen aus unseren eigenen Paketen zu verwenden (via
pkg:::foo
). Dies wäre ein Hinweis, dass wir diese Funktion lieber exportieren und dokumentieren sollten. (Diese Regel gilt natürlich nur für Skripte, nicht für Paketfunktionen.)
Wenn Sie loslegen möchten, empfehlen wir Ihnen, das Paket lintr zu installieren und die Hilfe für lintr::lint()
zu lesen.
Am Anfang mag es schwierig sein, all diese Regeln zu befolgen, besonders wenn sie von jemand anderem aufgestellt wurden und nicht Ihren persönlichen Vorlieben entsprechen. Aber es wird Ihnen helfen, saubereren Code zu schreiben. Darüber hinaus können Sie in Ihrem Team einen einheitlichen Code-Style finden, wodurch die Zusammenarbeit etwas einfacher wird.
Die Struktur eines Skripts
Header
Es ist sinnvoll, wenn jedes Skript mit einem Header anfängt. Dieser sollte den Zweck des Skripts, die als Input benötigten Dateien sowie den vom Skript erzeugten Output auflisten. Außerdem sollte der Header den Namen und die E-Mail-Adresse des Autors oder der Autorin enthalten. Dies kann auch ein Anreiz sein, sich stärker für den eigenen Code verantwortlich zu fühlen: Sie werden Ihren Code vielleicht sorgfältiger schreiben, wenn Ihr Name darübersteht…
Den Workspace aufräumen
Der erste Schritt besteht darin, Ihren Workspace aufzuräumen, also alle Objekte zu entfernen. Andernfalls kann es passieren, dass Ihr Code ein Objekt aus dem Workspace verwendet, das zu einem späteren Zeitpunkt nicht mehr verfügbar sein wird.
Den Suchpfad bereinigen
Der Suchpfad enthält alle geladenen Pakete, auf deren Funktionen Sie direkt zugreifen können. Dabei wissen Sie wahrscheinlich meistens nicht auswendig, welche Pakete gerade geladen sind und in welcher Reihenfolge. Wenn Sie die volle Kontrolle behalten möchten, ist es besser, zu wissen, wie der Suchpfad aussieht. Eine nützliche Funktion hierfür ist rmPkgs()
aus dem Paket INWTUtils: Sie versetzt den Suchpfad wieder in den Zustand einer neuen R-Sitzung. Anschließend können Sie die für die Analyse benötigten Pakete über library()
hinzufügen und source()
-Befehle ausführen.
Inhalt
Jetzt können Sie mit der eigentlichen Analyse beginnen. Setzen Sie zunächst Ihre globalen Variablen. Wenn alle globalen Variablen am Anfang des Codes definiert werden, sind Sie immer leicht zu finden. Um den Code übersichtlicher zu strukturieren, können Sie ihn in nummerierte Abschnitte unterteilen (z. B. "Lade Daten", "Datenvorbereitung", "Modellschätzung" usw.). So trivial dies auch klingt: Es hilft enorm dabei, schnell einen Überblick zu bekommen. Auch lassen sich in modernen Editoren oft einzelne Codebereiche ein- und ausklappen, was beim Durchsuchen der Datei hilft.
################################################################################
# This script is a template for the general structure of R scripts. This first #
# header should describe what the script does, which input files it needs and #
# which output it produces. This header also contains name and e-mail adress #
# of the author. #
# #
# Author: Mira Céline Klein #
# E-mail: mira.klein@inwt-statistic.de #
################################################################################
# 00 Preparation ---------------------------------------------------------------
# Clean up workspace
rm(list = ls(all.names = TRUE))
# Clean up search path
INWTUtils::rmPkgs()
# Load packages
library(INWTUtils)
# Define global variables
A_GLOBAL_VARIABLE <- 4 # Upper case and underlines for global variables
# 01a Load data ----------------------------------------------------------------
exampleVector <- 1:3 # lowerCamelCase for ordinary object names
# 01b Prepare data -------------------------------------------------------------
# 02 ... -----------------------------------------------------------------------