Wenn Sie Ihre bevorzugten (cleveren) Techniken für die defensive Codierung auswählen müssten, welche wären das? Obwohl meine aktuellen Sprachen Java und Objective-C sind (mit einem Hintergrund in C ++), können Sie jederzeit in einer beliebigen Sprache antworten. Der Schwerpunkt liegt hier auf anderen cleveren Abwehrtechniken als denen, die über 70% von uns hier bereits kennen. Jetzt ist es an der Zeit, tief in Ihre Trickkiste zu graben.
Mit anderen Worten, versuchen Sie, an etwas anderes als dieses uninteressante Beispiel zu denken :
if(5 == x)
stattif(x == 5)
: um eine unbeabsichtigte Zuordnung zu vermeiden
Hier sind einige Beispiele für einige faszinierende bewährte Methoden der defensiven Programmierung (sprachspezifische Beispiele sind in Java):
- Sperren Sie Ihre Variablen, bis Sie wissen, dass Sie sie ändern müssen
Das heißt, Sie können alle Variablen deklarieren , final
bis Sie wissen, dass Sie sie ändern müssen. An diesem Punkt können Sie die Variablen entfernen final
. Eine häufig unbekannte Tatsache ist, dass dies auch für Methodenparameter gilt:
public void foo(final int arg) { /* Stuff Here */ }
- Wenn etwas Schlimmes passiert, hinterlassen Sie eine Spur von Beweisen
Es gibt eine Reihe von Dingen, die Sie tun können, wenn Sie eine Ausnahme haben: Offensichtlich sind es einige, die Sie protokollieren und eine Bereinigung durchführen. Sie können aber auch eine Spur von Beweisen hinterlassen (z. B. das Setzen von Variablen auf Sentinel-Werte wie "UNABLE TO LOAD FILE" oder 99999 wäre im Debugger hilfreich, falls Sie zufällig einen Ausnahmeblock catch
überschreiten).
- Wenn es um Beständigkeit geht: Der Teufel steckt im Detail
Seien Sie genauso konsistent mit den anderen Bibliotheken, die Sie verwenden. Wenn Sie beispielsweise in Java eine Methode erstellen, die einen Wertebereich extrahiert, wird die Untergrenze inklusive und die Obergrenze exklusiv . Dies macht es konsistent mit Methoden, wie sie String.substring(start, end)
auf die gleiche Weise funktionieren. Sie finden alle diese Arten von Methoden im Sun JDK, um sich so zu verhalten, da verschiedene Operationen ausgeführt werden, einschließlich der Iteration von Elementen, die mit Arrays konsistent sind, wobei die Indizes von Null ( einschließlich ) bis zur Länge des Arrays ( exklusiv ) reichen .
Was sind deine Lieblingsverteidigungspraktiken?
Update: Wenn Sie es noch nicht getan haben, können Sie sich gerne einschalten. Ich gebe die Möglichkeit, dass weitere Antworten eingehen, bevor ich die offizielle Antwort auswähle .
quelle
Antworten:
In c ++ mochte ich es einmal, new neu zu definieren, damit es zusätzlichen Speicher zum Abfangen von Zaunpfostenfehlern bietet.
Derzeit ziehe ich es vor, defensive Programmierung zugunsten von Test Driven Development zu vermeiden . Wenn Sie Fehler schnell und extern abfangen, müssen Sie Ihren Code nicht mit Verteidigungsmanövern durcheinander bringen, sondern Ihren Code TROCKEN und Sie haben weniger Fehler, gegen die Sie sich verteidigen müssen.
Wie WikiKnowledge schrieb :
quelle
SQL
Wenn ich Daten löschen muss, schreibe ich
Wenn ich es ausführe, werde ich wissen, ob ich die where-Klausel vergessen oder verpfuscht habe. Ich habe eine Sicherheit. Wenn alles in Ordnung ist, hebe ich alles nach den Kommentartoken '-' hervor und führe es aus.
Bearbeiten: Wenn ich viele Daten lösche, verwende ich count (*) anstelle von nur *
quelle
Weisen Sie beim Start der Anwendung einen angemessenen Speicherplatz zu - ich denke, Steve McConnell hat dies als Speicherfallschirm bezeichnet in Code Complete .
Dies kann verwendet werden, wenn etwas Ernstes schief geht und Sie kündigen müssen.
Wenn Sie diesen Speicher im Voraus zuweisen, erhalten Sie ein Sicherheitsnetz, da Sie ihn freigeben und dann den verfügbaren Speicher verwenden können, um Folgendes zu tun:
quelle
In jeder switch-Anweisung, die keinen Standardfall hat, füge ich einen Fall hinzu, der das Programm mit einer Fehlermeldung abbricht.
quelle
Wenn Sie die verschiedenen Zustände einer Aufzählung (C #) behandeln:
Dann, in einer Routine ...
Irgendwann werde ich dieser Aufzählung einen weiteren Kontotyp hinzufügen. Und wenn ich das tue, werde ich vergessen, diese switch-Anweisung zu korrigieren. Das
Debug.Fail
stürzt also schrecklich ab (im Debug-Modus), um meine Aufmerksamkeit auf diese Tatsache zu lenken. Wenn ich das hinzufügecase AccountType.MyNewAccountType:
, hört der schreckliche Absturz auf ... bis ich einen weiteren Kontotyp hinzufüge und vergesse, die Fälle hier zu aktualisieren.(Ja, Polymorphismus ist hier wahrscheinlich besser, aber dies ist nur ein Beispiel aus meinem Kopf.)
quelle
Beim Ausdrucken von Fehlermeldungen mit einer Zeichenfolge (insbesondere einer, die von Benutzereingaben abhängt) verwende ich immer einfache Anführungszeichen
''
. Beispielsweise:Dieser Mangel an Anführungszeichen
%s
ist wirklich schlimm, weil der Dateiname eine leere Zeichenfolge oder nur ein Leerzeichen oder etwas anderes ist. Die ausgedruckte Nachricht wäre natürlich:Also immer besser machen:
Dann sieht zumindest der Benutzer Folgendes:
Ich finde, dass dies einen großen Unterschied in Bezug auf die Qualität der von Endbenutzern eingereichten Fehlerberichte macht. Wenn es eine komisch aussehende Fehlermeldung wie diese gibt, anstatt dass etwas generisch klingt, dann kopieren / fügen sie sie viel eher ein / ein, anstatt nur zu schreiben "es würde meine Dateien nicht öffnen".
quelle
SQL-Sicherheit
Bevor ich SQL schreibe, das die Daten ändert, verpacke ich das Ganze in eine Rollback-Transaktion:
Dies verhindert, dass Sie ein fehlerhaftes Löschen / Aktualisieren dauerhaft ausführen. Und Sie können das Ganze ausführen und angemessene Datensatzzahlen überprüfen oder
SELECT
Anweisungen zwischen Ihrem SQL und dem hinzufügenROLLBACK TRANSACTION
, um sicherzustellen, dass alles richtig aussieht.Wenn Sie ganz sicher sind, dass es das tut, was Sie erwartet haben, ändern Sie das
ROLLBACK
inCOMMIT
und führen Sie es aus.quelle
Für alle Sprachen:
Reduzieren Sie den Umfang der Variablen auf das geringstmögliche Maß. Vermeiden Sie Variablen , die nur bereitgestellt werden, um sie in die nächste Anweisung zu übernehmen. Variablen, die nicht existieren, sind Variablen, die Sie nicht verstehen müssen und für die Sie nicht verantwortlich gemacht werden können. Verwenden Sie Lambdas wann immer möglich aus demselben Grund.
quelle
Wenn Sie Zweifel haben, bombardieren Sie die Anwendung!
Überprüfen Sie jeden Parameter zu Beginn jeder Methode (ob es sich um eine explizite Codierung selbst oder die Verwendung einer vertragsbasierten Programmierung handelt, spielt hier keine Rolle) und bombardieren Sie mit der richtigen Ausnahme und / oder einer aussagekräftigen Fehlermeldung, wenn eine Voraussetzung für den Code vorliegt nicht angetroffen.
Wir alle kennen diese impliziten Voraussetzungen, wenn wir den Code schreiben , aber wenn sie nicht explizit überprüft werden, erstellen wir Labyrinthe für uns selbst, wenn später etwas schief geht und Stapel von Dutzenden von Methodenaufrufen das Auftreten des Symptoms und den tatsächlichen Ort trennen wo eine Voraussetzung nicht erfüllt ist (= wo das Problem / der Fehler tatsächlich ist).
quelle
param.NotNull("param")
anstattif ( param == null ) throw new ArgumentNullException("param");
Verwenden Sie in Java, insbesondere bei Sammlungen, die API. Wenn Ihre Methode beispielsweise den Typ List zurückgibt, versuchen Sie Folgendes:
Lassen Sie nichts aus Ihrer Klasse entkommen, was Sie nicht brauchen!
quelle
In Perl tut das jeder
ich mag
Dies führt dazu, dass der Code für jede Compiler- / Laufzeitwarnung stirbt. Dies ist vor allem beim Abfangen nicht initialisierter Zeichenfolgen hilfreich.
quelle
C #:
quelle
In c # Überprüfung von string.IsNullOrEmpty vor dem Ausführen von Operationen an der Zeichenfolge wie Länge, IndexOf, Mitte usw.
[Bearbeiten]
Jetzt kann ich in .NET 4.0 auch Folgendes tun, das zusätzlich prüft, ob der Wert nur ein Leerzeichen ist
quelle
if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */
.Geben Sie in Java und C # jedem Thread einen aussagekräftigen Namen. Dies schließt Thread-Pool-Threads ein. Dies macht Stack-Dumps viel aussagekräftiger. Es ist etwas aufwändiger, selbst Thread-Pool-Threads einen aussagekräftigen Namen zu geben. Wenn jedoch ein Thread-Pool in einer lang laufenden Anwendung ein Problem aufweist, kann ein Stack-Dump auftreten (Sie kennen SendSignal.exe , oder? ), nimm die Protokolle und ohne ein laufendes System unterbrechen zu müssen, kann ich erkennen, welche Threads ... was auch immer sind. Festgefahren, undicht, wachsend, was auch immer das Problem ist.
quelle
Aktivieren Sie in VB.NET standardmäßig Option Explicit und Option Strict für Visual Studio.
quelle
C ++
Ersetzen Sie dann alle Ihre Aufrufe ' delete pPtr ' und ' delete [] pPtr ' durch SAFE_DELETE (pPtr) und SAFE_DELETE_ARRAY (pPtr).
Wenn Sie nun versehentlich den Zeiger 'pPtr' nach dem Löschen verwenden, wird der Fehler 'Zugriffsverletzung' angezeigt. Es ist viel einfacher zu beheben als zufällige Speicherbeschädigungen.
quelle
delete
. Die Verwendungnew
ist in Ordnung, solange das neue Objekt einem intelligenten Zeiger gehört.Mit Java kann es nützlich sein, das Schlüsselwort assert zu verwenden, selbst wenn Sie Produktionscode mit deaktivierten Zusicherungen ausführen:
Selbst wenn die Behauptungen deaktiviert sind, dokumentiert zumindest der Code die Tatsache, dass erwartet wird, dass param irgendwo festgelegt wird. Beachten Sie, dass dies eine private Hilfsfunktion ist und kein Mitglied einer öffentlichen API. Diese Methode kann nur von Ihnen aufgerufen werden. Es ist daher in Ordnung, bestimmte Annahmen darüber zu treffen, wie sie verwendet wird. Bei öffentlichen Methoden ist es wahrscheinlich besser, eine echte Ausnahme für ungültige Eingaben auszulösen.
quelle
Ich habe das
readonly
Schlüsselwort erst gefunden, als ich ReSharper gefunden habe, aber jetzt verwende ich es instinktiv, insbesondere für Serviceklassen.quelle
Wenn in Java etwas passiert und ich nicht weiß warum, verwende ich manchmal Log4J wie folgt:
Auf diese Weise erhalte ich eine Stapelverfolgung, die mir zeigt, wie ich in die unerwartete Situation geraten bin, beispielsweise eine Sperre, die nie entsperrt wurde, etwas Null, das nicht Null sein kann, und so weiter. Wenn eine echte Ausnahme ausgelöst wird, muss ich dies natürlich nicht tun. Dann muss ich sehen, was im Produktionscode passiert, ohne etwas anderes zu stören. Ich möchte keine Ausnahme werfen und habe keine gefangen. Ich möchte nur, dass ein Stack-Trace mit einer entsprechenden Nachricht protokolliert wird, um mich auf das aufmerksam zu machen, was gerade passiert.
quelle
Wenn Sie Visual C ++ verwenden, verwenden Sie das Schlüsselwort override, wenn Sie die Methode einer Basisklasse überschreiben . Auf diese Weise wird, wenn jemand jemals die Signatur der Basisklasse ändert, ein Compilerfehler ausgegeben, anstatt dass die falsche Methode stillschweigend aufgerufen wird. Das hätte mich ein paar Mal gerettet, wenn es früher existiert hätte.
Beispiel:
quelle
Ich habe in Java gelernt, fast nie auf unbestimmte Zeit auf das Entsperren einer Sperre zu warten, es sei denn, ich erwarte wirklich, dass dies auf unbestimmte Zeit dauern kann. Wenn sich das Schloss realistischerweise innerhalb von Sekunden entriegeln lässt, warte ich nur eine bestimmte Zeit. Wenn sich das Schloss nicht entriegeln lässt, beschwere ich mich und lege den Stapel in die Protokolle. Je nachdem, was für die Stabilität des Systems am besten ist, fahren Sie entweder fort, als ob das Schloss entriegelt wäre, oder fahren Sie fort, als ob das Schloss nie entriegelt worden wäre.
Dies hat dazu beigetragen, einige Rennbedingungen und Pseudo-Deadlock-Bedingungen zu isolieren, die mysteriös waren, bevor ich damit anfing.
quelle
C #
sealed
viel für Klassen, um zu vermeiden, dass Abhängigkeiten dort eingeführt werden, wo ich sie nicht wollte. Das Zulassen der Vererbung sollte explizit und nicht zufällig erfolgen.quelle
Wenn Sie eine Fehlermeldung ausgeben, versuchen Sie zumindest, die gleichen Informationen bereitzustellen, die das Programm hatte, als es die Entscheidung traf, einen Fehler auszulösen.
"Berechtigung verweigert" zeigt an, dass ein Berechtigungsproblem aufgetreten ist, Sie jedoch keine Ahnung haben, warum oder wo das Problem aufgetreten ist. "Transaktionsprotokoll / my / file: Schreibgeschütztes Dateisystem kann nicht geschrieben werden" gibt Ihnen zumindest Auskunft darüber, auf welcher Grundlage die Entscheidung getroffen wurde, auch wenn sie falsch ist - insbesondere, wenn sie falsch ist: falscher Dateiname? falsch geöffnet? anderer unerwarteter Fehler? - und lässt Sie wissen, wo Sie waren, als Sie das Problem hatten.
quelle
Verwenden Sie in C # das
as
Schlüsselwort zum Umwandeln.löst eine Ausnahme aus, wenn obj keine Zeichenfolge ist
wird a als null belassen, wenn obj keine Zeichenfolge ist
Sie müssen immer noch Null berücksichtigen, aber das ist in der Regel einfacher als das Suchen nach Besetzungsausnahmen. Manchmal möchten Sie ein Verhalten vom Typ "Cast or Blow Up". In diesem Fall wird die
(string)obj
Syntax bevorzugt.In meinem eigenen Code verwende ich
as
ungefähr 75% der Zeit die Syntax und(cast)
ungefähr 25% die Syntax.quelle
is/instanceof
in den Sinn kommen.Seien Sie auf jede Eingabe vorbereitet , und jede Eingabe, die Sie unerwartet erhalten, wird in Protokollen gespeichert. (Im Rahmen des Zumutbaren. Wenn Sie Kennwörter vom Benutzer lesen, speichern Sie diese nicht in Protokollen! Und protokollieren Sie nicht Tausende dieser Arten von Nachrichten in Protokollen pro Sekunde. Begründen Sie den Inhalt, die Wahrscheinlichkeit und die Häufigkeit, bevor Sie sie protokollieren .)
Ich spreche nicht nur von der Validierung von Benutzereingaben. Wenn Sie beispielsweise HTTP-Anforderungen lesen, von denen Sie erwarten, dass sie XML enthalten, müssen Sie auf andere Datenformate vorbereitet sein. Ich war überrascht, HTML-Antworten zu sehen, bei denen ich nur XML erwartete - bis ich sah, dass meine Anfrage einen transparenten Proxy durchlief, von dem ich nichts wusste, und dass der Kunde Unwissenheit behauptete - und der Proxy beim Versuch, den Vorgang abzuschließen, eine Zeitüberschreitung aufwies Anfrage. Daher hat der Proxy eine HTML-Fehlerseite an meinen Client zurückgegeben, was den Client verwirrt, der nur XML-Daten erwartet hat.
Selbst wenn Sie glauben, beide Enden des Kabels zu kontrollieren, können Sie unerwartete Datenformate erhalten, ohne dass Schurken involviert sind. Seien Sie vorbereitet, codieren Sie defensiv und stellen Sie bei unerwarteten Eingaben eine Diagnoseausgabe bereit.
quelle
Ich versuche, den Design by Contract-Ansatz zu verwenden. Es kann zur Laufzeit von jeder Sprache emuliert werden. Jede Sprache unterstützt "Assert", aber es ist einfach und praktisch, eine bessere Implementierung zu schreiben, mit der Sie den Fehler nützlicher verwalten können.
In den Top 25 der gefährlichsten Programmierfehler ist die "Unsachgemäße Eingabevalidierung" der gefährlichste Fehler im Abschnitt "Unsichere Interaktion zwischen Komponenten".
Das Hinzufügen von Vorbedingungszusicherungen zu Beginn der Methoden ist ein guter Weg, um sicherzustellen, dass die Parameter konsistent sind. Am Ende der Methoden schreibe ich Nachbedingungen , die überprüfen, ob die Ausgabe so ist, wie sie sein soll.
Um Invarianten zu implementieren , schreibe ich eine Methode in jede Klasse, die die "Klassenkonsistenz" überprüft, die von einem Vorbedingungs- und einem Nachbedingungsmakro authomatisch aufgerufen werden sollte.
Ich evaluiere die Code Contract Library .
quelle
Java
Die Java-API hat kein Konzept von unveränderlichen Objekten, was schlecht ist! Final kann Ihnen in diesem Fall helfen. Kennzeichnen Sie jede Klasse, die unveränderlich ist, mit final und bereiten Sie die Klasse entsprechend vor .
Manchmal ist es nützlich, final für lokale Variablen zu verwenden, um sicherzustellen, dass sie ihren Wert niemals ändern. Ich fand dies nützlich in hässlichen, aber notwendigen Schleifenkonstrukten. Es ist einfach zu einfach, eine Variable versehentlich wiederzuverwenden, obwohl es sich um eine Konstante handelt.
Verwenden Sie Verteidigungskopien in Ihren Gettern. Wenn Sie keinen primitiven Typ oder kein unveränderliches Objekt zurückgeben, stellen Sie sicher, dass Sie das Objekt kopieren, um die Kapselung nicht zu verletzen.
Verwenden Sie niemals einen Klon, sondern einen Kopierkonstruktor .
Lernen Sie den Vertrag zwischen equals und hashCode. Dies wird so oft verletzt. Das Problem ist, dass es in 99% der Fälle keinen Einfluss auf Ihren Code hat. Die Leute überschreiben gleich, aber HashCode ist ihnen egal. Es gibt Fälle, in denen Ihr Code brechen kann oder sich seltsam verhält, z. B. veränderbare Objekte als Schlüssel in einer Karte verwenden.
quelle
Ich habe
echo
zu oft vergessen, in PHP zu schreiben :Ich würde ewig brauchen, um herauszufinden, warum -> baz () nichts zurückgab, obwohl ich es einfach nicht wiederholte! : -S Also habe ich eine
EchoMe
Klasse erstellt, die um jeden Wert gewickelt werden kann, der wiedergegeben werden soll:Und dann habe ich für die Entwicklungsumgebung ein EchoMe verwendet, um Dinge zu verpacken, die gedruckt werden sollten:
Mit dieser Technik würde das erste fehlende Beispiel
echo
jetzt eine Ausnahme auslösen ...quelle
AnObject.ToString
stattWriteln(AnObject.ToString)
?C ++
Wenn ich new eingebe, muss ich sofort delete eingeben. Besonders für Arrays.
C #
Suchen Sie vor dem Zugriff auf Eigenschaften nach Null, insbesondere wenn Sie das Mediator-Muster verwenden. Objekte werden übergeben (und sollten dann, wie bereits erwähnt, mit as umgewandelt werden) und dann gegen null prüfen. Auch wenn Sie denken, dass es nicht null sein wird, überprüfen Sie es trotzdem. Ich war überrascht
quelle
Verwenden Sie ein Protokollierungssystem, das dynamische Anpassungen der Laufzeitprotokollstufe ermöglicht. Wenn Sie ein Programm stoppen müssen, um die Protokollierung zu aktivieren, verlieren Sie häufig den seltenen Status, in dem der Fehler aufgetreten ist. Sie müssen in der Lage sein, weitere Protokollierungsinformationen zu aktivieren, ohne den Prozess anzuhalten.
Außerdem zeigt 'strace -p [pid]' unter Linux, dass Sie Systemaufrufe wünschen, die ein Prozess (oder ein Linux-Thread) ausführt. Es mag zunächst seltsam aussehen, aber sobald Sie sich daran gewöhnt haben, welche Systemaufrufe im Allgemeinen von welchen libc-Aufrufen getätigt werden, werden Sie dies in der Felddiagnose von unschätzbarem Wert finden.
quelle