10 Stimmen

Java - wo und wie sollten Ausnahmen verwendet werden?

Ich habe einige Dinge über die Behandlung von Ausnahmen in Java gelesen, um besseren Code schreiben zu können. OK, ich gebe zu, ich habe mich schuldig gemacht; ich habe zu viele try-catch{}-Blöcke verwendet, ich habe ex.printStackTrace() in den Fängen, nicht einmal mit einem richtigen Logger (eigentlich der System.out y System.err wurden umgeleitet zu einer PrintWriter so wurde ein Protokoll erstellt). Nach ein paar Stunden des Lesens befinde ich mich jedoch an einem seltsamen Ort: dem Unbekannten. Wenn die Ausnahmen so konzipiert sind, dass sie Informationen über abnormale Flusszustände weitergeben, woher weiß man dann, WO die richtige Ebene ist, um etwas mit dieser Information zu tun?

Wenn beispielsweise ein Datenbankfehler auftritt, sollte man dann einen Nullwert oder einen Fehlercode zurückgeben oder die Ausnahme auslösen? Wenn die Ausnahme ausgelöst wird, WO sollte diese Ausnahme behandelt werden? Ich verstehe, dass es keinen Sinn macht, eine Ausnahme zu protokollieren, wenn man nichts dagegen tun kann. In GUI-Anwendungen könnte das jedoch leicht die GUI zerstören (ich verwende SWT und habe das schon zu oft gesehen), selbst für den Fall, dass die menuShown() Methode (ein ArrayIndexOutOfBounds Ausnahme wird die Anwendung geschlossen, wenn sie nicht behandelt wird). Das Beispiel könnte ewig so weitergehen, aber hier ist die Zusammenfassung der Fragen:

  1. Hat die übermäßige Verwendung von try-catch() negative Auswirkungen auf die Leistung?
  2. Ist es besser, bestimmte Ausnahmetypen zu verwenden? Was ist, wenn ich eine der folgenden Ausnahmen nicht der möglichen X Arten von Ausnahmen, die auftreten können, nicht abzufangen?
    Ehrlich gesagt, habe ich in 2-3 Jahren gerade einmal 10% der Java-Standardausnahmen kennengelernt und verwendet. Ja, jemand hat gesagt, dass der Aufrufer, wenn er nicht weiß, wie er mit den geworfenen Ausnahmen umgehen soll, NICHT das RECHT haben sollte, die werfende Methode aufzurufen. Ist das richtig?
  3. Ich habe diesen Artikel von Anders Hejlsberg und sagt, dass geprüfte Ausnahmen schlecht sind. Sollte das darauf hindeuten, dass es in manchen Fällen ratsam ist, Ausnahmen zu schlucken?
  4. Ein Bild sagt mehr als 1000 Worte; ich denke, einige Beispiele werden hier sehr hilfreich sein hier.

Ich weiß, dass das Thema ewig währt, aber eigentlich freue ich mich darauf, ein mittelgroßes Projekt mit 150 Klassen zu überprüfen und dabei Ihre Ratschläge zu nutzen. Vielen Dank dafür.

1voto

meriton Punkte 65030

Rückgabewert vs. Auslösen einer Ausnahme

Der grundlegende Unterschied zwischen einer Ausnahme und einem Rückgabewert besteht darin, dass der Rückgabewert an den unmittelbaren Aufrufer geliefert wird, während eine Ausnahme an eine catch-Klausel an einer beliebigen Stelle im Aufrufstapel geliefert wird. Dies ermöglicht die Wiederverwendung desselben Ausnahmebehandlungsprogramms für viele verschiedene Arten von Ausnahmen. Ich empfehle Ihnen, Ausnahmen gegenüber Rückgabewerten zu bevorzugen, wenn und nur wenn Sie diese Funktion benötigen.

Auswirkungen auf die Leistung.

Jede Anweisung hat negative Auswirkungen auf die Leistung, auch die in Catch-Blöcken. Jede moderne CPU kann jedoch Millionen von Ausnahmen pro Sekunde auslösen und verarbeiten, so dass Sie nichts davon bemerken werden, solange Sie nicht Tausende davon auslösen.

Besondere Ausnahmen

Beim Werfen sollten Sie spezifisch sein, um eine bestimmte Handhabung zu ermöglichen. Für die Behandlung können Sie generisch sein, aber Sie sollten sich bewusst sein, dass beliebige Ausnahmen an Ihren Handler geliefert werden können, einschließlich ungeprüfter Ausnahmen, die nicht von Ihren Aufrufen deklariert wurden.

geprüft

Es ist umstritten, ob Methoden geprüfte oder ungeprüfte Ausnahmen verwenden sollten. Verschlucken Sie niemals einfach eine Ausnahme. Behandle sie oder wirf sie zurück. Es vereinfacht die Wartung, wenn Sie die Beweise für Fehler nicht verwerfen.

Exemple

Eine Anwendung, an der ich kürzlich gearbeitet habe, empfängt Befehle über das Netz, die sie dann ausführt. Dies erfordert in der Regel eine weitere Interaktion mit entfernten Systemen, die aus einer Vielzahl von Gründen fehlschlagen kann. Die Methoden zur Ausführung des Befehls fangen keine Ausnahmen ab und leiten sie über den Aufrufstapel an den zentralen Ausnahmebehandler im Befehlsempfänger weiter, der Folgendes tut:

for (int retries = 0;; retries++) {
    try {
        commandService.execute(command);
        return;
    } catch (Exception e}
        Log.error(e);
        if (retries < 3) {
            continue;
        } else {
            saveForAnalysis(command, e);
            alertOperator();
            return;
        }
    }
}

Wir haben absichtlich keine Ausnahmen in der Verarbeitungslogik abgefangen und zurückgeworfen, da wir der Meinung waren, dass dies keinen Mehrwert gebracht hätte.

1voto

Gzorg Punkte 769

se-radio hat eine Podcast-Episode zum Thema Fehlerbehandlung erstellt, in der eine Philosophie über die Verwendung von Ausnahmen erläutert wird, die sich als "Where to absorb them" umschreiben lässt.

Das Wichtigste, was ich beibehalten habe, ist, dass die meisten Funktionen sie aufsprudeln lassen sollten, und die meisten Ausnahmedetails sollten in einer Protokolldatei landen. Dann übergeben die Funktionen nur globale Meldungen, die besagen, dass etwas passiert ist.

In gewisser Weise führt dies zu einer Art Ausnahmehierarchie: eine für jede Codeschicht.

Ich glaube, es macht keinen Sinn, dem Benutzer zu erklären, dass ein solcher DB-Cluster fehlgeschlagen ist, weil der DNS nicht verfügbar war oder weil die Festplatte voll war. Auf dieser Ebene ist etwas passiert, das den Abschluss der Transaktion nicht zuließ, und das ist alles, was der Benutzer wissen muss.

Natürlich würden die Entwickler/Administratoren gerne mehr Details sehen, deshalb sollten auf der DB-Schicht die spezifischen Ausnahmen protokolliert werden.

1voto

NawaMan Punkte 24275

Die Ausnahme ist dazu da, dass der Programmierer eines Tasks sich nicht selbst um das Problem kümmern muss. (1): Falls das Problem für ihn NICHT LOGISCH ist, um es im Task zu behandeln. Ein Task, der eine Zeichenkette aus einem Stream liest, sollte keinen Plattenfehler behandeln, nicht wahr? Aber es sollte sehr logisch sein, damit umzugehen, wenn die Daten keinen String enthalten.

(2): Er kann es nicht allein bewältigen (nicht genügend Informationen) Eine Aufgabe zum Lesen einer Zeichenkette aus einer Datei und einer nicht gefundenen Datei kann den Benutzer auffordern, eine andere Datei auszuwählen, aber wie kann die Aufgabe wissen, in welchem Ordner sich die Datei befinden könnte und welche Erweiterung die Datei haben könnte. Wie kann die Aufgabe ohne dieses Wissen eine grafische Benutzeroberfläche erstellen, um diese Frage erneut zu stellen?

(3): Es gibt keine logische (oder handhabbare) Möglichkeit, zwischen verschiedenen Rückgaben zu unterscheiden. Wenn eine Aufgabe die Datei nicht lesen kann und null zurückgibt. Was ist, wenn die Datei das falsche Format hat und ebenfalls null zurückgibt? Wie können sich diese beiden unterscheiden? Ausnahmen können verwendet werden, um dies zu unterscheiden. Deshalb nennt man sie auch Exception :-D.

(4): Es gibt viele ähnliche Aufgaben, die ähnlich gehandhabt werden müssen, und das Schreiben von Code für alle Aufgaben ist schwer zu pflegen. Das Schreiben des Handle-Codes für alle Zugriffe kann zu einem Chaos führen, da viele Duplikationen erforderlich sein können.

interface DBAccess {
    public Result accessDB();
}

class DBOperation {
    static public void DoOperation(DBAccess pAccess) {
        try { return DBAccess.accessDB(); }
        catch(InvalidDBPasswordException IPE) {
             // Do anything about invalid password
        }
        catch(DBConnectionLostException DBCLE) {
             // Do anything about database connection lost
        }
        // Catch all possible DB problem
    }
}

...

private User[] ShowUserList_and_ReturnUsers() {
    // Find the used.

    // Show user list

    if (Users.count() == 0)
         return null;
    else return Users;

    // No need to handle DB connection problem here
}
private User[] GetUserProfile() {
    // Find the used and return
    // No need to handle DB connection problem here
}
...

/** An onClick event to show user list */ {
    DBOperation.DoOperation(new DBAccess() {
        public Result accessDB() {
            return ShowUserList_and_ReturnUsers();
        }
    });
}
/** An onClick event to show a user profile */ {
    DBOperation.DoOperation(new DBAccess() {
        public Result accessDB() {
            return GetUserProfile();
        }
    });
}
... Many more DB access

(5): Das Schreiben aller Fehlerprüfungen erschwert oder verlangsamt die Aufgabe. Das obige Problem sollte zeigen, wie es helfen kann, die Komplikation zu reduzieren. Hier ist, wie es helfen, nicht zu verlangsamen.

for(int i = 0; i < Users.length; i++) {
    User aUser = Users[i];
    // Do something with user
}

Replaced with

try {
  for(int i = 0; ; i++) {
      User aUser = Users[i];
      // Do something with user
  }
}
catch(ArrayOutOfBoundException AOBE) {}

Der Ersatzcode ist leistungsfähiger, wenn die Anzahl der Benutzer groß ist.


Sollte man beim Auftreten eines Datenbankfehlers einen Nullwert und einen Fehlercode zurückgeben oder die Ausnahme auslösen? Antwort: Je nachdem, um welche Art von Fehler es sich handelt. Wenn man zum Beispiel einen Benutzer nicht finden kann, ist das kein Fehler. Aber wenn das Passwort falsch ist oder die Verbindung unterbrochen ist, sind dies Fehler, da der Versuch, dies auf normale Weise zu behandeln, das Programm verkompliziert.

(1). Hat die übermäßige Verwendung von try-catch() negative Auswirkungen auf die Leistung? Antwort: Laut "Effective Java" hat es nur sehr geringe Auswirkungen (nur in Schleifen nicht gut), soweit ich mich erinnere (ich habe das Buch jetzt nicht bei mir).

(2). Ist die Verwendung bestimmter Ausnahmetypen besser? Antwort: Benutzerspezifische Ausnahmen sind besser, um zu vermeiden, dass das falsche Problem gelöst wird.

Was ist, wenn ich es versäumt habe, eine der X möglichen Ausnahmen abzufangen, die auftreten können? Ehrlich gesagt habe ich in 2-3 Jahren nur 10 % der Java-Standardausnahmen gehört und verwendet, denke ich. Antwort: Genauso wie Sie den Fehler ohne Ausnahme behandeln, können Sie ihn auch übersehen. Sie fügen sie einfach hinzu, wenn Sie das herausfinden.

Ja, jemand hat gesagt, dass der Aufrufer, wenn er nicht weiß, wie er mit den geworfenen Ausnahmen umgehen soll, NICHT das RECHT haben sollte, die Wurfmethode aufzurufen. Ist das richtig? Antwort: Nein, wenn ich nicht weiß, was ich mit einer Ausnahme machen soll, werfe ich sie erneut.

(3). Ich habe diesen Artikel von Anders Hejlsberg gelesen, der besagt, dass geprüfte Ausnahmen schlecht sind. Sollte das bedeuten, dass es in manchen Fällen ratsam ist, Ausnahmen zu schlucken? Antwort: Ich denke, er spricht über "Checking exception" als eine Funktion für den Compiler, um sicherzustellen, dass eine Ausnahme behandelt werden sollte. Die Idee, eine Ausnahme zu haben.

(4). Ein Bild sagt mehr als 1000 Worte..ich denke, einige Beispiele werden hier sehr helfen. Antwort: Der obige Code.

Ich habe jetzt den Lauf .... Sorry ... :-p (Bin gleich da, Schatz!!)

1voto

Bitte geben Sie im Falle von nicht fatalen Fehlern nicht null zurück. Geben Sie stattdessen ein NullObject zurück.

Andernfalls müssen Sie nach jedem Aufruf Ihres Codes eine Nullprüfung durchführen, was sehr mühsam ist und, wenn es vergessen wird, zum Absturz des Codes führt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X