Bevorzugte (clevere) Best Practices für die defensive Programmierung [geschlossen]

148

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) statt if(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 , finalbis 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 .

Ryan Delucchi
quelle
Ich möchte die unwissenden 30% von uns hier vertreten, die die einfachen Techniken möglicherweise nicht kennen . Hat jemand eine Verbindung zu den "offensichtlichen" Techniken, die jeder als Grundlage kennen sollte?
Elliot42
Siehe auch
Bill the Lizard
Warum würden Sie eine offizielle Antwort wählen? Das klingt einfach nur Onkel.
Bzlm
Nun, wenn es um Programmierung geht, sollte sogar die Klugheit zu Ehren der "Regel des geringsten Erstaunens" in den Hintergrund treten. Wenn ich mit dem Stack Overflow-Geist brechen würde, die beste Antwort zu markieren, würde dies gegen das Stack Overflow-Protokoll verstoßen (was gegen diese Regel verstößt). Abgesehen davon: Ich mag Schließung :-)
Ryan Delucchi

Antworten:

103

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 :

Vermeiden Sie defensive Programmierung und schlagen Sie stattdessen schnell fehl.

Mit defensiver Programmierung meine ich die Gewohnheit, Code zu schreiben, der versucht, einen Fehler in den Daten zu kompensieren, Code zu schreiben, der davon ausgeht, dass Anrufer möglicherweise Daten bereitstellen, die nicht dem Vertrag zwischen Anrufer und Unterprogramm entsprechen, und dass das Unterprogramm irgendwie damit umgehen muss damit.

Joe Seelenbringer
quelle
8
Defensive Programmierung versucht, mit illegalen Bedingungen umzugehen, die durch andere Teile eines Programms eingeführt wurden. Der Umgang mit unsachgemäßen Benutzereingaben ist etwas völlig anderes.
Joe Soul-Bringer
5
Ähm ... beachten Sie, dass diese Definition der defensiven Programmierung nicht einmal der in der Frage implizit verwendeten Definition nahe kommt.
Sol
15
Vermeiden Sie niemals die defensive Programmierung. Die Sache "kompensiert" nicht Fehler in den Daten, sondern schützt sich vor böswilligen Daten, die Ihren Code dazu bringen sollen, Dinge zu tun, die er nicht tun soll. Siehe Pufferüberlauf, SQL Injection. Nichts versagt schneller als eine Webseite unter XSS, aber es ist nicht schön
Jorge Córdoba
6
Ich würde argumentieren, dass "schnell scheitern" -Verfahren eine Form der defensiven Programmierung sind.
Ryan Delucchi
6
@ryan ist genau richtig, schnell scheitern ist ein gutes Defensivkonzept. Wenn der Zustand, in dem Sie sich befinden, nicht möglich ist, versuchen Sie nicht, weiter zu humpeln, SCHNELL UND LAUT! Besonders wichtig, wenn Sie metadatengesteuert sind. Defensive Programmierung überprüft nicht nur Ihre Parameter ...
Bill K
75

SQL

Wenn ich Daten löschen muss, schreibe ich

select *    
--delete    
From mytable    
Where ...

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 *

John MacIntyre
quelle
Ich habe dies auf meinem iPhone geschrieben, wo ich keinen Text auswählen kann. Wenn jemand meinen Code als Code markieren könnte, wäre er sehr dankbar. Vielen Dank.
John MacIntyre
6
Ich wickle es einfach in eine Transaktion ein, damit ich
zurückrollen
36
TRANSAKTION BEGINNEN | ROLLBACK TRANSACTION
Dalin Seivewright
3
+1 Ja, ich nehme an, dies könnte durch eine Transaktion ersetzt werden, aber ich mag die Einfachheit, dies auf diese Weise zu tun.
Ryan Delucchi
3
Die Verwendung einer Transaktion ist besser. Es bedeutet, dass Sie es Dutzende Male ausführen und die tatsächlichen Auswirkungen sehen können , bis Sie sicher sind, dass Sie es richtig gemacht haben und sich verpflichten können.
Ryan Lundy
48

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:

  • Speichern Sie alle persistenten Daten
  • Schließen Sie alle entsprechenden Dateien
  • Schreiben Sie Fehlermeldungen in eine Protokolldatei
  • Präsentieren Sie dem Benutzer einen bedeutungsvollen Fehler
LeopardSkinPillBoxHat
quelle
Ich habe gesehen, dass dies ein Regentag-Fonds genannt wird.
Sockel
42

In jeder switch-Anweisung, die keinen Standardfall hat, füge ich einen Fall hinzu, der das Programm mit einer Fehlermeldung abbricht.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}
Diomidis Spinellis
quelle
2
Oder ein Wurf in eine moderne Sprache.
Tom Hawtin - Tackline
2
Assert hat den Vorteil, dass seine Effekte beim Kompilieren global deaktiviert werden können. In einigen Situationen kann das Werfen jedoch angemessener sein, wenn Ihre Sprache dies unterstützt.
Diomidis Spinellis
2
Assert hat einen weiteren Vorteil, der es für den Produktionscode nützlich macht: Wenn etwas schief geht, erfahren Sie genau, was fehlgeschlagen ist und aus welcher Programmzeile der Fehler stammt. Ein solcher Fehlerbericht ist wunderbar!
Mason Wheeler
2
@Diomidis: Ein weiterer Aspekt ist: Assert hat den Nachteil, dass seine Effekte beim Kompilieren global deaktiviert werden können.
Desillusioniert
1
werfen ist in der Tat besser. Und dann stellen Sie sicher, dass Ihre Testabdeckung ausreichend ist.
Dominic Cronin
41

Wenn Sie die verschiedenen Zustände einer Aufzählung (C #) behandeln:

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Dann, in einer Routine ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

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.Failstü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.)

Kyralessa
quelle
4
Die meisten Compiler sind intelligent genug, um eine Warnung auszugeben, wenn Sie einige Aufzählungen in einem Fallblock nicht verarbeiten. Das Festlegen der Standardeinstellung "Fehlschlagen" ist jedoch immer noch eine gute Form. Eine Aufzählung ist nur eine Zahl. Wenn Sie eine Speicherbeschädigung erhalten, kann ein ungültiger Wert angezeigt werden.
Adam Hawes
In c habe ich am Ende einen invalidAccountType hinzugefügt. das ist manchmal nützlich.
Ray Tayek
1
@Adam - Compiler geben eine Warnung aus, wenn Sie alles neu kompilieren . Wenn Sie neue Klassen hinzufügen und nur teilweise neu kompilieren, werden Sie möglicherweise nichts wie das oben Gesagte bemerken, und der Standardfall wird Sie retten. Es wird nicht einfach stillschweigend scheitern.
Eddie
4
Die Pause ist in den Kommentaren Slashene impliziert. : P
Ryan Lundy
2
Nach dem Debug sollte ein 'werfen Sie eine neue NotSupportedException ()' für den Produktionscode.
user7116
35

Beim Ausdrucken von Fehlermeldungen mit einer Zeichenfolge (insbesondere einer, die von Benutzereingaben abhängt) verwende ich immer einfache Anführungszeichen ''. Beispielsweise:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Dieser Mangel an Anführungszeichen %sist wirklich schlimm, weil der Dateiname eine leere Zeichenfolge oder nur ein Leerzeichen oder etwas anderes ist. Die ausgedruckte Nachricht wäre natürlich:

ERROR: Could not open file

Also immer besser machen:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Dann sieht zumindest der Benutzer Folgendes:

ERROR: Could not open file ''

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".

Nik Reiman
quelle
4
Gut, ich habe dieses Problem auch gesehen
Alex Baranosky
28

SQL-Sicherheit

Bevor ich SQL schreibe, das die Daten ändert, verpacke ich das Ganze in eine Rollback-Transaktion:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

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 SELECTAnweisungen zwischen Ihrem SQL und dem hinzufügen ROLLBACK TRANSACTION, um sicherzustellen, dass alles richtig aussieht.

Wenn Sie ganz sicher sind, dass es das tut, was Sie erwartet haben, ändern Sie das ROLLBACKin COMMITund führen Sie es aus.

Amsimmon
quelle
Du hast mich in diesem Fall hart getroffen! :-)
Howard Pinsley
2
Früher habe ich das die ganze Zeit gemacht, aber es verursacht so viel zusätzlichen Aufwand für DB-Cluster, dass ich aufhören musste.
Zan Lynx
25

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.

le dorfier
quelle
5
Was genau bedeutet das Vermeiden von Teilen? Ich führe manchmal Variablen ein, die nur bis zur nächsten Zeile leben. Sie dienen als Name für einen Ausdruck, wodurch der Code besser lesbar wird.
Zoul
4
Ja, ich bin auch anderer Meinung. Bei sehr komplexen Ausdrücken ist es oft eine gute Idee, sie mithilfe temporärer Variablen in zwei oder manchmal kürzere und einfachere zu zerlegen. Es ist weniger fehleranfällig bei der Wartung und der Compiler wird die Temperaturen optimieren
Cruachan
1
Ich bin damit einverstanden, dass es Ausnahmefälle gibt, in denen ich Variablen aus Gründen der Klarheit deklariere (und manchmal um ein Ziel für das Debuggen zu erstellen). Ich habe die Erfahrung gemacht, dass die allgemeine Praxis darin besteht, in die entgegengesetzte Richtung zu irren.
Dkretz
Ich stimme beiden Standpunkten zu; Wenn ich Ausdrücke in temporäre Variablen aufteile, versuche ich dies in einem separaten Bereich. Lambdas sind dafür großartig, ebenso wie Hilfsmethoden.
Erik Forbes
19

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).

peSHIr
quelle
Und natürlich: Verwenden Sie generischen Code (eine kleine Bibliothek, Erweiterungsmethoden in C #, was auch immer), um dies zu vereinfachen. Sie können also etwas Glike schreiben param.NotNull("param")anstattif ( param == null ) throw new ArgumentNullException("param");
peSHIr
2
Oder verwenden Sie eine vertragsbasierte Programmierung wie Spec #!
Bzlm
Kommt darauf an, um welche Anwendung es sich handelt. Ich hoffe, die Leute, die Code für Fly-by-Wire-Flugzeuge und Herzschrittmacher schreiben, denken nicht wie PeSHIr.
MarkJ
6
@ MarkJ: Du verstehst es nicht wirklich, oder? Wenn es früh bombardiert (= während der Entwicklung und beim Testen), sollte es niemals bombardieren, wenn es in Produktion ist. Ich hoffe wirklich, dass sie so programmieren!
PeSHIr
Ich muss zugeben, dass mir das nicht besonders gefällt, besonders bei privaten und geschützten Methoden. Gründe: a) Sie überladen den Code mit Überprüfungen, die überhaupt nicht den Geschäftsanforderungen entsprechen. B) Diese Überprüfungen sind schwer auf nicht öffentliche Methoden zu testen. C) Sie sind in vielen Fällen nutzlos, z. B. weil ein Nullwert dazu führt, dass die Methode fehlschlägt sowieso zwei Zeilen später
Erich Kitzmüller
18

Verwenden Sie in Java, insbesondere bei Sammlungen, die API. Wenn Ihre Methode beispielsweise den Typ List zurückgibt, versuchen Sie Folgendes:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Lassen Sie nichts aus Ihrer Klasse entkommen, was Sie nicht brauchen!

David Grant
quelle
+1 In C # gibt es dafür schreibgeschützte Sammlungen.
Tobsen
1
+1 für Java. Ich benutze dies die ganze Zeit
Fortyrunner
+1 ... Ich mache das oft (obwohl ich mir wünsche, dass mehr Leute daran denken würden).
Ryan Delucchi
Stellen Sie nur sicher, dass nichts anderes eine Instanz der zugrunde liegenden
Listenvariablen
5
Zu Ihrer Information, Collections.unmodizableList gibt eine unveränderliche Ansicht der Liste zurück, keine unveränderliche Kopie . Wenn also die ursprüngliche Liste geändert wird, wird auch die Ansicht geändert!
Zarkonnen
17

In Perl tut das jeder

use warnings;

ich mag

use warnings FATAL => 'all';

Dies führt dazu, dass der Code für jede Compiler- / Laufzeitwarnung stirbt. Dies ist vor allem beim Abfangen nicht initialisierter Zeichenfolgen hilfreich.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here
Eric Johnson
quelle
Ich wünschte, dies wäre etwas mehr beworben ...
DJG
16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}
CMS
quelle
Gleiches gilt für Java und wahrscheinlich für die meisten OO-Sprachen.
MiniQuark
Dies ist in Objective-C hilfreich, wo es möglich ist, Nachrichten an nil zu senden ("Aufrufmethoden eines Nullobjekts" mit einer bestimmten Lizenz). Das Aufrufen von [nil isEqualToString: @ "Moo"] gibt false zurück.
Zoul
Ich bin mit dem C # -Beispiel nicht einverstanden. Eine schönere Lösung ist die Verwendung von "if (myString ==" someValue ")". Auch keine Nullreferenzausnahme und sicherlich besser lesbar.
Dan C.
13
In diesem Beispiel können Sie lediglich eine potenziell gefährliche Situation in Ihrem Programm verbergen. Wenn Sie nicht erwartet haben, dass es null ist, möchten Sie, dass es eine Ausnahme auslöst, und wenn Sie erwartet haben, dass es null ist, sollten Sie es als solches behandeln. Dies ist eine schlechte Praxis.
Rmeador
2
In C # verwende ich immer nur string.Equals (<string1>, <string2>). Es spielt keine Rolle, ob einer von ihnen null ist.
Darcy Casselman
15

In c # Überprüfung von string.IsNullOrEmpty vor dem Ausführen von Operationen an der Zeichenfolge wie Länge, IndexOf, Mitte usw.

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[Bearbeiten]
Jetzt kann ich in .NET 4.0 auch Folgendes tun, das zusätzlich prüft, ob der Wert nur ein Leerzeichen ist

string.IsNullOrWhiteSpace(myString)
Binoj Antony
quelle
7
Das ist nicht defensiv, es ignoriert das Problem. Sollte sein if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
Enashnash
14

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.

Eddie
quelle
Und - unter Windows - auch für C ++! (aktiviert mit speziellem SEH-Ausnahmewurf und folgendem Fang).
Brian Haak
12

Aktivieren Sie in VB.NET standardmäßig Option Explicit und Option Strict für Visual Studio.

ChrisA
quelle
Obwohl ich mit älterem Code arbeite, ist die Option strikt nicht aktiviert (verursacht viel zu viele Compilerfehler, um sie zu beheben). Wenn beide Optionen aktiviert sind, kann (und hätte dies in meinem Fall der Fall gewesen sein) viel Herz gespart schmerzen.
Kibbee
1
Übrigens: Wenn Sie alten Code konvertieren, für dessen Verwendung Option Strict nicht verwendet wurde, beachten Sie, dass bei Elementen, die automatisch in String konvertiert wurden, ToString () nicht verwendet wird. Sie verwenden eine Besetzung zum Bespannen. In meinen frühen .NET-Tagen habe ich diese geändert, um ToString () zu verwenden, und es würde Dinge kaputt machen, insbesondere mit Aufzählungen.
Ryan Lundy
10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

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.

Nitin Bhide
quelle
1
Verwenden Sie besser eine Vorlage. Es ist umfangreich und überladbar.
Ich wollte das gerade sagen. Verwenden Sie eine Vorlage anstelle eines Makros. Auf diese Weise können Sie den Code schrittweise durchgehen.
Steve Rowe
Ich habe gelernt, was auf die harte Tour in der Schule einfacher zu debuggen war. Das ist ein Fehler, den ich seitdem nicht mehr gemacht habe. :)
Greg D
3
Oder verwenden Sie Smart Pointers ... Oder vermeiden Sie Neu- / Löschvorgänge so oft wie möglich.
Arafangion
1
@Arafangion: einfach vermeiden delete. Die Verwendung newist in Ordnung, solange das neue Objekt einem intelligenten Zeiger gehört.
Alexandre C.
10

Mit Java kann es nützlich sein, das Schlüsselwort assert zu verwenden, selbst wenn Sie Produktionscode mit deaktivierten Zusicherungen ausführen:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

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.

Outlaw Programmer
quelle
In .NET macht Debug.Assert dasselbe. Jedes Mal , wenn Sie einen Platz haben , wo Sie denken „Diese Referenz kann nicht null hier, nicht wahr?“, Können Sie eine Debug.Assert setzen dort, so dass , wenn es kann null sein, können Sie entweder den Fehler beheben oder Ihre Annahmen ändern.
Ryan Lundy
1
+1, öffentliche Methoden sollten Vertragsfehler
auslösen
9

Ich habe das readonlySchlüsselwort erst gefunden, als ich ReSharper gefunden habe, aber jetzt verwende ich es instinktiv, insbesondere für Serviceklassen.

readonly var prodSVC = new ProductService();
JMS
quelle
Ich verwende Javas gleichwertiges Schlüsselwort 'final' für alle Felder, die ich kann. Es erspart Ihnen wirklich, Kopfbewegungen wie das Nicht-Setzen eines Feldes oder das Schreiben verwirrender Schalter zu machen, mit denen Felder mehrmals gesetzt werden können. Es ist weniger wahrscheinlich, dass ich lokale Variablen / Parameter markiere, aber ich denke, es könnte nicht schaden.
Outlaw Programmer
C # erlaubt Ihnen nicht, lokale Variablen als schreibgeschützt zu markieren, daher haben wir nicht einmal die Wahl ...
Jason Punyon
Ich bin mir nicht sicher, was schreibgeschützt hier bedeutet, aber Javas Finale reicht mir nicht aus. Es bedeutet nur, dass der Zeiger auf ein Objekt unverändert bleibt, aber es gibt keine Möglichkeit, Änderungen am Objekt selbst zu verhindern.
Slartibartfast
In C # würde eine schreibgeschützte Struktur verhindern, dass sie jemals geändert wird.
Samuel
9

Wenn in Java etwas passiert und ich nicht weiß warum, verwende ich manchmal Log4J wie folgt:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

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.

Eddie
quelle
Hmm, das ist ein bisschen ordentlich, aber ich befürchte, es würde zu einer Vermischung von Funktions- und Fehlerbehandlungscode führen. Während der Try-Catch-Mechanismus auf der ungeschickten Seite sein kann, zwingt er einen dazu, die Ausführung von Ausnahmen von einem Block (Versuch) zu einem anderen (Fang) umzuleiten, wo die gesamte Fehlerbehandlung stattfindet
Ryan Delucchi
1
Dies wird nicht zur Fehlerbehandlung verwendet, sondern nur zur Diagnose . Auf diese Weise können Sie den Pfad anzeigen, über den Ihr Code in eine unerwartete Situation geraten ist, ohne den Codefluss zu unterbrechen. Ich benutze dies, wenn die Methode selbst die unerwartete Situation bewältigen kann, aber es sollte trotzdem nicht passieren.
Eddie
2
Sie können auch eine neue Ausnahme ("Nachricht") verwenden. printStackTrace (); Kein Werfen oder Fangen erforderlich, aber Sie erhalten trotzdem eine schöne Stapelspur im Protokoll. Dies sollte natürlich nicht im Produktionscode enthalten sein, kann jedoch zum Debuggen sehr nützlich sein.
Jorn
9

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:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}
Steve Rowe
quelle
Für Java ist dies die Klasse Foo {void doSomething () {}} meine zu) und wenn die Annotation da ist, aber nichts überschreibt.
Jorn
Schön ... ich wusste nichts über dieses ... schade, es scheint Microsoft-spezifisch zu sein ... aber einige gute #defines können helfen, es portabel zu machen
e.tadeu
8

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.

Eddie
quelle
1
Wartezeiten mit solchen Timeouts sind ein Zeichen für andere, schlimmere Probleme.
Dicroce
2
In einem System mit hoher Betriebszeit ist es besser, zumindest Diagnoseinformationen zu erhalten, damit Sie das Problem finden können. Manchmal ziehen Sie es vor, einen Thread zu beenden oder eine Antwort an den Aufrufer zurückzugeben, wenn sich das System in einem bekannten Zustand befindet, sodass Sie auf ein Semaphor warten. Aber du willst nicht für immer hängen
Eddie
8

C #

  • Überprüfen Sie Nicht-Null-Werte für Referenztypparameter in der öffentlichen Methode.
  • Ich benutze sealedviel 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.
Brian Rasmussen
quelle
8

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.

Joe McMahon
quelle
1
Wenn Sie den Code für eine Fehlermeldung schreiben, lesen Sie die Nachricht und fragen Sie , was Sie als Nächstes wissen möchten. Fügen Sie sie dann hinzu. Wiederholen, bis es unvernünftig ist. Zum Beispiel: "Außer Reichweite." Was ist außerhalb der Reichweite? "Foo zählen außerhalb der Reichweite." Was war der Wert? "Foo count (42) außerhalb des Bereichs." Was ist die Reichweite? "Foo count (42) außerhalb des Bereichs (549 bis 666)."
HABO
7

Verwenden Sie in C # das asSchlüsselwort zum Umwandeln.

string a = (string)obj

löst eine Ausnahme aus, wenn obj keine Zeichenfolge ist

string a = obj as string

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)objSyntax bevorzugt.

In meinem eigenen Code verwende ich asungefähr 75% der Zeit die Syntax und (cast)ungefähr 25% die Syntax.

Matt Briggs
quelle
Richtig, aber jetzt müssen Sie nach Null suchen, bevor Sie die Referenz verwenden.
Brian Rasmussen
7
Ich habe es nicht verstanden. Scheint mir eine schlechte Entscheidung zu sein, die Null zu bevorzugen. Während der Laufzeit treten irgendwo Probleme auf, ohne dass der ursprüngliche Grund angegeben wird.
Kai Huppmann
Ja. Dies ist nur nützlich, wenn Sie tatsächlich null möchten, wenn es nicht der richtige Typ ist. In einigen Fällen nützlich: In Silverlight, wo Steuerlogik und Design häufig getrennt sind, möchte die Logik das Steuerelement "Up" nur verwenden, wenn es sich um eine Schaltfläche handelt. Wenn nicht, ist es so, als ob es nicht existiert hätte (= null).
Sander
2
Dies scheint ungefähr so ​​defensiv wie große Ballsaalfenster in einer Festung. Aber es ist eine schöne Aussicht!
Pontus Gagge
1
Dies erinnert mich an jemanden, der keine Ausnahmen verwendet hat, weil er Programme gebrochen hat. Wenn Sie ein bestimmtes Verhalten wünschen, wenn ein Objekt keine Klasse ist, die Sie erwarten, würde ich dies auf andere Weise codieren. is/instanceofin den Sinn kommen.
Kirschstein
6

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.

Eddie
quelle
6

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 .

Zen
quelle
6

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.

user45947
quelle
5

Ich habe echozu oft vergessen, in PHP zu schreiben :

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

Ich würde ewig brauchen, um herauszufinden, warum -> baz () nichts zurückgab, obwohl ich es einfach nicht wiederholte! : -S Also habe ich eine EchoMeKlasse erstellt, die um jeden Wert gewickelt werden kann, der wiedergegeben werden soll:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

Und dann habe ich für die Entwicklungsumgebung ein EchoMe verwendet, um Dinge zu verpacken, die gedruckt werden sollten:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

Mit dieser Technik würde das erste fehlende Beispiel echojetzt eine Ausnahme auslösen ...

zu viel PHP
quelle
Sollte der Destruktor nicht nach $ this-> print! == true suchen?
Karsten
Sie sollten die Verwendung eines Vorlagensystems in Betracht ziehen. Das Einbetten von PHP in HTML ist auf fast allen Systemen nicht optimal.
Cruachan
Vielleicht fehlt mir etwas? Aber es sieht für mich so aus, als würde dies versuchen, die Codierung zu kompensieren: AnObject.ToStringstatt Writeln(AnObject.ToString)?
Desillusioniert
Ja, aber der Fehler ist in PHP viel einfacher zu machen
zu viel PHP
4

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

mmr
quelle
Ich mag deinen ersten Punkt. Ich mache ähnliche Dinge, wenn ich zum Beispiel eine Methode schreibe, die eine Sammlung von etwas zurückgibt. Ich erstelle die Sammlung in der ersten Zeile und schreibe sofort die return-Anweisung. Sie müssen nur noch angeben, wie die Sammlung gefüllt ist.
Outlaw Programmer
5
Wenn Sie in C ++ new eingeben, sollten Sie diesen Zeiger sofort einem AutoPtr- oder referenzgezählten Container zuweisen. C ++ verfügt über Destruktoren und Vorlagen. Verwenden Sie sie mit Bedacht, um das Löschen automatisch zu handhaben.
An̲̳̳drew
4

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.

Dicroce
quelle