Zuverlässige Programmierung nach Sprache - Noob Review. Teil 1

Wieder einmal habe ich zwei Tage gebraucht, um nur vierhundert Zeilen Systembibliothekscode zu schreiben und zu debuggen. Es entstand die Idee - „als ob es gut wäre, wenn die Programme weniger schmerzhaft geschrieben würden“.

Und zuallererst, da das Debuggen viel länger dauert als das Schreiben von Code, müssen Sie beim Schreiben vor dem Narren (einschließlich sich selbst) geschützt werden. Und ich möchte es von der verwendeten Programmiersprache (YP) erhalten.

Natürlich müssen wir ein neues, das beste YaP erfinden!
Nein, zuerst werden wir versuchen, unsere Wünsche auszudrücken und uns anzusehen, was wir bereits erfunden haben.

Also, was ich erhalten möchte:

  • Widerstand gegen menschliche Fehler, Beseitigung von Unklarheiten bei der Zusammenstellung
  • Eingangswiderstand
  • Widerstand gegen Programm- oder Datenschäden - Medienfehler, Hacking
  • Gleichzeitig hat jeder mehr oder weniger tolerierbare Syntax und Funktionalität

Anwendungsbereiche sind Maschinen, Transport, industrielle Steuerungssysteme, IoT, eingebettet einschließlich Telefone.

Es ist für das Web kaum notwendig, es basiert (vorerst) auf dem Prinzip „Beenden und neu starten“ (Feuer und Vergessen).

Schnell genug können Sie zu dem Schluss kommen, dass die Sprache kompiliert werden muss (zumindest Pi-kompiliert), damit alle Überprüfungen in der Kompilierungsphase ohne VS maximal durchgeführt werden (Versus, im Folgenden der negative Widerspruch). „Oh, dieses Objekt hat keine solche Eigenschaft.“ zur Laufzeit. Selbst die Skripterstellung von Schnittstellenbeschreibungen macht es bereits obligatorisch, solche Skripte vollständig mit Tests abzudecken.

Persönlich möchte ich nicht mit meinen Ressourcen (einschließlich Geld für schnellere, teurere Hardware) für die Interpretation bezahlen, daher ist es ratsam, eine minimale JIT-Kompilierung zu haben.

Also die Anforderungen.

Menschliche Fehlertoleranz


Nachdem ich die Talmuds von PVS-Studio sorgfältig durchgesehen hatte, stellte ich fest, dass die häufigsten Fehler Tippfehler und unvollständiges Kopieren und Einfügen sind.

Ich werde auch einige Vorfälle aus meiner Erfahrung hinzufügen, die ich in verschiedenen Literaturstellen als negative Beispiele getroffen habe. Zusätzlich wurden die MISRA C-Regeln im Speicher aktualisiert.

Nachdem ich ein wenig darüber nachgedacht hatte, kam ich zu dem Schluss, dass die ex-facto angewendeten Linters unter einem „Überlebensfehler“ leiden, da in alten Projekten bereits schwerwiegende Fehler behoben wurden.

a) Entfernen Sie ähnliche Namen

- Die Sichtbarkeit von Variablen und Funktionen muss streng überprüft werden. Nach dem Versiegeln können Sie anstelle des gewünschten einen Bezeichner aus einem allgemeineren Bereich verwenden
- Verwenden Sie Namen, bei denen die Groß- und Kleinschreibung nicht berücksichtigt wird. (VS) "Rufen wir die Funktion als Variable auf, nur in Camelcase" und vergleichen Sie sie dann mit etwas - in C ist dies möglich (wir erhalten die Adresse der Funktion, die eine ziemliche Zahl ist).
- Namen mit einem Unterschied von 1 Buchstaben sollten eine Warnung auslösen (möglicherweise können Sie sie in der IDE hervorheben), aber einen sehr häufigen Fehler beim Kopieren und Einfügen .x, .y, .w, .h.
- Wir erlauben nicht, verschiedene Entitäten auf dieselbe Weise zu benennen. - Wenn es eine Konstante mit diesem Namen gibt, sollte es keine Variable mit demselben Namen oder Typnamen geben
- Es ist sehr wünschenswert, die Benennung für alle Projektmodule zu überprüfen. - Es ist leicht zu verwechseln, insbesondere wenn verschiedene Personen unterschiedliche Module schreiben

b) Einmal erwähnt - es muss modular und vorzugsweise hierarchisch sein - ist ein VS-Projekt mit 12.000 Dateien in einem Verzeichnis eine Suchhölle.
Für die Beschreibung von Datenaustauschstrukturen zwischen verschiedenen Teilen (Modulen, Programmen) eines Projekts ist dennoch Modularität erforderlich. VS Ich habe einen Fehler aufgrund unterschiedlicher Ausrichtung der Nummern in der Austauschstruktur im Empfänger und Sender festgestellt.

- Um die Möglichkeit einer doppelten Verknüpfung (Layout) auszuschließen.

c) Mehrdeutigkeiten
- Es muss eine bestimmte Reihenfolge von Funktionsaufrufen geben. Wenn Sie X = funcA () + fB () oder Fn (funcA (), fB (), callC ()) schreiben, müssen Sie verstehen, dass die Person erwartet, die Berechnungen in der schriftlichen Reihenfolge (VS) und nicht als vom Optimierer ausgedacht zu erhalten.
- Schließen Sie ähnliche Operatoren aus. Und nicht wie in C: + ++, <<<, | ||, & &&, = ==
- Es ist ratsam, einige verständliche Bediener mit einer offensichtlichen Priorität zu haben. Hallo vom ternären Operator.
- Das Überschreiben von Bedienern ist ziemlich schädlich. Sie schreiben i: = 2, aber (VS) bewirkt tatsächlich eine implizite Erstellung eines Objekts, für das nicht genügend Speicher vorhanden ist, und die Festplatte stürzt beim Tauschen ab und Ihr Satellit stürzt zum Mars ab :-(

Aus persönlicher Erfahrung habe ich einen Absturz in der Zeile ConnectionString = "DSN" beobachtet. Es stellte sich heraus, dass ein Setter die Datenbank geöffnet hat (und der Server im Netzwerk nicht sichtbar war).

- Wir müssen alle Variablen mit Standardwerten initialisieren.
- Außerdem bewahrt der OOP-Ansatz die Neuzuweisung aller Felder im Objekt in einer neuen hundertsten Funktion vor Vergesslichkeit.
- Das Typsystem muss sicher sein - Sie müssen die Abmessungen der zugewiesenen Objekte kontrollieren - Schutz vor Speicherüberschreibung, arithmetischem Überlauf vom Typ 65535 + 1, Genauigkeits- und Signifikanzverlust beim Casting, ausgenommen unvergleichliche Vergleiche - da Ganzzahl 2 im allgemeinen Fall nicht gleich 2,0 ist.

Und selbst eine typische Division durch 0 kann eine sehr eindeutige + INF ergeben. Anstelle eines Fehlers ist eine genaue Definition des Ergebnisses erforderlich.

Eingangswiderstand


- Das Programm sollte mit allen Eingabedaten und vorzugsweise ungefähr zur gleichen Zeit arbeiten. (VS) Hallo an Android mit einer Reaktion auf die Mobilteil-Taste von 0,2 bis 5 Sekunden; Es ist gut, dass nicht Android das Automobil-ABS antreibt.

Beispielsweise sollte ein Programm sowohl 1 KB Daten als auch 1 TB korrekt verarbeiten, ohne die Ressourcen des Systems erschöpft zu haben.

- Es ist sehr wünschenswert, eine zuverlässige und eindeutige Fehlerbehandlung in RAII zu haben, die nicht zu Nebenwirkungen führt (z. B. Ressourcenlecks). (VS) Eine sehr lustige Sache ist das Auslaufen von Griffen, das sich nach vielen Monaten manifestieren kann.
- Es wäre schön, sich vor Stapelüberlauf zu schützen - deaktivieren Sie die Rekursion.
- Das Problem der Überschreitung des verfügbaren Volumens mit dem erforderlichen Speicher, unkontrolliertes Wachstum des Verbrauchs aufgrund von Fragmentierung während der dynamischen Zuweisung / Freigabe. Wenn die Sprache eine Heap-abhängige Laufzeit hat, ist die Sache höchstwahrscheinlich schlecht - Hallo STL und Phobos. (VS) Es gab eine Geschichte mit der alten C-Zeit von Microsoft, die unzureichend Speicher an das System zurückgab, aufgrund dessen msbackup auf großen Volumes (für diese Zeit) abstürzte.
- Wir brauchen gute und sichere Arbeit mit Strings - nicht durch Ressourcen begrenzt. Es ist stark implementierungsabhängig (unveränderlich, COW, R / W-Arrays)
- Überschreiten der Systemreaktionszeit, unabhängig vom Programmierer. Dies ist ein typisches Problem mit dem Garbage Collector. Obwohl sie vor einigen Programmierfehlern sparen - andere bringen - schlecht diagnostiziert.
- In einer bestimmten Klasse von Aufgaben stellt sich heraus, dass Sie überhaupt auf dynamischen Speicher verzichten können oder ihn beim Start einmal auswählen müssen.
- Um den Exit über die Grenzen des Arrays hinaus zu steuern, ist es durchaus akzeptabel, eine Warnung zur Laufzeit zu schreiben und diese zu ignorieren. Sehr oft sind dies unkritische Fehler.
- Schutz vor Zugriffen auf einen Speicherabschnitt, der nicht vom Programm initialisiert wurde, einschließlich des Nullbereichs und des Adressraums einer anderen Person.
- Dolmetscher, JIT - zusätzliche Schichten verringern die Zuverlässigkeit, es gibt Probleme mit der Speicherbereinigung (ein sehr komplexes Subsystem - es wird seine Fehler machen) und mit einer garantierten Reaktionszeit. Wir schließen es aus, aber es gibt im Grunde eine Java Micro Edition (wo so viel von Java abgeschnitten ist, dass nur ich übrig bleibe, gab es einen interessanten Artikel von dernasherbrezon (sorry, shot) und das .NET Micro Framework mit C #.

In Anbetracht dessen sind diese Optionen jedoch verschwunden:

  • .NET Micro erwies sich als gewöhnlicher Interpreter (durchgestrichen durch Geschwindigkeit).
  • Java Micro ist nur für eingebettete Anwendungen geeignet, da es von der API zu kastriert wird und Sie für die Entwicklung zu mindestens SE Embedded wechseln müssen, das bereits geschlossen wurde, oder zu normalem Java, das als Reaktion zu monströs und unvorhersehbar ist.
    Es gibt jedoch immer noch Optionen , und obwohl dies für eine funktionsfähige Grundlage nicht wie ein Leerzeichen aussieht, kann es mit anderen Sprachen verglichen werden, sogar veraltet oder mit bestimmten Nachteilen.


- Widerstand gegen Multithread-Arbeit - Schutz privater Daten eines Streams und Mechanismen für einen garantierten Austausch zwischen Streams. Ein Programm mit 200 Threads funktioniert möglicherweise überhaupt nicht wie in zwei.
- Vertragsprogrammierung und integrierte Komponententests helfen auch dabei, ruhig zu schlafen.

Widerstand gegen Programm- oder Datenschäden - Medienfehler, Hacking


- Das Programm muss vollständig in den Speicher geladen sein - ohne Module zu laden, insbesondere aus der Ferne.
- Speicher bei Freigabe löschen (und nicht nur Zuordnung)
- Überwachung des Stapelüberlaufs, variabler Bereiche, insbesondere von Zeichenfolgen.
- Nach einem Fehler neu starten.

Übrigens, der Ansatz, wenn die Laufzeit ihre eigene Protokollierung hat und nicht nur zeigt, dass der Nordfuchs und Stackrace, ich bin sehr beeindruckt.

Sprachen - und Konformitätstabelle


Auf den ersten Blick nehmen wir zur Analyse speziell entwickelte sichere PLs:

  1. Aktives Oberon
  2. Ada
  3. BetterC (dlang-Teilmenge)
  4. IEC 61131-3 ST
  5. Safe-c

Und gehen Sie sie in Bezug auf die oben genannten Kriterien durch.

Aber dies ist bereits der Band für den Folgeartikel, wenn das Karma es erlaubt.

Mit den oben genannten Faktoren, die in der Tabelle hervorgehoben sind, wird vielleicht etwas anderes Sinnvolles aus den Kommentaren entnommen.

Was andere interessante Sprachen betrifft - C ++, Crystal, Go, Delphi, Nim, Rot, Rost, Zig (nach Geschmack hinzufügen), dann überlasse ich es denen, die die Korrespondenztabelle ausfüllen möchten.

Haftungsausschluss:

  • Wenn ein Programm, beispielsweise in Python, 30 MB verbraucht und die Reaktionsanforderungen Sekunden betragen und der Mikrocomputer über 600 MB freien Speicher und 600 MHz Prozent verfügt, warum dann nicht? Nur ein solches Programm wird mit einiger Wahrscheinlichkeit (wenn auch 96%) zuverlässig sein, nicht mehr.
  • Außerdem sollte die Sprache versuchen, für den Programmierer bequem zu sein - sonst wird sie niemand verwenden. Solche Artikel "Ich habe mir die ideale Programmiersprache ausgedacht, damit ich nur bequem schreiben konnte" sind auch in Habré keine Seltenheit, aber es geht um etwas anderes.

Source: https://habr.com/ru/post/de437830/


All Articles