Tratamento de erros

29 junho, 2019

É notório que tratamento de erros é uma etapa muito importante no desenvolvimento de software. Um bom tratamento possibilita um retorno relativamente mais amigável para o programador ou usuário. Dessa forma evitamos mensagens de erros padrões geradas pela própria linguagem de programação e exibimos uma informação mais fácil de ser compreendida.

No entanto, muito se vê a utilização da estrutura de condição IF para tratamento de erros. O uso dessa condicional torna a manutenção do código muito difícil, pois as validações ficam aninhadas e aumentar a complexidade ciclomática. A melhor alternativa para melhorar a leitura e manutanção é manter as validações em pequenos blocos de código.

A declaração TRY é uma ótima recomendação para manter o código mais compreensível.

Protocolo de erro do Swift

O protoco de erro é apenas um tipo para representar valores de erro que podem ser retornados por uma função ou como inicializador ao utilizar a declaração TRY. Em Swift é necessário criar um tipo de erro personalizado. Normalmente, um Enum é usado em conformidade com o protocolo de erro.

Exemplo de Enum básico de erros:

enum FetchError: Error {
    case url
    case taskError(error: Error)
    case noResponse
    case noData
    case responseStatusCode(code: Int)
    case invalidJson
}

throws e throw

Se uma função ou inicializador deve retornar um erro, o modificador throws precisa ser adicionado na sua assinatura, após os parâmetros e o tipo de retorno. Esse modificador é responsável por transmitir o erro da função até o local onde foi executado.

Estrutura de assinatura de método com throws:

func testData() throws -> <Return Type> {
    
}

Agora o throw é utilizado dentro da função e é responsável por retornar o tipo de error definido. Uma função com ambos ficaria dessa forma:

func testData() throws {
    if <condition> {
        // Code
    }
    else {
        throw FetchError.noData
    }
}

throw também é útil dentro da declaração Guard. Desta forma se essa validação também retornar um erro podemos invocar um tipo definido no Enum:

guard <condition> else { throw FetchError.noResponse }

do-catch

Diferentemente de algumas linguagens, Swift utiliza a declaração do-catch ao invés de try-catch. Independentemente da forma que é escrito, toda função que utiliza o modificador throws deve ser executada dentro de um tratamento TRY, devido a possibilidade de retornar erros.

Portanto, em Swift a execução deve ser feito da seguinte maneira:

do {
    try testData()
}
catch {
    print("Error: \(error)")
}

Quando o bloco catch não possui nenhum padrão, Swift automaticamente entende que pode ser qualquer erro e cria uma constante local ocultando o valor de retorno com o erro. A melhor maneira neste caso é utilizar o Enum de erros aplicado na função e tratar cada tipo que foi criado. Com o Enum podemos criar um bloco catch para cada tipo.

Ficaria desta forma:

do {
    try testData()
} catch FetchError.url {
    print("URL can not be reached")
} catch FetchError.taskError(error: Error) {
    print("Task error when fetch data. Message: \(error)")
} catch FetchError.noResponse {
    print("No response on fetch data")
} catch FetchError.noData {
    print("No data available")
} catch FetchError.responseStatusCode(code: Int) {
    print("Error on fetch data. Status code: \(code)")
} catch FetchError.invalidJson {
    print("Invalid JSON returned. Can not be processed")
}

try, try? e try!

Swift possui estas três variações do TRY:

  • TRY: maneira mais básica para lidar com funções que podem gerar erros.
  • TRY?: é utilizado para converter o retorno em um valor opcional. Dessa forma, se ocorrer um erro, a função retornará um valor nulo e o tratamento pode ser feito fora do bloco do-catch.
  • TRY!: é usado para afirmar que o erro não ocorrerá ao invocar a função. O sinal de exclamação também tira a obrigatóriedade de utilizar a declaração do-catch. Porém deve ser utilizado somente quando tiver certeza absoluta que a função não causará erros, caso contrário a aplicação irá travar. E este é um erro inadmissível durante o desenvolvimento.