Wann sollte ich Debug.Assert () verwenden?

220

Ich bin seit ungefähr einem Jahr ein professioneller Software-Ingenieur, nachdem ich einen CS-Abschluss gemacht habe. Ich kenne Assertions schon eine Weile in C ++ und C, hatte aber bis vor kurzem keine Ahnung, dass sie überhaupt in C # und .NET existieren.

Unser Produktionscode enthält keinerlei Aussagen und meine Frage ist diese ...

Soll ich Asserts in unserem Produktionscode verwenden? Und wenn ja, wann ist seine Verwendung am besten geeignet? Wäre es sinnvoller zu tun

Debug.Assert(val != null);

oder

if ( val == null )
    throw new exception();
Nicholas Mancuso
quelle
2
Die Dichotomie, die Sie eingerichtet haben, ist der Hinweis. Es ist keine Frage von entweder - oder für Ausnahmen und Behauptungen, es ist beides - und für Verteidigungscode. Wann zu tun ist, ist das, was Sie verstehen wollen.
Casper Leon Nielsen
5
Ich habe einmal gelesen, dass jemand vorschlägt, dass eine Ausnahme oder eine andere Methode des Absturzes für Bedingungen geeignet ist, bei denen "ich mich auf keinen Fall sinnvoll davon erholen kann", und zusätzlich ist eine Behauptung für Bedingungen geeignet, bei denen "dies niemals passieren sollte". Aber welche realistischen Umstände erfüllen die letzteren Bedingungen, ohne auch die ersteren zu erfüllen? Ich komme aus einem Python-Hintergrund, in dem Asserts in der Produktion aktiv bleiben, und habe den Java / C # -Ansatz, einige Ihrer Validierungen in der Produktion auszuschalten, nie verstanden. Der einzige Fall, den ich wirklich sehen kann, ist, ob die Validierung teuer ist.
Mark Amery
2
Persönlich verwende ich Ausnahmen für öffentliche Methoden und Behauptungen für private Methoden.
Fred

Antworten:

230

Beim Debuggen von Microsoft .NET 2.0-Anwendungen hat John Robbins einen großen Abschnitt über Behauptungen. Seine Hauptpunkte sind:

  1. Reichlich behaupten. Sie können nie zu viele Behauptungen haben.
  2. Behauptungen ersetzen keine Ausnahmen. Ausnahmen betreffen die Dinge, die Ihr Code verlangt; Behauptungen decken die Dinge ab, die es annimmt.
  3. Eine gut geschriebene Behauptung kann Ihnen nicht nur sagen, was und wo passiert ist (wie eine Ausnahme), sondern auch warum.
  4. Eine Ausnahmemeldung kann häufig kryptisch sein, sodass Sie den Code rückwärts durcharbeiten müssen, um den Kontext wiederherzustellen, der den Fehler verursacht hat. Eine Zusicherung kann den Status des Programms zum Zeitpunkt des Fehlers beibehalten.
  5. Behauptungen dienen gleichzeitig als Dokumentation und teilen anderen Entwicklern mit, von welchen impliziten Annahmen Ihr Code abhängt.
  6. In dem Dialogfeld, das angezeigt wird, wenn eine Zusicherung fehlschlägt, können Sie dem Prozess einen Debugger hinzufügen, sodass Sie den Stapel durchsuchen können, als hätten Sie dort einen Haltepunkt gesetzt.

PS: Wenn Ihnen Code Complete gefallen hat, empfehle ich, dieses Buch weiterzuverfolgen. Ich habe es gekauft, um mehr über die Verwendung von WinDBG- und Dump-Dateien zu erfahren, aber die erste Hälfte enthält viele Tipps, um Fehler zu vermeiden.

Rory MacLeod
quelle
3
+1 für die kurze und hilfreiche Zusammenfassung. Sehr direkt anwendbar. Die Hauptsache, die mir jedoch fehlt, ist, wann Trace.Assert vs. Trace.Assert verwendet werden soll. Dh etwas darüber, wann Sie sie in Ihrem Produktionscode haben / nicht wollen.
Jon Coombs
2
JonCoombs ist "Trace.Assert vs. Trace.Assert" ein Tippfehler?
Thelem
1
@thelem Jon gemeint Vielleicht Debug.Assertvs. Trace.Assert. Letzteres wird sowohl in einem Release-Build als auch in einem Debug-Build ausgeführt.
DavidRR
Warum sollte ich Debug.Assert dem Auslösen von Ausnahmen vorziehen?
Barış Akkurt
86

Fügen Sie Debug.Assert()den Code überall dort ein, wo Sie eine Überprüfung der Gesundheit durchführen möchten, um Invarianten sicherzustellen. Wenn Sie einen Release-Build kompilieren (dh keine DEBUGCompilerkonstante), werden die Aufrufe von Debug.Assert()entfernt, damit die Leistung nicht beeinträchtigt wird.

Sie sollten immer noch Ausnahmen auslösen, bevor Sie anrufen Debug.Assert(). Die Behauptung stellt nur sicher, dass alles wie erwartet ist, während Sie sich noch entwickeln.

Mark Cidade
quelle
35
Können Sie klarstellen, warum Sie eine Behauptung aufstellen, wenn Sie noch eine Ausnahme auslösen, bevor Sie sie aufrufen? Oder habe ich deine Antwort falsch verstanden?
Roman Starkov
2
@romkyns Sie müssen sie weiterhin einschließen, da sonst alle Überprüfungen / Fehlerprüfungen wegfallen, wenn Sie Ihr Projekt im Release- Modus erstellen.
Oscar Mederos
28
@Oscar Ich dachte, das wäre der springende Punkt, um Assertions zu verwenden ... OK, also setzen Sie die Ausnahmen vor sie - warum sollten Sie dann die Assertions danach setzen?
Roman Starkov
4
@superjos: Ich muss nicht zustimmen: Punkt 2 in MacLeods Antwort besagt, dass Sie zwar Behauptung UND Ausnahmen benötigen, aber nicht an derselben Stelle. Es ist sinnlos, eine NullRefEx auf eine Variable zu werfen und direkt danach eine Assert darauf auszuführen (die Assert-Methode zeigt in diesem Fall niemals eine Dialogbox an, was der springende Punkt der Assert ist). Was MacLeod bedeutet, ist, dass Sie an einigen Stellen eine Ausnahme benötigen, an anderen ist eine Bestätigung ausreichend.
David
1
Es kann chaotisch wird meine Interpretation von jemandem anderer Antwort zu interpretieren :) Wie auch immer ich mit Ihnen auf diese: Sie beide brauchen, und Sie sollen nicht setzen die Ausnahme vor der Assertion. Ich bin mir nicht sicher, was "nicht am selben Ort" bedeutet. Auch hier weigere ich mich zu interpretieren und sage nur meine Gedanken / Vorlieben: Setzen Sie eine oder mehrere Aussagen, um die Voraussetzungen zu überprüfen, bevor eine Operation beginnt, oder um die Nachbedingungen nach der Operation zu überprüfen. Überprüfen Sie neben den Behauptungen und auch danach, ob etwas schief geht und Ausnahmen auslösen muss.
Superjos
52

Vom Code abgeschlossen

8 Defensive Programmierung

8.2 Behauptungen

Eine Zusicherung ist Code, der während der Entwicklung verwendet wird - normalerweise eine Routine oder ein Makro -, mit dem sich ein Programm während der Ausführung selbst überprüfen kann. Wenn eine Behauptung wahr ist, bedeutet dies, dass alles wie erwartet funktioniert. Wenn es falsch ist, bedeutet dies, dass ein unerwarteter Fehler im Code festgestellt wurde. Wenn das System beispielsweise davon ausgeht, dass eine Kundeninformationsdatei niemals mehr als 50.000 Datensätze enthält, enthält das Programm möglicherweise die Behauptung, dass die Anzahl der Datensätze weniger als oder gleich 50.000 beträgt. Solange die Anzahl der Datensätze kleiner oder gleich 50.000 ist, wird die Behauptung still sein. Wenn es jedoch auf mehr als 50.000 Datensätze stößt, wird lautstark "behauptet", dass ein Fehler im Programm vorliegt.

Behauptungen sind besonders nützlich in großen, komplizierten Programmen und in Programmen mit hoher Zuverlässigkeit. Sie ermöglichen es Programmierern, nicht übereinstimmende Schnittstellenannahmen, Fehler, die sich beim Ändern von Code einschleichen, schneller auszublenden usw.

Eine Behauptung benötigt normalerweise zwei Argumente: einen booleschen Ausdruck, der die Annahme beschreibt, die wahr sein soll, und eine Meldung, die angezeigt werden soll, wenn dies nicht der Fall ist.

(…)

Normalerweise möchten Sie nicht, dass Benutzer Bestätigungsnachrichten im Produktionscode sehen. Aussagen sind in erster Linie für die Entwicklung und Wartung bestimmt. Zusicherungen werden normalerweise zur Entwicklungszeit in den Code kompiliert und für die Produktion aus dem Code kompiliert. Während der Entwicklung werden durch Behauptungen widersprüchliche Annahmen, unerwartete Bedingungen, an Routinen übergebene schlechte Werte usw. beseitigt. Während der Produktion werden sie aus dem Code kompiliert, damit die Zusicherungen die Systemleistung nicht beeinträchtigen.

Juan
quelle
7
Was passiert also, wenn eine in der Produktion gefundene Kundeninformationsdatei mehr als 50.000 Datensätze enthält? Wenn die Behauptung aus Produktionscode kompiliert wird und diese Situation nicht anders behandelt wird, ist dies dann kein Problem?
DavidRR
1
@ DavidRR Ja in der Tat. Sobald jedoch die Produktion ein Problem signalisiert und ein Entwickler (der diesen Code möglicherweise nicht gut kennt) das Problem debuggt, schlägt die Bestätigung fehl und der Entwickler weiß sofort, dass das System nicht wie beabsichtigt verwendet wird.
Marc
48

FWIW ... Ich finde, dass meine öffentlichen Methoden dazu neigen, das if () { throw; }Muster zu verwenden, um sicherzustellen, dass die Methode korrekt aufgerufen wird. Meine privaten Methoden tendieren dazu, zu verwenden Debug.Assert().

Die Idee ist, dass ich mit meinen privaten Methoden diejenige bin, die unter Kontrolle ist. Wenn ich also anfange, eine meiner eigenen privaten Methoden mit falschen Parametern aufzurufen, habe ich irgendwo meine eigene Annahme gebrochen - ich hätte es nie bekommen sollen in diesen Zustand. In der Produktion sollten diese privaten Behauptungen im Idealfall unnötige Arbeit sein, da ich meinen internen Zustand gültig und konsistent halten soll. Kontrast zu Parametern für öffentliche Methoden, die zur Laufzeit von jedem aufgerufen werden können: Ich muss dort noch Parametereinschränkungen durch Auslösen von Ausnahmen erzwingen.

Außerdem können meine privaten Methoden immer noch Ausnahmen auslösen, wenn zur Laufzeit etwas nicht funktioniert (Netzwerkfehler, Datenzugriffsfehler, fehlerhafte Daten, die von einem Drittanbieter-Dienst abgerufen wurden usw.). Meine Behauptungen sind nur da, um sicherzustellen, dass ich meine eigenen internen Annahmen über den Zustand des Objekts nicht gebrochen habe.

Nicholas Piasecki
quelle
3
Dies ist eine sehr klare Beschreibung einer guten Praxis und gibt eine sehr vernünftige Antwort auf die gestellte Frage.
Casper Leon Nielsen
42

Verwenden Sie Asserts, um Entwicklerannahmen und Ausnahmen zu überprüfen, um Umgebungsannahmen zu überprüfen.

Justin R.
quelle
31

Wenn ich du wäre, würde ich tun:

Debug.Assert(val != null);
if ( val == null )
    throw new exception();

Oder um eine wiederholte Zustandsprüfung zu vermeiden

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
Mark Ingram
quelle
5
Wie löst dies das Problem? Damit wird die debug.assert sinnlos.
Quibblesome
43
Nein, tut es nicht - es bricht an dem Punkt in Code ein, kurz bevor die Ausnahme ausgelöst wird. Wenn Sie irgendwo anders in Ihrem Code versuchen / fangen, bemerken Sie möglicherweise nicht einmal die Ausnahme!
Mark Ingram
2
+1 Ich hatte viele Probleme, bei denen Leute einfach versuchten, Ausnahmen zu fangen, ohne etwas zu tun, so dass das Verfolgen von Fehlern ein Problem war
dance2die
5
Ich nehme an, es gibt Fälle, in denen Sie dies tun möchten, aber Sie sollten niemals eine allgemeine Ausnahme machen!
Casebash
8
@MarkIngram -1 zu Ihrer Antwort und +1 zu Ihrem Kommentar, der dies rechtfertigt. Dies ist ein netter Trick für bestimmte besondere Umstände, aber es scheint eine bizarre Sache zu sein, die im Allgemeinen für alle Validierungen zu tun ist.
Mark Amery
24

Wenn Sie Asserts in Ihrem Produktionscode haben möchten (dh Release-Builds), können Sie Trace.Assert anstelle von Debug.Assert verwenden.

Dies erhöht natürlich den Overhead Ihrer ausführbaren Produktionsdatei.

Auch wenn Ihre Anwendung im Benutzeroberflächenmodus ausgeführt wird, wird standardmäßig das Dialogfeld "Bestätigung" angezeigt, was für Ihre Benutzer möglicherweise etwas beunruhigend ist.

Sie können dieses Verhalten überschreiben, indem Sie den DefaultTraceListener entfernen: Lesen Sie die Dokumentation zu Trace.Listeners in MSDN.

Zusammenfassend,

  • Verwenden Sie Debug.Assert großzügig, um Fehler in Debug-Builds zu erkennen.

  • Wenn Sie Trace.Assert im Benutzeroberflächenmodus verwenden, möchten Sie wahrscheinlich den DefaultTraceListener entfernen, um Benutzer nicht zu beunruhigen.

  • Wenn die Bedingung, die Sie testen, von Ihrer App nicht verarbeitet werden kann, ist es wahrscheinlich besser, eine Ausnahme auszulösen, um sicherzustellen, dass die Ausführung nicht fortgesetzt wird. Beachten Sie, dass ein Benutzer eine Behauptung ignorieren kann.

Joe
quelle
1
+1 für den Hinweis auf die entscheidende Unterscheidung zwischen Debug.Assert und Trace.Assert, da das OP speziell nach Produktionscode gefragt hat.
Jon Coombs
21

Asserts werden verwendet, um Programmiererfehler (Ihren) und nicht Benutzerfehler abzufangen. Sie sollten nur verwendet werden, wenn keine Chance besteht, dass ein Benutzer den Assert auslöst. Wenn Sie beispielsweise eine API schreiben, sollten Asserts nicht verwendet werden, um zu überprüfen, ob ein Argument in einer Methode, die ein API-Benutzer aufrufen könnte, nicht null ist. Es kann jedoch in einer privaten Methode verwendet werden, die nicht als Teil Ihrer API verfügbar gemacht wird, um zu behaupten, dass IHR Code niemals ein Nullargument übergibt, wenn dies nicht beabsichtigt ist.

Normalerweise bevorzuge ich Ausnahmen gegenüber Behauptungen, wenn ich mir nicht sicher bin.

user19113
quelle
11

Zusamenfassend

Asserts werden für Wachen und zur Überprüfung von Design by Contract-Einschränkungen verwendet, nämlich:

  • Assertssollte nur für Debug- und Nicht-Produktions-Builds sein. Asserts werden vom Compiler in Release-Builds normalerweise ignoriert.
  • Asserts kann nach Fehlern / unerwarteten Bedingungen suchen, die in der Kontrolle Ihres Systems liegen
  • Asserts sind KEIN Mechanismus zur Erstlinienvalidierung von Benutzereingaben oder Geschäftsregeln
  • Assertssollte nicht verwendet werden, um unerwartete Umgebungsbedingungen (die außerhalb der Kontrolle des Codes liegen) zu erkennen, z. B. Speichermangel, Netzwerkfehler, Datenbankfehler usw. Obwohl selten, sind diese Bedingungen zu erwarten (und Ihr App-Code kann Probleme wie nicht beheben Hardwarefehler oder Erschöpfung der Ressourcen). In der Regel werden Ausnahmen ausgelöst. Ihre Anwendung kann dann entweder Korrekturmaßnahmen ergreifen (z. B. einen Datenbank- oder Netzwerkvorgang wiederholen, zwischengespeicherten Speicher freigeben) oder ordnungsgemäß abbrechen, wenn die Ausnahme nicht behandelt werden kann.
  • Eine fehlgeschlagene Zusicherung sollte für Ihr System schwerwiegend sein. Versuchen Sie also im Gegensatz zu einer Ausnahme nicht, einen Fehler abzufangen oder zu behandeln. AssertsIhr Code wird in unerwartetem Gebiet ausgeführt. Stack-Traces und Crash-Dumps können verwendet werden, um festzustellen, was schief gelaufen ist.

Behauptungen haben enormen Nutzen:

  • Hilft bei der Suche nach fehlender Validierung von Benutzereingaben oder vorgelagerten Fehlern im Code höherer Ebene.
  • Behauptungen in der Codebasis vermitteln dem Leser klar die im Code getroffenen Annahmen
  • Assert wird zur Laufzeit in DebugBuilds überprüft .
  • Sobald der Code ausführlich getestet wurde, wird durch die Neuerstellung des Codes als Release der Leistungsaufwand für die Überprüfung der Annahme verringert (mit dem Vorteil, dass ein späterer Debug-Build die Überprüfungen bei Bedarf immer zurücksetzt).

... Mehr Details

Debug.Assertdrückt eine Bedingung aus, die vom Rest des Codeblocks innerhalb der Steuerung des Programms über den Zustand angenommen wurde. Dies kann den Status der bereitgestellten Parameter, den Status der Mitglieder einer Klasseninstanz oder die Tatsache umfassen, dass die Rückgabe eines Methodenaufrufs in seinem kontrahierten / entworfenen Bereich liegt. In der Regel sollten Asserts den Thread / Prozess / das Programm mit allen erforderlichen Informationen (Stack Trace, Crash Dump usw.) zum Absturz bringen, da sie auf das Vorhandensein eines Fehlers oder einer nicht berücksichtigten Bedingung hinweisen, für die nicht entwickelt wurde (dh nicht versuchen, oder zu fangen) Behandeln von Assertionsfehlern), mit einer möglichen Ausnahme, wenn eine Assertion selbst mehr Schaden verursachen könnte als der Fehler (z. B. würden Fluglotsen keine YSOD wünschen, wenn ein Flugzeug unter Wasser geht, obwohl es umstritten ist, ob ein Debug-Build bereitgestellt werden soll Produktion ...)

Wann sollten Sie verwenden? Asserts? - Zu jedem Zeitpunkt in einem System, einer Bibliotheks-API oder einem Dienst, an dem die Eingaben in eine Funktion oder einen Status einer Klasse als gültig angenommen werden (z. B. wenn die Benutzereingabe in der Präsentationsebene eines Systems bereits validiert wurde Die Klassen Business und Data Tier gehen normalerweise davon aus, dass bereits Nullprüfungen, Bereichsprüfungen, Zeichenfolgenlängenprüfungen usw. bei der Eingabe durchgeführt wurden. - Häufige AssertÜberprüfungen umfassen, wo eine ungültige Annahme zu einer Nullobjekt-Dereferenzierung, einem Nullteiler, einem numerischen oder datumsarithmetischen Überlauf und einem allgemeinen Außerband führen würde / nicht für das Verhalten ausgelegt ist (z. B. wenn ein 32-Bit-Int verwendet wurde, um das Alter eines Menschen zu modellieren Es wäre ratsam, Assertdass das Alter tatsächlich zwischen 0 und 125 liegt oder so - Werte von -100 und 10 ^ 10 wurden nicht für) entwickelt.

.Net - Code Contracts
in .NET Stack - Code Verträgen kann verwendet werden , zusätzlich zu oder als Alternative zu verwenden Debug.Assert. Codeverträge können die Statusprüfung weiter formalisieren und dabei helfen, Verstöße gegen Annahmen zum Zeitpunkt der Kompilierung (oder kurz danach, wenn sie als Hintergrundprüfung in einer IDE ausgeführt werden) zu erkennen.

Zu den verfügbaren DBC-Prüfungen (Design by Contract) gehören:

  • Contract.Requires - Vertragliche Voraussetzungen
  • Contract.Ensures - Vertraglich vereinbarte Nachbedingungen
  • Invariant - Drückt eine Annahme über den Zustand eines Objekts an allen Punkten seiner Lebensdauer aus.
  • Contract.Assumes - beruhigt den statischen Prüfer, wenn nicht vertraglich vereinbarte Methoden aufgerufen werden.
StuartLC
quelle
Leider sind Code-Verträge so gut wie tot, da MS die Entwicklung eingestellt hat.
Mike Lowery
10

Meistens nie in meinem Buch. Wenn Sie in den allermeisten Fällen überprüfen möchten, ob alles in Ordnung ist, werfen Sie, wenn dies nicht der Fall ist.

Was ich nicht mag, ist die Tatsache, dass sich ein Debug-Build funktional von einem Release-Build unterscheidet. Wenn eine Debug-Bestätigung fehlschlägt, die Funktionalität jedoch in der Version funktioniert, wie macht das dann Sinn? Es ist sogar noch besser, wenn der Asserter das Unternehmen schon lange verlassen hat und niemand diesen Teil des Codes kennt. Dann müssen Sie einen Teil Ihrer Zeit damit verbringen, das Problem zu untersuchen, um festzustellen, ob es wirklich ein Problem ist oder nicht. Wenn es ein Problem ist, warum wirft die Person dann nicht überhaupt?

Für mich bedeutet dies, dass Sie mit Debug.Asserts das Problem an eine andere Person weiterleiten und sich selbst mit dem Problem befassen. Wenn etwas der Fall sein soll und es nicht ist, dann werfen.

Ich denke, es gibt möglicherweise leistungskritische Szenarien, in denen Sie Ihre Asserts optimieren möchten, und sie sind dort nützlich, aber ich bin noch nicht auf ein solches Szenario gestoßen.

Streit
quelle
4
Ihre Antwort verdient jedoch einige Verdienste, da Sie einige häufig geäußerte Bedenken in Bezug auf sie, die Tatsache, dass sie die Debugging-Sitzung unterbrechen, und die Möglichkeit eines falsch positiven Ergebnisses hervorheben. Sie vermissen jedoch einige Feinheiten und schreiben "Optimize Away Asserts" - was nur darauf beruhen kann, dass das Auslösen einer Ausnahme und das Ausführen von debug.assert dasselbe sind. Es ist nicht so, sie dienen unterschiedlichen Zwecken und Eigenschaften, wie Sie in einigen der positiv bewerteten Antworten sehen können. Dw
Casper Leon Nielsen
+1 für "Was ich nicht mag, ist die Tatsache, dass sich ein Debug-Build funktional von einem Release-Build unterscheidet. Wenn ein Debug-Assert fehlschlägt, die Funktionalität jedoch im Release funktioniert, wie macht das dann Sinn?" System.Diagnostics.Trace.Assert()Wird in .NET sowohl in einem Release-Build als auch in einem Debug-Build ausgeführt.
DavidRR
7

Nach dem IDesign Standard sollten Sie

Behaupten Sie jede Annahme. Im Durchschnitt ist jede fünfte Zeile eine Behauptung.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

Als Haftungsausschluss sollte ich erwähnen, dass ich es nicht praktisch gefunden habe, diese IRL zu implementieren. Aber das ist ihr Standard.

Devlord
quelle
Sieht so aus, als würde Juval Lowy sich gerne selbst zitieren.
Devlord
6

Verwenden Sie Zusicherungen nur in Fällen, in denen die Prüfung für Release-Builds entfernt werden soll. Denken Sie daran, dass Ihre Zusicherungen nicht ausgelöst werden, wenn Sie nicht im Debug-Modus kompilieren.

In Anbetracht Ihres Check-for-Null-Beispiels kann ich eine Zusicherung verwenden, wenn dies in einer nur internen API enthalten ist. Wenn es sich um eine öffentliche API handelt, würde ich definitiv das explizite Überprüfen und Werfen verwenden.

Derek Park
quelle
In .NET kann System.Diagnostics.Trace.Assert()eine Assertion in einem Release- (Produktions-) Build ausgeführt werden.
DavidRR
Code-Analyseregel CA1062: Für die Überprüfung von Argumenten öffentlicher Methoden muss ein Argument überprüft werden, nullwenn: "Eine extern sichtbare Methode eines ihrer Referenzargumente dereferenziert, ohne zu überprüfen, ob dieses Argument null ist ." In einer solchen Situation sollte die Methode oder Eigenschaft werfen ArgumentNullException.
DavidRR
6

Alle Asserts sollten Code sein, der optimiert werden kann für:

Debug.Assert(true);

Weil es etwas überprüft, von dem Sie bereits angenommen haben, dass es wahr ist. Z.B:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

Oben gibt es drei verschiedene Ansätze für Nullparameter. Der erste akzeptiert es als zulässig (es tut einfach nichts). Der zweite löst eine Ausnahme aus, die der aufrufende Code verarbeiten soll (oder nicht, was zu einer Fehlermeldung führt). Der dritte geht davon aus, dass dies unmöglich passieren kann, und behauptet, dass dies der Fall ist.

Im ersten Fall gibt es kein Problem.

Im zweiten Fall gibt es ein Problem mit dem aufrufenden Code - er hätte nicht anrufen sollen GetFirstAndConsume mit null werden sollen, daher wird eine Ausnahme zurückgegeben.

Im dritten Fall gibt es ein Problem mit diesem Code, da bereits en != nullvor dem Aufruf überprüft werden sollte , dass es sich nicht um einen Fehler handelt. Oder mit anderen Worten, es sollte Code sein, der theoretisch optimiert werden könnte Debug.Assert(true), sicne en != nullsollte es immer sein true!

Jon Hanna
quelle
1
Was passiert also en == nullim dritten Fall in der Produktion? Wollen Sie damit sagen, dass dies in der Produktion niemals passieren en == nullkann (da das Programm gründlich getestet wurde)? Wenn ja, dann dient zumindest als Alternative zu einem Kommentar. Wenn zukünftige Änderungen vorgenommen werden, hat dies natürlich auch weiterhin einen Wert für die Erkennung einer möglichen Regression. Debug.Assert(en != null)
DavidRR
@DavidRR, ich behaupte tatsächlich, dass es niemals null sein kann, ebenso wie die Behauptung im Code, daher der Name. Ich könnte natürlich falsch liegen oder durch eine Änderung falsch gemacht werden, und das ist der Wert des Assert-Aufrufs.
Jon Hanna
1
Aufrufe von Debug.Assert()werden in einem Release-Build entfernt. Wenn Sie sich also irren , werden Sie es im dritten Fall in der Produktion nicht wissen (vorausgesetzt, Sie verwenden ein Release, das in der Produktion erstellt wurde). Das Verhalten des ersten und zweiten Falls ist jedoch in Debug- und Release-Builds identisch.
DavidRR
@DavidRR, was es nur dann angemessen macht, wenn ich der Meinung bin, dass es nicht passieren kann, da es sich wiederum um eine Tatsachenbehauptung handelt, nicht um eine Überprüfung des Zustands. Natürlich ist es auch sinnlos, wenn Sie die Behauptung haben, einen Fehler haben, den es abfangen würde, und diesen Fall beim Testen dennoch nie treffen.
Jon Hanna
4

Ich dachte, ich würde vier weitere Fälle hinzufügen, in denen Debug.Assert die richtige Wahl sein kann.

1) Was ich hier nicht erwähnt habe, ist die zusätzliche konzeptionelle Abdeckung, die Asserts während automatisierter Tests bieten können . Als einfaches Beispiel:

Wenn ein übergeordneter Anrufer von einem Autor geändert wird, der glaubt, den Umfang des Codes erweitert zu haben, um zusätzliche Szenarien zu behandeln, schreiben sie im Idealfall (!) Komponententests, um diese neue Bedingung abzudecken. Es kann dann sein, dass der vollständig integrierte Code gut zu funktionieren scheint.

Tatsächlich wurde jedoch ein subtiler Fehler eingeführt, der jedoch in den Testergebnissen nicht festgestellt wurde. Der Angerufene hat sich nicht deterministisch in diesem Fall und nur geschieht das erwartete Ergebnis zu liefern. Oder vielleicht hat es einen Rundungsfehler ergeben, der unbemerkt blieb. Oder verursachte einen Fehler, der an anderer Stelle gleichermaßen ausgeglichen wurde. Oder nicht nur den angeforderten Zugriff gewährt, sondern auch zusätzliche Berechtigungen, die nicht gewährt werden sollten. Etc.

Zu diesem Zeitpunkt können die im Angerufenen enthaltenen Debug.Assert () - Anweisungen in Verbindung mit dem neuen Fall (oder Randfall), der durch Komponententests ausgelöst wurde, während des Tests eine unschätzbare Benachrichtigung darüber liefern, dass die Annahmen des ursprünglichen Autors ungültig gemacht wurden, und der Code sollte dies nicht tun ohne zusätzliche Überprüfung veröffentlicht werden. Behauptungen mit Unit-Tests sind die perfekten Partner.

2) Darüber hinaus sind einige Tests einfach zu schreiben, jedoch teuer und angesichts der anfänglichen Annahmen unnötig . Beispielsweise:

Wenn auf ein Objekt nur von einem bestimmten gesicherten Einstiegspunkt aus zugegriffen werden kann, sollte von jeder Objektmethode eine zusätzliche Abfrage an eine Netzwerkrechtsdatenbank erfolgen, um sicherzustellen, dass der Anrufer über Berechtigungen verfügt? Sicher nicht. Möglicherweise umfasst die ideale Lösung das Zwischenspeichern oder eine andere Erweiterung von Funktionen, aber das Design erfordert dies nicht. Ein Debug.Assert () zeigt sofort an, wenn das Objekt an einen unsicheren Einstiegspunkt angehängt wurde.

3) In einigen Fällen verfügt Ihr Produkt möglicherweise nicht über eine hilfreiche diagnostische Interaktion für alle oder einen Teil seiner Vorgänge, wenn es im Release-Modus bereitgestellt wird . Beispielsweise:

Angenommen, es handelt sich um ein eingebettetes Echtzeitgerät. Das Auslösen von Ausnahmen und der Neustart, wenn ein fehlerhaftes Paket auftritt, ist kontraproduktiv. Stattdessen kann das Gerät von einem bestmöglichen Betrieb profitieren, bis es Rauschen in seiner Ausgabe erzeugt. Es verfügt möglicherweise auch nicht über eine Benutzeroberfläche, ein Protokollierungsgerät oder ist für Benutzer im Release-Modus überhaupt nicht physisch zugänglich. Die Erkennung von Fehlern erfolgt am besten durch die Bewertung derselben Ausgabe. In diesem Fall sind liberale Behauptungen und gründliche Tests vor der Veröffentlichung wertvoller als Ausnahmen.

4) Schließlich sind einige Tests nur deshalb nicht erforderlich, weil der Angerufene als äußerst zuverlässig wahrgenommen wird . In den meisten Fällen wurden je mehr wiederverwendbarer Code verwendet wird, desto mehr Anstrengungen unternommen, um ihn zuverlässig zu machen. Daher ist es üblich, Ausnahmen für unerwartete Parameter von Anrufern zu verwenden, aber für unerwartete Ergebnisse von Anrufern zu bestätigen. Beispielsweise:

Wenn eine Kernoperation String.Findangibt, dass sie a zurückgibt, -1wenn die Suchkriterien nicht gefunden werden, können Sie möglicherweise eine Operation sicher ausführen, anstatt drei. Wenn es jedoch tatsächlich zurückgegeben wird -2, haben Sie möglicherweise keine vernünftige Vorgehensweise. Es wäre nicht hilfreich, die einfachere Berechnung durch eine zu ersetzen, die separat auf einen -1Wert prüft und in den meisten Release-Umgebungen nicht zumutbar ist, um Ihren Code mit Tests zu verunreinigen, die sicherstellen, dass die Kernbibliotheken wie erwartet funktionieren. In diesem Fall sind Asserts ideal.

Shannon
quelle
4

Zitat aus dem Pragmatischen Programmierer: Vom Gesellen zum Meister

Lassen Sie die Zusicherungen aktiviert

Es gibt ein weit verbreitetes Missverständnis über Behauptungen, das von den Leuten verbreitet wird, die Compiler und Sprachumgebungen schreiben. Es geht ungefähr so:

Behauptungen erhöhen den Overhead des Codes. Da sie nach Dingen suchen, die niemals passieren sollten, werden sie nur durch einen Fehler im Code ausgelöst. Sobald der Code getestet und ausgeliefert wurde, werden sie nicht mehr benötigt und sollten deaktiviert werden, damit der Code schneller ausgeführt wird. Assertions sind eine Debugging-Funktion.

Hier gibt es zwei offensichtlich falsche Annahmen. Erstens gehen sie davon aus, dass beim Testen alle Fehler gefunden werden. In der Realität ist es unwahrscheinlich, dass Sie für ein komplexes Programm auch nur einen winzigen Prozentsatz der Permutationen testen, die Ihr Code durchlaufen wird (siehe Rücksichtsloses Testen).

Zweitens vergessen die Optimisten, dass Ihr Programm in einer gefährlichen Welt läuft. Während des Tests nagen Ratten wahrscheinlich nicht an einem Kommunikationskabel, jemand, der ein Spiel spielt, erschöpft den Speicher nicht und Protokolldateien füllen die Festplatte nicht. Diese Dinge können passieren, wenn Ihr Programm in einer Produktionsumgebung ausgeführt wird. Ihre erste Verteidigungslinie prüft auf mögliche Fehler, und Ihre zweite verwendet Behauptungen, um zu versuchen, diejenigen zu erkennen, die Sie verpasst haben.

Das Ausschalten von Behauptungen, wenn Sie ein Programm an die Produktion liefern, ist wie das Überqueren eines Hochseils ohne Netz, weil Sie es in der Praxis einmal geschafft haben . Es gibt einen dramatischen Wert, aber es ist schwierig, eine Lebensversicherung abzuschließen.

Auch wenn Sie Leistungsprobleme haben, deaktivieren Sie nur die Behauptungen, die Sie wirklich getroffen haben .

Teoman Shipahi
quelle
2

Sie sollten immer den zweiten Ansatz verwenden (Ausnahmen auslösen).

Auch wenn Sie in der Produktion sind (und ein Release-Build haben), ist es besser, eine Ausnahme auszulösen (und die App im schlimmsten Fall abstürzen zu lassen), als mit ungültigen Werten zu arbeiten und möglicherweise die Daten Ihres Kunden zu zerstören (was Tausende kosten kann) von Dollar).

Thomas Danecker
quelle
1
Nein, genau wie einige andere Antworten hier: Sie verstehen den Unterschied nicht wirklich, also entscheiden Sie sich für eines der Angebote und stellen dabei eine falsche Zweiteilung zwischen ihnen her. Dw
Casper Leon Nielsen
3
Dies ist die einzig richtige Antwort auf dieser Liste IMO. Entlassen Sie es nicht so einfach, Casper. Debug Assert ist ein Anti-Pattern. Wenn es zur Debug-Zeit invariant ist, ist es zur Laufzeit invariant. Wenn Sie zulassen, dass Ihre App mit einer gebrochenen Invariante fortfährt, befinden Sie sich in einem nicht deterministischen Zustand und haben möglicherweise schlimmere Probleme als ein Absturz. IMO ist es besser, in beiden Builds denselben Code zu haben, der bei fehlerhaften Verträgen schnell fehlschlägt, und dann eine robuste Fehlerbehandlung auf oberster Ebene zu implementieren. Beispiel: Isolieren Sie Komponenten und implementieren Sie die Möglichkeit, sie neu zu starten (wie ein Tab, der in einem Browser abstürzt, stürzt nicht der gesamte Browser ab).
justin.m.chase
1
Es kann hilfreich sein, Trace.Assert hier in Ihre Diskussion aufzunehmen, da es nicht mit demselben Argument verworfen werden kann.
Jon Coombs
0

Sie sollten Debug.Assert verwenden, um in Ihren Programmen auf logische Fehler zu testen. Der Complier kann Sie nur über Syntaxfehler informieren. Verwenden Sie daher unbedingt Assert-Anweisungen, um auf logische Fehler zu testen. Zum Beispiel ein Programm testen, das Autos verkauft, bei denen nur blaue BMWs einen Rabatt von 15% erhalten sollten. Der Complier kann Ihnen nichts darüber sagen, ob Ihr Programm dies logisch korrekt ausführt, eine Assert-Anweisung jedoch.

Orlando Calresian
quelle
2
Entschuldigung, aber Ausnahmen machen alle die gleichen Dinge, so dass diese Antwort nicht die eigentliche Frage anspricht.
Roman Starkov
0

Ich habe die Antworten hier gelesen und dachte, ich sollte eine wichtige Unterscheidung hinzufügen. Es gibt zwei sehr unterschiedliche Arten, wie Asserts verwendet werden. Eine davon ist eine temporäre Entwicklerverknüpfung für "Dies sollte eigentlich nicht passieren, wenn es mich wissen lässt, damit ich entscheiden kann, was zu tun ist", eine Art bedingter Haltepunkt für Fälle, in denen Ihr Programm fortgesetzt werden kann. Das andere ist eine Möglichkeit, Annahmen über gültige Programmzustände in Ihren Code aufzunehmen.

Im ersten Fall müssen die Aussagen nicht einmal im endgültigen Code enthalten sein. Sie sollten es Debug.Assertwährend der Entwicklung verwenden und können es entfernen, wenn es nicht mehr benötigt wird. Wenn Sie sie verlassen möchten oder wenn Sie vergessen, sie zu entfernen, ist dies kein Problem, da sie bei Release-Kompilierungen keine Konsequenzen haben.

Im zweiten Fall sind die Aussagen jedoch Teil des Codes. Sie behaupten, dass Ihre Annahmen wahr sind, und dokumentieren sie auch. In diesem Fall möchten Sie sie wirklich im Code belassen. Wenn sich das Programm in einem ungültigen Zustand befindet, sollte es nicht fortgesetzt werden dürfen. Wenn Sie sich den Leistungseinbruch nicht leisten könnten, würden Sie C # nicht verwenden. Einerseits kann es nützlich sein, in diesem Fall einen Debugger anhängen zu können. Auf der anderen Seite möchten Sie nicht, dass der Stack-Trace bei Ihren Benutzern angezeigt wird, und vielleicht noch wichtiger, dass sie ihn nicht ignorieren können. Außerdem wird es immer ignoriert, wenn es sich um einen Dienst handelt. Daher wäre es in der Produktion das richtige Verhalten, eine Ausnahme auszulösen und die normale Ausnahmebehandlung Ihres Programms zu verwenden, die dem Benutzer möglicherweise eine nette Nachricht anzeigt und die Details protokolliert.

Trace.Asserthat den perfekten Weg, um dies zu erreichen. Es wird in der Produktion nicht entfernt und kann mit app.config mit verschiedenen Listenern konfiguriert werden. Für die Entwicklung ist der Standardhandler also in Ordnung, und für die Produktion können Sie einen einfachen TraceListener wie unten erstellen, der eine Ausnahme auslöst und in der Produktionskonfigurationsdatei aktiviert.

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

Und in der Produktionskonfigurationsdatei:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>
AlexDev
quelle
-1

Ich weiß nicht, wie es in C # und .NET ist, aber in C funktioniert assert () nur, wenn es mit -DDEBUG kompiliert wurde - der Endbenutzer wird niemals assert () sehen, wenn es ohne kompiliert wird. Es ist nur für Entwickler. Ich benutze es sehr oft, es ist manchmal einfacher, Fehler zu verfolgen.

nicht vorhanden
quelle
-1

Ich würde sie nicht im Produktionscode verwenden. Ausnahmen auslösen, fangen und protokollieren.

Seien Sie auch in asp.net vorsichtig, da eine Bestätigung auf der Konsole angezeigt werden und die Anforderung (en) einfrieren kann.

mattlant
quelle