
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; }
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: