Was ist der beste Weg, um Ihr Design und Ihren Code vom ersten Tag an auf diese „unbekannten unbekannten“ Fehler vorzubereiten?

8

Ich frage mich nur, ob es irgendeine praktische Methode oder Technik oder sogar Tricks gibt, um diese "unbekannten unbekannten" Fehler zu vermeiden, insbesondere jene bösen und zufälligen, die oft in den letzten Minuten auftauchen, oder zumindest, um diese Dinge auf einem Mindestniveau zu halten. Ich frage dies, denn wenn Sie auf einer neuen Plattform arbeiten oder zum ersten Mal bestimmte neue Technologien verwenden, wie rechtfertigen Sie, dass Ihr Design und Ihr Code robust genug sind? Oder können diese Dinge nur durch Zeit und Fehler gelernt werden?

(Ich benutze C ++ für die meiste Zeit meiner Arbeit)

Vielen Dank!

CaptainJH
quelle

Antworten:

5

Vor fast zwanzig Jahren habe ich aus David Thielens ausgezeichnetem Buch "No Bugs: Bereitstellung von fehlerfreiem Code in C und C ++", das jetzt als kostenloses PDF verfügbar ist, viel Einblick in dieses Thema erhalten .

Er hat mir zwei großartige Ideen beigebracht ...

Bugs kommen nicht aus dem Nichts. Wir alle Programmierer setzen uns und schreiben sie mit unseren eigenen Fingern in unseren Code.

"Bug" bedeutet, dass eine externe Agentur beschlossen hat, Ihr Programm mit Bugs zu infizieren. Wenn Sie ein sauberes Leben führen und kleine Pelztiere am Fuße Ihres Computers opfern, verschwinden diese ... Dieses Konzept ist wichtig, weil es farbig ist Ihr Ansatz zum Debuggen Ihres Codes. Wenn Sie Fehler als "Fehler" betrachten, hoffen Sie, dass keine gefunden werden. (Sie hoffen, die gute Fee kam vorbei, besprengte Elfenstaub und die Käfer gingen.)

Bugs sollten nicht als Bugs bezeichnet werden, sondern als Massive Fuck-Ups [MFUs] ... MFUs existieren, weil Programme von Menschen geschrieben werden und Menschen Fehler machen ... Sie werden MFUs schreiben. Sie werden sich hinsetzen und mit völliger Bosheit der Voraussicht MFUs in Ihren Code einfügen. Denken Sie darüber nach - Sie wissen, dass Sie derjenige sind, der die Fehler dort hineinsteckt. Wenn Sie sich also zum Code setzen, werden Sie einige Fehler einfügen.

Da es das unausweichliche Schicksal aller Programmierer ist, Fehler zu schreiben, muss ich defensiv codieren, einschließlich Dinge, die aufspringen, schreien und rote Fahnen schwenken, wenn sie einen Fehler entdecken.

Nachdem sie in den frühen 90er Jahren geschrieben wurden, sind die Einzelheiten dazu in Thielens Buch ziemlich veraltet. Unter Linux und Mac OS X müssen Sie beispielsweise keinen eigenen Wrapper mehr für den neuen C ++ - Operator schreiben. Sie können dafür Valgrind verwenden.

Aber es gibt ein paar Dinge, die ich routinemäßig für C / C ++ / ObjC mache:

  1. Wenn ich es vernünftigerweise kann, aktivieren Sie die Option "Warnungen sind Fehler" des Compilers und beheben Sie sie alle. (Ich unterhalte ein Legacy-Projekt, bei dem das Beheben aller Probleme auf einmal Wochen dauern würde. Daher repariere ich alle paar Wochen eine Datei - und in einigen Jahren kann ich diese Option aktivieren.)
  2. Verwenden Sie ein statisches Code-Analyse-Tool wie Gimpels PC-Lint oder das sehr raffinierte, das jetzt in Apples Xcode integriert ist. Die Deckung ist noch besser, aber die Kosten sind für große Unternehmen, nicht nur für Sterbliche.
  3. Verwenden Sie dynamische Analysewerkzeuge wie Valgrind, um nach Speicherproblemen, Lecks usw. zu suchen.
  4. Wie Thielen sagt (und das Kapitel ist immer noch lesenswert): Assert The World . Natürlich wird niemand außer einem Idioten Ihre Funktion mit einem Null-Zeiger aufrufen - und das bedeutet, dass irgendwo jemand ein Idiot ist, der genau das tut. Vielleicht bist du es in drei Jahren sogar, wenn das, was du heute getan hast, neblig geworden ist. Fügen Sie einfach am Anfang der Funktion eine Zusicherung hinzu, um dieses Zeigerargument zu überprüfen. Die Eingabe dauert drei Sekunden und verschwindet in der ausführbaren Release-Datei.
  5. In C ++ ist RTTI dein Freund. Auch hier wird niemand außer einem Idioten Ihre Funktion mit einem Zeiger auf die falsche Art von Objekt aufrufen - was bedeutet, dass zwangsläufig ein Idiot dies tun wird - und die Kosten für die Verteidigung dagegen sind vernachlässigbar. In C-basiertem Code, der von GObject abgeleitet ist, können Sie dasselbe mit den defensiven dynamischen Cast-Makros tun.
  6. Automatisierte Unit- und Regressionstests sind jetzt ein wichtiger Bestandteil meines Repertoires. Bei einem Projekt sind sie ein integraler Bestandteil des Release-Build-Systems, und der Build wird erst abgeschlossen, wenn alle erfolgreich sind.
  7. Ein weiterer wichtiger Teil ist die Protokollierung von Code in den ausführbaren Debug- und Release-Dateien, der zur Laufzeit durch eine Umgebungsvariable aktiviert werden kann.
  8. Schreiben Sie Defensivtests, damit Programmierer, die ausführbare Debug-Dateien ausführen, diese nicht ignorieren können, wenn sie fehlschlagen. Laufzeitnachrichten an die Konsole können ignoriert werden. Das Programm, das mit einem Assert abstürzt, kann nicht ignoriert werden.
  9. Stellen Sie beim Entwerfen öffentliche APIs und private Implementierungen bereit, auf die externer Code nicht zugreifen kann. Auf diese Weise verlässt sich niemand auf eine magische innere Zustandsvariable oder so etwas, wenn Sie umgestalten müssen. In C ++ - Klassen bin ich ein großer Fan von geschützt und privat. Ich denke auch, dass Proxy-Klassen großartig sind, aber ich benutze sie nicht wirklich selbst.

Was Sie für eine neue Sprache oder Technologie tun, hängt natürlich von den Details ab. Aber wenn Sie einmal die Vorstellung in Ihr Herz geschlossen haben, dass Bugs massive Fuck-Ups sind, die Sie mit Ihren eigenen Fingern geschrieben haben, und Ihr Code ständig von einer Armee von Idioten angegriffen wird, mit Ihnen an der Spitze als General, bin ich sicher, dass Sie Ich werde geeignete Abwehrtechniken finden.

Bob Murphy
quelle
14

Nun, wenn Sie das wissen, dann schlüpfen sie in die Kategorie "bekannte unbekannte Fehler" (dh Sie wissen, dass etwas von "dieser" Natur auftreten wird). Eine beliebige Anzahl von Komponententests wird sie nicht fangen, sie sind nur für bekannte Fälle wirklich nützlich.

Die Art und Weise, wie wir damit umgehen, besteht darin, einen Fehlerprotokollierungsdienst für die laufende Anwendung bereitzustellen, bei Auftreten an die Basis zu melden und ihn beim Aufrufen zu behandeln. Ansonsten können Sie Jahre verbringen und trotzdem nichts abdecken ... irgendwann warten Sie nur noch ab, was in der realen Welt passiert, und entwickeln sich schnell weiter.

Design-Seite, Sie entwerfen für Wartbarkeit als einer der Schlüsselfaktoren.

  • Lernmuster, die funktionieren und zu vermeidende Muster. Je konsistenter und kohäsiver die Muster sind, desto sicherer können Sie sein, dass eine bestimmte Problemklasse auftritt oder nicht.
  • Machen Sie die Dinge offensichtlich. Dunkelheit führt zu Verwirrung, führt zu Fehlern.
  • Starke Namenskonventionen bis zum Ende. Wenn Sie die Dinge gut und konsequent benennen, gibt es eine Menge Vorteile, wenn Sie versuchen, Dinge zu ändern oder es ihnen zu erklären ... alles, was Factory heißt, wird X tun.
  • Buchstabiere die Dinge vollständig. Wir haben heutzutage Autocomplete verwendet keine Akronyme, wenn das vollständige Wort Verwirrung beseitigt.
  • Trennen Sie sich in Schichten oder Abstrationen ... so dass bestimmte Problemstile in einer bestimmten Schicht auftreten und nicht "irgendwo".
  • Isolieren Sie die Problemklassen in Ebenen und Aspekte. Je weniger ein Teil mit einem anderen Teil des Codes zu tun hat, desto besser im Allgemeinen. Auch wenn es etwas länger dauert, ähnliche Sachen zweimal zu schreiben.

Und der Schlüssel ... Finden Sie einen Mentor, der zuvor alle Fehler gemacht hat, ODER machen Sie mehrere durcheinander, bis Sie herausfinden, was funktioniert und was nicht.

Robin Vessey
quelle
1
Ich mag "Design für Wartbarkeit". Besonders gut schnelle Umdrehung um , wenn Probleme noch auftauchen schließlich. Das bedeutet gute, umfassende Komponententests, eine gute Bereitstellungsstrategie und gute Fehlerverfolgungs- / QS-Prozesse.
Dean Harding
4

Alle oben genannten Punkte sind gut. Aber es gibt etwas, das nicht erwähnt wird. Sie müssen Ihre Module und Funktionen paranoid machen. Bereichstest aller Funktionsparameter. Achten Sie auf Zeichenfolgen mit leeren Anfängen oder Enden, die zu kurz oder zu lang sind. Hüten Sie sich vor Booleschen Werten, die weder wahr noch falsch sind. Achten Sie in untypisierten Sprachen wie PHP auf unerwartete Variablentypen. Achten Sie auf NULL.

Dieser paranoide Code wird häufig als Asserts codiert, die in einem Produktionsbuild deaktiviert werden können, um die Arbeit zu beschleunigen. Aber es wird definitiv Panikfehler in letzter Minute verhindern.

Andy Canfield
quelle
Wie kann ein Boolescher Wert weder wahr noch falsch sein?
Zhehao Mao
@Zhehao Mao: Wenn der Boolesche Wert eine Spalte in einer Datenbank ist, kann er True, False oder NULL sein.
Mike Sherrill 'Cat Recall'
Als ich ein GI war, hatten wir ein Sprichwort. "Wenn alle wirklich ist Ihnen zu bekommen, ist Paranoia nur gut, Sound Denken.“ Einige Behörden nennen diese defensive Programmierung .
Mike Sherrill 'Cat Recall'
Oh, ich verstehe. SQL-Verrücktheit.
Zhehao Mao
3

Rob ist richtig, dass Unit - Tests zu sagen wird nicht sparen Sie von unbekannten Fehler BUT Unit - Tests werden von der Einführung Fehler speichern Sie helfen , wenn Sie die unbekannten Fehler zu beheben und werden Sie versehentlich speichern eine Wiederaufnahme der alte Fehler. TDD wird Sie auch dazu zwingen, Ihre Software von Anfang an so zu gestalten, dass sie testbar ist, und das hat einen enormen positiven Wert.

mcottle
quelle
Dieser Aspekt von Unit-Tests scheint der am meisten missverstandene zu sein: Sie beweisen die Richtigkeit Ihres Codes nicht mit Unittests, sondern verfälschen damit die Richtigkeit der folgenden Änderungen. Aber jedes Mal, wenn ein Fehler in nicht getestetem Code gefunden wird, schreit jemand: "Sehen Sie, Ihre Tests sind wertlos, sie haben diesen Fehler nicht gefunden"
keppla
Anschließend fügen Sie einen Test hinzu, um den Fehler zu reproduzieren. Beheben Sie den Fehler und Sie werden immer auf diesen Fehler testen, wenn Sie Ihre Testsuite
ausführen
Das würde ich tun, aber das führte oft zu "Ja, jetzt ist es zu spät, der Fehler ist bereits passiert". Die Tatsache, dass der Fehler nicht erneut eingeführt wird, wird oft
übersehen
Dies ist wahr, aber bis dahin sind sie in das bekannte Unbekannte gewandert :)
Robin Vessey
2

Vermeiden Sie nach Möglichkeit Status / "Nebenwirkungen". Während Computer deterministisch sind und dieselbe Ausgabe für dieselbe Eingabe liefern, ist die Übersicht über die Eingabe immer unvollständig. Leider merken wir die meiste Zeit nicht, wie unvollständig es ist.

Wenn es um Webanwendungen geht, ist die gesamte Datenbank, die aktuelle Anforderung, die Benutzersitzung, die installierten Bibliotheken von Drittanbietern und vieles mehr Teil der Eingabe. Wenn es um Threads geht, ist es noch schlimmer: Ihr gesamtes Betriebssystem mit allen anderen Prozessen, die von demselben Scheduler verwaltet werden, ist Teil der Eingabe.

Fehler werden durch eine Fehleinschätzung der Art und Weise, wie die Eingabe behandelt wird, oder durch eine Fehleinschätzung der Eingabe verursacht. Letztere sind meiner Erfahrung nach die Schwierigsten: Man kann sie nur "live" beobachten, oft hat man keinen Input mehr.

Wenn Sie neue Technologien, Infrastrukturen usw. erlernen, sollten Sie sich einen Überblick verschaffen, welche Komponenten zur Eingabe beitragen, und dann versuchen , so viele wie möglich zu vermeiden .

keppla
quelle
+1: Nebenwirkungen können oft vermieden werden, indem SOLID-Prinzipien angewendet und atomare Methoden entwickelt werden. Außerdem sollte der Code durch Asserts abgedeckt sein.
Falcon
0

Wenn Ihre Software komplexer wird, ist es unvermeidlich, dass einige Fehler auftreten. Die einzige Möglichkeit, dies vollständig zu vermeiden, besteht darin, nur triviale Software zu entwickeln - und selbst dann müssen Sie von Zeit zu Zeit einen blöden Fehler machen.

Das einzig Praktische, was Sie tun können, ist, unnötige Komplexität zu vermeiden - um Ihre Software so einfach wie möglich zu gestalten, aber nicht einfacher.

Das ist im Grunde das, worum es bei all den spezifischeren Designprinzipien und -mustern geht - Dinge so einfach wie möglich zu machen. Das Problem ist, dass "einfach auf welche Weise" subjektiv sein kann - meinen Sie das absolut einfachste Design für die aktuellen Anforderungen oder einfach zu modifizieren für zukünftige Anforderungen? Und dafür gibt es auch Prinzipien.

Unit-Tests sind ein Beispiel für diese Unsicherheit. Einerseits sind sie unnötige Komplexität - Code, der entwickelt und gepflegt werden muss, aber die Arbeit nicht erledigt. Auf der anderen Seite sind sie eine einfache Möglichkeit, das Testen zu automatisieren und die Menge an viel schwierigeren manuellen Tests zu reduzieren, die durchgeführt werden müssen.

Unabhängig davon, wie viel Designtheorie Sie lernen und wie viel Erfahrung Sie sammeln, besteht das übergeordnete Prinzip (und manchmal der einzige Leitfaden, den Sie haben) darin, auf Einfachheit zu zielen.

Steve314
quelle
0

Es gibt nichts Zufälliges an Softwarefehlern, Grundursachen sind vollkommen deterministischer Natur, falsche Anweisungen an den Computer.

Threading-Fehler können im Ausführungsverhalten nicht deterministisch sein, sind jedoch nicht zufällig .

Sie treten aus genau demselben Grund nur zu scheinbar unvorhersehbaren Zeitpunkten auf, aber das macht sie nicht zufällig , sondern nur scheinbar unvorhersehbar, sobald Sie die Grundursache kennen , können Sie deterministisch vorhersagen, wann sie eintreten werden.

Ich sagte anscheinend und machte die Unterscheidung aus einem Grund. Zufällig bedeutet eine Sache, gemacht, getan, geschehen oder ohne Methode oder bewusste Entscheidung ausgewählt , die impliziert, dass der Computer eine unabhängige Entscheidung trifft. Es gibt nicht genau das, was ihm gesagt wurde, Sie Ich habe ihm nur nicht gesagt, dass er in einigen sehr deterministischen Fällen das Richtige tun soll.

Die Semantik von Wörtern gibt es aus einem Grund, zufällig bedeutet nicht etwas anderes, nur weil jemand es falsch verwendet, es bedeutet immer dasselbe. Ein besserer Begriff wäre in unbeabsichtigten oder nicht offensichtlichen logischen Fehlern.

Das Betrachten von Fehlern als zufällig ist fast so, als würde man akzeptieren, dass eine andere unverständliche Kraft am Werk ist, die nicht vollständig verstanden wird und unabhängig von Ihrer Eingabe in den Computer wirkt, und die nicht sehr wissenschaftlich ist. Ich meine, sind die Götter wütend und schlagen Ihre Bewerbung aus einer Laune heraus?


quelle
+1 für einen gültigen Punkt, aber -1 für Nitpicking. Also, +/- 0. Ich denke, die meisten Leute, die die Frage lesen, haben "zufällig" nicht im ganz wörtlichen Sinne genommen (was genau bedeutet "zufällig" wirklich, bis zum Äußersten genommen?), Sondern eher als "ich" Ich verstehe nicht, wie sich dieses Verhalten eingeschlichen haben könnte oder warum die Software diese Sache falsch macht. "
Ein Lebenslauf
@Micheal - deshalb habe ich anscheinend gesagt und die Unterscheidung getroffen. Es gibt kein Extrem zu zufällig, es bedeutet eine Sache, gemacht, getan, geschehen oder ohne Methode oder bewusste Entscheidung ausgewählt. Die Semantik von Wörtern ist aus einem Grund da. Zufällig bedeutet nicht etwas anderes, nur weil jemand es falsch verwendet. es bedeutet immer das Gleiche. Was sie wahrscheinlich bedeuteten, war unbeabsichtigt , aus den Gründen, die ich in meiner Erklärung in meiner Antwort darlege.