A verificação de condição redundante está de acordo com as práticas recomendadas?

15

Tenho desenvolvido softwares nos últimos três anos, mas recentemente acordei com o quão ignorante sou de boas práticas. Isso me levou a começar a ler o livro Clean Code , que está melhorando minha vida, mas estou lutando para obter insights sobre algumas das melhores abordagens para escrever meus programas.

Eu tenho um programa em Python no qual eu ...

  1. use argparse required=True para impor dois argumentos, que são ambos nomes de arquivos. o primeiro é o nome do arquivo de entrada, o segundo é o nome do arquivo de saída
  2. tem uma função readFromInputFile que primeiro verifica se um nome de arquivo de entrada foi inserido
  3. tem uma função writeToOutputFile que primeiro verifica se um nome de arquivo de saída foi inserido

Meu programa é pequeno o suficiente para que eu acredite que as verificações em # 2 e # 3 sejam redundantes e devem ser removidas, liberando assim ambas as funções de uma condição if desnecessária. No entanto, também fui levado a acreditar que a "verificação dupla é ok" e pode ser a solução correta em um programa no qual as funções poderiam ser chamadas de um local diferente, onde a análise de argumentos não ocorre.

(Além disso, se a leitura ou gravação falhar, eu tenho um try except em cada função para gerar uma mensagem de erro apropriada.)

A minha pergunta é: é melhor evitar toda a verificação de condição redundante? A lógica de um programa deve ser tão sólida que as verificações só precisem ser feitas uma vez? Existem bons exemplos que ilustram isso ou o contrário?

EDIT: Obrigado a todos pelas respostas! Eu aprendi algo de cada um. Vendo tantas perspectivas me dá uma compreensão muito melhor de como abordar esse problema e determinar uma solução com base em meus requisitos. Obrigada!

    
por thesis 26.11.2016 / 06:59
fonte

9 respostas

15

O que você está pedindo é chamado de "robustez", e não há resposta certa ou errada. Depende do tamanho e da complexidade do programa, do número de pessoas que trabalham nele e da importância de detectar falhas.

Em pequenos programas que você escreve sozinho e apenas para si mesmo, a robustez é tipicamente uma preocupação muito menor do que quando você vai escrever um programa complexo que consiste em múltiplos componentes, talvez escritos por uma equipe. Em tais sistemas, há limites entre os componentes na forma de APIs públicas e, em cada limite, é geralmente uma boa ideia validar os parâmetros de entrada, mesmo que "a lógica do programa deva ser tão sólida que essas verificações sejam redundantes". ". Isso facilita a detecção de bugs e ajuda a manter os tempos de depuração menores.

No seu caso, você tem que decidir por si mesmo, que tipo de ciclo de vida você espera para o seu programa. É um programa que você espera que seja usado e mantido ao longo dos anos? Em seguida, adicionar uma verificação redundante provavelmente é melhor, pois não será improvável que seu código seja refatorado no futuro e suas funções read e write possam ser usadas em um contexto diferente.

Ou é um programa pequeno apenas para fins de aprendizagem ou diversão? Então essas verificações duplas não serão necessárias.

No contexto de "Código Limpo", pode-se perguntar se uma verificação dupla viola o princípio DRY. Na verdade, às vezes isso acontece, pelo menos em um grau menor: a validação de entrada pode ser interpretada como parte da lógica de negócios de um programa e tê-la em dois lugares pode levar a problemas de manutenção comuns causados pela violação de DRY. A robustez vs. DRY é frequentemente uma troca - a robustez requer redundância no código, enquanto o DRY tenta minimizar a redundância. E com o aumento da complexidade do programa, a robustez torna-se cada vez mais importante do que ser DRY na validação.

Por fim, deixe-me dar um exemplo do que isso significa no seu caso. Vamos supor que seus requisitos mudam para algo como

  • o programa também deve trabalhar com um argumento, o nome do arquivo de entrada, se não houver nenhum nome de arquivo de saída fornecido, ele será construído automaticamente a partir do nome do arquivo de entrada, substituindo o sufixo.

Isso faz com que você precise alterar sua validação dupla em dois lugares? Provavelmente não, esse requisito leva a uma alteração ao chamar argparse , mas nenhuma alteração em writeToOutputFile : essa função ainda exigirá um nome de arquivo. Então, no seu caso, eu votaria para fazer a validação de entrada duas vezes, o risco de ter problemas de manutenção por ter dois lugares para mudar é IMHO muito menor do que o risco de problemas de manutenção devido a erros mascarados causados por poucas verificações. / p>     

por 26.11.2016 / 08:29
fonte
5

Redundância não é o pecado. Redundância desnecessária é.

  1. Se readFromInputFile() e writeToOutputFile() forem funções públicas (e por convenções de nomenclatura do Python, eles não tiverem seus nomes iniciados com dois sublinhados), as funções poderão, algum dia, ser usadas por alguém que tenha evitado completamente a argparse. . Isso significa que, quando eles deixam os argumentos, não conseguem ver sua mensagem de erro de análise personalizada.

  2. Se readFromInputFile() e writeToOutputFile() verificarem os próprios parâmetros, mais uma vez você mostrará uma mensagem de erro personalizada que explica a necessidade de nomes de arquivos.

  3. Se readFromInputFile() e writeToOutputFile() não verificarem os próprios parâmetros, nenhuma mensagem de erro personalizada será mostrada. O usuário terá que descobrir a exceção resultante por conta própria.

Tudo se resume a 3. Escreva algum código que realmente use essas funções, evitando erros e produza a mensagem de erro. Imagine que você não tenha olhado dentro dessas funções e esteja apenas confiando em seus nomes para fornecer compreensão suficiente para usar. Quando isso é tudo que você sabe, há alguma maneira de ficar confuso com a exceção? Existe uma necessidade de uma mensagem de erro personalizada?

Desligar a parte do seu cérebro que lembra o interior dessas funções é difícil. Tanto é assim que alguns recomendam escrever o código usando antes do código que é usado. Dessa forma, você chega ao problema já sabendo como são as coisas do lado de fora. Você não precisa fazer o TDD para fazer isso, mas se você fizer o TDD, já estará vindo de fora primeiro.

    
por 26.11.2016 / 13:45
fonte
4

A medida em que você torna seus métodos independentes e reutilizáveis é uma coisa boa. Isso significa que os métodos devem perdoar o que eles aceitam e devem ter resultados bem definidos (precisos no que retornam). Isso também significa que eles devem ser capazes de lidar de forma graciosa com tudo transmitido a eles e não fazer nenhuma suposição sobre a natureza da entrada, qualidade, tempo, etc.

Se um programador tem o hábito de escrever métodos que fazem suposições sobre o que é passado, baseado em idéias como "se isso está quebrado, temos coisas maiores para nos preocuparmos" ou "o parâmetro X não pode ter valor Y porque o resto do código o impede ", então, de repente, você não tem mais componentes separados independentes. Seus componentes são essencialmente dependentes do sistema mais amplo. Isso é um tipo de acoplamento sutil e leva ao aumento exponencial do custo total de propriedade à medida que a complexidade do sistema aumenta.

Observe que isso pode significar que você está validando as mesmas informações mais de uma vez. Mas tudo bem. Cada componente é responsável por sua própria validação de seu próprio jeito . Isso não é uma violação de DRY, porque as validações são por componentes independentes dissociados, e uma mudança para a validação em uma não necessariamente tem que ser replicada exatamente na outra. Não há redundância aqui. X tem a responsabilidade de verificar suas entradas para suas próprias necessidades e passar algumas para Y. Y tem sua própria responsabilidade de verificar suas próprias entradas para suas necessidades .

    
por 27.11.2016 / 03:11
fonte
1

Suponha que você tenha uma função (em C)

void readInputFile (const char* path);

E você não pode encontrar nenhuma documentação sobre o caminho. E então você olha para a implementação e diz

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

Isso não apenas testa a entrada para a função, mas também informa ao usuário da função que o caminho não pode ser NULL ou uma string vazia.

    
por 26.11.2016 / 17:19
fonte
0

Em geral, a verificação dupla não é sempre boa ou ruim. Há sempre muitos aspectos da questão em seu caso particular sobre o qual o assunto depende. No seu caso:

  • Qual é o tamanho do programa? Quanto menor é, mais óbvio é que o chamador faz a coisa certa. Quando seu programa cresce, torna-se mais importante especificar exatamente quais são as pré-condições e pós-condições de cada rotina.
  • os argumentos já estão marcados pelo módulo argparse . Muitas vezes é uma má ideia usar uma biblioteca e depois fazer o trabalho sozinho. Por que usar a biblioteca então?
  • Qual a probabilidade de seu método ser reutilizado em um contexto em que o chamador não verifica argumentos? Quanto mais provável, mais importante é validar argumentos.
  • O que acontece se um argumento desaparecer ? Não encontrar um arquivo de entrada provavelmente interromperá o processamento. Esse é provavelmente um modo de falha óbvio que é fácil de corrigir. O tipo insidioso de erros são aqueles em que o programa continua trabalhando e produz resultados errados sem que você perceba .
por 26.11.2016 / 08:30
fonte
0

Seus cheques duplos parecem estar em lugares onde eles são usados raramente. Então, essas verificações estão simplesmente tornando seu programa mais robusto:

Um cheque demais não vai doer, um muito menos pode.

No entanto, se você estiver verificando dentro de um loop que é repetido com freqüência, você deve pensar em remover a redundância, mesmo que a verificação em si não seja, na maioria das vezes, dispendiosa em comparação com o que segue após a verificação.

    
por 26.11.2016 / 09:37
fonte
0

Talvez você possa mudar seu ponto de vista:

Se algo der errado, qual é o resultado? Isso prejudicará seu aplicativo / usuário?

É claro que você poderia sempre argumentar, se mais ou menos verificações são melhores ou piores, mas isso é uma questão bastante acadêmica. E já que você está lidando com o software mundo real , existem consequências para o mundo real.

Do contexto que você está dando:

  • um arquivo de entrada A
  • um arquivo de saída B

Suponho que você esteja fazendo uma transformação de A para B . Se A e B são pequenos e a transformação é pequena, quais são as consequências?

1) Você esqueceu de especificar de onde ler: Então o resultado é nada . E o tempo de execução será menor que o esperado. Você olha para o resultado - ou melhor: procure por um resultado perdido, veja que você invocou o comando de maneira errada, comece de novo e tudo está bem novamente

2) Você esqueceu de especificar o arquivo de saída. Isso resulta em diferentes cenários:

a) A entrada é lida de uma vez. Então a transformação começa e o resultado deve ser escrito, mas você recebe um erro. Dependendo do tempo, seu usuário tem que esperar (dependendo da massa de dados que deve ser processada) isso pode ser chato.

b) A entrada é lida passo a passo. Então o processo de escrita sai imediatamente como em (1) e o usuário começa de novo.

A verificação descuidada pode ser vista como OK em algumas circunstâncias. Depende totalmente do seu uso e qual é a sua intenção.

Além disso: Você deve evitar a paranóia e não fazer muitas duplicações.

    
por 26.11.2016 / 09:48
fonte
0

Eu diria que os testes não são redundantes.

  • Você tem duas funções públicas que exigem um nome de arquivo como um parâmetro de entrada. É apropriado validar seus parâmetros. As funções podem ser usadas em qualquer programa que precise de sua funcionalidade.
  • Você tem um programa que requer dois argumentos que devem ser nomes de arquivos. Acontece de usar as funções. É apropriado que o programa verifique seus parâmetros.

Enquanto os nomes de arquivos estão sendo verificados duas vezes, eles estão sendo verificados para diferentes finalidades. Em um pequeno programa onde você pode confiar que os parâmetros para as funções foram verificados, as verificações nas funções podem ser consideradas redundantes.

Uma solução mais robusta teria um ou dois validadores de nome de arquivo.

  • Para um arquivo de entrada, você pode querer verificar se o parâmetro especificou um arquivo legível.
  • Para um arquivo de saída, você pode querer verificar se esse parâmetro é um arquivo gravável ou um nome de arquivo válido que pode ser criado e gravado.

Eu uso duas regras para quando executar ações:

  • Faça isso o mais cedo possível. Isso funciona bem para coisas que sempre serão necessárias. Do ponto de vista deste programa, esta é a verificação dos valores argv, e as validações subseqüentes na lógica dos programas seriam redundantes. Se as funções forem movidas para uma biblioteca, elas não serão mais redundantes, pois a biblioteca não pode confiar que todos os chamadores validaram os parâmetros.
  • Faça isso o mais tarde possível. Isso funciona muito bem para coisas que raramente serão necessárias. Do ponto de vista deste programa, esta é a verificação dos parâmetros da função.
por 27.11.2016 / 00:43
fonte
0

A verificação é redundante. Corrigindo isto, requer que você remova readFromInputFile e writeToOutputFile e os substitua por readFromStream e writeToStream.

No ponto em que o código recebe o fluxo de arquivos, você sabe que tem um fluxo válido conectado a um arquivo válido ou a qualquer outro que um fluxo possa estar conectado. Isso evita verificações redundantes.

Você pode perguntar, bem, ainda precisa abrir o fluxo em algum lugar. Sim, mas isso acontece internamente no método de análise de argumentos. Você tem duas verificações lá, uma para verificar se um nome de arquivo é necessário, o outro é uma verificação de que o arquivo apontado pelo nome do arquivo é válido no contexto fornecido (por exemplo, arquivo de entrada existe, diretório de saída é gravável). Esses são tipos diferentes de verificações, portanto eles não são redundantes e acontecem dentro do método de análise de argumentos (perímetro do aplicativo) em vez de dentro do aplicativo principal.

    
por 17.02.2017 / 14:07
fonte