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();
language-agnostic
exception
testing
assertions
defensive-programming
Nicholas Mancuso
quelle
quelle
Antworten:
Beim Debuggen von Microsoft .NET 2.0-Anwendungen hat John Robbins einen großen Abschnitt über Behauptungen. Seine Hauptpunkte sind:
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.
quelle
Debug.Assert
vs.Trace.Assert
. Letzteres wird sowohl in einem Release-Build als auch in einem Debug-Build ausgeführt.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 keineDEBUG
Compilerkonstante), werden die Aufrufe vonDebug.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.quelle
Vom Code abgeschlossen
quelle
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 verwendenDebug.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.
quelle
Verwenden Sie Asserts, um Entwicklerannahmen und Ausnahmen zu überprüfen, um Umgebungsannahmen zu überprüfen.
quelle
Wenn ich du wäre, würde ich tun:
Oder um eine wiederholte Zustandsprüfung zu vermeiden
quelle
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.
quelle
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.
quelle
Zusamenfassend
Asserts
werden für Wachen und zur Überprüfung von Design by Contract-Einschränkungen verwendet, nämlich:Asserts
sollte 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 liegenAsserts
sind KEIN Mechanismus zur Erstlinienvalidierung von Benutzereingaben oder GeschäftsregelnAsserts
sollte 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.Asserts
Ihr 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:
Debug
Builds überprüft .... Mehr Details
Debug.Assert
drü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äufigeAssert
Ü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,Assert
dass 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 VoraussetzungenContract.Ensures
- Vertraglich vereinbarte NachbedingungenInvariant
- 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.quelle
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.
quelle
System.Diagnostics.Trace.Assert()
Wird in .NET sowohl in einem Release-Build als auch in einem Debug-Build ausgeführt.Nach dem IDesign Standard sollten Sie
Als Haftungsausschluss sollte ich erwähnen, dass ich es nicht praktisch gefunden habe, diese IRL zu implementieren. Aber das ist ihr Standard.
quelle
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.
quelle
System.Diagnostics.Trace.Assert()
eine Assertion in einem Release- (Produktions-) Build ausgeführt werden.null
wenn: "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 werfenArgumentNullException
.Alle Asserts sollten Code sein, der optimiert werden kann für:
Weil es etwas überprüft, von dem Sie bereits angenommen haben, dass es wahr ist. Z.B:
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 != null
vor 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önnteDebug.Assert(true)
, sicneen != null
sollte es immer seintrue
!quelle
en == null
im dritten Fall in der Produktion? Wollen Sie damit sagen, dass dies in der Produktion niemals passierenen == null
kann (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)
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.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.Find
angibt, dass sie a zurückgibt,-1
wenn 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-1
Wert 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.quelle
Zitat aus dem Pragmatischen Programmierer: Vom Gesellen zum Meister
quelle
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).
quelle
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.
quelle
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.Assert
wä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.Assert
hat 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.Und in der Produktionskonfigurationsdatei:
quelle
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.
quelle
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.
quelle