Dart-Ausnahmebehandlung
Eine Exception ist ein Fehlerereignis, das während der Programmausführung auftritt.
Eine gute Exception-Behandlung ermöglicht es dem Programm, bei Fehlern kontrolliert zu reagieren, anstatt direkt abzustürzen.
Dieses Kapitel führt in try/catch/finally in Dart ein, das Auslösen von Exceptions mit throw, das Abfangen bestimmter Typen mit on und das erneute Auslösen mit rethrow.
try / catch / finally Grundlagen
Der try-Block enthält Code, der Exceptions auslösen kann, der catch-Block fängt Exceptions ab und behandelt sie, und der finally-Block wird ausgeführt, unabhängig davon, ob eine Exception auftritt.
Beispiel
// Einfaches try-catch
try {
int result = 10 ~/ 0; // Division durch Null löst eine Exception aus
print(Ergebnis: $result); // Diese Zeile wird nicht ausgeführt
} catch (e) {
// e ist das Exception-Objekt
print(Abgefangene Exception: $e);
}
// Exception- und Stacktrace-Informationen abrufen
try {
List<int> numbers = [1, 2, 3];
print(numbers[10]); // Zugriff auf Index außerhalb der Grenzen
} catch (e, stackTrace) {
// e ist die Ausnahme, stackTrace ist der Aufrufstapel
print(BEISPIEL-Ausnahme: $e);
print(Stacktrace-Informationen:\n$stackTrace');
}
// finally: wird ausgeführt, unabhängig davon, ob eine Ausnahme auftritt
try {
print(Versuch, die Operation auszuführen...);
int result = 5 ~/ 0;
print(Diese Zeile wird nicht ausgeführt: $result);
} catch (e) {
print(Fehler: $e);
} finally {
// Code im finally-Block wird immer ausgeführt, oft verwendet, um Ressourcen aufzuräumen
print(Ressourcenbereinigung abgeschlossen (finally wird immer ausgeführt));
}
}
捕获到异常: IntegerDivisionByZeroException EXAMPLE 异常: RangeError (index): Invalid value: Not in inclusive range 0..2: 10 堆栈信息: #0 List.[] (dart:core-patch/growable_array.dart:264) #1 main (file:///...) ... 尝试执行操作... 出错: IntegerDivisionByZeroException 清理资源完成(finally 总是执行)
Der finally-Block eignet sich am besten für die Ressourcenbereinigung, wie das Schließen von Dateien, das Freigeben von Datenbankverbindungen usw. Selbst wenn es eine return-Anweisung in try oder catch gibt, wird finally trotzdem ausgeführt.
throw: Eine Exception auslösen
Mit dem Schlüsselwort throw können Sie aktiv eine Exception auslösen.
Dart erlaubt es, jedes Objekt als Exception zu werfen (nicht nur Unterklassen von Exception), aber die bewährte Vorgehensweise ist, Unterklassen von Exception oder Error zu werfen.
Beispiel
class InvalidAgeException implements Exception {
final int age;
final String message;
InvalidAgeException(this.age)
: message = Ungültiges Alter: $age (Alter muss zwischen 0 und 150 liegen);
@override
String toString() => 'InvalidAgeException: $message';
}
// Funktion, die eine benutzerdefinierte Exception verwendet
void validateAge(int age) {
if (age < 0) {
throw InvalidAgeException(age);
}
if (age > 150) {
throw InvalidAgeException(age);
}
print(Alter $age erfolgreich validiert);
}
// Eingebaute Exception auslösen
int divide(int a, int b) {
if (b == 0) {
throw ArgumentError(Divisor darf nicht 0 sein); // Eingebaute Exception verwenden
}
return a ~/ b;
}
void main() {
// Benutzerdefinierte Exception testen
try {
validateAge(-5);
} catch (e) {
print(e);
}
try {
validateAge(200);
} catch (e) {
print(e);
}
validateAge(25); // Normalfall
// Auslösen einer integrierten Exception testen
try {
divide(10, 0);
} catch (e) {
print(BEISPIEL Fehler: $e);
}
}
InvalidAgeException: 无效的年龄: -5(年龄必须在 0 到 150 之间) InvalidAgeException: 无效的年龄: 200(年龄必须在 0 到 150 之间) 年龄 25 验证通过 EXAMPLE 错误: Invalid argument(s): 除数不能为 0
Der Unterschied zwischen Exception und Error: Exception ist ein vorhersehbarer Fehler, den das Programm behandeln kann (z. B. Netzwerk-Timeout, Datei nicht gefunden); Error ist ein unvorhersehbares Problem, das normalerweise auf einen Programmfehler hinweist (z. B. Typfehler, Nullzeiger). Ihr Code sollte Exception abfangen, nicht Error.
on: Einen angegebenen Typ abfangen
Das Schlüsselwort on kann Ausnahmen nach Typ abfangen, was eine granularere Fehlerbehandlung ermöglicht.
Beispiel
// Unterschiedliche Ausnahmen nach Typ abfangen
try {
// Verschiedene Arten von Ausnahmen simulieren
int result = performOperation('divide_by_zero');
print(Ergebnis: $result);
} on IntegerDivisionByZeroException {
// Nur Division-durch-Null-Ausnahme abfangen
print(BEISPIEL Fehler: Division durch Null nicht möglich);
} on FormatException catch (e) {
// Format-Ausnahme abfangen und Ausnahmeinformationen abrufen
print('BEISPIEL Formatfehler: ${e.message}');
} on RangeError {
// Nur Bereichsfehler abfangen
print('BEISPIEL-Fehler: Index außerhalb des Bereichs');
} catch (e) {
// Fallback: alle anderen Ausnahmen abfangen
print('Unbekannter Fehler: $e');
} finally {
print('Operation beendet\n');
}
// Beispiel für Übereinstimmung in Reihenfolge
print('--- Verschiedene Operationen testen ---');
for (var op in ['divide_by_zero', 'parse_error', 'out_of_range', 'normal']) {
try {
performOperation(op);
} on IntegerDivisionByZeroException {
print('$op -> Ausnahme wegen Division durch Null');
} on FormatException {
print('$op -> Format-Ausnahme');
} on RangeError {
print('$op -> Bereichs-Ausnahme');
} catch (e) {
print('$op -> andere Ausnahme: $e');
}
}
}
int performOperation(String operation) {
switch (operation) {
case 'divide_by_zero':
return 10 ~/ 0; // IntegerDivisionByZeroException auslösen
case 'parse_error':
int.parse('not_a_number'); // FormatException auslösen
return 0;
case 'out_of_range':
var list = [1, 2, 3];
return list[100]; // RangeError auslösen
case 'normal':
return 42;
default:
throw Exception('Unbekannte Operation');
}
}
EXAMPLE 错误: 不能除以零 操作结束 --- 测试不同的操作 --- divide_by_zero -> 除零异常 parse_error -> 格式异常 out_of_range -> 范围异常
Die Reihenfolge von on und catch ist wichtig: Dart gleicht Ausnahmetypen von oben nach unten ab. Spezifischere Ausnahmetypen sollten zuerst platziert werden, allgemeinere zuletzt. Wenn catch (e) am Anfang steht, werden die nachfolgenden on-Klauseln nie ausgeführt.
rethrow: Die Ausnahme erneut auslösen
Manchmal müssen Sie nach dem Abfangen einer Ausnahme eine Verarbeitung durchführen (z. B. Protokollierung) und die Ausnahme dann weiter nach oben propagieren lassen.
Das Schlüsselwort rethrow wird verwendet, um die aktuell abgefangene Ausnahme erneut auszulösen und dabei die ursprünglichen Stack-Informationen beizubehalten.
Beispiel
void databaseOperation() {
print(' Verbindung zur Datenbank wird hergestellt...');
throw Exception('BEISPIEL Datenbankverbindung fehlgeschlagen');
}
void serviceLayer() {
try {
print(' Service-Schicht: Datenbankoperation wird aufgerufen');
databaseOperation();
} catch (e) {
// Zuerst protokollieren, dann erneut auslösen
print(' [Log] Datenbankoperationsausnahme: $e');
rethrow; // Ursprüngliche Ausnahme und Stack beibehalten, weiter nach oben propagieren
}
}
void controllerLayer() {
try {
print('Controller-Schicht: Anfrage wird verarbeitet');
serviceLayer();
} catch (e, stackTrace) {
// Äußerste Schicht fängt ab und behandelt
print('Controller-Schicht hat eine Ausnahme abgefangen');
print('Fehlermeldung: $e');
print('Rückgabe an Benutzer: "Dienst vorübergehend nicht verfügbar, bitte später erneut versuchen"');
}
}
void main() {
controllerLayer();
print('\n--- Unterschied zwischen rethrow und throw e ---');
// Demonstriert den Unterschied zwischen rethrow und throw e
try {
try {
throw FormatException('Ursprüngliche Ausnahme');
} catch (e) {
// throw e: setzt den Stacktrace zurück, wodurch die ursprüngliche Aufrufstelle verloren geht
print('Verwendung von throw e (verliert den ursprünglichen Stacktrace)...');
throw e; // Stacktrace beginnt hier
}
} catch (e, stack) {
print('Gefangen: $e');
print('Stacktrace zeigt hierhin (nicht auf die ursprüngliche Stelle):\n$stack');
}
}
控制器层: 处理请求 服务层: 调用数据库操作 正在连接数据库... [日志] 数据库操作异常: Exception: EXAMPLE 数据库连接失败 控制器层捕获到异常 错误信息: Exception: EXAMPLE 数据库连接失败 给用户返回: "服务暂时不可用,请稍后重试" --- rethrow vs throw e 的区别 --- 使用 throw e(丢失原始堆栈)... 捕获: FormatException: 原始异常 堆栈指向这里(不是原始位置): ...
Der Unterschied zwischen rethrow und throw e ist entscheidend: rethrow bewahrt den vollständigen Stacktrace der ursprünglichen Ausnahme, wodurch sich die Grundursache leichter finden lässt; throw e setzt den Stack zurück und beginnt von der aktuellen Position aus neu. Wenn Sie die Ausnahme nach dem Protokollieren weiter propagieren müssen, verwenden Sie rethrow.
Bewährte Verfahren für die Ausnahmebehandlung
| Übung | Beschreibung |
|---|---|
| Fange nur Ausnahmen ab, die du behandeln kannst. | Fange nicht blind alle Ausnahmen ab, nur um "Fehler zu vermeiden". Wenn du nicht weißt, wie du eine Ausnahme behandeln sollst, lass sie nach oben weiterreichen. |
| Nach Typ abfangen | Verwende on, um den Ausnahmetyp anzugeben, und vermeide ein einzelnes catch, das alle Ausnahmen verschluckt. |
| finally: Ressourcen aufräumen | Ressourcen wie Dateien und Netzwerkverbindungen sollten in finally geschlossen werden. |
| Ausnahmen nicht verschlucken | Eine Ausnahme abzufangen und nichts zu tun (leerer catch-Block) ist gefährlich; protokolliere sie zumindest. |
| rethrow verwenden | Wenn du die Ausnahme protokollieren und weiter propagieren musst, verwende rethrow statt throw e. |
| Ausnahmemeldungen sollten spezifisch sein | throw Exception('Database connection failed: 192.168.1.1:3306') ist nützlicher als throw Exception('An error occurred') |