Moderne Programmierung mit Swift, Teil 1
Seite 2: Verarbeitung von Fehlern
Es stellt sich als Nächstes die Frage, wie sich ein Fehler beim Aufruf einer solchen Methode abfangen und auswerten lässt. Swift bietet dazu unterschiedliche Optionen und Vorgehensweisen. Die beste und effizienteste Art, einen Fehler in der Sprache abzufangen und auszuwerten, besteht in der Verwendung eines sogenannten do-catch-Blocks. Er wurde zusammen mit dem Error Handling in Swift eingeführt und dient ausschließlich dazu, auf einen durch eine Methode ausgelösten Fehler zu reagieren. Zu dem Zweck besteht ein solcher Block aus mehreren Bestandteilen.
Zunächst ist da der do-Abschnitt. Darin führt das Programm typischerweise wenigstens eine Methode aus, die mittels throws deklariert ist und damit einen Fehler feuern kann (ohne den Aufruf einer solchen Methode macht die Verwendung von do-catch in Swift auch keinen Sinn). Außerdem enthält do noch alle weiteren Befehle, die ausgeführt werden sollen, falls Fehler ausbleiben.
Danach folgen ein oder mehrere sogenannte catch-Abschnitte. Jeder davon kann explizit auf einen spezifischen Fehler prüfen, der sich durch die in do aufgerufenen Methode auslösen lässt. Kommt es bei der Ausführung jener Methoden zu eben diesem Fehler, werden der do-Abschnitt verlassen und der catch-Block aufgerufen, der auf den Fehler prüft.
Es lässt sich zudem ein catch-Block ohne zugewiesenen und zu prüfenden Fehler erstellen. Er fungiert als Joker und kommt zum Einsatz, wenn alle vorangegangenen mittels catch explizit geprüften Fehler nicht aufgetreten sind und folglich ein nicht definierter vorliegt.
Zum besseren Verständnis zeigt der folgende Codeauszug den typischen Aufbau eines do-catch-Blocks und in welchen Abschnitten welcher Code untergebracht ist:
do {
// Aufruf einer Funktion, die möglicherweise einen Fehler feuert
// Implementierung der Befehle für den Fall, dass kein Fehler auftritt
} catch <ZU PRÜFENDER FEHLER> {
// Auszuführende Befehle, falls der zu prüfende Fehler auftritt
} catch <ANDERER ZU PRÜFENDER FEHLER> {
// Auszuführende Befehle, falls der andere zu prüfende Fehler auftritt
} catch {
// Auszuführende Befehle, falls ein anderer als die zuvor geprüften
// Fehler auftritt
}
Wichtig ist dabei, alle Methoden, die möglicherweise einen Fehler zurückgeben, mit dem vorangestellten Schlüsselwort try aufzurufen.
Wie das Ganze in der Praxis aussehen kann, zeigt das nächste Beispiel. Dort wird eine Instanz der zuvor deklarierten Structure Car erstellt und anschließend darüber in einem do-catch-Block die Instanzmethode startDriving() aufgerufen. Da die Property hasFuel jener Instanz auf false gesetzt ist, kommt es zum Fehler noFuel der Enumeration DrivingError, weshalb das Programm nach Aufruf jener Methode den catch-Block des Fehlers ausführt.
var myCar = Car(hasFuel: false, maximumSpeed: 200, currentSpeed: 0)
do {
try myCar.startDriving()
print("Auto fährt los...")
} catch DrivingError.noFuel {
print("Es fehlt Treibstoff zum Losfahren...")
}
// Es fehlt Treibstoff zum Losfahren...
Da kein Fehler eintritt, führt das Programm den Code nach der fehlgeschlagenen Methode startDriving() innerhalb des do-Abschnitts nicht aus, weshalb auf der Konsole nicht die Meldung "Auto fährt los" erscheint. Stattdessen wird dort "Es fehlt Treibstoff zum Losfahren" angezeigt, wofür der Code verantwortlich ist, den der zurückgegebene Fehler ausgeführt hat.
Nach dem Schema lassen sich mittels Error Handling alle Methoden ausführen, die aufgrund ihrer throws-Deklaration einen Fehler zurückgeben, den Entwickler abfangen und verarbeiten können. Es gibt allerdings noch weitere Optionen, mit derartigen Methoden umzugehen und sie auszuführen. Gibt eine Methode außer dem möglichen Fehler einen Wert zurück, gibt es die Gelegenheit, die Methode wie gewohnt auszuführen und den Rückgabewert als sogenanntes Optional zu erhalten. Dazu ist die Methode mit dem Schlüsselwort try? (man beachte das Fragezeichen) aufzurufen. Auf die Art und Weise liefert der Aufruf der Methode entweder – bei erfolgreichem Durchlauf – ihren Ergebniswert zurück oder nil, falls ein Fehler gefeuert wird. Entwickler erhalten beim Auftreten einer Fehlfunktion folglich kein Feedback darüber, um welchen Fehler es sich genau handelt, können damit aber Methoden mit try ausführen, ohne das aufwendigere Konstrukt eines do-catch-Blocks zu implementieren.
Ein Beispiel für die Verwendung von try? zum Aufruf einer Methode zeigt der folgende Codeausschnitt. Dort wird eine Variable namens myCarCurrentSpeed deklariert und ihr das Ergebnis des Aufrufs der Instanzmethode increaseCurrentSpeedWithSpeed(_:) über die zuvor erstellte myCar-Instanz zugewiesen. Da jene Methode mittels throws deklariert ist und so einen Fehler zurückgeben kann, ist sie mit try aufzurufen. Weil für das Prüfen und Abfangen des Fehlers aber kein do-catch-Block zum Einsatz kommt, können Entwickler stattdessen das Schlüsselwort try? verwenden und so den Rückgabewert der Methode in ein Optional umwandeln. Kommt es beim Ausführen der Methode zu einem Fehler, wird nil zurückgegeben.
var myCarCurrentSpeed = try? myCar.increaseCurrentSpeedWithSpeed(80)
// myCarCurrentSpeed entspricht 80
myCarCurrentSpeed = try? myCar.increaseCurrentSpeedWithSpeed(120)
// myCarCurrentSpeed entspricht 120
myCarCurrentSpeed = try? myCar.increaseCurrentSpeedWithSpeed(30)
// myCarCurrentSpeed entspricht nil
Dabei ist ein wichtiges Detail zu beachten: Beim Rückgabewert einer Methode, die mit try? aufgerufen wird, handelt es sich immer um ein Optional, selbst wenn der deklarierte Typ des Rückgabewerts nicht als solches definiert ist. Im gezeigten Beispiel entspricht die Variable myCarCurrentSpeed somit nicht dem Typ UInt (bei dem es sich um den Rückgabetyp der Methode increaseCurrentSpeedWithSpeed(_:) handelt), sondern UInt?.
Alternativ zu try? lassen sich Methoden durch das Schlüsselwort try! (man beachte das Ausrufezeichen) aufrufen. In dem Fall wird die Methode direkt ausgeführt und jeglicher Fehler ignoriert. Mögliche Rückgabewerte wandelt das Programm dabei nicht in ein Optional um, sie entsprechen daher exakt dem Typ, der in der jeweiligen Methode definiert ist.
Der Einsatz von try! ist allerdings mit Vorsicht zu genießen: Kommt es beim Ausführen der aufgerufenen Methode zu einem Fehler, stürzt die Anwendung ab. Entsprechend sollten Entwickler eine Methode immer nur dann so aufrufen, wenn sie sicher sind, dass sie in der jeweiligen Situation auch erfolgreich durchlaufen wird.