Desenvolvo software nos últimos três anos, mas recentemente acordei com a ignorância sobre as boas práticas. Isso me levou a começar a ler o livro Clean Code , que está melhorando minha vida, mas estou lutando para ter uma ideia de algumas das melhores abordagens para escrever meus programas.
Eu tenho um programa Python no qual eu ...
- use argparse
required=True
para aplicar dois argumentos, que são os dois nomes de arquivo. o primeiro é o nome do arquivo de entrada, o segundo é o nome do arquivo de saída - tem uma função
readFromInputFile
que primeiro verifica se um nome de arquivo de entrada foi inserido - 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 2 e 3 são redundantes e devem ser removidas, liberando ambas as funções de uma if
condição desnecessária . No entanto, também fui levado a acreditar que "a verificação dupla está correta" e pode ser a solução certa em um programa em que as funções podem 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.)
Minha pergunta é: é melhor evitar toda verificação de condição redundante? A lógica de um programa deve ser tão sólida que as verificações precisem ser feitas apenas uma vez? Existem bons exemplos que ilustram isso ou o contrário?
EDIT: Obrigado a todos pelas respostas! Eu aprendi algo com cada um. Ver tantas perspectivas me dá uma compreensão muito melhor de como abordar esse problema e determinar uma solução com base em meus requisitos. Obrigado!
fonte
Respostas:
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 somente para si, a robustez é normalmente uma preocupação muito menor do que quando você está escrevendo um programa complexo que consiste em vários componentes, talvez escritos por uma equipe. Em tais sistemas, existem 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 seja 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ê deve decidir por si mesmo, que tipo de ciclo de vida espera para o seu programa. É um programa que você espera que seja usado e mantido ao longo dos anos? A adição de uma verificação redundante provavelmente é melhor, pois não será improvável que seu código seja refatorado no futuro e que suas funções
read
ewrite
possam ser usadas em um contexto diferente.Ou é um pequeno programa apenas para fins de aprendizado 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 ocorre, 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ê-lo em dois locais pode levar aos problemas de manutenção habituais causados pela violação do DRY. Robustez vs. DRY geralmente é uma troca - a robustez requer redundância no código, enquanto o DRY tenta minimizar a redundância. E com a crescente complexidade do programa, a robustez se torna cada vez mais importante do que ser seco na validação.
Por fim, deixe-me dar um exemplo do que isso significa no seu caso. Vamos supor que seus requisitos mudem para algo como
Isso torna provável que você precise alterar sua dupla validação em dois lugares? Provavelmente não, esse requisito leva a uma alteração ao chamar
argparse
, mas nenhuma alteraçãowriteToOutputFile
: essa função ainda exigirá um nome de arquivo. Portanto, no seu caso, eu votaria na validação de entrada duas vezes, o risco de obter problemas de manutenção por causa de dois locais para mudar é IMHO muito menor do que o risco de problemas de manutenção devido a erros mascarados causados por poucas verificações.fonte
Redundância não é pecado. Redundância desnecessária é.
Se
readFromInputFile()
ewriteToOutputFile()
são funções públicas (e pelas convenções de nomenclatura do Python, elas são porque seus nomes não começaram com dois sublinhados), as funções poderão um dia ser usadas por alguém que evite o argparse por completo. Isso significa que, quando eles deixam de lado os argumentos, não conseguem ver sua mensagem de erro personalizada argparse.Se
readFromInputFile()
ewriteToOutputFile()
verificar os próprios parâmetros, você novamente poderá mostrar uma mensagem de erro personalizada que explica a necessidade de nomes de arquivos.Se
readFromInputFile()
ewriteToOutputFile()
não verificar 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 um código que realmente use essas funções, evitando argparse, e produza a mensagem de erro. Imagine que você não olhou para essas funções e confia apenas nos nomes delas para fornecer entendimento suficiente para usar. Quando isso é tudo que você sabe, existe alguma maneira de ser confundido com a exceção? Existe uma necessidade de uma mensagem de erro personalizada?
Desligar a parte do cérebro que lembra o interior dessas funções é difícil. Tanto é assim que alguns recomendam escrever o código de uso 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 o fizer, já estará vindo de fora primeiro.
fonte
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 aceitam e devem ter resultados bem definidos (precisos no que retornam). Isso também significa que eles devem ser capazes de lidar com tudo o que é passado com facilidade e não fazer suposições 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, com base em idéias como "se isso for quebrado, temos coisas maiores com que nos preocupar" ou "o parâmetro X não pode ter o valor Y porque o restante o código impede ", de repente você não tem mais componentes independentes e dissociados. Seus componentes são essencialmente dependentes do sistema mais amplo. Esse é um tipo de acoplamento sutil e rígido 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 , à sua maneira . Isso não é uma violação do DRY, porque as validações são por componentes independentes desacoplados, e uma alteração na validação em um não precisa necessariamente ser replicada exatamente no outro. 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 entradas para suas necessidades .
fonte
Suponha que você tenha uma função (em C)
E você não encontra nenhuma documentação sobre o caminho. E então você olha para a implementação e diz
Isso não apenas testa a entrada da função, mas também informa ao usuário da função que o caminho não pode ser NULL ou uma sequência vazia.
fonte
Em geral, a verificação dupla nem sempre é boa ou ruim. Sempre há muitos aspectos da questão em seu caso particular, dos quais o assunto depende. No seu caso:
argparse
módulo. Muitas vezes, é uma má idéia usar uma biblioteca e depois fazer o trabalho sozinho. Por que usar a biblioteca então?fonte
Suas verificações duplas parecem estar em locais onde raramente são usadas. Portanto, essas verificações estão simplesmente tornando seu programa mais robusto:
Um cheque a mais não vai doer, um a menos.
No entanto, se você estiver verificando dentro de um loop repetido com frequência, pense em remover a redundância, mesmo que a verificação em si na maioria das vezes não seja dispendiosa em comparação com o que se segue após a verificação.
fonte
Talvez você possa mudar seu ponto de vista:
Se algo der errado, qual é o resultado? Isso prejudicará seu aplicativo / usuário?
É claro que você sempre pode argumentar se mais ou menos verificações são melhores ou piores, mas essa é uma questão bastante escolástica. E como você está lidando com software do mundo real , há consequências no mundo real.
No contexto que você está dando:
Eu suponho que você está fazendo 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 não é nada . E o tempo de execução será menor que o esperado. Você olha para o resultado - ou melhor: procura um resultado ausente, vê que invocou o comando de maneira errada, recomeça 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 só vez. A transformação começa e o resultado deve ser gravado, mas você recebe um erro. Dependendo do tempo, o usuário precisa esperar (dependendo da massa de dados que deve ser processada) isso pode ser irritante.
b) A entrada é lida passo a passo. Em seguida, o processo de gravação é encerrado imediatamente como em (1) e o usuário começa novamente.
A verificação desleixada pode ser considerada OK em algumas circunstâncias. Depende totalmente do seu caso e da sua intenção.
Além disso: você deve evitar a paranóia e não fazer muitas checagens duplas.
fonte
Eu argumentaria que os testes não são redundantes.
Enquanto os nomes de arquivos estão sendo verificados duas vezes, eles estão sendo verificados para propósitos diferentes. Em um pequeno programa em que você pode confiar na verificação dos parâmetros para as funções, as verificações nas funções podem ser consideradas redundantes.
Uma solução mais robusta teria um ou dois validadores de nome de arquivo.
Eu uso duas regras para quando executar ações:
fonte
O cheque é redundante. Corrigindo isso, porém, é necessário remover readFromInputFile e writeToOutputFile e substituí-los por readFromStream e writeToStream.
No ponto em que o código recebe o fluxo de arquivos, você sabe que possui um fluxo válido conectado a um arquivo válido ou qualquer outra coisa à qual um fluxo possa ser conectado. Isso evita verificações redundantes.
Você pode perguntar: bem, você 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, a outra é para verificar se o arquivo apontado pelo nome de arquivo é um arquivo válido no contexto especificado (por exemplo, existe um arquivo de entrada, o diretório de saída é gravável). Esses são tipos diferentes de verificações, portanto, não são redundantes e ocorrem no método de análise de argumentos (perímetro do aplicativo) e não no aplicativo principal.
fonte