Há algum tempo, meu colega publicou
um artigo sobre tratamento de erros em Java / Kotlin. E ficou interessante para mim quais métodos de transmissão de erros existem na programação em geral. Se você também estiver interessado, o resultado é a pesquisa. Provavelmente, alguns métodos exóticos foram omitidos, mas há apenas uma esperança para comentários, que às vezes são mais interessantes e úteis no artigo de Habré. :)
Na história das linguagens de programação, não foram inventadas muitas maneiras de transmitir um erro. Se desengatarmos completamente, existem apenas três deles: um retorno direto de uma função, uma transferência de controle e a definição do estado. Tudo o resto é, em um grau ou outro, uma mistura dessas abordagens. Abaixo, tentei coletar e descrever os principais representantes dessas três espécies.
Isenção de responsabilidade: para concisão e simplificação da percepção para qualquer código executável isolado que gere um erro, usarei a palavra "função" e para quaisquer entidades não primitivas (número inteiro, string, booleano, etc ...) - "estrutura".
Retorno direto
O retorno direto é simples. Embora este seja provavelmente o método mais usado, existem muitas opções. O método de processamento une todos eles - comparando o valor de retorno com os valores predefinidos.
- Retornar status de execução. A opção mais banal é TRUE (se foi executada normalmente) ou FALSE (se houve uma falha).
- Retorne o valor correto em caso de sucesso e incorreto em caso de erro.
C / c ++
A função strchr () retorna um ponteiro para a primeira ocorrência do caractere ch na string apontada por str. Se ch não for encontrado, NULL será retornado.
Com bastante frequência, as abordagens 1 e 2 são usadas em conjunto com a definição do estado.
- Código de erro de retorno. Se queremos não apenas saber que a execução terminou incorretamente, mas também entender onde ocorreu o erro na função. Normalmente, se a função for concluída sem erro, o código 0. é retornado.No caso de um erro, o código é usado para determinar um local específico no corpo da função em que algo deu errado. Mas essa não é uma regra de ferro, veja, por exemplo, o HTTP com seus 200.
- Retorne o código de erro em um intervalo de valores inválido. Por exemplo, normalmente uma função deve retornar um número inteiro positivo e, no caso de um erro, seu código com um sinal de menos.
function countElements(param) { if (!isArray(param)) { return -10; } else if(!isInitialized(param)){ return -20 } else { return count(array); } }
- Retorne tipos diferentes para resultados positivos e negativos. Por exemplo, nominalmente - uma sequência, mas não nominalmente - um número ou classe Success e classe Error .
sealed class UserProfileResult { data class Success(val userProfile: UserProfileDTO) : UserProfileResult() data class Error(val message: String, val cause: Exception? = null) : UserProfileResult() } val avatarUrl = when (val result = client.requestUserProfile(userId)) { is UserProfileResult.Success -> result.userProfile.avatarUrl is UserProfileResult.Error -> "http://domain.com/defaultAvatar.png" }
Você também pode se lembrar do mundo da programação funcional. Embora aqui você possa discutir.
- Retornando uma estrutura contendo o resultado em si e o erro.
function doSomething(): array { ... if($somethingWrong === true) { return ["result" => null, "error" => "Alarm!!!"]; } else { return ["result" => $result, "error" => null]; } ... }
- Retorne vários valores. No começo, eu estava inclinado a não separar esse método do anterior, mas no final decidi colocá-lo em um parágrafo separado. Essa opção é bastante rara, porque pode ser usada exclusivamente em idiomas que permitem retornar vários valores de uma função e não existem muitos. O exemplo mais impressionante, mas não o único, é o idioma Go .
f, err := Sqrt(-1) if err != nil { fmt.Println(err) }
Configuração de estado
A versão mais antiga e hardcore, que não perdeu sua relevância até hoje. Consiste no fato de que a função não retorna nada e, em caso de erro, grava seu valor (de qualquer forma) em uma entidade separada, seja um registro do processador, uma variável global ou um campo de classe privada. Para lidar com esse tipo de erro, você precisa extrair independentemente o valor do lugar certo e verificá-lo.
- Definindo o estado "global". Eu o peguei entre aspas, porque na maioria das vezes estamos falando sobre globalidade em um determinado escopo.
- Definindo seu próprio estado. Quando temos alguma estrutura fornecendo uma função. A função define o estado para essa estrutura e o erro já é extraído da estrutura diretamente ou usando outra função especializada.
$mysqli = new mysqli("localhost", "my_user", "my_password", "world"); $result = $mysqli->query("SET a=1"); if ($mysqli->errno) { printf(" : %d\n", $mysqli->errno); }
- Definindo o estado do objeto retornado. Repete fortemente o parágrafo 6. da seção anterior. Diferentemente do parágrafo anterior, uma verificação de estado é realizada na estrutura retornada, e não na estrutura que fornece a função. Como um exemplo óbvio, o protocolo HTTP e inúmeras bibliotecas em uma ampla variedade de linguagens trabalham com ele.
Response response = client.newCall("https://www.google.com").execute(); Integer errorCode = response.getCode();
Transferência de controle
E agora chegamos ao paradigma mais moderno. Exceções, retornos de chamada, manipuladores de erros globais - tudo isso. O que une todos eles é que, no caso de um erro, o controle é transferido para um manipulador predeterminado, e não para o código que chamou a função.
- Exceções Todo mundo sabe jogar / tentar / pegar. Lançando uma exceção, a função forma uma estrutura que descreve o erro e, na maioria das vezes, contém vários metadados úteis que facilitam o diagnóstico do problema (por exemplo, a pilha de chamadas). Depois disso, essa estrutura é passada para um mecanismo especial que "reverte" ao longo da pilha de chamadas para o primeiro bloco de tentativa, associado ao catch, que pode manipular exceções desse tipo. Esse método é bom, pois toda a lógica de lançar uma exceção é implementada pelo próprio tempo de execução. O mesmo é ruim, uma vez que os custos indiretos (vamos apenas sem holivarov :)).
- Manipuladores de erro globais. Não é a maneira mais comum, mas sim. Eu nem sei o que dizer aqui. É possível observar que os mecanismos dos navegadores também podem ser atribuídos aqui: quando o código que trabalha fora do fluxo principal monitora os eventos que chegam nele.
function myErrorHandler($errno, $errstr, $errfile, $errline) { echo "<b>Custom error:</b> [$errno] $errstr<br>"; echo " Error on line $errline in $errfile<br>"; } set_error_handler("myErrorHandler");
- Retornos de chamada. Eles são muito amados pelos desenvolvedores para Android, JavaScript e apologistas pela programação reativa. A essência é simples: além dos dados processados, as funções do manipulador são transferidas para a função. Em caso de erro, a função principal chamará o manipulador correspondente e passará o erro para ele.
var observer = Rx.Observer.create( x => console.log(`onNext: ${x}`), e => console.log(`onError: ${e}`), () => console.log('onCompleted'));
Parece não ter esquecido nada.
E um fato engraçado. Provavelmente, a maneira mais original de retornar um erro, combinando ao mesmo tempo exceções, definindo o estado e retornando vários valores, conheci no Informix SPL (escrevo de memória):
CREATE PROCEDURE some_proc(...) RETURNING int, int, int, int; … ON EXCEPTION SET SQLERR, ISAMERR RETURN 0, SQLERR, ISAMERR, USRERR; END EXCEPTION; LET USRERR = 1;