Como lidar com os erros corretamente: o silêncio nem sempre é bom



Nunca tive uma opinião específica sobre o tratamento de erros. Se eu comecei a trabalhar com o código existente, continuei executando a tarefa em que o autor da fonte trabalhava; se eu escrevi o código do zero, fiz o que parecia certo para mim.

Mas, recentemente, encontrei um problema, um bug que se manifestava devido a um erro "silencioso" no código. Percebi que há algo para refletir. Talvez não possa mudar a maneira como lida com erros em toda a base de código em que estou trabalhando, mas algo pode definitivamente ser otimizado.

Lembramos que: para todos os leitores de "Habr" - um desconto de 10.000 rublos ao se inscrever em qualquer curso Skillbox usando o código promocional "Habr".

A Skillbox recomenda: O curso educacional on-line "Profissão Java-developer" .


Nem sempre vale a pena cortar o ombro


O primeiro passo no tratamento de erros deve ser entender quando "erro" não é "erro!" Obviamente, tudo isso depende da lógica comercial do seu aplicativo, mas, em geral, alguns erros são explícitos e podem ser corrigidos sem problemas.

  • Você tem um período em que o "antes" é anterior ao "de"? Reordenar.
  • Você tem um número de telefone que começa com + ou contém um traço no qual você não espera que caracteres especiais apareçam? Retire-os.
  • Problema de coleção nula? Certifique-se de inicializar isso antes do acesso (usando a inicialização lenta ou o construtor).

Não interrompa a execução do código devido a erros que você pode corrigir e, é claro, não interfere em suas ações para os usuários do seu serviço ou aplicativo. Se você conseguir entender o problema e resolvê-lo rapidamente - faça-o.



Retornar Nulos ou outros números mágicos


Valores zero, –1 onde se espera um número positivo e outros valores mágicos de retorno - tudo isso é produto do diabo, que transfere a responsabilidade pela verificação de erros para a função de chamada.

Com eles, seu código estará cheio desses blocos que tornarão a lógica do aplicativo pouco clara:

return_value = possibly_return_a_magic_value() if return_value < 0: handle_error() else: do_something() other_return_value = possibly_nullable_value() if other_return_value is None: handle_null_value() else: do_some_other_thing() 

Bem, ou mais simples, mas também ruim:
 var item = returnSomethingWhichCouldBeNull(); var result = item?.Property?.MaybeExists; if (result.HasValue) { DoSomething(); } 

Passar nulo para métodos também é um problema, embora no código de alguns desenvolvedores você possa encontrar locais onde os métodos começam com várias linhas de validação de entrada. Mas, de fato, tudo isso não é necessário. A maioria das linguagens modernas fornece não uma, mas várias ferramentas ao mesmo tempo, que permitem indicar claramente o que você espera. Eles também ignoram verificações inúteis, por exemplo, definindo parâmetros como diferente de zero ou com o decorador correspondente.

Códigos de erro


Esse é o mesmo problema que com valores nulos e outros semelhantes quando a complicação desnecessária do código é adicionada.

Por exemplo, você pode usar esta construção para retornar um código de erro:

 int errorCode; var result = getSomething(out errorCode); if (errorCode != 0) { doSomethingWithResult(result); } 

O resultado pode ser exibido da seguinte maneira:
 public class Result<T> { public T Item { get; set; } // At least "ErrorCode" is an enum public ErrorCode ErrorCode { get; set; } = ErrorCode.None; public IsError { get { return ErrorCode != ErrorCode.None; } } } public class UsingResultConstruct { ... var result = GetResult(); if (result.IsError) { switch (result.ErrorCode) { case ErrorCode.NetworkError: HandleNetworkError(); break; case ErrorCode.UserError: HandleUserError(); break; default: HandleUnknownError(); break; } } ActuallyDoSomethingWithResult(result); ... } 

Este não é realmente o melhor código. Acontece que o item ainda pode estar vazio. De fato, não há garantia (exceto um contrato) de que, quando o resultado não contiver um erro, você poderá acessar com segurança o Item.

Depois de concluir todo esse processamento, você ainda precisa converter o código do bug em uma mensagem de erro e fazer algo com ele. Às vezes acontece que, devido à complexidade excessiva do código, essa mensagem em si não é exibida como deveria.

Há um problema ainda mais sério: se você ou outra pessoa alterar a implementação interna para manipular um novo estado inválido com um novo código de erro, toda a estrutura deixará de funcionar.

Se não funcionou da primeira vez, tente novamente


Antes de continuar, vale enfatizar que uma falha "silenciosa" do programa é ruim. Um problema inesperado pode ocorrer no momento mais inoportuno - por exemplo, no fim de semana, quando você não pode consertar tudo rapidamente.



Se você lê Código Limpo , provavelmente está se perguntando por que não lançar uma exceção? Se não, então provavelmente você acha que as exceções são a raiz do mal. Eu também pensava assim, mas agora penso um pouco diferente.
Um ponto interessante, pelo menos para mim, é que a implementação padrão para o novo método em C # é gerar uma NotImplementedException, enquanto o padrão para o novo método em Python é "pass".

Como resultado, na maioria das vezes inserimos a configuração "erro silencioso" para Python. Gostaria de saber quantos desenvolvedores passaram muito tempo para entender o que está acontecendo e por que o programa não funciona. Mas, no final, depois de muitas horas, eles descobriram que se esqueceram de implementar o método de espaço reservado.
Mas dê uma olhada nisso:

 public MyDataObject UpdateSomething(MyDataObject toUpdate) { if (_dbConnection == null) { throw new DbConnectionError(); } try { var newVersion = _dbConnection.Update(toUpdate); if (newVersion == null) { return null; } MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { throw new DbConnectionError(); } catch (MyDataObjectUnhappyException dou) { throw new MalformedDataException(); } catch (Exception ex) { throw new UnknownErrorException(); } } 

Obviamente, as exceções por si só não o protegerão da criação de código ilegível e não gerenciado. Você deve aplicar exceções como uma estratégia bem pensada e equilibrada. Caso contrário, se o projeto for muito grande, seu aplicativo poderá estar em um estado inconsistente. Por outro lado, se o projeto for muito pequeno, você terá o caos.

Para evitar que isso aconteça, aconselho que você lembre-se disso:

Consistência acima de tudo. Você deve sempre garantir que o aplicativo seja consistente. Se isso significa que você precisa agrupar todas as linhas com um bloco try / catch, apenas oculte tudo em outra função.

 def my_function(): try: do_this() do_that() except: something_bad_happened() finally: cleanup_resource() 

A consolidação de erros é necessária. É bom que você forneça diferentes tipos de manipulação de diferentes tipos de erros. No entanto, isso é para você, não para usuários. Para eles, lance a única exceção para que os usuários simplesmente saibam que algo deu errado. Os detalhes são de sua área de responsabilidade.

 public MyDataObject UpdateSomething(MyDataObject toUpdate) { try { var newVersion = _dbConnection.Update(toUpdate); MyDataObject result = new MyDataObject(newVersion); return result; } catch (DbConnectionClosedException dbcc) { HandleDbConnectionClosed(); throw new UpdateMyDataObjectException(); } catch (MyDataObjectUnhappyException dou) { RollbackVersion(); throw new UpdateMyDataObjectException(); } catch (Exception ex) { throw new UpdateMyDataObjectException(); } } 

Vale a pena capturar exceções imediatamente. Eles são melhor interceptados no nível mais baixo. Manter a consistência e ocultar os detalhes vale a pena, conforme descrito acima. A manipulação de erros deve ser deixada no nível superior do aplicativo. Se tudo for feito corretamente, você poderá separar o fluxo lógico do próprio aplicativo do fluxo de processamento de erros. Isso permitirá que você escreva um código claro e legível.

 def my_api(): try: item = get_something_from_the_db() new_version = do_something_to_item(item) return new_version except Exception as ex: handle_high_level_exception(ex) 

Por enquanto é tudo, e se você quiser discutir o tópico dos erros e o processamento deles - Wellcome.

A Skillbox recomenda:

Source: https://habr.com/ru/post/pt441148/


All Articles