Costumo chegar a posições no meu código onde me vejo checando uma condição específica repetidamente.
Quero dar um pequeno exemplo: suponha que exista um arquivo de texto que contenha linhas começando com "a", linhas começando com "b" e outras linhas e, na verdade, só queira trabalhar com os dois primeiros tipos de linhas. Meu código seria algo parecido com isto (usando python, mas leia-o como pseudocódigo):
# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
if (line.startsWith("a")):
# do stuff
elif (line.startsWith("b")):
# magic
else:
# this else is redundant, I already made sure there is no else-case
# by using clear_lines()
# ...
Você pode imaginar que eu não apenas verificarei essa condição aqui, mas talvez também em outras funções e assim por diante.
Você pensa nisso como ruído ou agrega algum valor ao meu código?
coding-style
clean-code
marktani
fonte
fonte
assert()
lá para ajudar nos testes, mas além disso é provavelmente excessivo. Dito isto, variará dependendo da situação.elif (line.startsWith("b"))
? a propósito, você pode remover com segurança os parênteses que sobraram nas condições, eles não são idiomáticos no Python.Respostas:
Essa é uma prática extremamente comum e a maneira de lidar com ela é por meio de filtros de ordem superior .
Essencialmente, você passa uma função para o método de filtro, juntamente com a lista / sequência na qual deseja filtrar e a lista / sequência resultante contém apenas os elementos que deseja.
Não estou familiarizado com a sintaxe python (porém, ela contém uma função como vista no link acima), mas em c # / f # parece com isso:
c #:
f # (assume inumerável, caso contrário, List.filter seria usado):
Portanto, para deixar claro: se você usa códigos / padrões testados e aprovados, é um estilo ruim. Isso, e alterar a lista na memória da maneira que você parece via clear_lines (), perde a segurança do encadeamento e as esperanças de paralelismo que você poderia ter.
fonte
(line for line in lines if line.startswith("a") or line.startswith("b"))
.clear_lines
é realmente uma má ideia. No Python, você provavelmente usaria geradores para evitar carregar o arquivo completo na memória.lines
for uma coleção gerada.skip
,take
,reduce
(aggregate
em .NET),map
(select
na NET), e não há mais, mas isso é um começo realmente sólido.Recentemente, tive que implementar um programador de firmware usando o formato Motorola S-record , muito semelhante ao que você descreve. Como tivemos uma pressão de tempo, meu primeiro rascunho ignorou redundâncias e simplificou com base no subconjunto que eu realmente precisava usar no meu aplicativo. Ele passou nos meus testes com facilidade, mas falhou bastante assim que outra pessoa tentou. Não havia idéia de qual era o problema. Foi o caminho todo, mas falhou no final.
Portanto, não tive escolha a não ser implementar todas as verificações redundantes, a fim de diminuir onde estava o problema. Depois disso, levei cerca de dois segundos para encontrar o problema.
Levei talvez duas horas extras para fazer da maneira certa, mas perdi um dia do tempo de outras pessoas também na solução de problemas. É muito raro que alguns ciclos de processador valham um dia desperdiçado na solução de problemas.
Dito isto, no que diz respeito à leitura de arquivos, geralmente é benéfico projetar seu software para trabalhar com a leitura e o processamento de uma linha por vez, em vez de ler o arquivo inteiro na memória e processá-lo na memória. Dessa forma, ainda funcionará em arquivos muito grandes.
fonte
Você pode gerar uma exceção no
else
caso. Dessa forma, não é redundante. Exceções não são coisas que não deveriam acontecer, mas são verificadas de qualquer maneira.fonte
"c"
, pode ser menos claro.No design por contrato , supõe-se que cada função deve executar seu trabalho conforme descrito em sua documentação. Portanto, cada função tem uma lista de pré-condições, ou seja, condições nas entradas da função e pós-condições, ou seja, condições da saída da função.
A função deve garantir a seus clientes que, se as entradas respeitarem as pré-condições, a saída será a descrita pelas pós-condições. Se pelo menos uma das pré-condições não for respeitada, a função poderá fazer o que quiser (travar, retornar qualquer resultado, ...). Portanto, pré e pós-condições são uma descrição semântica da função.
Graças ao contrato, uma função tem certeza de que seus clientes a usam corretamente e um cliente tem certeza de que a função faz seu trabalho corretamente.
Alguns idiomas processam contratos de forma nativa ou por meio de uma estrutura dedicada. Para os outros, o melhor é verificar as condições pré e pós graças às declarações, como disse o @Lattyware. Mas eu não chamaria isso de programação defensiva, pois, na minha opinião, esse conceito está mais focado na proteção contra as entradas do usuário (humano).
Se você explorar contratos, poderá evitar a condição redundantemente verificada, pois a função chamada funciona perfeitamente e você não precisa da verificação dupla, ou a função chamada é disfuncional e a função de chamada pode se comportar como deseja.
O mais difícil é, então, definir qual função é responsável por quê e documentar estritamente essas funções.
fonte
Na verdade, você não precisa do clear_lines () no início. Se a linha não for "a" ou "b", os condicionais simplesmente não serão acionados. Se você quiser se livrar dessas linhas, faça o resto em clear_line (). Como está, você está fazendo duas passagens pelo documento. Se você pular o clear_lines () no início e fizer isso como parte do loop foreach, reduzirá o tempo de processamento pela metade.
Não é apenas estilo ruim, é ruim computacionalmente.
fonte
"a"
/"b"
linhas. Não dizendo que é provável (o nome claro implica que eles estão sendo descartados), apenas que existe a possibilidade de que seja necessário. Se esse conjunto de linhas for repetidamente repetido no futuro, também poderá valer a pena removê-las antecipadamente, para evitar muitas iterações inúteis.Se você realmente quiser fazer alguma coisa se encontrar uma string inválida (texto de depuração de saída, por exemplo), diria que está absolutamente correto. Algumas linhas extras e alguns meses depois, quando ele parar de funcionar por algum motivo desconhecido, você pode ver a saída para descobrir o porquê.
Se, no entanto, for seguro ignorá-lo, ou você tiver certeza de que nunca obterá uma sequência inválida, não haverá necessidade de ramificação extra.
Pessoalmente, sou sempre a favor de colocar pelo menos uma saída de rastreamento para qualquer condição inesperada - isso facilita muito a vida quando você tem um bug com a saída anexada, informando exatamente o que deu errado.
fonte
Eu odeio
if...then...else
construções. Eu evitaria todo o problema:fonte