Estou trabalhando no livro "Head First Python" (é minha língua para aprender este ano) e cheguei a uma seção em que eles discutem sobre duas técnicas de código:
Verificando o tratamento First vs Exception.
Aqui está uma amostra do código Python:
# Checking First
for eachLine in open("../../data/sketch.txt"):
if eachLine.find(":") != -1:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
# Exception handling
for eachLine in open("../../data/sketch.txt"):
try:
(role, lineSpoken) = eachLine.split(":",1)
print("role=%(role)s lineSpoken=%(lineSpoken)s" % locals())
except:
pass
O primeiro exemplo lida diretamente com um problema na .split
função. O segundo apenas permite que o manipulador de exceções lide com ele (e ignora o problema).
Eles argumentam no livro para usar o tratamento de exceções em vez de verificar primeiro. O argumento é que o código de exceção capturará todos os erros, onde a verificação primeiro capturará apenas o que você pensa (e você perde os casos extremos). Fui ensinado a verificar primeiro, então meu instinto inicial era fazer isso, mas a ideia deles é interessante. Eu nunca pensei em usar o tratamento de exceções para lidar com casos.
Qual das duas é geralmente considerada a melhor prática?
fonte
if -1 == eachLine.find(":"): continue
o restante do loop também não seria recuado.Respostas:
No .NET, é prática comum evitar o uso excessivo de exceções. Um argumento é o desempenho: no .NET, lançar uma exceção é computacionalmente caro.
Outro motivo para evitar o uso excessivo é que pode ser muito difícil ler código que depende muito deles. A entrada de Joel Spolsky no blog faz um bom trabalho ao descrever o problema.
No coração do argumento está a seguinte citação:
Pessoalmente, lanço exceções quando meu código não pode fazer o que é contratado. Costumo usar try / catch quando estou prestes a lidar com algo fora dos limites do meu processo, por exemplo, uma chamada SOAP, uma chamada de banco de dados, E / S de arquivo ou uma chamada de sistema. Caso contrário, tento codificar defensivamente. Não é uma regra difícil e rápida, mas é uma prática geral.
Scott Hanselman também escreve sobre exceções no .NET aqui . Neste artigo, ele descreve várias regras básicas sobre exceções. Meu favorito?
fonte
Em Python, em particular, geralmente é considerado uma boa prática capturar a exceção. Tende a ser chamado de mais fácil pedir perdão do que permissão (EAFP), em comparação com o Look Before You Leap (LBYL). Há casos em que o LBYL fornece erros sutis em alguns casos.
No entanto, tenha cuidado com as
except:
instruções simples e com o overbroad, exceto as instruções, pois elas também podem mascarar erros - algo assim seria melhor:fonte
try
/ emexcept
vez de configurar um condicional para "é o seguinte que provavelmente lançará uma exceção", e a prática do Python definitivamente prefere a anterior. Não prejudica que essa abordagem seja (provavelmente) mais eficiente aqui, pois, no caso em que a divisão é bem-sucedida, você apenas caminha a corda uma vez. Quanto a sesplit
deve lançar uma exceção aqui, eu diria que definitivamente deveria - uma regra comum é que você deve lançar quando não puder fazer o que seu nome diz e não puder se dividir em um delimitador ausente.Uma abordagem pragmática
Você deve estar na defensiva, mas até certo ponto. Você deve escrever o tratamento de exceções, mas até certo ponto. Vou usar a programação da Web como exemplo, porque é onde moro.
Isso é fruto da experiência de trabalhar em grandes cenários de equipe.
Uma Analogia
Imagine se você usasse um traje espacial dentro da ISS o tempo todo. Seria difícil ir ao banheiro ou comer. Seria super volumoso dentro do módulo espacial se movimentar. Seria péssimo. Escrever várias tentativas dentro do seu código é assim. Você tem que ter um ponto em que diz: ei, eu garanti a ISS e meus astronautas estão bem, então não é prático usar um traje espacial para todos os cenários que possam acontecer.
fonte
O principal argumento do livro é que a versão de exceção do código é melhor, pois ela captura tudo o que você pode ter esquecido se tentar escrever sua própria verificação de erro.
Penso que esta afirmação é verdadeira apenas em circunstâncias muito específicas - nas quais você não se importa se a saída está correta.
Não há dúvida de que levantar exceções é uma prática sólida e segura. Você deve fazer isso sempre que achar que há algo no estado atual do programa com o qual você (como desenvolvedor) não pode ou não deseja lidar.
Seu exemplo, no entanto, é sobre a captura de exceções. Se você capturar uma exceção, não estará se protegendo de cenários que possa ter esquecido. Você está fazendo exatamente o oposto: assume que não ignorou nenhum cenário que possa ter causado esse tipo de exceção e, portanto, está confiante de que está tudo bem em capturá-la (e, assim, impedir que o programa saia, como qualquer exceção não capturada).
Usando a abordagem de exceção, se você vir a
ValueError
exceção, pule uma linha. Usando a abordagem tradicional de não exceção, você conta o número de valores retornadossplit
e, se for menor que 2, pula uma linha. Você deve se sentir mais seguro com a abordagem de exceção, pois pode ter esquecido outras situações de "erro" em sua verificação de erro tradicional e asexcept ValueError
capturaria para você?Isso depende da natureza do seu programa.
Se você estiver escrevendo, por exemplo, um navegador da Web ou um reprodutor de vídeo, um problema com entradas não deve causar o travamento de uma exceção não detectada. É muito melhor produzir algo remotamente sensato (mesmo que, estritamente falando, incorreto) do que sair.
Se você estiver escrevendo um aplicativo em que a correção é importante (como software comercial ou de engenharia), essa seria uma abordagem terrível. Se você se esqueceu de algum cenário que levanta
ValueError
, a pior coisa a fazer é ignorar silenciosamente esse cenário desconhecido e simplesmente pular a linha. É assim que erros muito sutis e caros acabam no software.Você pode pensar que a única maneira de ver
ValueError
neste código é se forsplit
retornado apenas um valor (em vez de dois). Mas e se a suaprint
declaração mais tarde começar a usar uma expressão que aumentaValueError
sob algumas condições? Isso fará com que você pule algumas linhas não porque elas falham:
, mas porqueprint
falham nelas. Este é um exemplo de um bug sutil ao qual me referi anteriormente - você não notaria nada, apenas perderia algumas linhas.Minha recomendação é evitar capturar (mas não gerar!) Exceções no código em que produzir saída incorreta é pior do que sair. A única vez em que capturava uma exceção nesse código é quando tenho uma expressão verdadeiramente trivial, para que eu possa facilmente raciocinar o que pode causar cada um dos possíveis tipos de exceção.
Quanto ao impacto no desempenho do uso de exceções, é trivial (em Python), a menos que exceções sejam encontradas com freqüência.
Se você usar exceções para lidar com condições que ocorrem rotineiramente, em alguns casos poderá pagar um enorme custo de desempenho. Por exemplo, suponha que você execute remotamente algum comando. Você pode verificar se o texto do seu comando passa pelo menos na validação mínima (por exemplo, sintaxe). Ou você pode esperar que uma exceção seja levantada (o que ocorre apenas depois que o servidor remoto analisa seu comando e encontra um problema). Obviamente, o primeiro é ordens de magnitude mais rápidas. Outro exemplo simples: você pode verificar se um número é zero ~ 10 vezes mais rápido do que tentar executar a divisão e capturar a exceção ZeroDivisionError.
Essas considerações são importantes apenas se você enviar seqüências de comandos malformadas para servidores remotos ou receber argumentos de valor zero que você usa para divisão.
Nota: Presumo que você usaria em
except ValueError
vez do justoexcept
; como outros apontaram, e como o próprio livro diz em algumas páginas, você nunca deve usar nadaexcept
.Outra observação: a abordagem adequada de não exceção é contar o número de valores retornados por
split
, em vez de procurar:
. O último é muito lento, pois repete o trabalho realizadosplit
e pode quase dobrar o tempo de execução.fonte
Como regra geral, se você souber que uma instrução pode gerar um resultado inválido, teste-a e lide com ela. Use exceções para coisas que você não espera; coisas que são "excepcionais". Isso torna o código mais claro em um sentido contratual ("não deve ser nulo" como exemplo).
fonte
Use o que sempre funciona bem ..
O tratamento de exceções e a programação defensiva são maneiras diferentes de expressar a mesma intenção.
fonte
TBH, não importa se você usa o
try/except
mecânico ou umaif
verificação de instrução. Você geralmente vê o EAFP e o LBYL na maioria das linhas de base do Python, sendo o EAFP um pouco mais comum. Às vezes, o EAFP é muito mais legível / idiomático, mas, nesse caso em particular, acho que é bom de qualquer maneira.Contudo...
Seria cuidadoso ao usar sua referência atual. Alguns problemas flagrantes com seu código:
with
idioma ao ler arquivos no Python: existem muito poucas exceções. Este não é um deles.except
declaração simples que não captura uma exceção específica)role, lineSpoken = eachLine.split(":",1)
O Ivc tem uma boa resposta sobre isso e o EAFP, mas também está vazando o descritor.
A versão LBYL não é necessariamente tão eficiente quanto a versão do EAFP, pelo que dizer que lançar exceções é "caro em termos de desempenho" é categoricamente falso. Realmente depende do tipo de strings que você está processando:
fonte
Basicamente, o tratamento de exceções deveria ser mais apropriado para idiomas OOP.
O segundo ponto é o desempenho, porque você não precisa executar
eachLine.find
para cada linha.fonte
Eu acho que a programação defensiva prejudica o desempenho. Você também deve capturar apenas as exceções a serem tratadas; deixe o tempo de execução lidar com a exceção que você não sabe manipular.
fonte