Wann sollte eine IllegalArgumentException ausgelöst werden?

98

Ich mache mir Sorgen, dass dies eine Laufzeitausnahme ist, daher sollte es wahrscheinlich sparsam verwendet werden.
Standard-Anwendungsfall:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Aber das scheint das folgende Design zu erzwingen:

public void computeScore() throws MyPackageException {
      try {
          setPercentage(userInputPercent);
      }
      catch(IllegalArgumentException exc){
           throw new MyPackageException(exc);
      }
 }

Damit es wieder zu einer geprüften Ausnahme wird.

Okay, aber lass uns damit anfangen. Wenn Sie eine schlechte Eingabe machen, wird ein Laufzeitfehler angezeigt. Erstens ist es also ziemlich schwierig, diese Richtlinie einheitlich umzusetzen, da Sie möglicherweise genau das Gegenteil tun müssen:

public void scanEmail(String emailStr, InputStream mime) {
    try {
        EmailAddress parsedAddress = EmailUtil.parse(emailStr);
    }
    catch(ParseException exc){
        throw new IllegalArgumentException("bad email", exc);
    }
}

Und noch schlimmer: Während 0 <= pct && pct <= 100erwartet werden kann, dass die Überprüfung des Client-Codes statisch erfolgt, gilt dies nicht für erweiterte Daten wie eine E-Mail-Adresse oder noch schlimmer für etwas, das mit einer Datenbank verglichen werden muss. Daher kann der Client-Code im Allgemeinen nicht vorab überprüft werden bestätigen.

Grundsätzlich sage ich also, dass ich keine aussagekräftige konsistente Richtlinie für die Verwendung von sehe IllegalArgumentException. Es scheint, dass es nicht verwendet werden sollte und wir uns an unsere eigenen geprüften Ausnahmen halten sollten. Was ist ein guter Anwendungsfall, um dies zu werfen?

Djechlin
quelle

Antworten:

80

Das API-Dokument für IllegalArgumentException:

Wird ausgelöst, um anzuzeigen, dass einer Methode ein unzulässiges oder unangemessenes Argument übergeben wurde.

Wenn ich mir anschaue, wie es in den JDK-Bibliotheken verwendet wird , würde ich sagen:

  • Es scheint eine Abwehrmaßnahme zu sein, sich über offensichtlich schlechte Eingaben zu beschweren, bevor die Eingaben in Arbeit gehen und dazu führen können, dass etwas auf halbem Weg mit einer unsinnigen Fehlermeldung fehlschlägt.

  • Es wird in Fällen verwendet, in denen es zu ärgerlich wäre, eine aktivierte Ausnahme auszulösen (obwohl es im Code java.lang.reflect erscheint, in dem die Besorgnis über lächerliche Ebenen des Auslösens von überprüften Ausnahmen ansonsten nicht offensichtlich ist).

Ich würde es verwenden, IllegalArgumentExceptionum die letzte Argumentationsprüfung für allgemeine Dienstprogramme durchzuführen (um zu versuchen, mit der JDK-Verwendung konsistent zu bleiben). Oder wenn die Erwartung besteht, dass ein schlechtes Argument ein Programmiererfehler ist, ähnlich einem NullPointerException. Ich würde es nicht verwenden, um die Validierung im Geschäftscode zu implementieren. Ich würde es sicherlich nicht für das E-Mail-Beispiel verwenden.

Nathan Hughes
quelle
8
Ich denke, der Ratschlag "wo die Erwartung besteht, dass ein schlechtes Argument ein Programmiererfehler ist" stimmt am ehesten mit meiner Verwendung überein, also akzeptiere ich diese Antwort.
Djechlin
22

Wenn Sie über "schlechte Eingabe" sprechen, sollten Sie berücksichtigen, woher die Eingabe kommt.

Wird die Eingabe von einem Benutzer oder einem anderen externen System eingegeben, das Sie nicht steuern, sollten Sie erwarten, dass die Eingabe ungültig ist, und sie immer validieren. In diesem Fall ist es vollkommen in Ordnung, eine aktivierte Ausnahme auszulösen. Ihre Anwendung sollte sich von dieser Ausnahme erholen, indem sie dem Benutzer eine Fehlermeldung sendet.

Wenn die Eingabe von Ihrem eigenen System stammt, z. B. von Ihrer Datenbank oder einigen anderen Teilen Ihrer Anwendung, sollten Sie sich darauf verlassen können, dass sie gültig ist (sie sollte validiert worden sein, bevor sie dort ankam). In diesem Fall ist es vollkommen in Ordnung, eine ungeprüfte Ausnahme wie eine IllegalArgumentException auszulösen, die nicht abgefangen werden sollte (im Allgemeinen sollten Sie niemals ungeprüfte Ausnahmen abfangen). Es ist ein Programmiererfehler, dass der ungültige Wert überhaupt dort angekommen ist;) Sie müssen ihn beheben.

Tom
quelle
2
Warum "sollten Sie niemals ungeprüfte Ausnahmen abfangen"?
Koray Tugay
9
Weil eine ungeprüfte Ausnahme aufgrund eines Programmierfehlers ausgelöst werden soll. Es ist nicht zu erwarten, dass der Aufrufer einer Methode, die solche Ausnahmen auslöst, sich davon erholt, und daher ist es normalerweise nicht sinnvoll, sie abzufangen.
Tom
1
Because an unchecked exception is meant to be thrown as a result of a programming errorhat mir geholfen, eine Menge Sachen in meinem Kopf zu
klären
14

Das "sparsame" Auslösen von Laufzeitausnahmen ist keine wirklich gute Richtlinie. Effektives Java empfiehlt, dass Sie aktivierte Ausnahmen verwenden, wenn vernünftigerweise erwartet werden kann, dass der Anrufer wiederhergestellt wird . (Ein Programmiererfehler ist ein spezielles Beispiel: Wenn ein bestimmter Fall auf einen Programmiererfehler hinweist, sollten Sie eine ungeprüfte Ausnahme auslösen. Sie möchten, dass der Programmierer eine Stapelverfolgung darüber hat, wo das Logikproblem aufgetreten ist, und nicht versucht, es selbst zu behandeln.)

Wenn keine Hoffnung auf Wiederherstellung besteht, können Sie ungeprüfte Ausnahmen verwenden. Es macht keinen Sinn, sie zu fangen, also ist das vollkommen in Ordnung.

Aus Ihrem Beispiel geht jedoch nicht zu 100% hervor, in welchem ​​Fall sich dieses Beispiel in Ihrem Code befindet.

Louis Wasserman
quelle
Ich denke, "vernünftigerweise erwartet, dass es sich erholt" ist wieselhaft. Jede Operation foo(data)könnte als Teil einer Operation durchgeführt worden sein, for(Data data : list) foo(data);bei der der Anrufer so viele wie möglich zum Erfolg führen möchte, obwohl einige Daten fehlerhaft sind. Enthält auch programmatische Fehler. Wenn meine Anwendung fehlschlägt und eine Transaktion nicht ausgeführt wird, ist dies wahrscheinlich besser. Wenn die nukleare Kühlung offline geschaltet wird, ist dies schlecht.
Djechlin
StackOverflowErrorund dies sind Fälle, von denen der Anrufer vernünftigerweise keine Erholung erwarten kann. Es klingt jedoch so, als ob jeder Fall einer Daten- oder Anwendungslogikebene überprüft werden sollte. Das heißt, machen Sie Ihre Nullzeigerprüfungen!
Djechlin
4
In einer nuklearen Kühlanwendung würde ich bei Tests lieber hart versagen, als einen Fall zuzulassen, den der Programmierer für unmöglich hielt, unbemerkt zu lassen.
Louis Wasserman
Boolean.parseBoolean (..) löst eine IllegalArugmentException aus, obwohl "vernünftigerweise erwartet werden kann, dass der Aufrufer wiederhergestellt wird". Also ... es liegt an Ihrem Code, damit umzugehen oder zum Anrufer zurückzukehren.
Jeryl Cook
5

Wie im offiziellen Orakel-Tutorial angegeben, heißt es:

Wenn von einem Client vernünftigerweise erwartet werden kann, dass er sich von einer Ausnahme erholt, machen Sie ihn zu einer aktivierten Ausnahme. Wenn ein Client nichts tun kann, um die Ausnahme wiederherzustellen, machen Sie es zu einer nicht aktivierten Ausnahme.

Wenn ich eine Anwendung habe, die mit der Datenbank interagiert JDBC, und ich eine Methode habe, die das Argument als int itemund verwendet double price. Das priceentsprechende Element wird aus der Datenbanktabelle gelesen. Ich multipliziere einfach die Gesamtzahl der itemgekauften mit dem priceWert und gebe das Ergebnis zurück. Obwohl ich immer sicher an meinem Ende bin (Application Ende) , dass der Preisfeldwert in der Tabelle kann nie negativ sein .Aber was ist, wenn der Preis - Wert kommt aus negativ ? Es zeigt, dass es ein ernstes Problem mit der Datenbankseite gibt. Möglicherweise falsche Preiseingabe durch den Betreiber. Dies ist die Art von Problem, die der andere Teil der Anwendung, der diese Methode aufruft, nicht vorhersehen und nicht beheben kann. Es ist ein BUGin Ihrer Datenbank. So undIllegalArguementException()sollte in diesem Fall geworfen werden, der das angeben würde the price can't be negative.
Ich hoffe, dass ich meinen Standpunkt klar zum Ausdruck gebracht habe.

Vishal K.
quelle
Ich mag diesen (Orakel-) Rat nicht, weil es bei der Ausnahmebehandlung darum geht, wie man sich erholt, nicht darum, ob man sich erholt. Zum Beispiel ist eine fehlerhafte Benutzeranforderung nicht den Absturz eines gesamten Webservers wert.
Djechlin
5

Jede API sollte die Gültigkeit aller Parameter einer öffentlichen Methode überprüfen, bevor sie ausgeführt wird:

void setPercentage(int pct, AnObject object) {
    if( pct < 0 || pct > 100) {
        throw new IllegalArgumentException("pct has an invalid value");
    }
    if (object == null) {
        throw new IllegalArgumentException("object is null");
    }
}

Sie stellen 99,9% der Fehler in der Anwendung dar, da nach unmöglichen Vorgängen gefragt wird. Letztendlich handelt es sich also um Fehler, die die Anwendung zum Absturz bringen sollten (es handelt sich also um einen nicht behebbaren Fehler).

In diesem Fall sollten Sie die Anwendung beenden lassen, um eine Beschädigung des Anwendungsstatus zu vermeiden.

Ignacio Soler Garcia
quelle
Im Gegenteil, wenn ein API-Client mir eine schlechte Eingabe gibt, sollte ich nicht meinen gesamten API-Server zum Absturz bringen.
Djechlin
2
Natürlich sollte es Ihren API-Server nicht zum Absturz bringen, sondern eine Ausnahme an den Aufrufer zurückgeben, die nur den Client zum Absturz bringen sollte.
Ignacio Soler Garcia
Was Sie in dem Kommentar geschrieben haben, ist nicht das, was Sie in der Antwort geschrieben haben.
Djechlin
1
Lassen Sie mich erklären, wenn der Aufruf der API mit falschen Parametern (ein Fehler) von einem Drittanbieter-Client ausgeführt wird, sollte der Client abstürzen. Wenn es sich um den API-Server handelt, bei dem ein Fehler auftritt, der die Methode mit falschen Parametern aufruft, sollte der API-Server abstürzen. Überprüfen Sie: en.wikipedia.org/wiki/Fail-fast
Ignacio Soler Garcia
1

Treat IllegalArgumentExceptionals Voraussetzungen zu überprüfen, und betrachtet das Konstruktionsprinzip: Eine öffentliche Methode sollte sowohl kennen und öffentlich seine eigenen Voraussetzungen dokumentieren.

Ich würde zustimmen, dass dieses Beispiel richtig ist:

void setPercentage(int pct) {
    if( pct < 0 || pct > 100) {
         throw new IllegalArgumentException("bad percent");
     }
}

Wenn EmailUtil undurchsichtig ist , was bedeutet, dass die Voraussetzungen dem Endbenutzer aus irgendeinem Grund nicht beschrieben werden können, ist eine aktivierte Ausnahme korrekt. Die zweite Version, korrigiert für dieses Design:

import com.someoneelse.EmailUtil;

public void scanEmail(String emailStr, InputStream mime) throws ParseException {
    EmailAddress parsedAddress = EmailUtil.parseAddress(emailStr);
}

Wenn EmailUtil transparent ist , beispielsweise eine private Methode der betreffenden Klasse, IllegalArgumentExceptionist dies genau dann richtig, wenn die Voraussetzungen in der Funktionsdokumentation beschrieben werden können. Dies ist auch eine korrekte Version:

/** @param String email An email with an address in the form [email protected]
 * with no nested comments, periods or other nonsense.
 */
public String scanEmail(String email)
  if (!addressIsProperlyFormatted(email)) {
      throw new IllegalArgumentException("invalid address");
  }
  return parseEmail(emailAddr);
}
private String parseEmail(String emailS) {
  // Assumes email is valid
  boolean parsesJustFine = true;
  // Parse logic
  if (!parsesJustFine) {
    // As a private method it is an internal error if address is improperly
    // formatted. This is an internal error to the class implementation.
    throw new AssertError("Internal error");
  }
}

Dieser Entwurf könnte in beide Richtungen gehen.

  • Wenn die Beschreibung von Voraussetzungen teuer ist oder wenn die Klasse von Clients verwendet werden soll, die nicht wissen, ob ihre E-Mails gültig sind, verwenden Sie ParseException. Die hier beschriebene Methode der obersten Ebene scanEmailweist darauf hin, dass der Endbenutzer beabsichtigt, nicht untersuchte E-Mails zu senden, sodass dies wahrscheinlich korrekt ist.
  • Wenn die Voraussetzungen in der Funktionsdokumentation beschrieben werden können und die Klasse keine ungültige Eingabe beabsichtigt und daher ein Programmiererfehler angezeigt wird, verwenden Sie IllegalArgumentException. Obwohl nicht "markiert", wird die "Prüfung" in das Javadoc verschoben, um die Funktion zu dokumentieren, an die sich der Client voraussichtlich halten wird. IllegalArgumentExceptionWo der Kunde nicht sagen kann, dass sein Argument im Voraus illegal ist, ist falsch.

Hinweis zu IllegalStateException : Dies bedeutet, dass der interne Status dieses Objekts (private Instanzvariablen) diese Aktion nicht ausführen kann. Der Endbenutzer kann den privaten Status nicht so lose sehen, dass er Vorrang hat, IllegalArgumentExceptionwenn der Clientaufruf nicht erkennen kann, dass der Status des Objekts inkonsistent ist. Ich habe keine gute Erklärung, wenn es gegenüber geprüften Ausnahmen bevorzugt wird, obwohl Dinge wie zweimaliges Initialisieren oder der Verlust einer nicht wiederhergestellten Datenbankverbindung Beispiele sind.

Djechlin
quelle