Sintaxis rápida de do-try-catch

162

Intento comprender la nueva cuestión de manejo de errores en Swift 2. Esto es lo que hice: Primero declare una enumeración de error:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

Y luego declaró un método que arroja un error (no una excepción amigos. Es un error). Aquí está ese método:

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

    return sandwich
}

El problema es del lado de la llamada. Aquí está el código que llama a este método:

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

Después de que el docompilador de línea dice Errors thrown from here are not handled because the enclosing catch is not exhaustive. Pero en mi opinión es exhaustivo porque solo hay dos casos SandwichErrorenum.

Para las declaraciones de cambio regulares, Swift puede comprender que es exhaustivo cuando se maneja cada caso.

mustafa
fuente
3
No especificas el tipo de error que lanzas, por lo que Swift no puede determinar todas las opciones posibles
Farlei Heinen
¿Hay alguna manera de especificar el tipo de error?
mustafa
No puedo encontrar nada en la nueva versión del libro Swift - solo la palabra clave throws en este momento
Farlei Heinen
Funciona para mí en un patio de recreo sin errores ni advertencias.
Fogmeister
2
Los campos de juego parecen permitir dobloques en el nivel superior que no son exhaustivos: si envuelve el do en una función de no lanzar, generará el error.
Sam

Respuestas:

267

Hay dos puntos importantes para el modelo de manejo de errores Swift 2: exhaustividad y resistencia. Juntos, se reducen a su do/ catchsentencia necesitando detectar todos los errores posibles, no solo los que sabe que puede lanzar.

Tenga en cuenta que no declara qué tipos de errores puede lanzar una función, solo si arroja algo. Es un tipo de problema de cero uno-infinito: como alguien que define una función para que otros (incluido su yo futuro) lo usen, no quiere tener que hacer que cada cliente de su función se adapte a cada cambio en la implementación de su función, incluidos los errores que puede arrojar. Desea que el código que llama a su función sea resistente a dicho cambio.

Debido a que su función no puede decir qué tipo de errores arroja (o podría arrojar en el futuro), los catchbloques que detectan errores no saben qué tipos de errores podría arrojar. Por lo tanto, además de manejar los tipos de error que conoce, debe manejar los que no tiene con una catchdeclaración universal ; de esa manera, si su función cambia el conjunto de errores que arroja en el futuro, las personas que llaman aún captarán su errores

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

Pero no nos quedemos ahí. Piensa en esta idea de resiliencia un poco más. De la forma en que diseñó su emparedado, debe describir los errores en cada lugar donde los usa. Eso significa que cada vez que cambia el conjunto de casos de error, tiene que cambiar cada lugar que los usa ... no es muy divertido.

La idea detrás de definir sus propios tipos de error es permitirle centralizar cosas como esas. Podría definir un descriptionmétodo para sus errores:

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

Y luego, su código de manejo de errores puede pedirle a su tipo de error que se describa a sí mismo, ahora cada lugar donde maneja errores puede usar el mismo código y manejar posibles casos de errores futuros también.

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

Esto también allana el camino para que los tipos de error (o extensiones en ellos) admitan otras formas de informar errores; por ejemplo, podría tener una extensión en su tipo de error que sepa cómo presentar un UIAlertControllerinforme de error a un usuario de iOS.

rickster
fuente
1
@rickster: ¿Realmente podrías reproducir el error del compilador? El código original se compila sin errores ni advertencias para mí. Y si se produce una excepción no detectada, el programa aborta con error caught in main().- Entonces, aunque todo lo que ha dicho suena sensato, no puedo reproducir ese comportamiento.
Martin R
55
Me encanta cómo separaste los mensajes de error en una extensión. Muy buena manera de mantener limpio su código! Gran ejemplo!
Konrad77
Es muy recomendable que evite el uso de la forzada - tryexpresión en el código de producción, ya que puede causar un error de ejecución y hacer que su aplicación se bloquee
Otar
@Otar es un buen pensamiento en general, pero está un poco fuera de tema: la respuesta no aborda el uso (o no) try!. Además, podría decirse que hay casos de uso válidos y "seguros" para las diversas operaciones de "fuerza" en Swift (desenvolver, probar, etc.) incluso para el código de producción, si por condición previa o configuración ha eliminado de manera confiable la posibilidad de falla, podría Ser más razonable de cortocircuito a falla instantánea que escribir código de manejo de errores que no se pueda comprobar
rickster
Si todo lo que necesita es mostrar el mensaje de error, SandwichErrortiene sentido poner esa lógica dentro de la clase. Sin embargo, sospecho que para la mayoría de los errores, la lógica de manejo de errores no puede estar tan encapsulada. Esto se debe a que generalmente requiere el conocimiento del contexto de la persona que llama (ya sea para recuperarse, volver a intentarlo o informar un error en sentido ascendente, etc.). En otras palabras, sospecho que el patrón más común tendría que coincidir con tipos de error específicos de todos modos.
max
29

Sospecho que esto simplemente no se ha implementado correctamente todavía. La Guía de programación Swift definitivamente parece implicar que el compilador puede inferir coincidencias exhaustivas "como una declaración de cambio". No hace mención de la necesidad de un general catchpara ser exhaustivo.

También notará que el error está en la trylínea, no al final del bloque, es decir, en algún momento el compilador podrá determinar qué trydeclaración en el bloque tiene tipos de excepciones no controladas.

Sin embargo, la documentación es un poco ambigua. He hojeado el video 'Qué hay de nuevo en Swift' y no pude encontrar ninguna pista; Seguiré intentando.

Actualizar:

Ahora estamos en Beta 3 sin ningún indicio de inferencia ErrorType. Ahora creo que si esto se planeó alguna vez (y sigo pensando que fue en algún momento), el despacho dinámico en las extensiones de protocolo probablemente acabó.

Actualización Beta 4:

Xcode 7b4 agregó soporte para comentarios de documentos para Throws:, que "se debe usar para documentar qué errores se pueden lanzar y por qué". Supongo que esto al menos proporciona algún mecanismo para comunicar errores a los consumidores API. ¡Quién necesita un sistema de tipos cuando tienes documentación!

Otra actualización:

Después de pasar un tiempo esperando la ErrorTypeinferencia automática y resolviendo cuáles serían las limitaciones de ese modelo, he cambiado de opinión: esto es lo que espero que Apple implemente en su lugar. Esencialmente:

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

Otra actualización

La justificación del manejo de errores de Apple ahora está disponible aquí . También ha habido algunas discusiones interesantes sobre la lista de correo de evolución rápida . Esencialmente, John McCall se opone a los errores mecanografiados porque cree que la mayoría de las bibliotecas terminarán incluyendo un caso de error genérico de todos modos, y que es poco probable que los errores mecanografiados agreguen mucho al código aparte de repetitivo (utilizó el término 'farol aspiracional'). Chris Lattner dijo que está abierto a errores mecanografiados en Swift 3 si puede funcionar con el modelo de resiliencia.

Sam
fuente
Gracias por los enlaces. Sin embargo, John no lo persuadió: "muchas bibliotecas incluyen el tipo 'otro error'" no significa que todos necesiten un tipo "otro error".
Franklin Yu
El contador obvio es que no hay una manera simple de saber qué tipo de error arrojará una función, hasta que lo haga, lo que obliga al desarrollador a detectar todos los errores y tratar de manejarlos lo mejor posible. Es bastante molesto, para ser sincero.
William T Froggard
4

A Swift le preocupa que su declaración de caso no cubra todos los casos, para solucionarlo debe crear un caso predeterminado:

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")
}
Icaro
fuente
2
¿Pero no es eso incómodo? Solo tengo dos casos y todos figuran en catchdeclaraciones.
mustafa
2
Ahora es un buen momento para una solicitud de mejora que agregue func method() throws(YourErrorEnum), o incluso throws(YourEnum.Error1, .Error2, .Error3)para que sepa lo que se puede lanzar
Matthias Bauch
8
El equipo compilador de Swift en una de las sesiones de WWDC dejó en claro que no querían listas pedantes de todos los posibles errores 'como Java'.
Sam
44
No hay error predeterminado / predeterminado; simplemente deje una captura en blanco {} como otros carteles han señalado
Opus1217
1
@Icaro Eso no me hace seguro; si "agrego una nueva entrada en la matriz" en el futuro, el compilador debería gritarme por no actualizar todas las cláusulas catch afectadas.
Franklin Yu
3

También me decepcionó la falta de tipo que una función puede lanzar, pero ahora lo entiendo gracias a @rickster y lo resumiré así: digamos que podríamos especificar el tipo que arroja una función, tendríamos algo como esto:

enum MyError: ErrorType { case ErrorA, ErrorB }

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

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

El problema es que incluso si no cambiamos nada en myFunctionThatThrows, si solo agregamos un caso de error a MyError:

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

estamos jodidos porque nuestro do / try / catch ya no es exhaustivo, así como cualquier otro lugar donde llamamos funciones que lanzan MyError

greg3z
fuente
3
No estoy seguro de seguir por qué estás jodido. Obtendría un error de compilación, que es lo que quiere, ¿verdad? Eso es lo que sucede al cambiar las declaraciones si agrega un caso enum.
Sam
En cierto sentido, me pareció más probable que esto suceda con las enumeraciones de error / do case, pero es exactamente como sucedería en enumeraciones / switch, tiene razón. Todavía estoy tratando de convencerme de que la elección de Apple de no escribir lo que tiramos es la buena, ¡pero no me ayudas con esto! ^^
greg3z
Escribir manualmente errores lanzados terminaría como un gran desastre en casos no triviales. Los tipos son una unión de todos los posibles errores de todas las declaraciones throw and try dentro de la función. Si mantiene manualmente enumeraciones de error, será doloroso. Sin catch {}embargo, A al final de cada bloque es posiblemente peor. Espero que el compilador finalmente infiera los tipos de error automáticamente donde pueda, pero no he podido confirmar.
Sam
Estoy de acuerdo en que el compilador debería, en teoría, poder inferir los tipos de error que arroja una función. Pero creo que también tiene sentido que el desarrollador las escriba explícitamente para mayor claridad. En los casos no triviales de los que está hablando, enumerar los diferentes tipos de error me parece bien: func f () arroja ErrorTypeA, ErrorTypeB {}
greg3z
Definitivamente, falta una gran parte en que no existe un mecanismo para comunicar los tipos de error (aparte de los comentarios del documento). Sin embargo, el equipo de Swift ha dicho que no quiere listas explícitas de tipos de error. Estoy seguro de que la mayoría de las personas que han tratado con Java verificó excepciones en el pasado estarían de acuerdo 😀
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
}

Ahora validar número:

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

Crea una enumeración como esta:

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

Crear método como:

 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
}

Ahora verifique si el error está ahí o no y manejarlo:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}
Sr. Javed Multani
fuente
Cerca pero sin cigarro. Intenta arreglar el espacio y crea la caja de enum camel.
Alec