Java™ und Objektorientierung
Java/OO kleiner Drucker Druckversion

Fallen und Fehler in der Computerprogrammierung

Typische Fallen von Programmiersprachen und Fehler von Programmieranfängern


Keywords: Common, Beginner, Pitfall, Problem, Mistake

Fehler passieren allen. Die häufigsten sind hier aufgelistet. Ein weit größeres Problem sind die Fallen der Programmiersprachen. Häufig handelt es sich dabei um Abkürzungen, die dem Programmierer viel Tipparbeit abnehmen wollen und dann vor allem die Anfänger in große Schwierigkeiten bringen.

Der Artikel richtet sich an Trainer, die rasch die Fehler der Schüler entdecken müssen oder an Personen im Selbststudium, die nach einigen Progrämmchen die Problemquellen von vornherein ausschließen wollen.

Dieser Artikel behandelt vorwiegend C-ähnliche Programmiersprachen (Java, JavaScript, ...). Der generelle Teil am Anfang kann jedoch für alle Programmiersprachen angewendet werden. Java-Programmierer lassen den JavaScript-Teil weg; JavaScript-Programmierer lassen den Java-Teil weg.

Inhalt

Generelle Programmierfehler


Wahl der Programmiersprache

Ein häufiger "Fehler" ist die Tatsache, dass sich Anfänger zunächst fragen, welche Sprache sich zum Erlernen von Programmierfähigkeit eignet. Ist jedoch ein Ziel vor Augen (Web-Applikation, iPhone-App, Android-App, ...), so ist die Sprache in der Regel vorgegeben.
Will man einfach nur Programmieren lernen, so bieten sich diverse Sprachen an. Zu den meisten gibt es gute Lehrbücher oder Tutorials im Internet.

Tipps fürs Selbststudium

In diesem Fall ist es jedoch wichtiger, als "die richtige" Sprache zu finden, die richtigen Konzepte zu erlernen. Viele Trainingsaufgaben zu den folgenden Konzepten finden sich in meinem Buch (Programmieren lernen, Philipp Gressly Freimann | Martin Guggisberg, 2011 Orell Füssli Verlag) oder auf der dazu gehörigen Webseite.

Konzepte

Die folgenden Grundkonzepte sollten bekannt sein:

Datentyp Werte werden im Computer aus Nullern (0) und Einsen (1) zusammengesetzt:
Boolean: wahr = 1; falsch = 0
Zweiersystem: 0, 1, 10, 11, 100, 101, 110, 111, 1000, 1001, ...
Buchstabentabellen: 'a' = 97 = 1100001 (z. B. ASCII)
Gebrochene Zahlen: 3.5 = 01000000011000000000000000000000 (ieee 754 Notation)
Zeichenketten (Strings) "Hallo" = 'h', 'a', 'l', 'l', 'o'
Variable Jede Variable ist entweder nicht initialisiert oder sie enthält ein Bitmuster, das einem Wert eines Datentypen entspricht.
Ausdrücke (Term) Ausdrücke werden meist in einer algebraischen Form dargestellt: 3+9, 2*durchmesser, ...
Ein Ausdruck ist etwas, das einen Wert aufweist; also etwas, das einer Variable zugewiesen werden kann, oder das in einem größeren Ausdruck verwendet werden kann.
Ausdrücke sind:
  1. Literale (z. B. 5, true, 'c', "Hallo Welt", -3.7e6, ...)
  2. Variable (z. B. x, betragInCHF, ...)
  3. Funktionsresultate (z. B. random(), sin(phi), ...)
  4. Durch Operatoren zusammengesetzte Terme (z. B. 5+x, min(x+4, max(3, y)), ...)
Anweisung (Statement) Die typischen drei Anweisungen sind:
  1. Die Deklaration
    Beispiel
    betragInCHF: real
  2. Die Zuweisung
    Beispiel:
    x ← 1.5 + sin(3.8 * phi + nue);
  3. und der Unterprogrammaufruf
    Beispiel:
    print("Hallo", " Welt!");
Kontrollfluss Sequenz : a(); b(); c(); ...
Selektion: if (...) {...}
Iteration: while(...) {...}
Subroutinen Unterprogramme selbst schreiben:
«function» ausgabe(wert) { print("Das Resultat ist: ", wert); }
Felder (Arrays) Anstatt Variable a1, a2, a3, ..., a20 zu deklarieren, deklarieren wir a[1..20]. Das hat den Vorteil, die einzelnen Variable mit einer Indexvariablen anzusteuern: a[index]

Wissen ist genug

«Programmieren lernt man, indem man die ca. 20 wichtigsten Grundkonzepte kennenlernt.»

Falsch: Es ist zwar richtig, dass die Programmierung wie die Kunstmalerei, die Geometrie oder das Go-Spiel aus wenigen Grundkonzepten und wenigen wichtigen Regeln aufgebaut sind. Aber Malen lerne ich auch nicht mit dem Kennenlernen der Theorie zu Pinsel, Bleistift, Farben und Leinwand. Hier gilt: üben, üben und nochmals üben. Nochmals: programmieraufgaben lösen!


Kenne den Debugger

Die Grundlegenden debug-Möglichkeiten sind:
print: printf(), alert(), System.out.println(), ... kurz, im Folgenden print() genannt. Damit kann an beliebiger Stelle zwischen den Zeilen gelesen werden.
system.log: Irgendwelche Loging-Funktionen, wenn ein print() nicht ausgeführt werden kann. Beispiel: Fehlerkonsole bei Javascript.
Debugger: Verwenden eines Debuggers (Zeilenweise, bzw. Anweisung für Anweisung). Die Unterscheidung: step-into und step-over sollten bekannt sein.


Zu viel aufs Mal programmieren

Die Fehlersuche wächst exponentiell zur Anzahl der programmierten Zeilen. Dabei bietet es sich gerade für Anfänger an, jeden programmierten Schritt mittels Ausgabe 'print()' oder Debugger sofort zu überprüfen. Komplizierte Algorithmen sollten zunächst von Hand (Wertetabelle, Flussdiagramm, Objekt-Theater, ...) mit Bleistift und Papier vor dem Programmieren einmal durchgespielt werden.

1.

eur ← eingabe("Euro" ); proc ← eingabe("Prozente"); print("Euro: ", eur, " Prozente: ", proc);

2.

eur ← eingabe("Euro" ); proc ← eingabe("Prozente"); zins ← berechneZins(eur, proc); print("Zins: ", zins);

3.

eur ← eingabe("Euro" ); proc ← eingabe("Prozente"); zins ← berechneZins(eur, proc); neuerSaldo ← eur + zins; ausgabe(neuerSaldo);

Ignorieren (od. nicht verstehen) der Fehlermeldung

Oft werden Fehlermeldungen nicht gelesen, oder nur flüchtig und dann nicht verstanden. Meist sind sie jedoch ganz nützlich. So heißt z. B. 'missing semicolon' nichts anderes als "Fehlender Strichpunkt(;)".


Schreibfehler

Aufruf von doSomething(); mit gegebener Funktion

«function» dosomething() { print("Hallo Welt"); }

Offensichtlich wurde beim Aufruf das 's' groß geschrieben. Abhilfe: längere Namen sollten kopiert, statt getippt werden.


Nicht Aufrufen des Hauptprogramms

Auch wenn die Anweisungsreihenfolge innerhalb einer Funktion korrekt ist, läuft das Programm nicht:

«function» haupt() { e ← einlesen("Eingabe Zahl"); a ← rechneDoppel(e); ausgabeDoppel(a); }

Wird nun haupt(); nie aufgerufen, so kann es noch so korrekt sein. Dies kann einfach mit einem print(); auf der ersten Zeile gemerkt werden.


Reihenfolge der Funktionsdefinitionen ist meist irrelevant

Gerade Anfänger haben das Verständnisproblem, dass die Reihenfolge des Funktionsaufrufs wichtig ist, und nicht die Reihenfolge der Funktionsdefinitionen. So ist es unnötig, die Reihenfolge wie folgt vorzugeben:

«function» einlesen(wert) { print("Bitte ", wert, " eingeben"); zahl ← get(); // ab Konsole Lesen return zahl; } «function» rechneDoppel(z) { return 2 * z; } «function» ausgabeDoppel(zahl) { print("Doppel ist ", zahl); }

Die Reihenfolge obiger Funktionen kann komplett umgestellt werden. Wichtig ist, dass der Aufruf im Hauptprogramm korrekt ist:

«function» haupt() { e ← einlesen("Zahl"); a ← rechneDoppel(e); ausgabeDoppel(a); } haupt();

Nicht initialisieren von Variablen

var x; // integer while(x < 101) { print(x, " Quadrat ist ", (x*x)); x ← x + 1; }

Fehlermeldung: x ist nicht initialisiert.

Abhilfe:

var x ← 0;

Nicht Ändern der Schleifenbedingung

Der folgende Code produziert eine Endlosschleife:

var x; //integer x ← 0; while(x < 101) { print(x, " Quadrat ist ", (x*x)); }

Abhilfe: Ein einfaches print(); am Anfang der Schleife wird dies zum Ausdruck bringen.


Logische Verwechslung

Oft werden die Symbole < und > beziehungsweise die Funktionen min() und max() vertauscht.

var x; x ← 0; while(x > 101) { print(x, " Quadrat ist ", (x*x)); x ← x + 1; }

Lösung < statt >.

Der Debugger kann diesen Fehler leicht finden. Warum ist 0 > 101 denn falsch?


Strings statt Zahlen

Die Eingabe von Zahlen in ein Programm geschieht häufig via Zeichenketten (Strings). Diese müssen zunächst in Zahlen umgewandelt werden.

a ← "4"; b ← "5"; a + b liefert "45" !!

Lösung:

a ← (integer) "4"; b ← (integer) "5"; a + b liefert 9.

Ganze vs. gebrochene Zahlen

Speziell bei der Division ist es wichtig, sich zu überlegen, ob man mit ganzen oder mit gebrochenen Zahlen (Dezimalbrüchen) rechnen muss:

x ← 9 / 2 ; // integer liefert 4 x ← 9.0 / 2.0; // real liefert 4.5

Werte statt variable Parameter

Nur wenige Programmiersprachen (z. B. PL/1) übergeben standardmäßig die Variable an Unterprogramme.
Die meisten modernen Sprachen übergeben die Werte - m. a. W. sie betrachten die Variable als Ausdrücke und werten diese vor der Übergabe an das Unterprogramm aus. Beispiel:

«function» f(x, y) { x ← 7 * y; y ← y * 2; } // call x ← 3; y ← 6; f(x, y); print(x, y); // liefert "3, 6" statt der gewünschten "42, 12"!

Kommentare und Namensgebung

Programmieren für Maschinen kann jeder. Wir schreiben Programme für Menschen; das ist die hohe Kunst. Gehen Sie davon aus, dass Ihr Programmcode nicht einfach funktionieren muss, sondern dass er auch unterhalten werden will. Jemand muss Ihren Code verstehen (vielleicht Sie selbst?)!

Fallen der C-Sprachen

Die folgenden Fallen in C-Sprachen betreffen nicht nur Anfänger in der Computerprogrammierung. Auch alte Hasen, die eine andere Sprache gekannt hatten, oder die schon länger keine C-Sprache mehr programmiert haben, werden getäuscht. Es handelt sich i. d. R. um abkürzende Schreibweisen, die Weniger Tipparbeit, aber massiv mehr Fehlerquellen bedeuten.


Angabe eines Ausdrucks statt einer Anweisung

Folgendes sieht man leider zu oft:

1. var passwort; 2. passwort ← einlesen(); 3. "Geben Sie das Passwort ein"; 4. if("Schwertfisch" = passwort) { 5. "Sie sind eingeloggt"; 6. } else { 7. "Kein Login möglich (falsches Passwort)"; 8. }

In obigem Code auf Zeile 3, 5 und 7 stehen keine Anweisungen, sondern Ausdrücke! Einige Programmiersprachen erlauben diese Syntax, führen dann aber nichts aus!

Was gemeint war ist etwas in der Art:

1. var passwort; 2. passwort ← einlesen("Geben Sie das Passwort ein"); 3. 4. if("Schwertfisch" = passwort) { 5. print("Sie sind eingeloggt"); 6. } else { 7. print("Kein Login möglich (falsches Passwort)"); 8. }

Vernichten des Funktionsresultates

Hierbei handelt es sich um eine der perfidesten Fallen der C-Sprachen. Es handelt sich nicht um einen Anfängerfehler sondern um eine Unzulänglichkeit oder eben eine "Falle" der Programmiersprache. C-Sprachen unterscheiden beim Aufruf nicht, ob ein Unterprogramm einen Wert zurückgibt oder nicht. Somit funktioniert der folgende Code ohne Compilerfehler:

int f() { ...; return x; } // Aufruf: f();

Der korrekte Aufruf müsste lauten:

// Aufruf: z = f();

Andere Programmiersprachen (wie z. B. PL/1) sind in diesem Bereich hoch zu loben!
Am besten gewöhnt man sich an, beim Aufruf von (nicht void) Funktionen, dies zu kommentieren, falls das Funktionsresultat vernichtet werden soll:

// Aufruf: f(); // value not used!

Iteration, Selektion und Strichpunkte

Eine weitere Falle bietet die Möglichkeit, bei Selektionen oder Iterationen, die nur aus einer einzigen Anweisung bestehen, die geschweiften Klammern wegzulassen.

Die generelle Syntax der while-Schleife (Iteration) ist wie folgt (abgesehen vom else ist das if analog):

while(bedingung()) { anweisung1(); ... // optional weitere Anweisungen. }

Probleme

Folgender abkürzende Code (ohne {}) wurde zwar korrekt geschrieben:

while(bedingung()) anweisung1();

Wird dieser Code mit einer zweiten Anweisung ergänzt, so geschieht das unerwartete:

while(bedingung()) anweisung1(); anweisung2();

Obige zweite Anweisung (anweisung2();) wird erst nach der Schleife ausgeführt, dafür in jedem Fall

Ein weiteres Problem ergibt sich durch die Strichpunkte:

while(bedingung()); anweisung1();

Was aufs das selbe herauskommt wie

while(bedingung()); { anweisung1(); anweisung2(); }

Die Lösung des Problems liegt darin immer die geschweiften Klammern zu verwenden und die Strichpunkte immer erst nach einer Anweisung zu setzen:

while(bedingung()) { anweisung1(); }
oder
while(bedingung()) { anweisung1(); anweisung2(); }

Ausnahme else if

Wir haben gelernt, dass es robuster ist, die geschweiften Klammern bei while und if immer anzufügen.

Die klassische if/else-Konstruktion sieht wie folgt aus:

if(b) { a(); } else { b(); }

Hier gibt es jedoch eine

Ausnahme.

Wenn wir eine Mehrfach-Selektion mit mindestens drei (3) Varianten haben, so wird die Anweisung rasch unübersichtlich:

if(b1) { a1(); } else { if(b2) { a2(); } else { a3(); } }

Lösung else if

Lassen wir hier beim ersten else-Block die oben generell vorgeschlagenen geschweiften Klammern weg...

if(b1) { a1(); } else { if(b2) { a2(); } else { a3(); } }

... und rücken neu ein (so, bei einer oder zwei Anweisungen pro Block) ...

if(b1) { a1(); } else if(b2) { a2(); } else { a3(); }

... oder so (bei mehreren Anweisungen) ...

if(b1) { a1(); } else if(b2) { a2(); } else { a3(); }

... so verletzen wir zwar die "Immer geschweifte Klammern"-Regel, doch es wird viel lesbarer und wartbarer.


Verwenden spezieller abkürzender Operatoren

Wenn es auch Tipparbeit spart, so sollten abkürzende Operatoren nur sparsam und gezielt eingesetzt werden.

So ist die folgende Anweisung kompletter Nonsense; und das nur, weil meine Schüler denken, sie haben mit dem ++-Operator etwas schlaues gelernt:

i = i++; anstelle von i++;.

Damit solche Probleme gar nicht erst auftreten, verwende ich als Trainer in den ersten Semestern den ++-Operator überhaupt nicht und schreibe statt dessen konsequent:

i = i + 1;

Dies funktioniert in den meisten Programmiersprachen. Eine Adaption ist daher einfach. Natürlich darf man den gewitzten Schülern den ++-Operator zeigen, muss sie dann aber auf deren Gefahren hinweisen. Ebenso ist es nützlich (gerade im Zusammenhang mit Feldern (= Arrays)) die for-Schleife als zusammenhängendes Konstrukt einzuführen:

for(i = 10; i >= 0 ; i--) {...}

Hier «wird es einfach so gemacht». Damit geht auch die Schleifenvariable nicht vergessen.

Sind wir dann endlich gewohnt, so schreiben wir später sehr wohl:

++i;

Eine weitere katastrophale Abkürzung: +=

Genau genommen kann die folgende Abkürzung effizient und elegant sein, wenn sie korrekt angewendet wird. Sagen wir, wir wollen in einem Array (Feld) einen Wert an variabler Stelle (nennen wir sie mal einfach index) um eine Konstante (sagen wir 3) vergrößern:

int[] arr; int index; ... arr[index] += 3;

Gefährlich wirds bei einer einzigen Variable:

i += 1;

Schreibt jemand fällschlicherweise nun

i =+ 1;

so ists um ihn geschehen (ich habe dies selbst bei erfahrenen Programmierern erlebt). Es wird immer einfach die Zahl (+)1 in die Variable i geschrieben.

Um nicht in die Falle zu tappen, bleiben wir doch besser bei

arr[index] = arr[index] + 3;

Wohlweislich haben wir aber gerade bei komplexen Indizes damit ein weiteres Problem: Die Arrayposition muss doppelt berechnet werden (Performanz) und beim doppelten Schreiben der Indizes, können Fehler passieren. Also auch so nicht:

arr[x + 3*y - 6*x*y + 4] = arr[x + 3*y - 4*x*y + 6] + 3;

Tippfehler sind so schnell passiert. Hier dann doch besser:

arr[x + 3*y - 6*x*y + 4] += 3;// Performance

oder so:

i = i + 1; // oder ++i;

Merke: Bei += gilt der goldene Mittelweg!


Vergleiche, Boole'sche Ausdrücke

Da in C-Sprachen eine Zuweisung gleichzeitig ein Ausdruck ist, sind die folgenden Fehler - gestatten Sie das Wortspiel - vorprogrammiert:

if(x = 4) { ... }
if(ok = true) { ... }
Hier wird zugewiesen, statt verglichen!
Lösung:
Konstanten wenn möglich nach links: Boole'sche Variable direkt verwenden:
if(4 == x) { ... }
if(ok) { ... }

Intervallprüfung

Um zu prüfen, ob eine Zahl in einem gegebene Intervall (sagen wir zwischen 10 und 20) liegt, müssen zwei Vergleiche angestellt werden:

if(10 < x < 20) {...}
if(10 < x && x < 20) {...}

switch ohne break

Obschon ein switch wie eine Fallunterscheidung aussieht, ist es lediglich eine Einsprungtabelle! In diesem Sinne ist weder C noch Java eine geeignete Sprache, um nach den Vorgaben der strukturierten Programmierung vorzughen. Ein Sprung ist dabei nämlich nur erlaubt, wenn es sich um eine Subroutine, eine Schleife oder eine Selektion handelt. Betrachten wir dazu den folgenden Code:

switch(note) { case 1: print("sehr " ); case 2: print("schwach" ); break; case 3: print("un" ); case 4: print("genügend"); break; case 6: print("sehr " ); case 5: print("gut" ); }

Drei Lösungen:

1. Am besten ist es ganz auf die switch-Anweisung zu Gunsten des Polymorphismus zu verzichten.

2. Sobald auf ein break bewusst verzichtet wird, so soll dies mit Kommentar angegeben werden (z. B. //falls through).

switch(note) { case 1: print("sehr " ); // falls through case 2: print("schwach" ); break; case 3: print("un" ); // falls through case 4: print("genügend"); break; case 6: print("sehr " ); // falls through case 5: print("gut" ); }

3. Im Entwicklungstool (sofern vorhanden) eine Fehlermeldung bei "missing break" einschalten und komplett aufs "falls through" zu verzichten:

switch(note) { case 1: print("sehr schwach"); break; case 2: print("schwach" ); break; case 3: print("ungenügend" ); break; case 4: print("genügend" ); break; case 5: print("gut" ); break; case 6: print("sehr gut" ); break; }

Feldgrenzen (Array Bounds)

Häufig haben wir ein Feld (Array) gegeben (z. B. fld) und kennen davon die Anzahl der Elemente (z. B. anz = 20)

Nun kann in C-Sprachen der folgende Code nicht funktionieren:

i = 1; while(i <= anz) { anweisung(fld[i]); i = i + 1; }

Lösung: C-Sprachen indizieren immer ab Element Null (0). Mit anderen Worten: Das erste Element hat index Null, das zweite Element hat index 1 usw.

i = 0; while(i < anz) { anweisung(fld[i]); i = i + 1; }

Java Fallen

Obschon sich Java in den letzten Jahren sehr rasch entwickelt hat, so bleiben vor allem für Anfänger viele Fallen vorhanden. Diese zu korrigieren ist beinahe nicht mehr möglich, denn in der Zwischenzeit ist bereits zu viel Code vorhanden, der unter Umständen nicht mehr funktionieren würde, wenn diese Fehler korrigiert würden. Der Pfad der Technik ist bereits zu weit fortgeschritten.


Anweisungen außerhalb von Methoden

In Java müssen alle Anweisungen immer innerhalb von Methoden stehen (Ausnahmen sind sog. Initialisierer). Somit wird folgender Code nicht funktionieren:

public class Bad{ int e = einlesen("Eingabe"); int a = verdopple(e); System.out.println("Doppel: " + a); }

Tipp an Trainer: Zeige wo immer möglich - und vor allem den Programmieranfängern - keine Initialisierungen außerhalb von Methoden. Also bitte nicht so:

public class BadBottle{ double pi = 3.1415; String colour = getColour(); String default = "water"; public BadBottle() { } }

Doch besser so:

public class GoodBottle{ double pi ; String colour ; String default; public GoodBottle() { pi = 3.1415; colour = getColour(); default = "water"; } }

Java Strings sind speziell

Strings in Java verhalten sich nicht wie herkömmliche Objekte. Strings sind mit der virtuellen Maschine stark verknüpft. Es geht sogar so weit, dass Strings nicht veränderbar sind, auch wenn das der Name ihrer Methoden manchmal vermuten lässt. Wie folgt kann einem bestehenden String beispielsweise kein einziger Leerschlag entfernt werden:

String s = " hallo "; s.trim();

Doch so funktioniert es:

String s = " hallo "; String neuesS; neuesS = s.trim();

Strings immer mit equals() vergleichen

Betrachten Sie die folgenden String Definitionen:

String a, b, c, d; a = "Hallo"; b = "Hallo"; c = new String("Hallo"); d = c.intern();

Nun gilt, dass alle Vergleiche mit x.equals(y) immer wahr (true) sind. Hingegen sind nicht alle Vergleiche mit dem ==-Operator zufriedenstellend. So gelten die folgenden Beziehungen:

if(a == b) {System.out.println("a == b");} if(a == d) {System.out.println("a == d");} if(b == d) {System.out.println("b == d");}

aber:

if(b != c) {System.out.println("b != c");} if(c != d) {System.out.println("c != d");} if(a != c) {System.out.println("a != c");}

Der '+'-Operator ist hinterlistig

Der '+'-Operator (plus) zählt zwei Zahlen zusammen. Ist jedoch einer der beiden Operatoren ein String, so wird zunächst der andere auch in einen String verwandelt, bevor die beiden Strings miteinander einfach verkettet werden.

Daher ist es nicht verwunderlich, dass der folgende Ausdruck

'#' + 4 + 2 + " != " + '4' + 2
diesen String erzeugt:
41 != 42

Integers sind böse!

Es gibt kaum eine andere Programmiersprache mit dem folgenden Fehler! Warum sollte denn 127 = 127 sein, aber 128 ≠ 128 ?

Integer a127 = 127; Integer b127 = 127; Integer a128 = 128; Integer b128 = 128; if(a127 == b127) { System.out.println("127 = 127");} if(a128 != b128) { System.out.println("128 ≠ 128");}

Daher: Java Integers (Ausnahme primitive Typen int,... ) immer mit equals() vergleichen.

if(a127.equals(b127)) { System.out.println("127 equals 127");} if(a128.equals(b128)) { System.out.println("128 equals 128");}

Referenzen sind keine Objekte

Java Referenzvariable zeigen auf Objekte. Wird eine Referenz kopiert, so wird nicht ein neues Objekt angelegt: Die Kopie zeigt auf das selbe Objekt:

Person max, anna; max = new Person(); max.name = "Maximilian"; System.out.println(max.name); // ==> "Max" ! anna = max ; anna.name = "Anna" ; System.out.println(max.name); // ==> "Anna" !

Um ein Objekt zu kopieren müssen alle Attributwerte auch kopiert werden (clone):

anna = max.clone();

Java-File ist kein «File»

Obschon sich Java Mühe gab, sinnvolle Namen zu vergeben, passierte es hin und wieder dennoch, dass die Entwickler gehörig daneben griffen, wie folgendes Beispiel zeigt.

Bei der Klasse java.io.File handelt es sich nicht um eine Datei (engl. File). Es handelt sich vielmehr um einen möglichen Eintrag im File-System. Genaugenommen sind hier Methoden zum Umgang mit Dateinamen, Verzeichnsnamen und generell dem File-Handling anzutreffen. Ein File kann damit aber weder geöffnet werden, noch können Inhalte in einem File verändert oder gelesen werden.

Das Problem schien rasch erkannt und es wurde innerhalb der Klasse File eine Methode boolean isFile() eingeführt, welche vom File prüfen soll, ob es sich um ein File handelt. Dies machte das Problem aber nur noch schlimmer: Um welche Art von File soll sich das File denn handeln? Um ein Java-File oder um ein File im üblichen Wortgebrauch?

Besser gewesen wären Namen wie "FileHandling", "DirectoryEntry", ...

JavaScript Fallen

Boolean ist nicht bool

Obschon es eine Klasse Boolean gibt, verhalten sich deren Objekte im Boole'schen Kontext leider nicht korrekt:

if(new Boolean(false)) { window.alert("new Boolean(false)"); // ← wird ausgeführt! }

Dieser Fehler ist kein generelles Programmierer Anfängerproblem. Aber JavaScript-Anfänger sollten unbedingt vor den ersten größeren Programmen etwas mit ==, ===, 0,null,undefined und NaN üben.


Strings und Zahlen

Nutzereingaben in das Programm sind meist Zeichenketten (Strings), auch wenn diese oft Zahlen bedeuten sollten.

var a = "4"; var b = 5; a + b → "45" !

Abhilfe schafft man, indem man die Strings zunächst in Zahlen verwandelt. Dazu bieten sich die folgenden Methoden an:

a = +a; a = a - 0; a = a * 1;

Alle drei Verfahren müssen zunächst die Zeichenkette in eine Zahl verwandeln und rechnen danach korrekt.

Es ist auch ratsam nicht die Methode parseInt() zu verwenden, denn diese kann Nutzereingaben auch falsch verstehen, wohingegen bei obigem Vorgehen NaN (not a number) resultiert:

var a = "2,5"; // Benutzereingabe a = parseInt(a); // → 2, statt 2.5 oder Fehler

Globale Variable

In JavaScript sind Variable standardmäßig global. Es ist gerade in größeren Applikationen von Vorteil, jede Variable vor dem ersten Gebrauch zu deklarieren.
Sei dies als Globale Variable:

var myGlobalVar;

Oder als lokale Variable:

function myFunction() { var myLocalVar; ... }

Generell gilt: Je weniger globale Variable verwendet werden, umso geringer die Gefahr, Namen mehrfach zu vergeben.

Nie sollte man jedoch die Deklaration mittels var vergessen. Variablen werden dadurch sofort global!

function myFunction() { // ohne Deklaration, ist dies eine globale Variable: myVar = ...; ... }

Anweisungen immer mit Semikolon (;) beenden

Auch wenn JavaScript dies nicht vorschreibt, so sollten Anweisungen dennoch stets mit einem Strichpunkt (= Semikolon ';') beendet werden. Dies hat zwar den Nachteil, dass man sich evtl. angewöhnt auch dort einen Strichpunkt zu setzen, wo dies nicht gewünscht ist:

while(bed()) ; { doSomething(); }

Der klare Vorteil hingegen kommt dann zur Geltung, wenn wir mehrere Anweisungen auf eine Zeile bringen wollen, oder wenn wir Anweisungen benötigen, die über mehrere Zeilen hinweggehen.


Browser Kompatibilität

Obschon es bereits mehrere verbindliche Standards gibt, wie sich gewisse JavaScript Funktionen verhalten sollten, so gibt es dennoch immer noch Abweichungen im Verhalten verschiedener Browser.
Es lohnt sich, die JavaScript-Applikationen auf den gängigsten Webbrowsern zu testen.


Sicherheit

Javascript sollte nie herhalten, die Sicherheit von Webapplikationen zu verbessern. Da Javascript im Browser läuft, hat der Server keine Kontrolle darüber, ob ein Skript korrekt abläuft, oder ob ein Programmierer das Skript auf der Clientseite sogar modifiziert

Javascript kann aber bei Formularen sehr gut genommen werden, um dem Benutzer die Eingabe zu erleichtern. So kann bereits im Browser geprüft werden, ob eine E-Mailadresse syntaktisch korrekt ist oder z. B. ein Kalenderdatum kann via Auswahlfenster eingegeben werden.

Bedenken Sie aber immer, dass ca. 1.5% der User ohne JavaScript surft. Mit ausgeschaltetem JavaScript sollte ein Formular immer auch noch bedient werden können. (Dies gilt natürlich nicht für Browser-Spiele oder sonstige reine Browser-Applikationen.)


Danke für die Rückmeldungen

Bitte Feedbacks direkt an mich (Philipp): Kontaktformular.


© Philipp Gressly Freimann 2012-2017


© (2006-2017) Philipp Gressly Freimann