Sollte eine Methode mit den übergebenen Argumenten verzeihen? [geschlossen]


21

Angenommen, wir haben eine Methode foo(String bar), die nur Zeichenfolgen verarbeitet, die bestimmte Kriterien erfüllen. Es muss beispielsweise klein geschrieben sein, darf nicht leer sein oder nur Leerzeichen enthalten und muss mit dem Muster übereinstimmen [a-z0-9-_./@]+. In der Dokumentation der Methode sind diese Kriterien aufgeführt.

Sollte die Methode alle Abweichungen von diesem Kriterium ablehnen, oder sollte die Methode einige Kriterien eher verzeihen? Zum Beispiel, wenn die anfängliche Methode ist

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
    }
    this.bar = bar;
}

Und die zweite Vergebungsmethode ist

public void foo(String bar) {
    if (bar == null) {
        throw new IllegalArgumentException("bar must not be null");
    }
    if (!bar.matches(BAR_PATTERN_STRING)) {
        bar = bar.toLowerCase().trim().replaceAll(" ", "_");
        if (!bar.matches(BAR_PATTERN_STRING) {
            throw new IllegalArgumentException("bar must match pattern: " + BAR_PATTERN_STRING);
        }
    }
    this.bar = bar;
}

Sollte die Dokumentation dahingehend geändert werden, dass sie transformiert und wenn möglich auf den transformierten Wert gesetzt wird, oder sollte die Methode so einfach wie möglich gehalten werden und alle Abweichungen verworfen werden? In diesem Fall barkönnte vom Benutzer eine Anwendung eingestellt werden.

Der primäre Anwendungsfall hierfür wären Benutzer, die über eine bestimmte Zeichenfolgen-ID von einem Repository aus auf Objekte zugreifen. Jedes Objekt im Repository sollte eine eindeutige Zeichenfolge haben, um es zu identifizieren. In diesen Repositorys konnten die Objekte auf verschiedene Arten gespeichert werden (SQL Server, JSON, XML, Binary usw.). Daher habe ich versucht, den kleinsten gemeinsamen Nenner zu identifizieren, der den meisten Namenskonventionen entspricht.


1
Dies hängt wahrscheinlich stark von Ihrem Anwendungsfall ab. Entweder könnte man vernünftig sein, und ich habe sogar Klassen gesehen, die beide Methoden bereitstellen und den Benutzer entscheiden lassen. Könnten Sie näher erläutern, was diese Methode / Klasse / dieses Feld bewirken soll, damit wir Ihnen echte Ratschläge geben können?
Ixrec

1
Kennen Sie alle, die die Methode aufrufen? Wie in, wenn Sie es ändern, können Sie alle Kunden zuverlässig identifizieren? Wenn ja, würde ich so freizügig und nachsichtig vorgehen, wie es die Leistungsaspekte zulassen. Ich könnte auch die Dokumentation löschen. Wenn nicht, und es ist Teil einer Bibliotheks-API, würde ich sicherstellen, dass der Code genau die angekündigte API implementiert, da ansonsten das Ändern des Codes entsprechend der Dokumentation in Zukunft dazu neigt, Fehlerberichte zu generieren.
Jon Chesterfield

7
Sie könnten argumentieren, dass die Trennung von Bedenken besagt, dass Sie, falls erforderlich, eine strenge fooFunktion haben sollten, die genau festlegt, welche Argumente akzeptiert werden, und eine zweite Hilfsfunktion, die versuchen kann, ein Argument zu "bereinigen", mit dem es verwendet werden soll foo. Auf diese Weise hat jede Methode für sich weniger zu tun und kann sauberer verwaltet und integriert werden. Wenn Sie diesen Weg gehen, wäre es wahrscheinlich auch hilfreich, sich von einem außergewöhnlichen Design zu entfernen. Sie können Optionalstattdessen so etwas wie verwenden und dann die Funktionen verwenden, die foobei Bedarf Ausnahmen auslösen.
gntskn

1
Das ist wie die Frage "Jemand hat mir Unrecht getan, soll ich ihm vergeben?" Offensichtlich gibt es Umstände, unter denen entweder der eine oder der andere angemessen ist. Das Programmieren mag nicht so kompliziert sein wie die zwischenmenschlichen Beziehungen, aber es ist definitiv komplex genug, dass ein umfassendes Rezept wie dieses nicht funktionieren wird.
Kilian Foth

2
@ Boggin Ich möchte Sie auch auf das neu überdachte Robustheitsprinzip hinweisen . Die Schwierigkeit tritt auf, wenn Sie die Implementierung erweitern müssen und die fehlerverzeihende Implementierung zu einem mehrdeutigen Fall mit der erweiterten Implementierung führt.

Antworten:


47

Ihre Methode sollte das tun, was sie sagt.

Auf diese Weise wird verhindert, dass Fehler verwendet werden und das Verhalten von Betreuern später geändert wird. Das spart Zeit, da die Betreuer nicht so viel Zeit aufwenden müssen, um herauszufinden, was los ist.

Das heißt, wenn die definierte Logik nicht benutzerfreundlich ist, sollte sie möglicherweise verbessert werden.


8
Das ist der Schlüssel. Wenn Ihre Methode genau das tut, was sie sagt, kompensiert der Codierer, der Ihre Methode verwendet, den jeweiligen Anwendungsfall. Machen Sie mit der Methode niemals etwas Undokumentiertes, nur weil Sie es für nützlich halten. Wenn Sie es ändern müssen, schreiben Sie einen Container oder ändern Sie die Dokumentation.
Nelson

Zu @ Nelsons Kommentar möchte ich hinzufügen, dass die Methode nicht im luftleeren Raum entworfen werden sollte. Wenn die Programmierer angeben, dass sie es verwenden, es aber kompensieren und ihre Kompensationen einen allgemeinen Wert haben, sollten Sie erwägen, sie zu einem Teil der Klasse zu machen. (ZB haben foound fooForUncleanStringMethoden, bei denen letzterer die Korrekturen vornimmt, bevor er sie an ersteren
weitergibt

20

Es gibt ein paar Punkte:

  1. Ihre Implementierung muss das tun, was im dokumentierten Vertrag angegeben ist, und sollte nichts weiter tun.
  2. Einfachheit ist wichtig, sowohl für den Vertrag als auch für die Implementierung, jedoch mehr für die erstere.
  3. Der Versuch, fehlerhafte Eingaben zu korrigieren, erhöht die Komplexität, und zwar nicht nur kontrahierend und implementierend, sondern auch benutzerfreundlich.
  4. Fehler sollten nur frühzeitig abgefangen werden, wenn dies die Debug-Fähigkeit verbessert und die Effizienz nicht zu sehr beeinträchtigt.
    Denken Sie daran, dass es Debug-Assertions für die Diagnose von Logikfehlern im Debug-Modus gibt, mit denen Leistungsprobleme meistens behoben werden.
  5. Effizienz, soweit Zeit und Geld zur Verfügung stehen, ohne die Einfachheit zu sehr zu beeinträchtigen, ist immer ein Ziel.

Wenn Sie eine Benutzeroberfläche implementieren, sind benutzerfreundliche Fehlermeldungen (einschließlich Vorschläge und andere Hilfe) Teil eines guten Designs.
Denken Sie jedoch daran, dass APIs für Programmierer und nicht für Endbenutzer bestimmt sind.


Ein echtes Experiment, um unscharf und tolerant mit Eingaben umzugehen, ist HTML.
Was dazu führte, dass jeder etwas anders vorging und die Spezifikation, die jetzt dokumentiert ist, ein Mammutbuch voller Sonderfälle war.
Siehe Postels Gesetz (" Sei konservativ in dem, was du tust, sei liberal in dem, was du von anderen akzeptierst. ") Und einen Kritiker, der das berührt ( oder einen viel besseren, auf den mich MichaelT aufmerksam gemacht hat ).


Ein weiteres wichtiges Stück des Autors von sendmail: Das

15

Das Verhalten einer Methode sollte klar, intuitiv, vorhersehbar und einfach sein. Im Allgemeinen sollten wir sehr zögern, die Eingaben eines Anrufers zusätzlich zu verarbeiten. Solche Vermutungen darüber, was der Anrufer beabsichtigt hat, haben ausnahmslos viele Randfälle, die unerwünschtes Verhalten hervorrufen. Betrachten Sie eine Operation so einfach wie das Verbinden von Dateipfaden. Viele (oder vielleicht sogar die meisten) Dateipfadverknüpfungsfunktionen verwerfen im Hintergrund alle vorhergehenden Pfade, wenn einer der Pfade, die verknüpft werden, verwurzelt zu sein scheint! Wenn Sie zum Beispiel /abc/xyzmit verbinden /evil, erhalten Sie nur /evil. Das ist fast nie das, was ich vorhabe, wenn ich Dateipfade verbinde, aber da es keine Schnittstelle gibt, die sich nicht so verhält, muss ich entweder Fehler haben oder zusätzlichen Code schreiben, der diese Fälle abdeckt.

Trotzdem gibt es seltene Fälle, in denen es sinnvoll ist, dass eine Methode "verzeiht", aber es sollte immer im Ermessen des Anrufers liegen, zu entscheiden, wann und ob diese Verarbeitungsschritte auf seine Situation zutreffen. Wenn Sie also einen allgemeinen Vorverarbeitungsschritt gefunden haben, den Sie in verschiedenen Situationen auf Argumente anwenden möchten, sollten Sie Schnittstellen für Folgendes bereitstellen:

  • Die rohe Funktionalität, ohne Vorverarbeitung.
  • Der Vorverarbeitungsschritt für sich .
  • Die Kombination der Rohfunktionalität und der Vorverarbeitung.

Der letzte ist optional; Sie sollten es nur bereitstellen, wenn eine große Anzahl von Anrufen es verwenden wird.

Durch die Bereitstellung der Raw-Funktionalität kann der Aufrufer diese bei Bedarf ohne Vorverarbeitungsschritt verwenden. Wenn der Präprozessor-Schritt allein verfügbar ist, kann er vom Aufrufer in Situationen verwendet werden, in denen er die Funktion nicht einmal aufruft oder Eingaben vor dem Aufrufen der Funktion vorverarbeiten möchte (z. B. wenn er sie zuerst an eine andere Funktion übergeben möchte). Durch das Bereitstellen der Kombination können Anrufer beide Funktionen problemlos aufrufen. Dies ist vor allem dann hilfreich, wenn die meisten Anrufer dies auf diese Weise tun.


2
+1 für vorhersehbar. Und noch ein +1 (ich wünsche) für einfach. Ich würde es vorziehen, wenn Sie mir helfen, meine Fehler zu erkennen und zu korrigieren, anstatt sie zu verbergen.
John M Gant

4

Wie andere bereits gesagt haben, bedeutet das "Vergeben" der Zeichenfolgenübereinstimmung, dass zusätzliche Komplexität eingeführt wird. Das bedeutet mehr Arbeit bei der Implementierung des Matchings. Sie haben zum Beispiel jetzt viel mehr Testfälle. Sie müssen zusätzliche Arbeit leisten, um sicherzustellen, dass der Namespace keine semantisch gleichen Namen enthält. Mehr Komplexität bedeutet auch, dass in Zukunft mehr schief gehen muss. Ein einfacher Mechanismus wie ein Fahrrad erfordert weniger Wartung als ein komplexerer Mechanismus wie ein Auto.

Ist der milde String-Matching all diese zusätzlichen Kosten wert? Es hängt vom Anwendungsfall ab, wie andere angemerkt haben. Wenn es sich bei den Zeichenfolgen um eine externe Eingabe handelt, über die Sie keine Kontrolle haben, und wenn ein milder Abgleich einen eindeutigen Vorteil hat, kann sich dies lohnen. Möglicherweise kommt der Input von Endbenutzern, die in Bezug auf Leerzeichen und Großschreibung möglicherweise nicht sehr gewissenhaft sind, und Sie haben einen starken Anreiz, die Verwendung Ihres Produkts zu vereinfachen.

Wenn die Eingabe jedoch beispielsweise aus Eigenschaftendateien stammen würde, die von Technikern zusammengestellt wurden und die dies verstehen sollten "Fred Mertz" != "FredMertz", wäre ich eher geneigt, die Übereinstimmung zu verschärfen und die Entwicklungskosten zu senken.

Ich denke auf jeden Fall, dass es sinnvoll ist, führende und nachfolgende Leerzeichen zu kürzen und nicht zu berücksichtigen - ich habe zu viele Stunden damit verbracht, solche Probleme zu beheben.


3

Sie erwähnen einen Teil des Kontextes, aus dem diese Frage stammt.

Angesichts dessen würde ich die Methode nur eine Sache tun lassen, sie stellt die Anforderungen an den String, lässt ihn basierend darauf ausführen - ich würde nicht versuchen, ihn hier umzuwandeln. Halte es einfach und halte es klar; Dokumentieren Sie es und bemühen Sie sich, die Dokumentation und den Code miteinander zu synchronisieren.

Wenn Sie die aus der Benutzerdatenbank stammenden Daten noch fehlerverzeihender transformieren möchten, stellen Sie diese Funktionalität in eine separate Transformationsmethode und dokumentieren Sie die damit verbundene Funktionalität .

Irgendwann müssen die Anforderungen der Funktion erfasst, klar dokumentiert und die Ausführung fortgesetzt werden. Die "Vergebung" ist seiner Meinung nach ein wenig stumm, es ist eine Designentscheidung und ich würde argumentieren, dass die Funktion ihr Argument nicht mutiert. Wenn die Funktion die Eingabe mutiert, wird ein Teil der Validierung ausgeblendet, die für den Client erforderlich wäre. Eine Funktion, die die Mutation ausführt, hilft dem Client, sie richtig zu machen.

Das Hauptaugenmerk liegt hier auf Klarheit und der Dokumentation der Code-Funktionen .


-1
  1. Sie können eine Methode entsprechend der Aktion wie doSomething (), takeBackUp () benennen.
  2. Um die Wartung zu vereinfachen, können Sie die gemeinsamen Verträge und die Validierung für verschiedene Verfahren beibehalten. Rufen Sie sie gemäß Anwendungsfällen an.
  3. Defensive Programmierung: Ihre Prozedur verarbeitet eine Vielzahl von Eingaben, einschließlich
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.