Ich bin gerade dabei, eine neue Programmiersprache zu entwickeln, um einige Geschäftsanforderungen zu erfüllen, und diese Sprache richtet sich an unerfahrene Benutzer. Daher gibt es keine Unterstützung für die Ausnahmebehandlung in der Sprache, und ich würde nicht erwarten, dass sie diese auch dann verwenden, wenn ich sie hinzufüge.
Ich habe den Punkt erreicht, an dem ich den Divisionsoperator implementieren muss, und ich frage mich, wie ich am besten mit einer Division durch Null umgehen kann.
Ich habe anscheinend nur drei Möglichkeiten, mit diesem Fall umzugehen.
- Ignoriere den Fehler und erzeuge
0
als Ergebnis. Wenn möglich eine Warnung protokollieren. - Fügen Sie
NaN
als einen möglichen Wert für Zahlen, aber das wirft Fragen darüber , wie zu handhabenNaN
in anderen Bereichen der Sprachwerte. - Beenden Sie die Ausführung des Programms und melden Sie dem Benutzer einen schwerwiegenden Fehler.
Option 1 scheint die einzig vernünftige Lösung zu sein. Option 3 ist nicht praktikabel, da diese Sprache verwendet wird, um Logik als nächtliches Cron auszuführen.
Was sind meine Alternativen zur Behandlung einer Division durch Null und welche Risiken birgt die Entscheidung für Option 1?
quelle
reject "Foo"
es implementiert wurde, sondern nur, dass es ein Dokument ablehnt, wenn es das Schlüsselwort enthältFoo
. Ich versuche, die Sprache mit Begriffen, mit denen der Benutzer vertraut ist, so einfach wie möglich zu lesen. Indem Sie einem Benutzer eine eigene Programmiersprache geben, können Sie Geschäftsregeln hinzufügen, ohne vom technischen Personal abhängig zu sein.Antworten:
Ich würde dringend von Nummer 1 abraten, da das Ignorieren von Fehlern ein gefährliches Anti-Pattern ist. Dies kann zu schwer zu analysierenden Fehlern führen. Das Ergebnis einer Division von Null auf 0 zu setzen, macht überhaupt keinen Sinn, und die Fortsetzung der Programmausführung mit einem unsinnigen Wert wird Probleme verursachen. Besonders wenn das Programm unbeaufsichtigt läuft. Wenn der Programminterpreter feststellt, dass das Programm einen Fehler enthält (und eine Division durch Null fast immer einen Entwurfsfehler darstellt), wird es in der Regel vorgezogen, die Datenbank mit Müll zu füllen, und alles so zu belassen, wie es ist.
Es ist auch unwahrscheinlich, dass Sie erfolgreich sind, wenn Sie dieses Muster genau befolgen. Früher oder später werden Sie auf Fehlersituationen stoßen, die einfach nicht ignoriert werden können (z. B. Speichermangel oder Stapelüberlauf), und Sie müssen trotzdem eine Möglichkeit implementieren, um das Programm zu beenden.
Option 2 (mit NaN) wäre ein wenig Arbeit, aber nicht so viel, wie Sie vielleicht denken. Der Umgang mit NaN in verschiedenen Berechnungen ist im IEEE 754-Standard gut dokumentiert, sodass Sie wahrscheinlich nur die Sprache verwenden können, in der Ihr Dolmetscher geschrieben ist.
Übrigens: Wir versuchen seit 1964, eine Programmiersprache zu erstellen, die von Nicht-Programmierern verwendet werden kann (Dartmouth BASIC). Bisher waren wir erfolglos. Aber trotzdem viel Glück.
quelle
PHP
hat mich schlecht beeinflusst.NaN
in der Sprache eines Anfängers, aber im Allgemeinen eine gute Antwort.Das ist keine gute Idee. Überhaupt. Die Leute werden abhängig davon anfangen und sollten Sie es jemals reparieren, werden Sie eine Menge Code brechen.
Sie sollten NaN so behandeln, wie es Laufzeiten anderer Sprachen tun: Jede weitere Berechnung ergibt auch NaN und jeder Vergleich (sogar NaN == NaN) ergibt falsch.
Ich finde das akzeptabel, aber nicht unbedingt neulingfreundlich.
Dies ist die beste Lösung, denke ich. Mit diesen Informationen sollten Benutzer in der Lage sein, mit 0 umzugehen. Sie sollten eine Testumgebung bereitstellen, insbesondere, wenn sie einmal pro Nacht ausgeführt werden soll.
Es gibt auch eine vierte Option. Mache Division zu einer ternären Operation. Jeder dieser beiden wird funktionieren:
quelle
NaN == NaN
jedoch tun,false
müssen Sie eineisNaN()
Funktion hinzufügen , damit BenutzerNaN
s erkennen können .isNan(x) => x != x
. Wenn SieNaN
jedoch in Ihrem Programmcode auftauchen, sollten Sie nicht mit dem Hinzufügen vonisNaN
Überprüfungen beginnen, sondern die Ursache aufspüren und die erforderlichen Überprüfungen dort vornehmen. Daher ist es wichtigNaN
, sich vollständig zu vermehren.NaN
s sind größtenteils kontraintuitiv. In der Sprache eines Anfängers sind sie bei der Ankunft tot.1/0
- man muss etwas damit anfangen . Es gibt kein anderes möglicherweise nützliches Ergebnis alsInf
oderNaN
- etwas, das den Fehler weiter in das Programm überträgt. Andernfalls besteht die einzige Lösung darin, an dieser Stelle mit einem Fehler zu stoppen.Beenden Sie die laufende Anwendung mit äußersten Vorurteilen. (Während ausreichende Debug-Informationen zur Verfügung gestellt werden)
Informieren Sie dann Ihre Benutzer, um Bedingungen zu identifizieren und zu behandeln, bei denen der Divisor Null sein kann (vom Benutzer eingegebene Werte usw.).
quelle
In Haskell (und ähnlich in Scala) können die Wrapper-Typen
Maybe
undEither
verwendet werden , anstatt Ausnahmen auszulösen (oder Null-Referenzen zurückzugeben) . MitMaybe
dem Benutzer eine Chance hat , zu testen , ob der Wert bekam er „leer“ ist, oder er könnte einen Standardwert zur Verfügung stellen , wenn „auspackt“.Either
ist ähnlich, kann aber verwendet werden, um ein Objekt (z. B. eine Fehlerzeichenfolge) zurückzugeben, das das Problem beschreibt, sofern es eines gibt.quelle
error "some message"
zu bewertende Funktion.Haskell
lässt nicht zu, dass reine Ausdrücke Ausnahmen auslösen.Andere Antworten haben bereits die relativen Vorzüge Ihrer Ideen berücksichtigt. Ich schlage ein anderes: Grundflussanalyse verwenden , um zu bestimmen , ob eine Variable kann Null sein. Dann können Sie die Division durch Variablen, die möglicherweise Null sind, einfach nicht zulassen.
Alternativ können Sie eine intelligente Assert-Funktion verwenden, die Invarianten erstellt:
Dies ist so gut wie das Auslösen eines Laufzeitfehlers - Sie umgehen undefinierte Operationen vollständig -, hat jedoch den Vorteil, dass der Codepfad nicht einmal getroffen werden muss, damit der potenzielle Fehler offengelegt wird. Dies kann ähnlich wie beim normalen Typecheck durchgeführt werden, indem alle Zweige eines Programms mit verschachtelten Typisierungsumgebungen ausgewertet werden, um Invarianten zu verfolgen und zu überprüfen:
Darüber hinaus erstreckt es sich natürlich auch auf die Reichweite und
null
Überprüfung, ob Ihre Sprache solche Merkmale aufweist.quelle
def foo(a,b): return a / ord(sha1(b)[0])
. Der statische Analysator kann SHA-1 nicht invertieren. Clang hat diese Art der statischen Analyse und eignet sich hervorragend zum Auffinden flacher Fehler, aber es gibt viele Fälle, mit denen es nicht umgehen kann.Nummer 1 (undebuggable Null einfügen) ist immer schlecht. Die Wahl zwischen # 2 (NaN verbreiten) und # 3 (den Prozess beenden) hängt vom Kontext ab und sollte idealerweise eine globale Einstellung sein, wie es in Numpy der Fall ist.
Wenn Sie eine große, integrierte Berechnung durchführen, ist die Verbreitung von NaN eine schlechte Idee, da sie sich schließlich ausbreiten und Ihre gesamte Berechnung infizieren wird - wenn Sie sich die Ergebnisse morgens ansehen und feststellen, dass sie alle NaN sind. ' d muss die ergebnisse rausschmeißen und trotzdem neu anfangen. Es wäre besser gewesen, wenn das Programm beendet worden wäre, Sie mitten in der Nacht einen Anruf erhalten und ihn behoben hätten - zumindest in Bezug auf die Anzahl der verschwendeten Stunden.
Wenn Sie viele kleine, größtenteils unabhängige Berechnungen durchführen (z. B. kartenreduzierte oder peinlich parallele Berechnungen) und Sie tolerieren können, dass ein gewisser Prozentsatz davon aufgrund von NaNs unbrauchbar ist, ist dies wahrscheinlich die beste Option. Das Programm zu beenden und nicht die 99% zu tun, die aufgrund der 1%, die fehlerhaft sind und durch Null dividieren, gut und nützlich wären, könnte ein Fehler sein.
Eine weitere Option, die sich auf NaNs bezieht: Dieselbe IEEE-Gleitkommaspezifikation definiert Inf und -Inf und diese werden anders als NaN weitergegeben. Zum Beispiel bin ich mir ziemlich sicher, dass Inf> eine beliebige Zahl und -Inf <eine beliebige Zahl ist, was genau das wäre, was Sie wollten, wenn Ihre Division durch Null passieren würde, weil die Null nur eine kleine Zahl sein sollte. Wenn Ihre Eingaben gerundet sind und Messfehler aufweisen (wie bei von Hand durchgeführten physischen Messungen), kann die Differenz zweier großer Mengen zu Null führen. Ohne die Division durch Null hätten Sie eine große Zahl erhalten, und es ist Ihnen vielleicht egal, wie groß sie ist. In diesem Fall sind In und -Inf absolut gültige Ergebnisse.
Es kann auch formal korrekt sein - sagen Sie einfach, Sie arbeiten im erweiterten Real.
quelle
Natürlich ist es praktisch: Es liegt in der Verantwortung der Programmierer, ein Programm zu schreiben, das tatsächlich Sinn macht. Teilen durch 0 macht keinen Sinn. Wenn der Programmierer eine Division durchführt, liegt es auch in seiner Verantwortung, vorab zu überprüfen, ob der Divisor ungleich 0 ist. Wenn der Programmierer diese Validierungsprüfung nicht durchführt, sollte er diesen Fehler sofort bemerken möglich, und denormalisierte (NaN) oder falsche (0) Berechnungsergebnisse helfen in dieser Hinsicht einfach nicht weiter.
Option 3 ist die, die ich Ihnen übrigens empfohlen hätte, um die direkteste, ehrlichste und mathematisch korrekteste zu sein.
quelle
Es scheint mir eine schlechte Idee zu sein, wichtige Aufgaben (z. B. "nightly cron") in einer Umgebung auszuführen, in der Fehler ignoriert werden. Es ist eine schreckliche Idee, dies zu einer Funktion zu machen. Dies schließt die Optionen 1 und 2 aus.
Option 3 ist die einzig akzeptable Lösung. Ausnahmen müssen nicht Teil der Sprache sein, aber sie sind Teil der Realität. Ihre Kündigungsnachricht sollte so spezifisch und informativ wie möglich über den Fehler sein.
quelle
IEEE 754 hat tatsächlich eine gut definierte Lösung für Ihr Problem. Ausnahmebehandlung ohne Verwendung von
exceptions
http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handlingAuf diese Weise sind alle Ihre Operationen mathematisch sinnvoll.
\ lim_ {x \ to 0} 1 / x = Inf
Meiner Meinung nach ist IEEE 754 am sinnvollsten, da es sicherstellt, dass Ihre Berechnungen so korrekt sind wie auf einem Computer und Sie auch mit dem Verhalten anderer Programmiersprachen übereinstimmen.
Das einzige Problem, das auftritt, ist, dass Inf und NaN Ihre Ergebnisse verunreinigen und Ihre Benutzer nicht genau wissen, woher das Problem kommt. Schauen Sie sich eine Sprache wie Julia an, die das ganz gut kann.
Der Teilungsfehler wird korrekt durch die mathematischen Operationen weitergegeben, aber am Ende weiß der Benutzer nicht notwendigerweise, aus welcher Operation der Fehler stammt.
edit:
Ich habe den zweiten Teil der Antwort von Jim Pivarski nicht gesehen. Mein Fehler.quelle
SQL, die Sprache, die am häufigsten von Nicht-Programmierern verwendet wird, ist die Nummer 3 für alle Fälle. Nach meiner Erfahrung beim Beobachten und Unterstützen von Nicht-Programmierern, die SQL schreiben, ist dieses Verhalten im Allgemeinen gut verstanden und leicht zu kompensieren (mit einer case-Anweisung oder dergleichen). Es hilft, dass die Fehlermeldung, die Sie erhalten, ziemlich direkt ist, z. B. in Postgres 9 erhalten Sie "FEHLER: Division durch Null".
quelle
Ich denke, das Problem ist "auf Anfänger ausgerichtet. -> Es gibt also keine Unterstützung für ..."
Warum ist die Ausnahmebehandlung Ihrer Meinung nach für unerfahrene Benutzer problematisch?
Was ist schlimmer Haben Sie eine "schwierige" Funktion oder haben Sie keine Ahnung, warum etwas passiert ist? Was könnte mehr verwirren? Ein Absturz mit einem Core-Dump oder "Fataler Fehler: Division durch Null"?
Stattdessen halte ich es für VIEL besser, nach GROSSEN Nachrichtenfehlern zu suchen. Tun Sie stattdessen Folgendes: "Schlechte Berechnung, 0/0 teilen" (dh: Zeigen Sie immer die DATEN an, die das Problem verursachen, nicht nur die Art des Problems). Schau dir an, wie PostgreSql die Nachrichtenfehler behebt, die meiner Meinung nach großartig sind.
Sie können sich jedoch auch andere Möglichkeiten ansehen, um mit Ausnahmen zu arbeiten:
http://dlang.org/exception-safe.html
Ich habe auch den Traum, eine Sprache zu bauen, und in diesem Fall denke ich, dass das Mischen eines Vielleicht / Optional mit normalen Ausnahmen das Beste sein könnte:
quelle
Aus meiner Sicht sollte Ihre Sprache einen allgemeinen Mechanismus zum Erkennen und Behandeln von Fehlern bieten. Programmierfehler sollten zur Kompilierzeit (oder so früh wie möglich) erkannt werden und normalerweise zum Programmabbruch führen. Fehler, die auf unerwartete oder fehlerhafte Daten oder auf unerwartete äußere Umstände zurückzuführen sind, sollten erkannt und für geeignete Maßnahmen zur Verfügung gestellt werden. Das Programm kann jedoch nach Möglichkeit fortgesetzt werden.
Zu den plausiblen Aktionen gehören (a) Beenden (b) Auffordern des Benutzers, eine Aktion auszuführen (c) Protokollieren des Fehlers (d) Ersetzen eines korrigierten Werts (e) Setzen eines zu testenden Indikators in Code (f) Aufrufen einer Fehlerbehandlungsroutine. Welche davon Sie zur Verfügung stellen und mit welchen Mitteln müssen Sie Entscheidungen treffen.
Nach meiner Erfahrung sind häufige Datenfehler wie fehlerhafte Konvertierungen, Division durch Null, Überlauf und Werte außerhalb des Bereichs harmlos und sollten standardmäßig durch Ersetzen eines anderen Werts und Setzen eines Fehlerflags behoben werden. Der (Nicht-Programmierer), der diese Sprache verwendet, erkennt die fehlerhaften Daten und versteht schnell, dass nach Fehlern gesucht und diese behandelt werden müssen.
[Betrachten Sie als Beispiel eine Excel-Tabelle. Excel beendet Ihre Tabelle nicht, weil eine Zahl übergelaufen ist oder was auch immer. Die Zelle erhält einen seltsamen Wert, und Sie finden heraus, warum und beheben ihn.]
Um Ihre Frage zu beantworten: Sie sollten auf keinen Fall kündigen. Möglicherweise ersetzen Sie NaN, aber Sie sollten dies nicht sichtbar machen. Stellen Sie lediglich sicher, dass die Berechnung abgeschlossen ist und einen seltsam hohen Wert generiert. Setzen Sie ein Fehler-Flag, damit Benutzer, die es benötigen, feststellen können, dass ein Fehler aufgetreten ist.
Offenlegung: Ich habe genau eine solche Sprachimplementierung (Powerflex) erstellt und genau dieses Problem (und viele andere) in den 1980er Jahren angesprochen. In den letzten 20 Jahren wurden in Bezug auf Sprachen für Nicht-Programmierer kaum oder gar keine Fortschritte erzielt, und Sie werden eine Menge Kritik für Versuche auf sich ziehen, aber ich hoffe wirklich, dass Sie Erfolg haben.
quelle
Ich mochte den ternären Operator, bei dem Sie einen alternativen Wert angeben, falls der Denumerator 0 ist.
Eine weitere Idee, die ich nicht gesehen habe, ist, einen allgemeinen "ungültigen" Wert zu erzeugen. Eine allgemeine "Diese Variable hat keinen Wert, weil das Programm etwas Schlechtes getan hat", die einen vollständigen Stack-Trace mit sich führt. Wenn Sie diesen Wert dann jemals irgendwo verwenden, ist das Ergebnis erneut ungültig, wobei die neue Operation oben versucht wird (dh wenn der ungültige Wert jemals in einem Ausdruck erscheint, ergibt der gesamte Ausdruck ungültig und es werden keine Funktionsaufrufe versucht; dies wäre eine Ausnahme) Boolesche Operatoren sein - wahr oder ungültig ist wahr und falsch und ungültig ist falsch - es kann auch andere Ausnahmen geben. Sobald auf diesen Wert nirgendwo mehr verwiesen wird, protokollieren Sie eine ausführliche Beschreibung der gesamten Kette, in der die Fehler aufgetreten sind, und setzen die Arbeit wie gewohnt fort. Vielleicht schicken Sie den Trace per E-Mail an den Projektleiter oder so.
So etwas wie die Vielleicht Monade im Grunde. Es funktioniert auch mit allem anderen, was fehlschlagen kann, und Sie können den Leuten erlauben, ihre eigenen Invaliden zu konstruieren. Und das Programm läuft so lange weiter, wie der Fehler nicht zu tief ist, was hier wirklich gewünscht ist, denke ich.
quelle
Es gibt zwei fundamentale Gründe für eine Division durch Null.
1. Sie müssen den Benutzern mitteilen, dass sie einen Fehler begangen haben, weil sie dafür verantwortlich sind und am besten wissen, wie sie die Situation beheben können.
Für 2. Dies ist kein Benutzerfehler. Sie können mit dem Finger auf den Algorithmus, die Hardware-Implementierung usw. zeigen. Dies ist jedoch kein Benutzerfehler. Sie sollten daher das Programm nicht beenden oder sogar eine Ausnahme auslösen (falls zulässig, was in diesem Fall nicht der Fall ist). Eine vernünftige Lösung besteht darin, den Betrieb auf eine vernünftige Weise fortzusetzen.
Ich kann die Person sehen, die diese Frage für Fall 1 gestellt hat. Sie müssen dem Benutzer also zurückmelden. Bei Verwendung eines beliebigen Gleitkommastandards passt Inf, -Inf, Nan, IEEE nicht in diese Situation. Grundsätzlich falsche Strategie.
quelle
Verbieten Sie es in der Sprache. Das heißt, Sie dürfen das Teilen durch eine Zahl nicht zulassen, bis es nachweislich nicht mehr null ist. Testen Sie dies in der Regel zuerst. Dh
quelle
int
erlaubt der C-Typ Nullwerte, aber GCC kann immer noch bestimmen, wo in dem Code bestimmte Ints nicht Null sein dürfen.Wenn Sie eine Programmiersprache schreiben, sollten Sie die Tatsache ausnutzen und die Aufnahme einer Aktion für das Gerät mit dem Status Null obligatorisch machen. a <= n / c: 0 Division durch Null
Ich weiß, was ich gerade vorgeschlagen habe, ist im Wesentlichen das Hinzufügen eines "Goto" zu Ihrem PL.
quelle