Wie soll ich mit ungültigen Benutzereingaben umgehen?

12

Ich habe eine Weile über dieses Problem nachgedacht und wäre gespannt auf Meinungen anderer Entwickler.

Ich neige zu einem sehr defensiven Programmierstil. Mein typischer Block oder meine typische Methode sieht folgendermaßen aus:

T foo(par1, par2, par3, ...)
{
    // Check that all parameters are correct, return undefined (null)
    // or throw exception if this is not the case.

    // Compute and (possibly) return result.
}

Außerdem überprüfe ich während der Berechnung alle Zeiger, bevor ich sie dereferenziere. Meine Idee ist, dass, wenn es einen Fehler gibt und irgendwo ein NULL-Zeiger erscheinen sollte, mein Programm dies gut handhaben und sich einfach weigern sollte, die Berechnung fortzusetzen. Natürlich kann es über das Problem mit einer Fehlermeldung im Protokoll oder einem anderen Mechanismus informieren.

Um es abstrakter auszudrücken, ist mein Ansatz

if all input is OK --> compute result
else               --> do not compute result, notify problem

Andere Entwickler, darunter einige meiner Kollegen, verfolgen eine andere Strategie. ZB prüfen sie keine Zeiger. Sie gehen davon aus, dass ein Teil des Codes korrekt eingegeben werden sollte und dass er nicht dafür verantwortlich sein sollte, was passiert, wenn die Eingabe falsch ist. Wenn eine NULL-Zeiger-Ausnahme das Programm zum Absturz bringt, kann ein Fehler beim Testen leichter gefunden werden und es besteht eine höhere Wahrscheinlichkeit, dass er behoben wird.

Meine Antwort darauf lautet normalerweise: Aber was ist, wenn der Fehler beim Testen nicht gefunden wird und angezeigt wird, wenn das Produkt bereits vom Kunden verwendet wird? Was ist ein bevorzugter Weg, damit sich der Fehler manifestiert? Sollte es ein Programm sein, das eine bestimmte Aktion nicht ausführt, aber trotzdem weiterarbeiten kann, oder ein Programm, das abstürzt und neu gestartet werden muss?

Zusammenfassend

Welchen der beiden Ansätze zum Umgang mit falschen Eingaben würden Sie empfehlen?

Inconsistent input --> no action + notification

oder

Inconsistent input --> undefined behaviour or crash

Bearbeiten

Danke für die Antworten und Vorschläge. Ich bin auch ein Fan von Design nach Vertrag. Aber selbst wenn ich der Person vertraue, die den Code geschrieben hat, der meine Methoden aufruft (vielleicht bin es ich selbst), kann es immer noch Fehler geben, die zu falschen Eingaben führen. Daher gehe ich davon aus, dass eine Methode niemals die richtige Eingabe erhält.

Außerdem würde ich einen Mechanismus verwenden, um das Problem abzufangen und darüber zu benachrichtigen. Auf einem Entwicklungssystem würde es zB einen Dialog öffnen, um den Benutzer zu benachrichtigen. In einem Produktionssystem würde es nur einige Informationen in das Protokoll schreiben. Ich glaube nicht, dass zusätzliche Überprüfungen zu Leistungsproblemen führen können. Ich bin mir nicht sicher, ob Behauptungen ausreichen, wenn sie in einem Produktionssystem ausgeschaltet sind: Vielleicht tritt eine Situation in der Produktion auf, die während des Testens nicht aufgetreten ist.

Wie auch immer, ich war wirklich überrascht, dass viele Leute den umgekehrten Ansatz verfolgen: Sie lassen die Anwendung "absichtlich" abstürzen, weil sie behaupten, dass dies das Auffinden von Fehlern während des Testens erleichtert.

Giorgio
quelle
Code immer defensiv. Aus Gründen der Leistung können Sie eventuell einen Schalter setzen, um einige Tests im Freigabemodus zu deaktivieren.
Deadalnix
Heute habe ich einen Fehler behoben, der mit einer fehlenden Nullzeigerprüfung zusammenhängt. Ein Objekt wurde beim Abmelden der Anwendung erstellt und der Konstruktor verwendete einen Getter, um auf ein anderes Objekt zuzugreifen, das nicht mehr vorhanden war. Das Objekt sollte zu diesem Zeitpunkt nicht erstellt werden. Es wurde aufgrund eines anderen Fehlers erstellt: einige Timer wurden beim Abmelden nicht angehalten -> ein Signal wurde gesendet -> Empfänger hat versucht, ein Objekt zu erstellen -> Konstruktor abgefragt und ein anderes Objekt verwendet -> NULL-Zeiger -> Absturz ). Ich würde es wirklich nicht mögen, wenn eine so schlimme Situation meine Anwendung zum Absturz bringt.
Giorgio
1
Reparaturregel: Wenn Sie versagen müssen, versagen Sie laut und so schnell wie möglich.
Deadalnix
"Regel der Reparatur: Wenn Sie versagen müssen, versagen Sie laut und so schnell wie möglich.": Ich denke, all diese Windows-BSODs sind eine Anwendung dieser Regel. :-)
Giorgio

Antworten:

8

Du hast es richtig gemacht. Sei paranoid. Vertrauen Sie keinem anderen Code, auch wenn es sich um Ihren eigenen Code handelt. Sie vergessen Dinge, Sie nehmen Änderungen vor, Code entwickelt sich. Vertraue keinem externen Code.

Oben wurde ein guter Punkt angesprochen: Was ist, wenn die Eingaben ungültig sind, das Programm aber nicht abstürzt? Dann bekommen Sie Müll in der Datenbank und Fehler auf der ganzen Linie.

Wenn ich nach einer Zahl gefragt werde (z. B. Preis in Dollar oder Stückzahl), gebe ich gerne "1e9" ein und sehe, was der Code bewirkt. Es kann vorkommen.

Als ich vor vier Jahrzehnten meinen Bachelor in Informatik bei UC Berkeley ablegte, wurde uns gesagt, dass ein gutes Programm zu 50% Fehlerbehandlung ist. Sei paranoid.

Andy Canfield
quelle
Ja, meiner Meinung nach ist dies eine der wenigen Situationen, in denen es kein Problem ist, paranoid zu sein.
Giorgio
"Was ist, wenn die Eingaben ungültig sind, das Programm aber nicht abstürzt? Dann werden Datenmüll und Fehler auf der ganzen Linie angezeigt." Undefinierte Daten breiten sich durch die Berechnung aus und es wird kein Müll erzeugt. Das Programm muss jedoch nicht abstürzen, um dies zu erreichen.
Giorgio
Ja, aber - mein Punkt ist, dass das Programm die ungültige Eingabe erkennen und damit umgehen muss. Wenn die Eingabe nicht überprüft wird, funktioniert sie bis weit in das System hinein, und böse Dinge kommen später. Sogar Crashing ist besser als das!
Andy Canfield
Ich stimme Ihnen voll und ganz zu: Meine typische Methode oder Funktion beginnt mit einer Reihe von Überprüfungen, um sicherzustellen, dass die eingegebenen Daten korrekt sind.
Giorgio
Heute hatte ich wieder die Bestätigung, dass die Strategie "alles überprüfen, nichts vertrauen" oft eine gute Idee ist. Ein Kollege von mir hatte eine NULL-Zeiger-Ausnahme wegen einer fehlenden Prüfung. Es stellte sich heraus, dass es in diesem Zusammenhang richtig war, einen NULL-Zeiger zu haben, da einige Daten nicht geladen wurden, und es richtig war, den Zeiger zu überprüfen und einfach nichts zu tun, wenn er NULL ist. :-)
Giorgio
7

Sie haben bereits die richtige Idee

Welchen der beiden Ansätze zum Umgang mit falschen Eingaben würden Sie empfehlen?

Inkonsistente Eingabe -> keine Aktion + Benachrichtigung

oder besser

Inkonsistente Eingabe -> entsprechend behandelte Aktion

Man kann beim Programmieren nicht wirklich einen Ausstecher-Ansatz wählen (man könnte es), aber man hätte ein formelhaftes Design, das Dinge eher aus Gewohnheit als aus bewusster Wahl macht.

Temperament Dogmatismus mit Pragmatismus.

Steve McConnell sagte es am besten

Steve McConnell schrieb so ziemlich das Buch ( Code Complete ) über defensive Programmierung und dies war eine der Methoden, die er anwies, dass Sie Ihre Eingaben immer validieren sollten.

Ich kann mich nicht erinnern, dass Steve dies erwähnt hat. Sie sollten dies jedoch für nicht-private Methoden und Funktionen in Betracht ziehen , und nur für andere, wenn dies als notwendig erachtet wird.

Justin Shield
quelle
2
Anstatt öffentlich, würde ich vorschlagen, alle nicht-privaten Methoden, um defensiv Sprachen abzudecken, die geschützt, geteilt oder kein Konzept der Zugriffsbeschränkung haben (alles ist öffentlich, implizit).
JustinC
3

Hier gibt es keine "richtige" Antwort, insbesondere ohne Angabe der Sprache, des Codetyps und des Produkttyps, in den der Code eingefügt werden könnte. Erwägen:

  • Sprache ist wichtig. In Objective-C ist es oft in Ordnung, Nachrichten an nil zu senden. es passiert nichts, aber das Programm stürzt auch nicht ab. Java hat keine expliziten Zeiger, daher sind Null-Zeiger dort kein großes Problem. In C müssen Sie etwas vorsichtiger sein.

  • Paranoid zu sein ist ein unvernünftiger, ungerechtfertigter Verdacht oder Misstrauen. Das ist wahrscheinlich nicht besser für Software als für Menschen.

  • Ihr Anliegen sollte dem Risiko im Code und der wahrscheinlichen Schwierigkeit entsprechen, auftretende Probleme zu identifizieren. Was passiert im schlimmsten Fall? Der Benutzer startet das Programm neu und fährt dort fort, wo er aufgehört hat? Das Unternehmen verliert Millionen von Dollar?

  • Sie können schlechte Eingaben nicht immer identifizieren. Sie können Ihre Zeiger religiös mit Null vergleichen, aber das fängt nur einen von 2 ^ 32 möglichen Werten ab, von denen fast alle schlecht sind.

  • Es gibt viele verschiedene Mechanismen für den Umgang mit Fehlern. Auch dies hängt bis zu einem gewissen Grad von der Sprache ab. Sie können Assert-Makros, Bedingungsanweisungen, Komponententests, Ausnahmebehandlung, sorgfältiges Design und andere Techniken verwenden. Keiner von ihnen ist narrensicher und keiner ist für jede Situation geeignet.

Es kommt also hauptsächlich darauf an, wo Sie Verantwortung übernehmen möchten. Wenn Sie eine Bibliothek für die Verwendung durch andere schreiben, möchten Sie wahrscheinlich so vorsichtig wie möglich mit den erhaltenen Eingaben sein und versuchen, hilfreiche Fehler zu melden, wenn dies möglich ist. In Ihren eigenen privaten Funktionen und Methoden könnten Sie Asserts verwenden, um dumme Fehler aufzufangen, aber ansonsten dem Anrufer (das sind Sie) die Verantwortung auferlegen, keinen Müll weiterzugeben.

Caleb
quelle
+1 - Gute Antwort. Meine Hauptsorge ist, dass eine falsche Eingabe ein Problem verursachen kann, das auf einem Produktionssystem auftritt (wenn es zu spät ist, etwas dagegen zu unternehmen). Natürlich glaube ich, dass Sie völlig zu Recht sagen, dass es von dem Schaden abhängt, den ein solches Problem für den Benutzer verursachen kann.
Giorgio
Die Sprache spielt eine große Rolle. In PHP überprüft die Hälfte Ihres Methodencodes den Typ der Variablen und ergreift die entsprechenden Maßnahmen. Wenn die Methode in Java ein int akzeptiert, können Sie nichts anderes übergeben, damit Ihre Methode klarer wird.
Kap
1

Es sollte auf jeden Fall eine Benachrichtigung geben, z. B. eine ausgelöste Ausnahme. Es dient anderen Programmierern als Hinweis darauf, dass ihre Eingabe ungültig ist oder zu Fehlern führt. Dies ist sehr nützlich, um Fehler aufzuspüren. Wenn Sie einfach null zurückgeben, wird der Code so lange fortgesetzt, bis versucht wird, das Ergebnis zu verwenden und eine Ausnahme von einem anderen Code zu erhalten.

Wenn Ihr Code während eines Aufrufs eines anderen Codes (möglicherweise einer fehlgeschlagenen Datenbankaktualisierung) auf einen Fehler stößt, der über den Umfang dieses bestimmten Codeteils hinausgeht, haben Sie wirklich keine Kontrolle darüber, und Sie müssen lediglich eine Ausnahme auslösen, in der erläutert wird, was passiert Sie wissen (nur, was Ihnen durch den von Ihnen aufgerufenen Code mitgeteilt wird). Wenn Sie wissen, dass bestimmte Eingaben unweigerlich zu einem solchen Ergebnis führen, können Sie sich einfach nicht die Mühe machen, Ihren Code auszuführen und eine Ausnahme auszulösen, in der angegeben wird, welche Eingabe nicht gültig ist und warum.

In Bezug auf Endbenutzer ist es am besten, etwas Beschreibendes, aber Einfaches zurückzugeben, damit jeder es verstehen kann. Wenn Ihr Client anruft und sagt "das Programm ist abgestürzt, beheben Sie es", haben Sie viel Arbeit damit, herauszufinden, was und warum schief gelaufen ist, und hoffen, dass Sie das Problem reproduzieren können. Die ordnungsgemäße Behandlung von Ausnahmen kann nicht nur einen Absturz verhindern, sondern auch wertvolle Informationen liefern. Ein Anruf von einem Client, der sagt "Das Programm gibt mir einen Fehler. Es sagt" XYZ ist keine gültige Eingabe für Methode M, weil Z zu groß ", oder so etwas, auch wenn sie keine Ahnung haben, was es bedeutet, Sie genau wissen, wo sie suchen müssen. Abhängig von den Geschäftspraktiken Ihres Unternehmens liegt es möglicherweise auch nicht an Ihnen, diese Probleme zu beheben. Lassen Sie ihnen daher am besten eine gute Karte.

Die kurze Version meiner Antwort lautet also, dass Ihre erste Option die beste ist.

Inconsistent input -> no action + notify caller
yoozer8
quelle
1

Ich hatte mit dem gleichen Problem zu kämpfen, als ich einen Universitätskurs in Programmierung absolvierte. Ich beugte mich zur paranoiden Seite und neigte dazu, alles zu überprüfen, aber mir wurde gesagt, dass dies ein irrtümliches Verhalten war.

Uns wurde "Design by contract" beigebracht. Hervorzuheben ist, dass die Voraussetzungen, Invarianten und Nachbedingungen in den Kommentaren und Konstruktionsunterlagen angegeben werden. Als Person, die meinen Teil des Codes implementiert, sollte ich dem Softwarearchitekten vertrauen und ihn befähigen, indem ich die Spezifikationen befolge, die die Voraussetzungen enthalten (welche Eingaben müssen meine Methoden verarbeiten können und welche Eingaben ich nicht senden werde). . Übermäßiges Einchecken bei jedem Methodenaufruf führt zu Aufblähungen.

Assertions sollten während Build-Iterationen verwendet werden, um die Programmkorrektheit zu überprüfen (Validierung von Vorbedingungen, Invarianten, Post-Bedingungen). Assertions würden dann in der Produktionskompilierung deaktiviert.

Richard
quelle
0

Die Verwendung von "Behauptungen" ist der Weg, um andere Entwickler zu benachrichtigen, dass sie es falsch machen, natürlich nur in "privaten" Methoden . Das Aktivieren / Deaktivieren ist nur ein Flag, das zum Zeitpunkt der Kompilierung hinzugefügt / entfernt werden muss. Daher ist es einfach, Zusicherungen aus dem Produktionscode zu entfernen. Es gibt auch ein großartiges Tool, mit dem Sie herausfinden können , ob Sie mit Ihren eigenen Methoden etwas falsch machen.

Was die Überprüfung von Eingabeparametern in öffentlichen / geschützten Methoden betrifft, arbeite ich lieber defensiv und überprüfe Parameter und wirfe eine InvalidArgumentException oder ähnliches. Deshalb gibt es hier für. Es hängt auch davon ab, ob Sie eine API schreiben oder nicht. Wenn es sich um eine API handelt, und noch mehr, wenn es sich um eine geschlossene Quelle handelt, sollten Sie alles besser validieren, damit die Entwickler genau wissen, was schief gelaufen ist. Andernfalls ist die Quelle, wenn sie anderen Entwicklern zur Verfügung steht, nicht schwarz / weiß. Seien Sie einfach im Einklang mit Ihren Entscheidungen.

Bearbeiten: Nur um hinzuzufügen, wenn Sie sich beispielsweise das Oracle-JDK ansehen, werden Sie feststellen, dass es niemals nach "null" sucht und den Code abstürzen lässt. Da es sowieso eine NullPointerException auslöst, sollte man sich die Mühe machen, auf Null zu prüfen und eine explizite Ausnahme auszulösen. Ich denke, es macht Sinn.

Jalayn
quelle
In Java erhalten Sie eine Nullzeigerausnahme. In C ++ stürzt ein Nullzeiger die Anwendung ab. Vielleicht gibt es noch andere Beispiele: Division durch Null, Index außerhalb des Bereichs und so weiter.
Giorgio