web-dev-qa-db-de.com

Swift do-try-catch-Syntax

Ich versuche, neue Fehlerbehandlungssache in Swift 2 zu verstehen. Hier ist, was ich getan habe: Ich habe zuerst eine Fehleraufzählung deklariert:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

Und dann habe ich eine Methode deklariert, die einen Fehler auslöst (keine Ausnahme, Leute. Es ist ein Fehler.). Hier ist diese Methode:

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

Das Problem ist von der anrufenden Seite. Hier ist der Code, der diese Methode aufruft:

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

Nach dem do Zeilen-Compiler steht Errors thrown from here are not handled because the enclosing catch is not exhaustive. Aber meiner Meinung nach ist es erschöpfend, weil es in SandwichError enum nur zwei Fälle gibt.

Für reguläre switch-Anweisungen Swift kann verstehen, dass es erschöpfend ist, wenn jeder Fall behandelt wird.

155
mustafa

Es gibt zwei wichtige Punkte für das Swift 2 Fehlerbehandlungsmodell: Vollständigkeit und Ausfallsicherheit. Zusammen ergeben sich daraus, dass Ihre do/catch -Anweisung abgefangen werden muss jeden möglichen Fehler, nicht nur die, von denen du weißt, dass du sie werfen kannst.

Beachten Sie, dass Sie nicht deklarieren, welche Arten von Fehlern eine Funktion auslösen kann, sondern nur, ob sie überhaupt ausgelöst wird. Es ist eine Art Null-Eins-Unendlich-Problem: Als jemand, der eine Funktion für andere (einschließlich Ihres zukünftigen Selbst) definiert, möchten Sie nicht, dass jeder Client Ihrer Funktion sich an jede Änderung in der Implementierung Ihrer anpasst Funktion, einschließlich der Fehler, die es werfen kann. Sie möchten, dass Code, der Ihre Funktion aufruft, solchen Änderungen standhält.

Da Ihre Funktion nicht sagen kann, welche Art von Fehlern sie auslöst (oder in Zukunft auslösen könnte), wissen die catch -Blöcke, die sie abfangen, nicht, welche Arten von Fehlern sie auslösen könnten. Zusätzlich zur Behandlung der Ihnen bekannten Fehlertypen müssen Sie also diejenigen, die Sie nicht kennen, mit einer universellen catch -Anweisung behandeln - auf diese Weise, wenn Ihre Funktion die Menge der Fehler ändert, die sie in Zukunft auslöst , werden Anrufer immer noch ihre Fehler abfangen.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

Aber lasst uns nicht damit aufhören. Denken Sie noch etwas über diese Resilienzidee nach. So wie Sie Ihr Sandwich entworfen haben, müssen Sie Fehler an jedem Ort beschreiben, an dem Sie sie verwenden. Das bedeutet, dass Sie jedes Mal, wenn Sie die Fehlerfälle ändern, jeden Ort ändern müssen, an dem sie verwendet werden ... das macht keinen großen Spaß.

Die Idee hinter dem Definieren Ihrer eigenen Fehlertypen ist, dass Sie solche Dinge zentralisieren können. Sie könnten eine description Methode für Ihre Fehler definieren:

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try Sudo"
        }
    }
}

Und dann kann Ihr Fehlerbehandlungscode Ihren Fehlertyp auffordern, sich selbst zu beschreiben - jetzt kann jeder Ort, an dem Sie Fehler behandeln, denselben Code verwenden und auch mögliche zukünftige Fehlerfälle behandeln.

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

Dies ebnet auch den Weg für Fehlertypen (oder deren Erweiterungen), um andere Arten der Fehlerberichterstattung zu unterstützen. Beispielsweise könnten Sie eine Erweiterung für Ihren Fehlertyp haben, die weiß, wie ein UIAlertController für die Berichterstattung der Fehler dargestellt wird Fehler an einen iOS-Benutzer.

253
rickster

Ich vermute, dass dies noch nicht richtig implementiert wurde. Das Swift Programming Guide scheint definitiv darauf hinzudeuten, dass der Compiler erschöpfende Übereinstimmungen "wie eine switch-Anweisung" ableiten kann. Es wird nicht erwähnt, dass ein allgemeines catch erforderlich ist, um vollständig zu sein.

Sie werden auch feststellen, dass sich der Fehler in der Zeile try befindet, nicht am Ende des Blocks, dh, der Compiler kann irgendwann feststellen, welche try -Anweisung im Block nicht verarbeitet wurde Ausnahmetypen.

Die Dokumentation ist allerdings etwas mehrdeutig. Ich habe das Video "Was ist neu in Swift" durchgesehen und konnte keine Hinweise finden. Ich werde es weiter versuchen.

Update:

Wir sind jetzt auf Beta 3 und haben keine Hinweise auf eine Fehlertyp-Schlussfolgerung. Ich glaube jetzt, wenn dies jemals geplant wurde (und ich denke immer noch, dass es irgendwann war), hat der dynamische Versand auf Protokollerweiterungen es wahrscheinlich getötet.

Beta 4 Update:

Xcode 7b4 fügte die Dokumentkommentarunterstützung für Throws: Hinzu. Diese sollte verwendet werden, um zu dokumentieren, welche Fehler geworfen werden können und warum. Ich denke, dies bietet zumindest einen Mechanismus, um den API-Verbrauchern Fehler mitzuteilen. Wer braucht ein Typensystem, wenn Sie Dokumentation haben!

Ein weiteres Update:

Nachdem ich einige Zeit damit verbracht hatte, auf automatische ErrorType Inferenz zu hoffen und herausgefunden hatte, welche Einschränkungen dieses Modell haben würde, habe ich meine Meinung geändert - dies ist das, was ich hoffe Apple implementiert stattdessen. Im Wesentlichen:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

Noch ein Update

Die Gründe für die Fehlerbehandlung bei Apple sind jetzt verfügbar hier . Es gab auch einige interessante Diskussionen auf der Swift-evolution Mailingliste. Im Wesentlichen lehnt John McCall Tippfehler ab, da er der Ansicht ist, dass die meisten Bibliotheken ohnehin einen generischen Fehlerfall enthalten werden und Tippfehler, abgesehen von Boilerplate (er hat den Begriff "aspirational bluff" verwendet), kaum zu dem Code beitragen werden. Chris Lattner sagte, er sei offen für Tippfehler in Swift 3), wenn dies mit dem Ausfallsicherheitsmodell funktioniert.

26
Sam

Swift ist besorgt, dass Ihre Fallbeschreibung nicht alle Fälle abdeckt. Um dies zu beheben, müssen Sie einen Standardfall erstellen:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}
4
Icaro

Ich war auch enttäuscht von dem Mangel an Typ, den eine Funktion auslösen kann, aber ich bekomme es jetzt dank @rickster und ich werde es so zusammenfassen: Nehmen wir an, wir könnten den Typ spezifizieren, den eine Funktion auslöst, wir hätten so etwas:

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

Das Problem ist, dass, selbst wenn wir in myFunctionThatThrows nichts ändern, wir MyError nur einen Fehlerfall hinzufügen:

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

wir sind durchgeknallt, weil unser do/try/catch nicht mehr erschöpfend ist, ebenso wie jeder andere Ort, an dem wir Funktionen aufgerufen haben, die MyError auslösen

3
greg3z
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

Überprüfen Sie jetzt die Nummer:

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }
1
Yogendra Singh