Semantische Ebene einführen

15 views
Skip to first unread message

Rüdiger

unread,
Mar 20, 2010, 3:00:28 PM3/20/10
to BSP-Praxis
Je ähnlicher unser Code einer natürlichen Sprache wird, desto besser
ist er verständlich. Mit guten Benennungen für Variablen, Methoden,
Klassen, Macros kann man schon sehr viel erreichen.

Dazu kommen sprachspezifische Features, die es erlauben, fürs
Verständnis unwesentliche Inhalte des Codes zu reduzieren oder
auszulagern. Neben der überall bestehenden Möglichkeit, Code in gut
benannte Methoden zu extrahieren und damit erst auf Anforderung
sichtbar zu machen, gibt es in ABAP z.B. die Doppelpunkt-Komma-
Notation, mit der sich aufeinanderfolgende ähnliche Anweisungen sehr
kompakt notieren lassen, vor allem in Kombination mit einem
sprechenden Macro-Namen.

Man muss sich vor Augen halten, dass der Code nicht nur von einer
Maschine ausgeführt werden soll ("es läuft"), sondern darüberhinaus
auch von Menschen gelesen und verstanden werden soll, dass man als
Software-Entwickler also auch Autor ist.

Ein Autor denkt beim Schreiben an den Leser: Kommt der noch mit?
Drücke ich mich verständlich aus? Habe ich zu viele implizite Annahmen
gemacht oder die Anweisungen zu kompakt formuliert? Auch wenn es ein
Prozentpünktchen Abzug in der Effizienz kostet, lohnt es sich,
semantische Ebenen einzuziehen, um den Code für den Leser verständlich
zu machen. Das können eigene Methoden mit einem guten Namen sein, oder
Variablen.

Ein gutes Beispiel hat sich mir aus dem Kamelbuch eingeprägt
(Progamming Perl, http://docstore.mik.ua/orelly/perl/prog3/ch05_09.htm).

Um festzustellen, ob ein String als Zahl interpretiert werden kann,
könnte man schreiben:

if ($num =~ /^[-+]?\d+\.?\d*$/) { ... }

Der Leser muss sein inneres Dictionary für reguläre Ausdrücke
durchgehen und übersetzt: "Aha - am Anfang ein Plus oder Minus, aber
nicht notwendig. Dann eine Folge von Ziffern, mindestens eine, dann
optional ein Punkt und optional weitere Ziffern."

Der folgende Code macht das Gleiche, ist aber lesbarer (die
Variablendeklarationen muss man sich irgendwo ganz am Anfang des
Programms vorstellen, vielleicht in einem Initialisierungsblock,
jedenfalls nicht an der Codestelle, wo das Feld $num getestet wird):

$sign = '[-+]?';
$digits = '\d+';
$decimal = '\.?';
$more_digits = '\d*';
...
$number = "$sign$digits$decimal$more_digits";
...
if ($num =~ /^$number$/o) { ... }

Hier wurde also eine semantische Ebene mit Hilfe von geeignet
benannten Variablen eingeführt. Die technischen Details - die
Spezifikation dessen, was der Variablenname bedeutet, in der Sprache
der regulären Ausdrücke, wurde in den Wert gesteckt. Der Ausdruck
selbst gewinnt dadurch an Lesbarkeit.

Schön an dem Beispiel ist auch, dass man in verschiedene Tiefen
absteigen kann, je nach Interesse. Die meisten Leser werden sich damit
zufriedengeben, dass "irgendwie" geprüft wird, ob $num eine Zahl ist.
Wer es genauer wissen will, schaut sich den regulären Ausdruck $number
an. Von denen, die sich $number anschauen, werden wieder die meisten
mit dem zufrieden sein, was sie dort sehen: "Aha, ein Vorzeichen, dann
Ziffern, dann ein Dezimalpunkt, dann weitere Ziffern. Also kein
wissenschaftlich-technisches, sondern z.B. ein kaufmännisches
Zahlenformat." Und nur ganz wenige wollen es noch genauer wissen: "Ist
denn das Vorzeichen obligatorisch?" Dann - erst dann - schauen sie
sich $sign an: "Nein: der reguläre Ausdruck hat den ?-Quantifier."

Ein anderes Beispiel: Im clientCheck() einer BSP-Applikation fand ich
folgenden JavaScript-Code:

...
// Datenverlust OK?
if (!$("name1").readOnly && ! confirm(msgText["Z033"]) ) return false;
...

Der Zweck des Codes war, bei bestimmten über genericSubmit() laufenden
Requests (wie dem Druck eines "Zurück"-Buttons) eine
Sicherheitsabfrage über den Verlust der zuletzt in einem
Addressblock
eingegeben Daten zu senden und den Request nicht durchzuführen, wenn
der Benutzer die Sicherheitsabfrage nicht bestätigt.

Die Notwendigkeit eines Kommentars deutet schon darauf hin, dass der
Code selbst hier nicht sprechend genug ist. Was macht der Code? Er
prüft, ob das für den Nachnamen vorgesehene Eingabefeld "name1"
änderbar ist. Falls ja, wird eine Nachricht über Datenverlust gesendet
und die Bestätigung oder Ablehnung des Benutzers erfragt. Wenn der
Benutzer ablehnt, wird die Funktion clientCheck() mit dem Rückgabewert
false verlassen.

Warum gerade "name1"? Offenbar wurde der Adressblock so entworfen,
dass entweder alle Felder im Anzeige- oder im Änderungsmodus sind.
Also kann man sich ein Feld herausnehmen und dessen Änderbarkeit
prüfen, um zu wissen, ob die Adressdaten insgesamt änderbar sind oder
nicht.

Hier dieselbe Abfrage nach einer Refaktorisierung:

...
if ( adressdatenAenderbar() ) {
if (! datenVerlustOK() ) return false;
}
...

Den Trick, die Änderbarkeit mit dem readOnly-Attribut des "name1"-
Feldes zu ermitteln, habe ich in die Funktion adressdatenAenderbar()
ausgelagert. Vielleicht gibt es ja auch mal andere (gemischte)
Situationen, oder vielleicht wäre es geschickter, dem Client ein Mode-
Feld mit dem aktuellen Transaktionsmodus (Hinzufügen | Verändern |
Anzeigen) bekanntzumachen. Jedenfalls ist die *Ermittlung* der
Änderbarkeit der Adressdaten nun getrennt von der Codestelle, die dies
abfragen will. Das gleiche gilt für die Datenverlustabfrage. Der
Wiederverwendbarkeitsgedanke war bei der Entscheidung, diese Abfrage
in eine Funktion auszulagern, zweitrangig (auch wenn die Funktion
mittlerweile tatsächlich an drei Orten verwendet wird). Mir ging es
vor allem um die Verbesserung der Lesbarkeit. Dafür habe ich in Kauf
genommen, dass zur Laufzeit zwei weitere Funktionen evaluiert werden
müssen. Der Code wird ja nicht millionenmal durchlaufen, sondern nach
dem User Event genau einmal. Da spielt es wirklich keine Rolle, ob
noch zehn Mikrosekunden für den Aufbau einer weiteren Stackebene
konsumiert werden oder nicht.

Das bedeutet, mit den Funktionen adressdatenAenderbar() und
datenVerlustOK() habe ich eine semantische Ebene in den Code
eingezogen, um ihn lesbarer zu machen.

- Rüdiger

Reply all
Reply to author
Forward
0 new messages