it-swarm.com.de

Sollte es Aussagen in Release Builds geben

Das Standardverhalten von assert in C++ besteht darin, in Release-Builds nichts zu tun. Ich gehe davon aus, dass dies aus Leistungsgründen und möglicherweise getan wird, um zu verhindern, dass Benutzer böse Fehlermeldungen sehen.

Ich würde jedoch argumentieren, dass Situationen, in denen ein assert ausgelöst, aber deaktiviert worden wäre, noch problematischer sind, da die Anwendung dann wahrscheinlich noch schlimmer abstürzen wird, weil eine Invariante gebrochen wurde.

Außerdem zählt das Leistungsargument für mich nur, wenn es sich um ein messbares Problem handelt. Die meisten asserts in meinem Code sind nicht viel komplexer als

assert(ptr != nullptr);

dies hat nur geringe Auswirkungen auf die meisten Codes.

Dies führt mich zu der Frage: Sollten Assertions (dh das Konzept, nicht die spezifische Implementierung) in Release-Builds aktiv sein? Warum nicht)?

Bitte beachten Sie, dass es bei dieser Frage nicht darum geht, wie Asserts in Release-Builds aktiviert werden (wie #undef _NDEBUG oder unter Verwendung einer selbst definierten Assert-Implementierung). Darüber hinaus geht es nicht darum, Asserts in Drittanbieter-/Standardbibliothekscode zu aktivieren, sondern in von mir kontrolliertem Code.

22
Nobody

Das klassische assert ist ein Tool aus der alten Standard-C-Bibliothek, nicht aus C++. Zumindest aus Gründen der Abwärtskompatibilität ist es in C++ weiterhin verfügbar.

Ich habe keine genaue Zeitleiste der C-Standardbibliotheken zur Hand, aber ich bin mir ziemlich sicher, dass assert kurz nach der Einführung von K & R C (um 1978) verfügbar war. In klassischem C muss zum Schreiben robuster Programme das Hinzufügen von NULL-Zeigertests und das Überprüfen der Array-Grenzen viel häufiger erfolgen als in C++. Das Brutto von NULL-Zeigertests kann vermieden werden, indem Referenzen und/oder intelligente Zeiger anstelle von Zeigern verwendet werden und std::vector, die Überprüfung der Array-Grenzen ist häufig nicht erforderlich. Darüber hinaus war der Performance-Hit von 1980 definitiv viel wichtiger als heute. Ich denke, das ist sehr wahrscheinlich der Grund, warum "assert" standardmäßig nur in Debug-Builds aktiv war.

Darüber hinaus ist für eine echte Fehlerbehandlung im Produktionscode eine Funktion, die nur eine Bedingung oder Invariante testet und das Programm zum Absturz bringt, wenn die Bedingung nicht erfüllt ist, in den meisten Fällen nicht flexibel genug. Für das Debuggen ist das wahrscheinlich in Ordnung, da derjenige, der das Programm ausführt und den Fehler beobachtet, normalerweise einen Debugger zur Hand hat, um zu analysieren, was passiert. Für Produktionscode muss eine sinnvolle Lösung jedoch eine Funktion oder ein Mechanismus sein, der/die

  • testet eine Bedingung (und stoppt die Ausführung in dem Bereich, in dem die Bedingung fehlschlägt)

  • gibt eine eindeutige Fehlermeldung aus, falls die Bedingung nicht erfüllt ist

  • ermöglicht dem äußeren Bereich, die Fehlermeldung aufzunehmen und an einen bestimmten Kommunikationskanal auszugeben. Dieser Kanal kann so etwas wie stderr, eine Standardprotokolldatei, ein Meldungsfeld in einem GUI-Programm, ein allgemeiner Rückruf zur Fehlerbehandlung, ein netzwerkfähiger Fehlerkanal oder was auch immer für die jeweilige Software am besten geeignet sein.

  • ermöglicht dem äußeren Bereich auf Einzelfallbasis zu entscheiden, ob das Programm ordnungsgemäß beendet oder fortgesetzt werden soll.

(Natürlich gibt es auch Situationen, in denen das sofortige Beenden des Programms im Falle einer nicht erfüllten Bedingung die einzig sinnvolle Option ist, aber in solchen Fällen sollte dies auch in einem Release-Build geschehen, nicht nur in einem Debug-Build.).

Da das klassische assert diese Funktionen nicht bietet, eignet es sich nicht für einen Release-Build, vorausgesetzt, der Release-Build wird in der Produktion bereitgestellt.

Jetzt können Sie sich fragen, warum es in der C-Standardbibliothek keine solche Funktion oder keinen solchen Mechanismus gibt, der diese Art von Flexibilität bietet. Tatsächlich gibt es in C++ einen Standardmechanismus mit all diesen Funktionen (und mehr), und Sie wissen es: Er heißt Ausnahmen.

In C ist es jedoch schwierig, einen guten allgemeinen Standardmechanismus für die Fehlerbehandlung mit allen genannten Funktionen zu implementieren, da keine Ausnahmen als Teil der Programmiersprache vorhanden sind. Daher haben die meisten C-Programme ihre eigenen Fehlerbehandlungsmechanismen mit Rückkehrcodes oder "goto" oder "Weitsprüngen" oder einer Mischung daraus. Dies sind oft pragmatische Lösungen, die zu der jeweiligen Art von Programm passen, aber nicht "allgemein genug" sind, um in die C-Standardbibliothek zu passen.

21
Doc Brown

Wenn Sie sich wünschen, dass Asserts in einer Version aktiviert wurden, haben Sie Asserts gebeten, den falschen Job auszuführen.

Der Punkt der Behauptungen ist, dass sie in einer Version nicht aktiviert sind. Dies ermöglicht das Testen von Invarianten während der Entwicklung mit Code, der sonst Gerüstcode sein müsste. Code, der vor der Veröffentlichung entfernt werden muss.

Wenn Sie etwas haben, von dem Sie glauben, dass es auch während der Veröffentlichung getestet werden sollte, schreiben Sie Code, der es testet. Das Konstrukt If throw Funktioniert sehr gut. Wenn Sie etwas anderes als die anderen Würfe sagen möchten, verwenden Sie einfach eine beschreibende Ausnahme, die sagt, was Sie sagen möchten.

Es ist nicht so, dass Sie nicht ändern können, wie Sie Asserts verwenden. Es ist so, dass Sie dadurch nichts Nützliches erhalten, den Erwartungen zuwiderlaufen und keine Möglichkeit mehr haben, das zu tun, was die Behauptungen tun sollten. Fügen Sie Tests hinzu, die in einer Version inaktiv sind.

Ich spreche nicht von einer bestimmten Implementierung von Assert, sondern vom Konzept einer Assertion. Ich möchte nicht behaupten, Leser zu behaupten oder zu verwirren. Ich wollte fragen, warum das überhaupt so ist. Warum gibt es keinen zusätzlichen release_assert? Ist das nicht nötig? Was ist der Grund für die Behauptung, bei der Veröffentlichung deaktiviert zu sein? - Niemand

Warum kein relase_assert? Ehrlich gesagt, weil Behauptungen nicht gut genug für die Produktion sind. Ja, es gibt ein Bedürfnis, aber nichts erfüllt dieses Bedürfnis gut. Oh sicher, Sie können Ihre eigenen entwerfen. Mechanisch benötigt Ihre Funktion throwIf nur einen Bool und eine Ausnahme zum Werfen. Und das kann Ihren Bedürfnissen entsprechen. Aber Sie schränken das Design wirklich ein. Aus diesem Grund wundert es mich nicht, dass es in Ihrer Sprachbibliothek kein System gibt, das wie eine Ausnahmebedingung wirkt. Es ist sicher nicht so, dass du es nicht kannst. Andere haben . Der Umgang mit dem Fall, dass etwas schief geht, macht für die meisten Programme 80% der Arbeit aus. Und bisher hat uns niemand eine gute Einheitslösung gezeigt. Ein effektiver Umgang mit diesen Fällen kann kompliziert werden. Wenn wir ein eingemachtes release_assert-System gehabt hätten, das unsere Anforderungen nicht erfüllt hätte, hätte es meiner Meinung nach mehr Schaden als Nutzen gebracht. Sie fragen nach einer guten Abstraktion, die bedeuten würde, dass Sie nicht über dieses Problem nachdenken müssten. Ich möchte auch eine, aber es sieht nicht so aus, als wären wir noch da.

Warum werden Asserts in der Version deaktiviert? Asserts wurden auf dem Höhepunkt des Alters des Gerüstcodes erstellt. Code, den wir entfernen mussten, weil wir wussten, dass wir ihn nicht in der Produktion haben wollten, aber wussten, dass wir ihn in der Entwicklung ausführen wollten, um Fehler zu finden. Asserts waren eine sauberere Alternative zum Muster if (DEBUG), mit der wir den Code verlassen, aber deaktivieren konnten. Dies war vor dem Start des Komponententests als Hauptmethode zur Trennung von Testcode und Produktionscode. Behauptungen werden auch heute noch von erfahrenen Unit-Testern verwendet, um die Erwartungen klar zu machen und um Fälle abzudecken, in denen sie immer noch besser sind als Unit-Tests.

Warum nicht einfach Debugging-Code in der Produktion belassen? Weil Produktionscode das Unternehmen nicht in Verlegenheit bringen, die Festplatte nicht formatieren, die Datenbank nicht beschädigen und dem Präsidenten keine bedrohlichen E-Mails senden darf. Kurz gesagt, es ist schön, Debugging-Code an einem sicheren Ort schreiben zu können, an dem Sie sich darüber keine Sorgen machen müssen.

16
candied_orange

Behauptungen sind ein Debugging-Tool, keine defensive Programmiertechnik. Wenn Sie unter allen Umständen eine Validierung durchführen möchten, führen Sie die Validierung durch, indem Sie eine Bedingung schreiben - oder erstellen Sie ein eigenes Makro, um die Boilerplate zu reduzieren.

4
amon

assert ist eine Form der Dokumentation, wie Kommentare. Wie Kommentare würden Sie sie normalerweise nicht an Kunden versenden - sie gehören nicht zum Release-Code.

Das Problem mit Kommentaren ist jedoch, dass sie veraltet sein können und dennoch belassen werden. Deshalb sind Asserts gut - sie werden im Debug-Modus überprüft. Wenn die Behauptung veraltet ist, erkennen Sie sie schnell und wissen immer noch, wie Sie die Behauptung korrigieren können. Dieser Kommentar, der vor 3 Jahren veraltet war? Jeder kann es erraten.

4
MSalters

Wenn Sie nicht möchten, dass ein "Assert" deaktiviert wird, können Sie eine einfache Funktion schreiben, die einen ähnlichen Effekt hat:

void fail_if(bool b) {if(!b) std::abort();}

Das heißt, assert ist für Tests vorgesehen, bei denen Sie do möchten, dass sie im ausgelieferten Produkt verschwinden. Wenn Sie möchten, dass dieser Test Teil des definierten Verhaltens des Programms ist, ist assert das falsche Werkzeug.

3
Nicol Bolas

Es ist sinnlos zu streiten, was Assert tun soll, es tut, was es tut. Wenn Sie etwas anderes wollen, schreiben Sie Ihre eigene Funktion. Zum Beispiel habe ich Assert, das im Debugger stoppt oder nichts tut, ich habe AssertFatal, das die App zum Absturz bringt, ich habe Bool-Funktionen Asserted und AssertionFailed, die das Ergebnis bestätigen und zurückgeben, damit ich die Situation sowohl bestätigen als auch behandeln kann.

Bei unerwarteten Problemen müssen Sie entscheiden, wie Entwickler und Benutzer am besten damit umgehen können.

3
gnasher729

Wie andere betonten, ist assert Ihre letzte Bastion der Verteidigung gegen Programmiererfehler, die niemals passieren sollten. Es handelt sich um Gesundheitsprüfungen, die zum Zeitpunkt des Versands hoffentlich nicht links und rechts fehlschlagen sollten.

Es wurde auch entwickelt , um in stabilen Release-Builds weggelassen zu werden, aus welchen Gründen auch immer die Entwickler es für nützlich halten: Ästhetik, Leistung, was auch immer sie wollen. Es ist Teil dessen, was einen Debug-Build von einem Release-Build trennt, und per Definition enthält ein Release-Build keine solchen Aussagen. Es gibt also eine Subversion des Designs, wenn Sie den analogen "Release-Build mit vorhandenen Assertions" veröffentlichen möchten, der ein Versuch wäre, einen Release-Build mit zu erstellen eine _DEBUG Präprozessordefinition und keine NDEBUG definiert; Es ist nicht mehr wirklich ein Release-Build.

Das Design erstreckt sich sogar bis in die Standardbibliothek. Als sehr einfaches Beispiel unter zahlreichen werden viele Implementierungen von std::vector::operator[]assert eine Überprüfung der Integrität durchführen, um sicherzustellen, dass Sie den Vektor nicht außerhalb der Grenzen überprüfen. Und die Standardbibliothek wird viel, viel schlechter funktionieren, wenn Sie solche Überprüfungen in einem Release-Build aktivieren. Ein Benchmark von vector mit operator[] Und einem Füllfaktor mit solchen Zusicherungen für ein einfaches altes dynamisches Array zeigt häufig, dass das dynamische Array erheblich schneller ist, bis Sie solche Überprüfungen deaktivieren, sodass sie häufig Auswirkungen haben Leistung in weit, weit von trivialen Möglichkeiten. Eine Nullzeigerprüfung hier und eine Prüfung außerhalb der Grenzen dort können tatsächlich zu einem enormen Aufwand werden, wenn solche Prüfungen millionenfach über jeden Frame in kritischen Schleifen vor dem Code angewendet werden, so einfach wie das Dereferenzieren eines intelligenten Zeigers oder der Zugriff auf ein Array.

Sie wünschen sich also höchstwahrscheinlich ein anderes Tool für den Job und eines, das nicht für Release-Builds vorgesehen ist, wenn Sie Release-Builds wünschen, die solche Sicherheitsüberprüfungen in Schlüsselbereichen durchführen. Am nützlichsten finde ich persönlich die Protokollierung. In diesem Fall wird es viel einfacher, wenn ein Benutzer einen Fehler meldet, wenn er ein Protokoll anfügt, und die letzte Zeile des Protokolls gibt mir einen großen Hinweis darauf, wo der Fehler aufgetreten ist und was er sein könnte. Wenn ich dann ihre Schritte in einem Debug-Build reproduziere, kann es ebenfalls zu einem Assertionsfehler kommen, und dieser Assertionsfehler gibt mir weitere wichtige Hinweise, um meine Zeit zu optimieren. Da die Protokollierung jedoch relativ teuer ist, verwende ich sie nicht, um Sanitätsprüfungen auf extrem niedriger Ebene durchzuführen, z. B. um sicherzustellen, dass in einer generischen Datenstruktur nicht außerhalb der Grenzen auf ein Array zugegriffen wird. Ich verwende es in übergeordneten Kontexten mit mehr Informationen, die für die Domäne der Anwendung spezifisch sind.

Schließlich und in gewisser Übereinstimmung mit Ihnen konnte ich einen vernünftigen Fall sehen, in dem Sie Testern tatsächlich etwas übergeben möchten, das einem Debug-Build während eines Alphatests ähnelt, beispielsweise mit einer kleinen Gruppe von Alphatestern, die beispielsweise eine NDA unterzeichnet haben . Dort kann es das Alpha-Testen rationalisieren, wenn Sie Ihren Testern etwas anderes als einen vollständigen Release-Build mit einigen angehängten Debugging-Informationen sowie einigen Debug-/Entwicklungsfunktionen wie Tests, die sie ausführen können, und einer ausführlicheren Ausgabe während der Ausführung der Software übergeben. Ich habe zumindest einige große Spielefirmen gesehen, die solche Dinge für Alpha gemacht haben. Aber das ist für so etwas wie Alpha oder interne Tests, bei denen Sie wirklich versuchen, den Testern etwas anderes als einen Release-Build zu geben. Wenn Sie tatsächlich versuchen, einen Release-Build zu versenden, sollte per Definition nicht _DEBUG Definiert sein, da dies den Unterschied zwischen einem "Debug" - und einem "Release" -Build wirklich verwirrt.

Warum muss dieser Code vor der Veröffentlichung entfernt werden? Die Überprüfungen sind kein großer Leistungsverlust, und wenn sie fehlschlagen, gibt es definitiv ein Problem, über das ich eine direktere Fehlermeldung bevorzugen würde.

Wie oben ausgeführt, sind die Überprüfungen unter Leistungsgesichtspunkten nicht unbedingt trivial. Viele sind wahrscheinlich trivial, aber auch die Standardbibliothek verwendet sie und kann in vielen Fällen die Leistung auf inakzeptable Weise für viele Menschen beeinträchtigen, wenn beispielsweise das Durchlaufen von std::vector Mit wahlfreiem Zugriff viermal so lange dauert, wie angenommen wird ein optimierter Release-Build zu sein, da die Grenzen der Überprüfung niemals fehlschlagen sollten.

In einem früheren Team mussten wir unsere Matrix- und Vektorbibliothek tatsächlich dazu bringen, einige Asserts in bestimmten kritischen Pfaden auszuschließen, damit Debug-Builds schneller ausgeführt werden, da diese Asserts die mathematischen Operationen um mehr als eine Größenordnung bis zu dem Punkt verlangsamten, an dem sie sich befanden Wir müssen zunächst 15 Minuten warten, bevor wir überhaupt den Code von Interesse finden können. Meine Kollegen wollten eigentlich nur das asserts sofort entfernen, weil sie fanden, dass nur das einen großen Unterschied machte. Stattdessen haben wir uns darauf festgelegt, dass die kritischen Debug-Pfade sie vermeiden. Wenn wir dafür gesorgt haben, dass diese kritischen Pfade die Vektor-/Matrixdaten direkt verwenden, ohne die Grenzüberprüfung zu durchlaufen, wurde die Zeit, die für die Ausführung der vollständigen Operation (die mehr als nur Vektor-/Matrixmathematik umfasste) erforderlich ist, von Minuten auf Sekunden reduziert. Das ist also ein extremer Fall, aber definitiv sind die Behauptungen vom Standpunkt der Leistung nicht immer vernachlässigbar, nicht einmal in der Nähe.

Aber es ist auch nur die Art und Weise, wie asserts entworfen werden. Wenn sie nicht auf ganzer Linie einen so großen Einfluss auf die Leistung hatten, würde ich es vielleicht bevorzugen, wenn sie mehr als nur eine Debug-Build-Funktion sind, oder wir könnten vector::at Verwenden, das die Überprüfung der Grenzen auch in Release-Builds beinhaltet und wirft außerhalb der Grenzen Zugang, z (doch mit einem riesigen Performance-Hit). Derzeit finde ich ihr Design jedoch viel nützlicher, da sie in meinen Fällen einen enormen Einfluss auf die Leistung haben, da es sich nur um eine Debug-Build-Funktion handelt, die bei der Definition von NDEBUG weggelassen wird. Zumindest für die Fälle, mit denen ich gearbeitet habe, macht es für einen Release-Build einen großen Unterschied, Sanity-Checks auszuschließen, die eigentlich gar nicht fehlschlagen sollten.

vector::at Vs. vector::operator[]

Ich denke, die Unterscheidung dieser beiden Methoden steht ebenso im Mittelpunkt wie die Alternative: Ausnahmen. vector::operator[] Implementierungen in der Regel assert, um sicherzustellen, dass ein Zugriff außerhalb der Grenzen einen leicht reproduzierbaren Fehler auslöst, wenn versucht wird, auf einen Vektor außerhalb der Grenzen zuzugreifen. Die Bibliotheksimplementierer tun dies jedoch unter der Annahme, dass ein optimierter Release-Build keinen Cent kostet.

In der Zwischenzeit wird vector::at Bereitgestellt, das auch in Release-Builds immer die Out-of-Bound-Prüfung und -Würfe durchführt, aber es hat eine Leistungsbeeinträchtigung bis zu dem Punkt, an dem ich mit vector::operator[] Oft weit mehr Code sehe als vector::at. Ein Großteil des Designs von C++ spiegelt die Idee wider, "für das zu bezahlen, was Sie verwenden/benötigen", und viele Leute bevorzugen häufig operator[], Was sich nicht einmal mit den Grenzen befasst, die beim Testen von Release-Builds auftreten auf der Vorstellung, dass sie die Grenzen nicht benötigen, um ihre optimierten Release-Builds zu überprüfen. Wenn Assertions in Release-Builds aktiviert wären, wäre die Leistung dieser beiden plötzlich identisch, und die Verwendung von Vektoren wäre immer langsamer als bei einem dynamischen Array. Ein großer Teil des Designs und des Nutzens von Behauptungen basiert auf der Idee, dass sie in einem Release-Build frei werden.

release_assert

Dies ist interessant, nachdem diese Absichten entdeckt wurden. Natürlich wären alle Anwendungsfälle anders, aber ich denke, ich würde eine Verwendung für einen release_assert Finden, der die Prüfung durchführt und die Software zum Absturz bringt, die auch in Release-Builds eine Zeilennummer und eine Fehlermeldung anzeigt.

Es wäre für einige obskure Fälle in meinem Fall, in denen ich nicht möchte, dass die Software ordnungsgemäß wiederhergestellt wird, wie es der Fall wäre, wenn eine Ausnahme ausgelöst wird. Ich möchte, dass es in diesen Fällen auch in der Version abstürzt, damit der Benutzer eine Zeilennummer erhält, die er meldet, wenn die Software auf etwas stößt, das niemals passieren sollte, immer noch im Bereich der Überprüfung der Integrität auf Programmiererfehler, nicht auf externe Eingabefehler wie Ausnahmen, aber billig genug, um ohne sich Gedanken über die Kosten bei der Veröffentlichung zu machen.

Es gibt tatsächlich einige Fälle, in denen ich einen harten Absturz mit einer Zeilennummer und einer Fehlermeldung vorziehen würde , um mich ordnungsgemäß von einer ausgelösten Ausnahme zu erholen, die möglicherweise billig genug ist in einer Veröffentlichung behalten. In einigen Fällen ist es unmöglich, eine Ausnahme zu beheben, z. B. ein Fehler, der beim Versuch auftritt, eine vorhandene Ausnahme zu beheben. Dort würde ich eine perfekte Passform für eine release_assert(!"This should never, ever happen! The software failed to fail!"); finden, und das wäre natürlich spottbillig, da die Prüfung in erster Linie in einem außergewöhnlichen Pfad durchgeführt würde und in normalen Ausführungspfaden nichts kosten würde.

2
user204677

Ich codiere in Ruby, nicht in C/C++, und daher werde ich nicht über den Unterschied zwischen Asserts und Ausnahmen sprechen, aber ich möchte darüber als etwas, das die Laufzeit stoppt sprechen. Ich bin mit den meisten Antworten oben nicht einverstanden, da das Programm durch Drucken eines Backtraces gestoppt wird funktioniert gut für mich als defensive Programmiertechnik.
Wenn es eine Möglichkeit gibt, Assert-Routine (absolut unabhängig davon, wie sie syntaktisch geschrieben ist und ob das Wort "assert" verwendet wird oder überhaupt in der Programmiersprache oder in dsl vorhanden ist) aufzurufen, bedeutet dies, dass einige Arbeiten ausgeführt werden sollten getan werden und das Produkt kehrt sofort von "freigegeben, einsatzbereit" zu "benötigt Patch" zurück - schreiben Sie es jetzt entweder um, um echte Ausnahmen zu behandeln, oder beheben Sie einen Fehler, der dazu führte, dass falsche Daten angezeigt wurden.

Ich meine, Assert ist keine Sache, mit der Sie leben und häufig anrufen sollten - es ist ein Stoppsignal, das anzeigt, dass Sie etwas tun sollten, damit es nie wieder passiert. Und zu sagen, dass "Release Build keine Asserts haben sollte" ist wie zu sagen, dass "Release Build keine Bugs haben sollte" - Alter, es ist fast unmöglich, damit umzugehen.
Oder denken Sie an sie als "fehlgeschlagene Unittests, die vom Endbenutzer erstellt und ausgeführt werden". Sie können nicht alle Dinge vorhersagen, die Benutzer mit Ihrem Programm tun werden, aber wenn etwas zu ernstes schief geht, sollte es angehalten werden - ähnlich wie beim Erstellen von Build-Pipelines - Sie stoppen den Prozess und veröffentlichen nicht, oder? ? Die Behauptung zwingt den Benutzer, anzuhalten, zu melden und auf Ihre Hilfe zu warten.

2
Nakilon