Coding-Meta-Guideline:
Guideline zum Entwurf von Coding Guidelines

Coding Guidelines und Verantwortung der Entwickler

An organization that treats its programmers as morons will soon have programmers that are willing and able to act like morons only.

Bjarne Stroustrup, The C++ Programming Language (Second Edition)

Entwickler von der Verantwortung für bestimmte Implementierungsentscheidungen zu entbinden kann nicht sinnvollerweise das Ziel beim Entwurf von Coding Guidelines sein. Dennoch habe ich gelegentlich Coding Guidelines vorgefunden, deren Formulierungen diese Interpretation nahelegten. Umgekehrt sind mir Entwickler begegnet, die ein solches Verständnis und teilweise sogar diese Erwartung an Coding Guidelines hatten.

Gute Qualität und hohe Effizienz lassen sich nicht in einem Umfeld erreichen, in dem Entwickler bewusst oder unbewusst nach der Maxime arbeiten "wenn ich den Regeln folge, kann man mir nichts vorwerfen, denn ich habe dann alles richtig gemacht". Die Existenz derartiger Auffassungen ist allerdings eine Frage der Unternehmenskultur, und damit nicht durch Handlungsanweisungen zu beeinflussen.

Um zumindest zu vermeiden, dass die Coding Guideline Dokumente selbst zu Fehlinterpretationen verleiten, habe ich folgendes Vorgehen gewählt: Den Dokumenten eine Präambel in der Art des unten angegebenen Beispiels vorangestellt, und es wurde darauf geachtet, dass der weitere Inhalt der Dokumente im gleichen Sinne formuliert wurde:

Diese Guidelines sollen der Steigerung der Qualität und der Effizienz dienen. Sie entbinden nicht von der Verantwortung für die bei der Entwicklung getroffenen Entscheidungen. Jeder Entwickler ist sowohl für die Konsequenzen verantwortlich, die aus der Nichtbeachtung dieser Guidelines entstehen, als auch für die Konsequenzen aus der gedankenlosen Befolgung dieser Guidelines.

Die Zweckdienlichkeit von Regeln ist selten offensichtlich

Werden bei der Softwareentwicklung Fehler gemacht, so entsteht der Wunsch, ähnliche Fehler zukünftig durch geeignete Regeln zu unterbinden. Die Schwierigkeit der Formulierung geeigneter Regeln wird dabei nicht selten unterschätzt: Wer nur das Ziel "diese Art Fehler darf zukünftig nicht wieder auftreten" im Blick hat, tendiert dazu, ungeeignete Regeln zu formulieren.

Beispiel für eine ungeeignete Regel

Der Zugriff auf nicht-initialisierte Variable stellt in Programmiersprachen wie C ein typisches Fehlermuster dar:

int incorrectValueReturner(void) {
  int v;
  return v; /* access to uninitialised variable "v" */
}

Die folgende Regel erscheint auf den ersten Blick angemessen, um das Problem grundsätzlich und im Wesentlichen nachteilsfrei zu lösen: "Jeder Variable muss bei der Definition ein Wert zugewiesen werden". Das obige Stück C-Code könnte dann folgendermaßen aussehen:

int correctValueReturner(void) {
  int v = 0; /* v is initialised from the start */
  return v;
}

Wenn die Regel konsequent angewendet wird, erhält jede Variable im Moment ihrer Entstehung einen Wert zugewiesen. Folglich kommt es nie wieder zu der Situation, dass eine Variable zum Zeitpunkt der Verwendung noch keinen Wert hat. Bei der Diskussion über die Einführung einer solchen Regel wird allenfalls noch auf den folgenden Aspekt hingewiesen: Dass der Wert, der einer Variablen bei der Definition zugewiesen wird, unter Umständen später auf allen Pfaden durch den Code überschrieben wird, und dass die Regel daher zu einem (marginal) höheren Laufzeitbedarf und einer (marginal) erhöhten Codegröße führen kann.

Tatsächlich ist diese Regel aber aus anderen Gründen fragwürdig, sogar schädlich. Das Fehlermuster, dass eine Variable auf einem Ausführungspfad nicht initialisiert ist, wird durch die Regel in ein anderes Fehlermuster transformiert: Dass eine Variable auf einem Ausführungspfad nicht den gewünschten Wert enthält.

int incorrectAddOrSubtract42(int value, int has42ToBeAdded) {
  int result;
  if (has42ToBeAdded) {
    result = value + 42;
  } else {
    /* Oops, forgot to calculate result here */
  }
  return result;
}

Hier wurde vergessen, der Variable auf dem else-Zweig den gewünschten Wert "value - 42" zuzuweisen. Wird im späteren Ablauf tatsächlich einmal der else-Zweig durchlaufen, wird bei der return-Anweisung auf die Variable result zugegegriffen, die in diesem Fall nicht initialisiert ist. Wäre der Code von vornherein unter Anwendung der Beispielregel entwickelt worden, hätte die Funktion folgendermaßen ausgesehen:

int incorrectWorseAddOrSubtract42(int value, int has42ToBeAdded) {
  int result = 0; /* initialisation demanded by coding rule */
  if (has42ToBeAdded) {
    result = value + 42;
  } else {
    /* Oops, forgot to calculate result here */
  }
  return result;
}

Auch hier wird beim Durchlaufen des else-Zweigs ein fehlerhaftes Ergebnis zurückgeliefert. Der Code ist folglich inkorrekt, unabhängig von der Anwendung der Coding Rule. Aber die Anwendung der Coding Rule hat die Situation sogar verschlechtert, denn es ist jetzt schwerer als vorher, den Fehler frühzeitig zu entdecken:

Der Zugriff auf nicht initialisierte Variablen wird von heutigen C-Compilern als Warnung ausgegeben. Und selbst wenn der verwendete Compiler diese Fähigkeit nicht besitzt, können Werkzeuge zur statischen Codeanalyse wie splint, pclint, QA-C etc. herangezogen werden. Alle diese Werkzeuge hätten für die urspüngliche C-Funktion eine Meldung ausgegeben, die darauf hingewiesen hätte, dass die Variable result nicht auf allen Verwendungspfaden initialisiert ist. Ungünstigerweise ist die Variable result in der zweiten Version der C-Funktion auf allen Pfaden initialisiert. Es wird also keine Warnung geben, der Fehler in der Funktion kann auf diese Weise nicht mehr erkannt werden.

Das Beispiel soll demonstrieren, wie behutsam bei der Festlegung von Codierrichtlinien vorgegangen werden muss. Auch scheinbar "offensichtlich" sinnvolle Regeln sollten gründlich durchdacht werden.