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:
- 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.)
- 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.
- Verwenden Sie dynamische Analysewerkzeuge wie Valgrind, um nach Speicherproblemen, Lecks usw. zu suchen.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
quelle
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.
quelle
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 .
quelle
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.
quelle
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