Style Guide

Unsere Best Practices für guten und lesbaren Code

Präambel: Was gilt es bei Style Guides zu beachten?

Style Guides geben häufig persönliche Präferenzen gemischt mit allgemein anerkannten Konventionen wieder (so auch hier). Alleine aus diesem Grund ist es nicht ratsam, einem Style Guide blind zu folgen.

Was wir Dir hier anbieten, sind gute Richtlinien, an denen Du Dich orientieren kannst. Wir empfehlen Dir ausdrücklich nicht, dass Du unsere Vorschläge ungeprüft übernimmst. Im Allgemeinen gilt (wie auch für viele andere Programmiersprachen):

Lesbarkeit zählt

Wenn Du unsere Vorschläge also als weniger gut lesbar empfindest, dann verwende sie nicht, sondern folge Deinen eigenen Regeln.

§1 Codestruktur

§1.1 Datenflusslogik

Guter Code zeichnet sich insbesondere durch eine gute und sinnvolle Struktur aus. Dadurch lassen sich seine Inhalte schnell erfassen, selbst wenn man nicht jede Zeile explizit liest. Dabei ist es zentral, dass zusammengehöriger Code auch zusammen gruppiert ist. Nichts ist unübersichtlicher, als wenn Operationen an verschiedenen Datensätzen wild gemischt werden. Hier ist es besser, diese Operationen für einzelne Datensätze getrennt durchzuführen.

§1.2 Programmflusslogik

Ebenso ist es natürlich wichtig, Logiksprünge zu vermeiden. Wenn die Kontrollflusslogik (also nicht die Daten, sondern der Programmablauf) stark ineinander verschlungen ist, gestaltet sich das Erfassen der Zusammenhänge als langwierig und unnötig schwierig. Auch hier gilt: fasse Gemeinsamkeiten im Programmablauf zusammen. Gibt es im Wesentlichen zwei Fälle, die Dein Code verarbeiten soll, dann trenne deinen Code in diese zwei Fälle anstatt ihn immer wieder zusammen zu führen. Dabei sparst Du Dir unzählige if ... else ... endif Statements und gleichzeitig wird Dein Code auch wesentlich besser wart- und testbar. Tatsächlich ist es sogar eine gute Idee, auf else bzw. elseif weitestgehend zu verzichten, da dadurch auch zugleich die Gefahr der überflüssigen Verschachtelungen abnimmt.

§1.3 Komplexität, Modularisierung und Prozedurlänge

Trotzdem solltest Du aber darauf achten, dass Dein Code nicht aus riesigen Kontrollfluss-Blöcken (z.B. einer riesigen for ... endfor Schleife) besteht, sondern aus einfach zu verdauenden, gut zu überblickenden Blöcken. Diese sollten nach Möglichkeit auch nur eine einzige Aufgabe erfüllen. Allgemein solltest Du Deinen Code so schreiben, dass je Prozedur nur eine einzige Aufgabe erledigt wird. Kann man diese Aufgabe weiter unterteilen, dann verteile die Aufgabe an weitere, untergeordnete Prozeduren. Es ist viel einfacher, die Korrektheit von kurzen Prozeduren sicherzustellen, als sich durch lange, ineinander verschachtelte Kontrollflussblöcke zu quälen. Als Richtwert kann gesagt werden, dass eine Prozedur, die mehr als drei ineinander verschachtelte Kontrollflussblöcke enthält, überarbeitet werden muss. Um das ganze noch weiter zu Quantifizieren: eine Prozedur sollte unter 100 Zeilen Code und eine zyklomatische Komplexität von unter 15 besitzen. Verwende den statischen Code-Analyzer von NumeRe, um Dir diese Werte berechnen zu lassen.

§1.4 Testbarkeit und Tests

Ja, auch wir waren lange der Meinung, dass Tests für Prozeduren unnötig sind. Die relevanten Fälle von Hand testen und dann wird das schon klappen. Leider nein. Wir wurden wiederholt eines besseren belehrt, so dass wir inzwischen zu allen Packages, die wir bereitstellen, soweit möglich Test-Prozeduren schreiben. Der Vorteil ist die schnelle Ausführbarkeit bei allen folgenden Codeänderungen. Somit lässt sich bei jeder Änderung prüfen, ob alle anderen Funktionen auch noch korrekt funktionieren. Des Weiteren kann man sich umfassend mit den Grenzfällen beschäftigen und vergisst auch nicht, diese bei einem Update zu testen. Damit man aber Tests gut schreiben kann, ist es wichtig, Prozeduren von Anfang an mit dem Aspekt der Testbarkeit im Kopf zu entwickeln: möglichst klare, einfache Aufgaben und keine oder nur wenige Nebeneffekte.

§2 Benennung von Symbolen und valide Zeichen

§2.1 Namensgebung von Symbolen

Ach ja. Das Thema mit den Namen für Variablen und Prozeduren. In wahrscheinlich jedem Style-Guide wird dieses Thema vorkommen und nahezu immer wird dabei die "sprechende Namensgebung" als das A und O aufgeführt. Trotzdem gibt es immer noch Code, in dem genau das nicht praktiziert wird. Warum eigentlich? Vermutlich weil es tatsächlich richtig schwer ist, einen wirklich guten Namen für ein Symbol zu finden, der seinen Zweck und Inhalt gut beschreibt. Wir gehen dabei folgendermaßen vor: alles, was Daten enthält (also Variablen), bekommt ein Substantiv mit einer "statischen" Konnotation (z.B. calculationResult, fileContent, simulatedData). Interessanterweise verbietet das bereits die Verwendung von "temp" in einer Variable, da dieses Wort eben nicht die "statische" Konnotation besitzt.

Prozeduren bekommen dagegen einen "aktiven" Namen, indem sie durch ein Verb starten (z.B. $calculateResult, $loadFile, $simulateData). Prozeduren, die eine Konversion durchführen, können alternativ auch mit "to" oder "from" begonnen werden (z.B. $toKeyVal, $fromXml). "get" und "set" sind ebenfalls legitime Anfänge von Prozeduren, die Werte zurückgeben oder solche modifizieren (z.B. $getValue, $setFileName).

§2.2 Typen-Präfixe

Machen wir uns nichts vor. Bei diesem Thema sind die Meinungen gespalten: manche verabscheuen Typen-Präfixe vor Variablennamen, andere können nicht ohne. Für die, die damit nichts anfangen können: Typen-Präfixe bezeichnen einzelne Buchstaben am Anfang von Variablennamen, die deren Typ beschreiben. Das ist zum Beispiel n für natürliche Zahl, f für eine Fließkommazahl und s für eine Zeichenkette (für string), so wie z.B. in nId, fValue und sFileName. Auch NumeRe empfiehlt diese Konvention, da sie einen unmittelbaren Vorteil hat: man kann bereits am Namen erkennen, was sich hinter einer Variable für ein Typ verbirgt und sogar ob man damit rechnen muss, dass eine Variable auch inf oder nan enthalten kann. Allerdings birgt es auch die Gefahr, dass man zu sehr darauf vertraut und den Inhalt von Variablen nicht mehr validiert. Wir haben in der Vergangenheit eigentlich immer gute Erfahrungen mit diesen Präfixen gemacht, aber das muss nicht für jeden gelten.

§2.3 Valide Zeichensätze

Das ist ein kurzer Abschnitt: für alle Symbole sollte und (kann) nur der Standard-ASCII-Zeichensatz verwendet werden. Variablen oder Prozeduren mit einem "ä" oder einem "µ" zu bezeichnen ist nicht möglich und sollte (muss sogar) vermieden werden.

§2.4 Prozedurargumente

Es hat sich bei uns als eine Good Practice herausgestellt, Prozedurargumente einen zusätzlichen Unterstrich voranzustellen (sogar noch vor der eigentlichen Typisierung), wie z.B. _sFileName, _nId. Der Vorteil hiervon ist, dass man immer einen Überblick darüber hat, was lokale Variablen und was Argumente sind. Das ist insbesondere von Bedeutung, wenn die Argumente als Referenz übergeben werden. Man möchte ja nicht unabsichtlich Daten des Benutzers überschreiben. NumeRe wird die Verwendung von führenden Unterstrichen emfehlen, aber diese zugleich im Prozedurtooltip ausblenden, um hier die Länge der Signatur überblickbar zu halten.

§2.5 CamelCase vs. snake_case

Um "mehrwortige" Namen für Symbole zusammenfassen, gibt es im wesentlichen zwei Systeme: CamelCase, bei dem jedes Wort mit einem Großbuchstaben beginnt und dann klein weitergeführt wird (z.B. fromXmlFile) oder snake_case, bei dem alles klein geschrieben und Worte durch einen Unterstrich verbunden werden (z.B. get_data_from_file). Letztere Variante hat einen Vorteil, der zugleich ein Nachteil ist: da mehr Abstand zwischen den Wörtern ist, sind solche Namen leichter lesbar, allerdings brauchen sie zugleich mehr Platz. Daher empfehlen wir, CamelCase zu verwenden und nur in speziellen Fällen snake_case einzustreuen. NumeRe verwendet z.B. bei Funktionen wie "is_table()" oder "to_string()" snake_case-Präfixe um diese stärker abzuheben. Alle anderen Funktionen werden allerdings zusammen und nur in Kleinbuchstaben geschrieben. Außerdem wäre istable() oder tostring() auch deutlich schlechter lesbar als z.B. getfilelist().

§3 Formatierung und Zeilenlänge

§3.1 Einrückungen

Wir sind große Freunde der Verwendung von Einrückungen, um die Struktur von Code deutlicher zu machen. Dabei ist es irrelevant, ob man hier TABs oder Leerzeichen bevorzugt. Der NumeRe-Editor lässt sich für beides konfigurieren und wird auch beides gemischt akzeptieren. Viel wichtiger ist die Frage, wie man optimal einrückt. Tatsächlich ist das recht einfach zu beantworten: Code, der sich in einem Block befindet (also zwischen Kommandos der Form CMD ... endCMD), sollte pro Block um eine Ebene (also 1 TAB oder 4 Leerzeichen) eingerückt werden. NumeRe kann den Code während der Eingabe automatisch passend einrücken, wenn man die entsprechende Option in der Toolbar oder im Werkzeuge-Menü auswählt.

§3.2 Leerzeichen und Leerzeilen

Die Verwendung von Leerzeichen zwischen Operatoren und Symbolen kann die Lesbarkeit deutlich verbessern. Dabei folgen wir der Daumenregel, dass binäre Operatoren (z.B. +, * oder ==) beiderseits von je einem Leerzeichen von den umgebenden Symbolen abgetrennt sind. Das hält dann zugleich die Zeilenlänge im Rahmen, während es die Symbole und Operatoren optisch stärker voneinander trennt. Tatsächlich kann man auch die Reihenfolge von Operatoren mit den Leerzeichen deutlich machen, z.B.: a + b * c und a + b*c sind beides valide Vorgehen, wohingegen a^2 im Vergleich zu a ^ 2 zu bevorzugen ist. Wir machen übrigens keine extra Leerzeichen zwischen Klammern und den enthaltenen Ausdrücken. Für so etwas gibt es Klammerhervorhebung. Dafür gerne Leerzeichen nach Kommata, also z.B.: getfilelist("<savepath>/*.ndat", 1)

Ebenso wie Leerzeichen können auch leere Zeilen helfen, die Struktur eines Codes besser zu erfassen. Trenne logisch zusammenhängende Blöcke mit einer Leerzeile von dem umgebenden Code ab. Trenne auch mehrere Prozeduren durch mindestens zwei Leerzeilen voneinander ab. Tatsächlich kann vieles davon NumeRe selbst durchführen. Im Werkzeuge-Menü gibt es die Option die Formatierung auf das "NumeRe-Standardformat" anpassen zu lassen. Dabei werden Leerzeichen, Einrückungen und Leerzeilen automatisch ergänzt und angepasst, so dass die aus unserer Sicht optimale Lesbarkeit gegeben ist.

§3.3 Zeilenumbrüche und Zeilenlänge

Lange Zeilen solltest Du prinzipiell immer vermeiden. Zwar kann der Editor überlange Zeilen in mehreren Zeilen anzeigen, aber die Arbeit mit solchen Zeilen gestaltet sich als mühsam, insbesondere wenn der Editor in der Mitte geteilt ist, so dass nur die Hälfte oder sogar noch weniger Platz bereit steht. Wir empfehlen hier eine Zeilenlänge von 100 Zeichen (andere eher 80, aber seien wir mal ehrlich: die meisten von uns haben sowieso einen Ultra-Wide auf dem Tisch vor sich stehen). Du kannst in den Einstellungen nach den 100 Zeichen eine vertikale Linie anzeigen lassen, um Dich daran zu orientieren.

Was aber, wenn die Zeile nun mal länger sein muss? Da gibt es zwei Möglichkeiten: die Regel hier ignorieren, oder aber die Zeile händisch umbrechen. Letzteres geht mit der Zeichensequenz \\ am Ende einer Zeile. Findet NumeRe dieses Zeichen, wird es bei der Ausführung die folgende(n) Zeile(n) an die vorherige Zeile anhängen. Eine gute Stelle, um diese Umbrüche durchzuführen, ist vor binären Operatoren, so dass die umgebrochenen Zeilen mit diesen anfangen und damit die Zusammengehörigkeit noch deutlicher machen. Außerdem sollten umgebrochene Zeilen ab der zweiten Zeile ein Level weiter eingerückt sein, z.B.:

## This is a long espression broken down

## to multiple lines

surf 1 + sin(_2pi*t)*Y(3, 2, y, x) \\

+ 0.5*cos(_2pi*t)*Y(5, 2, y, x) \\

+ 0.7*sin(_2pi*t+2)*Y(8, 5, y, x) \\

-set coords=spherical_pt animate

(Wenn das hier komisch aussieht, dann drehe Dein Smartphone.)

§4 KISS & POLS

§4.1 KISS - Keep It Short & Simple

Machen wir's kurz: wenn Du Prozeduren schreibst, dann halte diese möglichst kurz. Achte darauf, dass jede Prozedur nur eine einzige Aufgabe erledigt. Wenn diese Aufgabe sich aus Teilaufgaben zusammensetzt, dann kannst Du das an untergeordnete Prozeduren delegieren. Je einfacher die Aufgaben der einzelnen Prozeduren sind, desto weniger fehleranfällig ist die Implementierung. Außerdem sind Lösungen für einfache Aufgabenstellungen viel einfacher zu erarbeiten, als wenn man versucht, alle Aufgaben auf einmal zu bearbeiten.

Wenn Du Deinen Code schreibst, dann verwende keine undokumentierten Hacks. Auch wenn die ggf. schneller sein können, sind sie tendenziell unsicher und können in zukünftigen Versionen von NumeRe auch entfernt werden. Und wir wollen nicht, dass Code stark von Versionen abhängt und dann sicherlich nicht aufwärtskompatibel ist. Leider ist letzteres eine nicht zu vermeidende Herausforderung bei Programmiersprachen, die im Wandel sind.

§4.2 POLS - Principle Of Least Surprise

Was ist mit diesem "Prinzip der kleinsten Überraschung" gemeint? Die Idee ist tatsächlich recht einfach zu vermitteln: bei der Verwendung von Code möchte man so wenig wie möglich von dessen Verhalten überrascht werden. Das heißt zum Beispiel, dass wenn eine Prozedur $loadData() heißt, dass man erwartet, dass diese Prozedur Daten lädt. Führt sie dann gleich noch Korrekturen auf den Daten aus, ist das hingegen überraschend. So etwas sollte immer vermieden werden. Man kann das lösen, indem man entweder den Namen der Prozedur auf $loadAndCorrectData() ändert oder stattdessen eine neue Prozedur $correctData() erzeugt und das Korrigieren hierhin verschiebt. Zusätzlich muss sich das Verhalten auch aus der Dokumentation der Prozedur ergeben.

Ebenfalls wird unter POLS auch noch verstanden, dass Objekte, die einen geläufigen Namen abseits des Codes haben, im Code auch so genannt werden. Dabei ist auch darauf zu achten, dass sich aus dem Name auch ergibt, was man hier verwaltet. xml{} ist z.B. auch ein Name abseits des Codes, aber es ist nicht klar, ob es sich um einen spezifischen XML-Knoten oder eine ganze Datei handelt. xmlFile{} wäre also zu bevorzugen. Achte auch auf mögliche Mehrdeutigkeiten. Zwar kann man den *.int-Dateityp als int{} anlegen, allerdings wird dem Leser dann nicht klar sein, ob es sich um eine Ganzzahl (integer), ein Integral oder vielleicht doch um diesen Dateityp handelt. Besser ist es, auch hier intFile{} zu verwenden.

§5 Namespaces

§5.1 Zweck von Namespaces

Kurze Antwort: Namespaces organisieren den Code. Sie können verwendet werden, um voneinander getrennte Module zu organisieren. Durch die Verwendung der private Prozeduren-Flag ist es sogar möglich, Prozeduren nur von innerhalb eines Moduls aufrufbar zu machen. Außerdem kann man durch Namespaces auch denselben Prozedurnamen mehrmals verwenden und trotzdem noch eindeutig bleiben, z.B. kann es für verschiedene Dateitypen eine $read()-Prozedur geben, sofern die Dateitypen durch voneinander getrennte Namespaces repräsentiert werden, also $TYP1~read() und $TYP2~read(). Durch die explizite Voranstellung des Namespaces sind diese auch bei der Verwendung klar voneinander unterscheidbar. Verwendet man nur einen dieser beiden Dateitypen, dann kann man durch namespace TYP1 sich sogar das Vorneanstellen des Namespaces sparen.

Im Prinzip kann man sich Namespaces einfach als Ordner vorstellen, was sie (bis auf wenige Ausnahmen) für die Prozedurdateien auch sind. Innerhalb eines Ordners sind die Dateinamen ebenfalls eindeutig. Verschiebt man eine Prozedur von einem (Namespace-)Ordner in einen anderen, so wird auch deren Namespace entsprechend angepasst.

Wie in den Beispielen bereits gezeigt, werden Namespaces in NumeRe mittels des ~ Operators angegeben.

§5.2 Organisation von Funktionalitäten

Wie verwendet man Namespaces möglichst sinnvoll? Wir nutzen sie gerne zur Organisation von Funktionalitäten. Das bedeutet, dass ein Namespace ähnlich zu einem Modul eine Funktionalität bereitstellt. Dabei ist es auch möglich, dass diese Funktionalitäten durch untergeordnete Namespaces präzisiert werden und der übergeordnete Namespace eher abstrakter Natur ist, z.B. files~ini~, files~logging~ und files~tools~. Durch die Verwendung von untergeordneten Namespaces kann man zugleich auch ähnliche Funktionalitäten zusammenfassen.

Warum solltest Du Namespaces verwenden? In erster Linie, um Deinen Code organisiert zu halten. Das schafft auch Übersicht, da in den einzelnen Namespaces weniger Prozeduren vorhanden sind. Du kannst durch Namespaces auch kürzere, prägnantere Prozedurnamen verwenden, so dass sich der Zweck einer Prozedur auch aus dem Namespace ergibt, z.B. $files~ini~read() statt $readIniFile(). Ebenfalls ergibt sich aus der Verwendung von Namespaces der Vorteil, dass Code leichter externalisierbar, also wiederverwendbar ist. Zu guter Letzt kannst Du Deine Architektur (sofern vorhanden) auch direkt in den Namespaces abbilden:

myapp~view~

myapp~controller~

myapp~model~

§5.3 Verwendung des Kommandos namespace

In einem vorhergehenden Abschnitt hatten wir das Kommando namespace als Abkürzung erwähnt. Bei einem Kommando dieser Art bestehen häufiger Vorbehalte gegenüber der verbleibenden Eindeutigkeit, da bei Verwendung wieder nicht erkennbar ist, welche Prozedur verwendet wird. Tatsächlich ist das aber nicht so. Das Kommando namespace kann nur einen Namespace zur gleichen Zeit aktiv halten, d.h. bei erneuter Verwendung von namespace wird der Namensraum gewechselt und nicht ein neuer hinzugefügt. Daher bietet es sich an, namespace nur für den hauptsächlichen Namensraum zu verwenden und alle anderen Namensräume direkt anzuprechen. Außerdem kann namespace auch nur innerhalb von Prozeduren verwendet werden und gilt dann auch nur für den folgenden Code innerhalb dieser Prozedur. Ein Hinauslecken von Namespaces hinein in andere Prozeduren ist daher nicht möglich.

§6 Kommentare und Dokumentation

§6.1 Schreiben von Kommentaren

Machen wir uns nichts vor: niemand liebt es wirklich, seinen Code zu kommentieren. Im Moment, in dem man den Code schreibt, erscheint es doch den meisten als vergeudete Zeit, weil er doch vollkommen selbsterklärend ist. Es macht doch gar keinen Sinn, diese 20 Zeilen Code mit einem Kommentar zu versehen. Erst nachdem entweder einige Monate vergangen sind oder man eine neue Version verwendet, in der durch neue Features bestimmte Probleme eleganter und effizienter genutzt werden können, erst dann fragt man sich doch: "Was, zum Teufel, habe ich denn hier getan?"

Das Problem bei den Kommentaren ist nun mal nicht, dass man sie selbst in dem Moment bräuchte, in dem man den Code schreibt. Man braucht sie vor allem beim Lesen. Und da Code viel häufiger gelesen als geschrieben wird, ist es unablässig, dass man es dem Leser so einfach wie möglich macht, die Logik und die Gedanken dahinter zu verstehen. Außerdem ist es viel einfacher, Fehler in einem Code zu finden, wenn man die Ideen und Gedanken hinter dem Code kennt. (An alle, die glauben, das nicht zu kennen: es gibt keinen nicht-trivialen, fehlerfreien Code.) Dabei fordern wir nicht, dass Kommentare gleichzeitig zum Code geschrieben werden sollen. De facto hat der erste Code-Entwurf im Allgemeinen eine sehr geringe Halbwertszeit, daher ist eine Kommentierung erst dann sinnvoll, wenn die Lösung einigermaßen stabil und aufgeräumt ist.

§6.2 Dokumentationsblöcke

Neben den gewöhnlichen Kommentaren gibt es auch noch Dokumentationskommentare bzw. -blöcke. Diese haben den Zweck, den Code tatsächlich zu Dokumentieren. Aus ihnen kann eine PDF-Dokumentation erzeugt werden, die auch als Handbuch verwendet werden kann. Außerdem werden Dokumentationsblöcke vor Prozeduren auch verwendet, um die Tooltips mit sinnvollen Informationen zu befüllen. Es bietet sich daher immer an, diese Blöcke zu verwenden und auch zu befüllen - selbst erzeugen musst Du sie nämlich nicht. Eine Schablone kann NumeRe Dir mit der entsprechenden Menüoption einfügen. Die einzigen Dinge, die NumeRe nicht kann, sind das Befüllen der Schablone sowie die Bestimmung des Rückgabewertes der Prozedur. Daher wirst Du den Rückgabewert mit dem Keyword \return selbst dokumentieren müssen. Aber auch dieser Zusatzaufwand lohnt sich direkt, da dann der Tooltip auch den Rückgabetyp anzeigt.

§6.3 Auskommentierter Code

Wir kennen es alle: wir sind gerade dabei, eine Lösung zu entwickeln und wollen mal eben schnell einen anderen Weg probieren, also kommentieren wir die aktuelle Lösung aus und ergänzen eine neue. Das ist an sich nichts Verwerfliches und ein super Vorgehen, um genau solchen Herausforderungen zu begegnen, aber man tendiert dann gelegentlich dazu, den auskommentierten (toten) Code in der Datei zu belassen. Das hat natürlich keinen Einfluss auf die Laufzeit, aber kann die Lesbarkeit und das Verständnis erheblich stören.

Daher solltest Du Code, den Du nicht mehr brauchst, auch wirklich entfernen. Und falls Du doch noch mal darauf zurückgreifen willst, gibt es Versionskontrollsysteme wie Git oder SVN, die es Dir erlauben, zu vorherigen Versionen zurückzuspringen. Tatsächlich hat NumeRe eine vereinfachte Versionierung bereits eingebaut, die bei jedem Speichern einer Datei eine neue Version anlegt. Die Versionskontrolle kannst Du in den Einstellungen aktivieren, falls das nicht bereits ist. Anderenfalls sind Dir bestimmt schon die ominösen (rev123) Angaben in den Tooltips aufgefallen: diese zeigen die aktuelle Versionsnummer (also 123 in diesem Fall). Du kannst Dir mit der entsprechenden Funktion im Kontextmenü jeder Datei auch die vorherigen Versionen anzeigen lassen und Dir sogar einen Diff, also eine Vergleichsdatei von zwei Versionen erzeugen lassen.

Die Versionskontrolle hat sogar noch einen weiteren, unmittelbaren Vorteil: wenn Du am Rumprobieren bist und plötzlich feststellst, dass Deine Ergebnisse wieder schlechter werden, kannst Du problemlos zu einer besser funktionierenden vergangenen Version zurückkehren.

§7 Lokale vs. globale Variablen

Wenn man die Wahl hat, lokale Variablen zu verwenden, dann sollte man diese gegenüber globalen Variablen stets bevorzugen. Lokale Variablen haben den Vorteil, dass ihr Einflussbereich sehr begrenzt ist und sie auch nicht von irgendwoher überraschend modifiziert werden können. Das heißt jetzt aber nicht, das globale Variablen per Definition schlecht sind. Im Gegenteil - es gibt auch hier valide Gründe, sie zu verwenden. Das sind beispielsweise Fälle, in denen es mit globalen Variablen lesbarer wird. Auch Fälle, in denen ein Debuggen ohne sie mühselig wäre, sind mögliche und akzeptable Fälle, globale Variablen zu verwenden. Nur sollte in letzterem Fall auf lokale Variablen umgebaut werden, sobald der Fehler behoben wurde.

Lokale Variablen sind nur in Prozeduren möglich. In allen anderen Fällen (aka Scripte oder Console) sind Variablen automatisch global. Um lokale Variablen in Prozeduren zu erzeugen, verwendest Du die Kommandos var (numerische Variable), str (Zeichenkette), cst (Cluster) und tab (Tabelle). Variablen, die Du abseits dieser Kommandos definierst, sind dagegen ebenfalls global.

§8 Konstanten

In fast jedem Code gibt es Konstanten, also Werte, die sich während der Auswertung nicht ändern. Das können zum Beispiel Dateipfade oder -namen sein, in denen die Konfiguration gespeichert werden soll. Es ist Good Practice, diese Konstanten nicht als Literale (also Werte, keine Variablen) direkt in den Code zu schreiben, sondern hierfür eigene Symbole zu verwenden. NumeRe bietet Dir die Möglichkeit, mit dem Kommando declare Konstanten für die aktuelle Datei zu erzeugen (ähnlich zu dem #define-Präprozessor-Macro von C oder C++). Die dabei erzeugten Symbole kannst Du dann ähnlich wie Variablen im Code verwenden (mit der Einschränkung, dass sie nicht überschrieben werden können).

Die unmittelbaren Vorteile sind zwei: zuerst wird Dein Code deutlich lesbarer, da keine ominösen Zahlen oder Zeichenketten auftauchen. Stattdessen sind es Symbole mit einer verständlichen Bezeichnung. Als zweiter Vorteil nennen wir hier die Modifizierbarkeit: es ist um ein Vielfaches einfacher, den Wert einer Konstanten an einer Stelle zu ändern, anstatt den gesamten Code zu durchsuchen. Search n' Replace tut's hier außerdem auch nicht immer: es kann durchaus sein, dass derselbe Wert für verschiedene Konstanten verwendet wird. So kann 32 als Zeichenwert oder bei Bitwerten verwendet werden.

isWhiteSpace = ascii(myString.at(1)) == 32;

nLength32Bit = 2^32;

§9 Wiederverwendbar vs. spezifisch

Die zentrale Aufgabe beim Programmieren ist in erster Linie das Lösen eines oder mehrerer Probleme. Dabei bricht man die Aufgaben soweit herunter, dass sie im Prinzip nicht mehr weiter unterteilbar sind. Erst dann beginnt man mit der Implementierung der Einzelaufgaben. Genau hier kann man zwei verschiedenen Ansätzen folgen: man kann die Implementierung spezifisch für das aktuelle Problem entwickeln, oder man löst sich vom expliziten Problem und schreibt den Code gleich so abstrakt wie möglich. Beides sind valide Vorgehen und beide jeweils ihre Vor- und Nachteile.

Schreibt man Code spezifisch, so hat man den unmittelbaren Vorteil, dass das Testen recht einfach wird und man nicht alle eventuellen Randfälle testen muss. Außerdem ist man schneller, da man sich keine aufwändigen Gedanken über ein möglichst gutes Interface machen muss. Nachteil ist dagegen, dass die Lösung nur für diesen einen Fall funktionieren wird. Sobald sich die Anforderungen auch nur leicht ändern, ist es wahrscheinlich, dass man die Implementierung nochmal anfassen muss. Außerdem kann man die Implementierung wahrscheinlich nicht bei einem anderen Problem einsetzen, da auch hier kein komplettes Matching gegeben ist.

Bei einer abstrakten Implementierung hat man das letztere Problem hingegen nicht. Durch die abstrakte Lösung ist die Wiederverwendbarkeit trivial. Und es ist auch nicht so wahrscheinlich, dass man abstrakte Lösungen mehrmals anfassen muss, wenn die Anforderungen sich ändern. Zusätzlich kann man abstrakte Lösungen einfacher in Bibliotheken bzw. Packages umwandeln. Nachteile sind im Prinzip die Vorteile der spezifischen Implementierung: es dauert länger, man muss sich Gedanken über das Interface machen und das Testen ist aufwändiger. Außerdem ist die Lesbarkeit bei abstrakten Implementierungen geringer. Sobald also die Lesbarkeit deutlich leidet, ist es vielleicht sinnvoll, eine spezifische Lösung zu verwenden.