Sintassi rapida do-try-catch


162

Provo a capire la nuova cosa di gestione degli errori in swift 2. Ecco cosa ho fatto: ho prima dichiarato un errore enum:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

E poi ho dichiarato un metodo che genera un errore (non un'eccezione gente. È un errore). Ecco quel metodo:

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

    return sandwich
}

Il problema è dal lato chiamante. Ecco il codice che chiama questo metodo:

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")
}

Dopo il docompilatore di riga dice Errors thrown from here are not handled because the enclosing catch is not exhaustive. Ma secondo me è esaustivo perché ci sono solo due casi in SandwichErrorenum.

Per le normali istruzioni di commutazione, swift può capire che è esaustivo quando viene gestito ogni caso.


3
Non specifichi il tipo di errore che lanci, quindi Swift non può determinare tutte le possibili opzioni
Farlei Heinen

C'è un modo per specificare il tipo di errore?
mustafa,

Non riesco a trovare nulla nella nuova versione del libro Swift - solo la parola chiave getta in questo momento
Farlei Heinen

Funziona per me in un parco giochi senza errori o avvisi.
Fogmeister,

2
I campi da gioco sembrano consentire doblocchi di livello superiore non esaustivi: se si avvolge il do in una funzione non gettante, si genererà l'errore.
Sam,

Risposte:


267

Vi sono due punti importanti nel modello di gestione degli errori di Swift 2: esaustività e resilienza. Insieme, si riducono alla tua do/ catchdichiarazione che deve cogliere ogni possibile errore, non solo quelli che sai di poter lanciare.

Si noti che non si dichiarano i tipi di errori che una funzione può generare, solo se si lancia affatto. È un tipo di problema zero-infinito: come qualcuno che definisce una funzione che gli altri (incluso il tuo sé futuro) possono usare, non devi fare in modo che ogni cliente della tua funzione si adatti ad ogni cambiamento nell'implementazione del tuo funzione, inclusi gli errori che può generare. Vuoi che il codice che chiama la tua funzione sia resistente a tale cambiamento.

Poiché la tua funzione non è in grado di dire che tipo di errori genera (o che potrebbe essere generato in futuro), i catchblocchi che li rilevano non conoscono i tipi di errori che potrebbero essere generati. Quindi, oltre a gestire i tipi di errore che conosci, devi gestire quelli che non hai con catchun'istruzione universale - in questo modo se la tua funzione cambia l'insieme di errori che genera in futuro, i chiamanti ne prenderanno comunque errori.

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)
}

Ma non fermiamoci qui. Pensa ancora a questa idea di resilienza. Nel modo in cui hai progettato il tuo sandwich, devi descrivere gli errori in ogni luogo in cui li usi. Ciò significa che ogni volta che cambi l'insieme dei casi di errore, devi cambiare ogni luogo che li utilizza ... non molto divertente.

L'idea alla base della definizione dei propri tipi di errore è quella di consentire di centralizzare cose del genere. Puoi definire un descriptionmetodo per i tuoi errori:

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

E poi il tuo codice di gestione degli errori può chiedere al tuo tipo di errore di descriverlo - ora ogni posto in cui gestisci gli errori può usare lo stesso codice e gestire anche possibili casi di errore futuri.

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

Questo apre anche la strada a tipi di errore (o estensioni su di essi) per supportare altri modi di segnalazione degli errori - ad esempio, potresti avere un'estensione sul tuo tipo di errore che sa come presentare un UIAlertControllerper segnalare l'errore a un utente iOS.


1
@Rickster: potresti davvero riprodurre l'errore del compilatore? Il codice originale viene compilato senza errori o avvisi per me. E se viene generata un'eccezione senza pari, il programma si interrompe con error caught in main().- Quindi, mentre tutto ciò che hai detto sembra ragionevole, non riesco a riprodurre quel comportamento.
Martin R,

5
Adoro come hai separato i messaggi di errore in un'estensione. Davvero un bel modo per mantenere pulito il tuo codice! Ottimo esempio!
Konrad77,

Si consiglia vivamente di evitare di usare l' tryespressione forzata nel codice di produzione poiché può causare un errore di runtime e causare l'arresto anomalo dell'applicazione
Otar

@Otar buona idea in generale, ma è un po 'fuori tema - la risposta non si rivolge usando (o non usando) try!. Inoltre, ci sono probabilmente casi d'uso validi e "sicuri" per le varie operazioni "forzate" in Swift (scartare, provare, ecc.) Anche per il codice di produzione - se attraverso il presupposto o la configurazione si eliminasse in modo affidabile la possibilità di errore, potrebbe essere più ragionevoli in caso di cortocircuito verso guasto istantaneo piuttosto che scrivere codice di gestione degli errori non verificabile.
rickster,

Se tutto ciò che serve è visualizzare il messaggio di errore, è logico inserire quella logica all'interno della SandwichErrorclasse. Tuttavia, sospetto per la maggior parte degli errori, la logica di gestione degli errori non può essere così incapsulata. Questo perché di solito richiede la conoscenza del contesto del chiamante (se ripristinare, o riprovare o segnalare un errore a monte, ecc.). In altre parole, sospetto che lo schema più comune dovrebbe essere quello di corrispondere comunque a tipi di errore specifici.
massimo

29

Sospetto che questo non sia ancora stato implementato correttamente. La Guida alla programmazione Swift sembra sicuramente implicare che il compilatore possa dedurre corrispondenze esaurienti "come un'istruzione switch". Non fa menzione della necessità di un generale catchper essere esaustivo.

Noterai anche che l'errore si trova sulla tryriga, non alla fine del blocco, ovvero ad un certo punto il compilatore sarà in grado di individuare quale tryistruzione nel blocco ha tipi di eccezione non gestiti.

La documentazione è un po 'ambigua. Ho sfogliato il video "Novità di Swift" e non ho trovato alcun indizio; Continuerò a provare.

Aggiornare:

Passiamo ora alla Beta 3 senza alcun suggerimento sull'inferenza ErrorType. Ora credo che se questo fosse mai stato pianificato (e penso ancora che lo fosse ad un certo punto), l'invio dinamico sulle estensioni del protocollo probabilmente lo ha ucciso.

Aggiornamento Beta 4:

Xcode 7b4 ha aggiunto il supporto per i commenti doc Throws:, che "dovrebbe essere usato per documentare quali errori possono essere generati e perché". Immagino che questo fornisca almeno un meccanismo per comunicare errori ai consumatori API. Chi ha bisogno di un sistema di tipi quando hai la documentazione!

Un altro aggiornamento:

Dopo aver trascorso un po 'di tempo a sperare ErrorTypenell'inferenza automatica e aver scoperto quali sarebbero i limiti di quel modello, ho cambiato idea: questo è ciò che spero invece Apple attui. Essenzialmente:

// 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

Ancora un altro aggiornamento

La logica di gestione degli errori di Apple è ora disponibile qui . Ci sono state anche alcune discussioni interessanti sulla mailing list di rapida evoluzione . In sostanza, John McCall si oppone agli errori di battitura perché ritiene che la maggior parte delle biblioteche finirà comunque per includere un caso di errore generico e che è improbabile che errori di battitura aggiungano molto al codice a parte il bollettino (ha usato il termine "bluff aspirazionale"). Chris Lattner ha dichiarato di essere aperto agli errori di battitura in Swift 3 se funziona con il modello di resilienza.


Grazie per i collegamenti. Non convinto da John però: "molte librerie includono il tipo" altro errore "" non significa che tutti abbiano bisogno di un tipo "altro errore".
Franklin Yu,

L'ovvio contatore è che non esiste un modo semplice per sapere che tipo di errore genererà una funzione, fino a quando non lo fa, costringendo lo sviluppatore a rilevare tutti gli errori e provare a gestirli nel miglior modo possibile. È piuttosto fastidioso, essere sinceri.
William T Froggard,

4

Swift teme che l'istruzione case non copra tutti i casi, per risolverlo è necessario creare un caso predefinito:

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")
}

2
Ma non è imbarazzante? Ho solo due casi e tutti elencati nelle catchdichiarazioni.
mustafa,

2
Ora è un buon momento per una richiesta di miglioramento che aggiunge func method() throws(YourErrorEnum), o anche throws(YourEnum.Error1, .Error2, .Error3)così sai cosa può essere lanciato
Matthias Bauch,

8
Il team del compilatore Swift in una delle sessioni del WWDC ha chiarito che non volevano elenchi pedanti di tutti i possibili errori "come Java".
Sam,

4
Non vi è alcun errore predefinito / predefinito; lascia un fermo in bianco {} come hanno sottolineato altri poster
Opus1217,

1
@Icaro Questo non mi rende sicuro; se "aggiungo una nuova voce nell'array" in futuro, il compilatore dovrebbe gridarmi per non aggiornare tutte le clausole di cattura interessate.
Franklin Yu,

3

Sono stato anche deluso dalla mancanza di tipo che una funzione può lanciare, ma ora lo capisco grazie a @rickster e lo riassumo in questo modo: diciamo che potremmo specificare il tipo che una funzione genera, avremmo qualcosa del genere:

enum MyError: ErrorType { case ErrorA, ErrorB }

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

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

Il problema è che anche se non cambiamo nulla in myFunctionThatThrows, se aggiungiamo un caso di errore a MyError:

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

siamo fregati perché il nostro do / try / catch non è più esaustivo, così come qualsiasi altro posto in cui abbiamo chiamato funzioni che generano MyError


3
Non sono sicuro di seguire il motivo per cui sei fregato. Riceverai un errore del compilatore, che è quello che vuoi, giusto? Questo è ciò che accade per cambiare le istruzioni se aggiungi un caso enum.
Sam,

In un certo senso mi è sembrato molto probabile che ciò accadesse con enumerazioni di errore / caso, ma è esattamente come accadrebbe in enumerazioni / switch, hai ragione. Sto ancora cercando di convincermi che la scelta di Apple di non digitare ciò che lanciamo è quella buona, ma non mi aiuti su questo! ^^
greg3z,

La digitazione manuale degli errori generati finirebbe come un gran casino in casi non banali. I tipi sono l'unione di tutti i possibili errori da tutte le istruzioni di lancio e di prova all'interno della funzione. Se stai mantenendo manualmente gli errori, questo sarà doloroso. A catch {}nella parte inferiore di ogni blocco è probabilmente peggio. Spero che il compilatore alla fine inferirà automaticamente i tipi di errore, ma non sono stato in grado di confermare.
Sam,

Sono d'accordo che il compilatore dovrebbe, teoricamente, essere in grado di dedurre i tipi di errore generati da una funzione. Ma penso che abbia senso anche per lo sviluppatore scriverle esplicitamente per chiarezza. Nei casi non banali di cui parli, elencare i diversi tipi di errore mi sembra giusto: func f () genera ErrorTypeA, ErrorTypeB {}
greg3z,

C'è sicuramente una grande parte mancante in quanto non esiste alcun meccanismo per comunicare i tipi di errore (diversi dai commenti dei documenti). Tuttavia, il team Swift ha affermato di non voler elenchi espliciti di tipi di errore. Sono sicuro che la maggior parte delle persone che hanno avuto a che fare con Java ha verificato le eccezioni in passato sarebbero d'accordo
Sam

1
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
}

Ora convalida il numero:

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

-2

Crea enum in questo modo:

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

Crea un metodo come:

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

Ora controlla l'errore c'è o no e gestiscilo:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}

Chiudi ma niente sigaro. Prova a fissare la distanza e crea la custodia per cammello enum.
Alec,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.