36 Punkte von GN⁺ 2025-02-17 | 6 Kommentare | Auf WhatsApp teilen
  • Eine kritische Analyse der 10 Regeln für die Softwareentwicklung von NASA
    • Diese Regeln sind für extrem kritische eingebettete Systeme gedacht, z. B. Raumfahrzeug-Software
    • Es muss jedoch diskutiert werden, ob diese Regeln auch in anderen Entwicklungsumgebungen angemessen sind oder sich auf andere Sprachen als C anwenden lassen

1. Einen einfachen Kontrollfluss beibehalten (goto, setjmp/longjmp, Rekursion verboten)

  • Diese Regel verbietet Ausnahmebehandlung (setjmp()/longjmp()) und Rekursion.
  • Rekursion ist nicht zwangsläufig ineffizient. Mit geeigneten Methoden kann auch bei Rekursion die Terminierung garantiert werden.
  • Wenn man Rekursion zwanghaft in Schleifen umwandelt, besteht das Risiko, dass schwer wartbarer Code entsteht.

Kritik:

  • Die Garantie der Terminierung ist wichtig, aber extreme Einschränkungen können Lesbarkeit und Wartbarkeit beeinträchtigen.
  • Ein pauschales Verbot von Rekursion kann leicht unnötige Komplexität verursachen.

2. Alle Schleifen müssen eine klare obere Grenze haben

  • Der Compiler sollte die Anzahl der Schleifendurchläufe statisch analysieren können.
  • Doch allein das Setzen einer Obergrenze macht es schwer, die tatsächliche Laufzeit zu garantieren.
  • Eine Begrenzung der Rekursionstiefe kann genauso sicher sein wie eine Obergrenze für Schleifen.

Kritik:

  • Eine bloße Obergrenze garantiert noch keine realistisch ausführbare Laufzeit.
  • Selbst mit einer Obergrenze ist ein zu großer Wert praktisch kaum von einer Endlosschleife zu unterscheiden.

3. Keine dynamische Speicherallokation nach der Initialisierung

  • In eingebetteten Systemen ist Speicher begrenzt; das Ziel ist, Abstürze durch Speichermangel zu verhindern.
  • Vorhersagbare dynamische Allokation kann jedoch sicherer sein als manuelles Speichermanagement.
  • Zum Beispiel kann man mit einem Echtzeit-Garbage-Collector (RTGC) auch dynamische Allokation vorhersagbar machen.

Kritik:

  • Statt dynamische Allokation grundsätzlich zu verbieten, kann es besser sein, Speichernutzungsmuster zu analysieren und so Sicherheit zu gewährleisten.
  • Mit modernen statischen Analysetools (z. B. SPlint) lassen sich Fehler im Zusammenhang mit dynamischem Speicher vorab erkennen.

4. Die Größe einer Funktion auf höchstens eine A4-Seite begrenzen (ca. 60 Zeilen)

  • Die Begründung ist, dass zu lange Funktionen die Lesbarkeit verschlechtern.
  • In modernen Entwicklungsumgebungen gibt es jedoch Code-Folding, sodass die Größe logischer Einheiten wichtiger ist als die reine Funktionslänge.

Kritik:

  • Maßgeblich sollte eher die logische Komplexität sein als die physische Größe (Zeilenzahl).
  • Das Zerlegen von Funktionen in kleine Teile darf kein Selbstzweck werden → es kann die Wartung sogar erschweren.

5. Pro Funktion mindestens zwei assert-Anweisungen verwenden

  • assert ist für Debugging und Dokumentation sehr nützlich.
  • Eine starre Mindestanzahl kann jedoch ineffizient sein.

Kritik:

  • Wichtiger als die Anzahl der assert-Anweisungen ist, klar festzulegen, an welchen Stellen Daten validiert werden müssen.
  • Es ist praxisnäher, alle Argumente und externen Eingaben zu prüfen.

6. Den Gültigkeitsbereich von Datenobjekten minimieren

  • Ein gutes Prinzip, das die Verwendung lokaler Variablen empfiehlt.
  • Allerdings sollte man nicht nur den Gültigkeitsbereich von Funktionen, sondern auch von Typen minimieren.

Kritik:

  • In Ada, Pascal, JavaScript und funktionalen Sprachen können auch Typen und Funktionen lokal deklariert werden → ein besserer Ansatz als die NASA-Regeln.

7. Rückgabewerte von Funktionen und Gültigkeit von Parametern müssen geprüft werden

  • Rückgabewerte müssen unbedingt überprüft werden.
  • Allerdings ist es in der Praxis schwierig, wirklich alle Fälle abzudecken.

Kritik:

  • Um Laufzeitfehler zu vermeiden, sind möglichst viele Prüfungen nötig, aber praktische Grenzen müssen berücksichtigt werden.
  • Gerade in C ist die Prüfung von Rückgabewerten wichtig, während moderne Sprachen (Java, Rust usw.) durch ihr Typsystem sicherere Wege bieten.

8. Nutzung des Präprozessors einschränken (nur Header-Einbindungen und einfache Makros erlaubt)

  • Komplexe Makros, Token-Verkettung und variadische Makros (...) sind verboten.
  • Variadische Makros können jedoch als Debugging-Werkzeug nützlich sein.

Kritik:

  • Sinnvoller als die Nutzung des Präprozessors zu beschränken ist es, gut lesbare Makro-Stile zu empfehlen.
  • Wenn bedingte Kompilierung wie #ifdef verhindert wird, kann das Schreiben plattformunabhängigen Codes erschwert werden.

9. Einsatz von Zeigern einschränken (keine Doppelzeiger, keine Funktionszeiger)

  • Das Verbot von Funktionszeigern zielt auf hohe Stabilität ab.
  • Funktionszeiger sind jedoch für Callbacks, das Strategy Pattern und Gerätetreiber unverzichtbar.

Kritik:

  • Wenn die Funktionsauswahl ohne Funktionszeiger per switch-case erzwungen wird, leidet die Lesbarkeit des Codes und die Wartung wird schwieriger.
  • Bei der Entwicklung von Betriebssystemen, Netzwerk-Stacks und Treibern sind Funktionszeiger unverzichtbar.
  • Bessere Lösungen als Pointer-Beschränkungen sind Verfahren, die eine sichere Nutzung von Zeigern gewährleisten (z. B. Smart Pointer in C++, Rust usw.).

10. Für den gesamten Code Compiler-Warnungen maximal aktivieren und statische Analysetools verwenden

  • Diese Regel ist eine sehr gute Empfehlung.
  • Compiler-Warnungen beseitigen + statische Analysetools einsetzen = höhere Stabilität.

Kritik:

  • Andere NASA-Regeln (z. B. Pointer-Verbot, Begrenzung der Funktionsgröße) sollen teils einfach die Grenzen statischer Analysetools ausgleichen.
  • Da moderne statische Analysetools jedoch stark fortgeschritten sind, ist es nützlicher, ausgefeiltere Analysetechniken zu nutzen als übermäßige Einschränkungen zu verhängen.

6 Kommentare

 
regentag 2025-02-18

Wenn man alles aus der Perspektive von Echtzeit- und Embedded-Systemen betrachtet, sind das Regeln, die man versteht und die notwendig sind. Könnten statische Analysewerkzeuge diese Regeln ersetzen?

Könnte man zum Beispiel, wenn dynamische Allokation erlaubt ist, garantieren, dass die Speicherallokation in allen Nutzungsszenarien erfolgreich ist?

Wenn man Softwaretests lernt, gibt es Leitsätze, die immer am ersten Tag in der ersten Stunde erwähnt werden. Einer davon ist: „Perfektes Testen ist unmöglich.“

 
smboy86 2025-02-18

Daran, dass mir eher das Gegenteil ins Auge fällt,
merke ich wohl, dass diese Regeln nicht zu mir passen, haha

 
rtyu1120 2025-02-17

Es scheint, dass nicht nur bei der NASA, sondern auch in Branchen wie Luftfahrt und Automobil, in denen Menschenleben direkt auf dem Spiel stehen, oft ähnliche Coding-Regeln angewendet werden, haha.

 
ssssut 2025-02-17

https://github.com/kubernetes/kubernetes/…
Beim Kubernetes-Sourcecode musste ich an den Codeblock im „Space-Shuttle-Stil“ denken, von dem es heißt, er sei nach der Methode zum Schreiben des Sourcecodes für die NASA-Space-Shuttle-Anwendungen verfasst worden.
Zugehöriger HN-Thread: https://news.ycombinator.com/item?id=18772873

 
GN⁺ 2025-02-17
Hacker-News-Kommentar
  • Liest man den Originaltext, wird der Zweck jedes einzelnen Punkts erläutert
  • Der Originaltext richtet sich hauptsächlich an C und versucht, die Prüfung der Zuverlässigkeit wichtiger in C geschriebener Anwendungen gründlicher zu optimieren
  • Der Originalautor versteht ganz klar, was er tut, und beschreibt mehrere Methoden zur Verifikation von C-Code
  • Die Logik im Original ist durchweg vollkommen nachvollziehbar
    • Vermutlich, weil ich C auf kleinen Systemen gelernt habe
    • Ich habe C für Hardware in implantierbaren Medizinprodukten gelernt und im Labor ebenfalls ähnliche Richtlinien befolgt
  • Der letzte Absatz ist hervorragend
    • Die Regeln mögen sich anfangs streng anfühlen, aber man muss Fälle bedenken, in denen das Leben von der Korrektheit des Codes abhängen kann
    • Wie der Sicherheitsgurt im Auto mag es zunächst unbequem sein, aber mit der Zeit benutzt man ihn ganz selbstverständlich
  • Meine Kritik an diesen Regeln wäre eine ganz andere als die des OP
    • Einen Text, der setjmp/longjmp verteidigt, konnte ich von Anfang an kaum ernst nehmen
    • Dieses Muster ist für jeden, der sich damit beschäftigt hat, offensichtlich problematisch
    • Der Text behauptet, setjmp/longjmp sei Exception Handling
    • Er behauptet, Exception Handling sei gut
    • Mit der zweiten Prämisse gibt es ein schwerwiegendes Problem
  • Gemeint ist, für Schleifen eine maximale Anzahl von Iterationen festzulegen
    • 10^90 ist albern und ohne Relevanz
    • Ab diesem Punkt habe ich den Text nicht weitergelesen
  • Wenn ich die Regeln kritisieren würde, dann würde ich mich auf folgende Punkte konzentrieren
    • Die Länge eines Funktionsrumpfs korreliert nicht mit der Einfachheit des Verständnisses; im Gegenteil könnte es sogar anders sein, als die Regel andeutet
    • 2 Assertions sind völlig willkürlich, und man sollte alles per Assertion absichern, was sich per Assertion absichern lässt
    • Wer Ada, Pascal (Delphi), JavaScript oder funktionale Sprachen verwendet, sollte Typen und Funktionen möglichst lokal deklarieren
  • Mein persönlicher Ansatz in JavaScript ist es, Funktionen nicht verschachtelt zu definieren, außer wenn ich Werte ausdrücklich capturen will
    • Das könnte an einem alten mentalen Modell liegen
    • Im Performance-Profiling wurde gezeigt, dass sie bei jedem Funktionsaufruf neu definiert werden
    • Ich glaube nicht, dass moderne JavaScript-Interpreter noch so arbeiten
    • Seit der Einführung von Arrow Functions dürfte es tiefgreifende Optimierungen gegeben haben
    • Alte Gewohnheiten verschwinden nicht leicht
    • Benannte Funktionen, die keine lokalen Variablen capturen, lasse ich im Datei-/Modul-Scope
  • Viele andere Anmerkungen sind interessant und sehr detailverliebt
    • So nach dem Motto: „Technisch korrekt ist die beste Form von Korrektheit“, was alte Ingenieure mögen
    • Ich finde den allgemeinen vorsichtigen Ton, den die NASA-Regeln vermitteln wollen, sehr gut und unterstütze das meiste davon
  • Im Kontext sind das eher vorgeschlagene Praktiken als „Regeln“
    • Formale „Regeln“ stehen in Dokumenten mit Namen wie „NPR“
    • Entwickler sind nicht verpflichtet, diese „Regeln“ zu befolgen oder zu ignorieren
  • GCC kann nach dem Kompilieren die Stack-Nutzung sowie Caller-Callee-Beziehungen liefern
    • setjmp() und longjmp() sind ein schlechter Weg, Exceptions zu behandeln
    • Cleanup-Code wird nicht ausgeführt
    • Folgt man dem Geist der Regeln, sollte es keine Ressourcen geben, die Cleanup benötigen
  • Die Hauptprobleme zeigen sich in jeder Anwendung anders
    • Was tun, wenn das Iterationslimit überschritten wird oder wenn die beim Start zugewiesenen festen Ressourcen nicht ausreichen?
  • Heutige Programmierer lesen Code auf dem Bildschirm, daher ist nicht klar, warum Papiergröße keine Rolle mehr spielen sollte
    • Es gab Wiederholungen zu Standardseiten und Schriftgrößen
    • Es geht nicht nur um die Grenzen des Papiers, sondern auch um die Grenzen des Menschen
  • Die Regel zur Rekursion soll eine statisch bekannte Obergrenze für den benötigten Stack-Speicher garantieren
    • Die Kritik, dass dies vom Compiler abhängt, ist richtig, aber es ist eine Voraussetzung, um eine Obergrenze für die Laufzeit herzuleiten
    • In sicherheitskritischen Systemen braucht man garantierte Antwortzeiten
  • Der Titel sollte deutlich machen, dass es sich um eine Kritik der Regeln handelt
  • Die Verwendung strenger Typisierung wird empfohlen
    • Strenge Typisierung für alle skalaren Typen
    • Keine Vermischung von imperialen und metrischen Einheiten
 
roxie 2025-02-21

> Der Titel muss darauf hinweisen, dass es sich um eine Kritik der Regeln handelt.

222