Bom estilo de código para introduzir verificações de dados em todos os lugares?

10

Eu tenho um projeto que é suficientemente grande e não consigo mais manter todos os aspectos da minha cabeça. Estou lidando com várias classes e funções e passando dados.

Com o tempo, notei que continuava recebendo erros, porque esqueci a forma precisa dos dados quando os transmito para diferentes funções ( por exemplo, uma função aceita e gera uma matriz de strings, outra função, que escrevi muito mais tarde, aceita seqüências de caracteres que são mantidas em um dicionário etc., portanto, eu tenho que transformar as seqüências de caracteres com as quais estou trabalhando, de tê-las em uma matriz para tê-las em um dicionário ).

Para evitar sempre ter que descobrir o que ocorreu onde, comecei a tratar cada função e classe como uma "entidade isolada", no sentido de que não pode confiar no código externo, fornecendo a entrada correta e tem que executar as verificações de entrada (ou, em alguns casos, reformule os dados, se os dados forem fornecidos na forma errada).

Isso reduziu bastante o tempo que gasto, certificando-me de que os dados que eu transmito "se encaixam" em todas as funções, porque as próprias classes e funções agora me alertam quando alguma entrada é ruim (e às vezes até corrige isso) e eu não tem que ir com um depurador por todo o código para descobrir onde algo deu errado.

Por outro lado, isso também aumentou o código geral.
Minha pergunta é, se esse estilo de código apropriado para resolver esse problema?
Obviamente, a melhor solução seria refatorar completamente o projeto e garantir que os dados tenham uma estrutura uniforme para todas as funções - mas, como esse projeto cresce constantemente, eu acabaria gastando mais e me preocupando com o código limpo do que realmente adicionando coisas novas. .

(FYI: ainda sou iniciante, desculpe se essa pergunta foi ingênua; meu projeto é em Python.)

user7088941
fonte
1
Possível duplicata de Quão defensivo devemos estar?
Gnat #
3
@gnat É semelhante, mas em vez de responder à minha pergunta, fornece conselhos ("seja o mais defensivo possível") para a instância específica mencionada pelo OP, que é diferente da minha consulta mais geral.
user7088941
2
"mas como esse projeto está crescendo constantemente, eu acabaria gastando mais e me preocupando com código limpo do que realmente adicionando coisas novas" - isso parece muito com você precisar começar a se preocupar com código limpo. Caso contrário, você encontrará sua produtividade diminuindo e diminuindo, pois cada nova parte da funcionalidade é cada vez mais difícil de adicionar devido ao código existente. Nem todas as necessidades de refatoração para ser "completo", se adicionar algo novo é difícil por causa do código existente que toca, refatorar apenas que o toque de código e fazer uma nota do que você gostaria de revisitar mais tarde
Matt freake
3
Esse é um problema que as pessoas frequentemente enfrentam usando idiomas de tipo fraco. Se você não deseja ou pode mudar para um idioma de tipo mais estrito, a resposta é simplesmente "sim, esse estilo de código é apropriado para resolver esse problema" . Próxima questão?
Doc Brown
1
Em uma linguagem estritamente digitada, com tipos de dados adequados definidos, o compilador teria feito isso por você.
SD

Respostas:

4

Uma solução melhor é tirar mais proveito dos recursos e ferramentas da linguagem Python.

Por exemplo, na função 1, a entrada esperada é uma matriz de cadeias, onde a primeira cadeia denota o título de algo e a segunda uma referência bibliográfica. Na função 2, a entrada esperada ainda é uma matriz de cadeias, mas agora os papéis das cadeias são invertidos.

Esse problema é mitigado com um namedtuple. É leve e oferece um significado semântico fácil para os membros da sua matriz.

Para aproveitar os benefícios de alguma verificação automática de tipo sem alternar idiomas, você pode tirar proveito das dicas de tipo . Um bom IDE pode usar isso para informar quando você faz algo estúpido.

Você também parece preocupado com as funções ficando obsoletas quando os requisitos mudam. Isso pode ser detectado por testes automatizados .

Embora eu não esteja dizendo que a verificação manual nunca é apropriada, um melhor uso dos recursos de idioma disponíveis pode ajudá-lo a resolver esse problema de uma maneira mais sustentável.


fonte
+1 por apontar para mim namedtuplee para todas as outras coisas legais. Eu não namedtuplesabia - e, embora eu soubesse sobre o teste automatizado, nunca o usei muito e não percebi o quanto isso me ajudaria neste caso. Tudo isso realmente parece ser tão bom quanto uma análise estática. (O teste automatizado pode até ser melhor, pois consigo captar todas as coisas sutis que não seriam capturadas em uma análise estática!) Se você conhece algum outro, informe-me. Vou manter a pergunta em aberto por mais um tempo, mas se não houver outras respostas, eu aceito as suas.
usar o seguinte comando
9

OK, o problema real é descrito em um comentário abaixo desta resposta:

Por exemplo, na função 1, a entrada esperada é uma matriz de cadeias, onde a primeira cadeia denota o título de algo e a segunda uma referência bibliográfica. Na função 2, a entrada esperada ainda é uma matriz de cadeias, mas agora os papéis das cadeias são invertidos

O problema aqui é o uso de uma lista de strings em que o pedido significa semântica. Esta é realmente uma abordagem propensa a erros. Em vez disso, você deve criar uma classe personalizada com dois campos chamados titlee bibliographical_reference. Dessa forma, você não irá confundi-los e evitar esse problema no futuro. É claro que isso requer alguma refatoração se você já usa listas de cadeias de caracteres em muitos lugares, mas acredite, será mais barato a longo prazo.

A abordagem comum em linguagens de tipos dinâmicos é a "digitação de pato", o que significa que você realmente não se importa com o "tipo" do objeto passado, mas apenas se ele suporta os métodos que você chama. No seu caso, você simplesmente lerá o campo chamado bibliographical_referencequando precisar. Se esse campo não existir no objeto passado, você receberá um erro e isso indica que o tipo errado é passado para a função. É uma verificação de tipo tão boa quanto qualquer outra.

JacquesB
fonte
Às vezes, o problema é ainda mais sutil: estou passando o tipo correto, mas a "estrutura interna" da minha entrada atrapalha a função: Por exemplo, na função 1, a entrada esperada é uma matriz de cadeias, onde a primeira cadeia denota o título de algo e o segundo uma referência bibliográfica. Na função 2, a entrada esperada ainda é uma matriz de cadeias, mas agora os papéis das cadeias são invertidos: a primeira cadeia deve ser a referência bibliográfica e a segunda, a referência bibliográfica. Eu acho que para essas verificações são apropriadas?
user7088941
1
@ user7088941: O problema que você descreve pode ser facilmente resolvido com uma classe com dois campos: "title" e "bibliographical_reference". Você não vai confundir isso. Confiar na ordem em uma lista de strings parece muito propenso a erros. Talvez este seja o problema subjacente?
precisa saber é o seguinte
3
Essa é a resposta. Python é uma linguagem orientada a objetos, não uma linguagem orientada a lista de dicionários de cadeias de caracteres para números inteiros (ou qualquer outra coisa). Então, use objetos. Os objetos são responsáveis ​​por gerenciar seu próprio estado e impor seus próprios invariantes; outros objetos não podem corrompê-los, jamais (se corretamente projetados). Se dados não estruturados ou semiestruturados entrarem no sistema a partir do exterior, você valida e analisa uma vez no limite do sistema e converte em objetos ricos o mais rápido possível.
Jörg W Mittag
3
"Eu realmente evitaria refatoração constante" - esse bloqueio mental é seu problema. Um bom código surge apenas da refatoração. Muita refatoração. Suportado por testes de unidade. Especialmente quando os componentes precisam ser estendidos ou evoluídos.
Doc Brown
2
Agora eu entendi. +1 para todas as informações e comentários interessantes. E obrigado a todos por seus comentários incrivelmente úteis! (Enquanto eu estava usando algumas classes / objetos, intercalei-os com as listas mencionadas, o que, como agora vejo, não era uma boa ideia. A questão permaneceu como melhor implementá-lo, onde usei as sugestões concretas das respostas dos JETMs , que realmente fez a diferença radical em termos de velocidade de alcançar um estado livre de bugs).
user7088941
3

Primeiro, o que você está enfrentando agora é o cheiro do código - tente lembrar o que o levou a ficar consciente do cheiro e tente aprimorar o nariz "mental", pois quanto mais cedo você notar um código, mais cedo - e mais fácil - você é capaz de corrigir o problema subjacente.

Para evitar sempre ter que descobrir o que ocorreu onde, comecei a tratar cada função e classe como uma "entidade isolada", no sentido de que não pode confiar no código externo, fornecendo a entrada correta e tem que executar as verificações de entrada.

A programação defensiva - como essa técnica é chamada - é uma ferramenta válida e frequentemente usada. No entanto, como em todas as coisas, é importante usar a quantidade certa, poucas verificações e você não detectará problemas, muitas e seu código ficará sobrecarregado.

(ou, em alguns casos, reformule os dados, se os dados forem fornecidos na forma incorreta).

Essa pode ser uma ideia menos boa. Se você perceber que uma parte do seu programa está chamando uma função com dados formatados incorretamente, CORRECIONE A PARTE , não altere a função chamada para poder digerir os dados incorretos de qualquer maneira.

Isso reduziu bastante o tempo que gasto, certificando-me de que os dados que eu transmito "se encaixam" em todas as funções, porque as próprias classes e funções agora me alertam quando alguma entrada é ruim (e às vezes até corrige isso) e eu não tem que ir com um depurador por todo o código para descobrir onde algo deu errado.

Melhorar a qualidade e a manutenção do seu código economiza tempo a longo prazo (nesse sentido, devo advertir novamente sobre a funcionalidade de autocorreção que você incorporou em algumas de suas funções - elas podem ser uma fonte insidiosa de bugs. programa não falha e queima não significa que funciona corretamente ...)

Para finalmente responder à sua pergunta: Sim, a programação defensiva (ou seja, verificar a validade dos parâmetros fornecidos) é - em um nível saudável - uma boa estratégia. Dito isto , como você mesmo disse, seu código é inconsitente, e eu recomendo fortemente que você gaste algum tempo para refatorar as partes que cheiram - você disse que não deseja se preocupar com código limpo o tempo todo, gastando mais tempo com "limpeza" do que em novos recursos ... Se você não mantiver seu código limpo, poderá gastar duas vezes o tempo "salvar" de não manter um código limpo em erros de esmagamento E terá dificuldade em implementar novos recursos - a dívida técnica pode esmagar você.

CharonX
fonte
1

Tudo bem. Eu costumava codificar no FoxPro, onde eu tinha um bloco TRY..CATCH quase em todas as grandes funções. Agora, eu codigo em JavaScript / LiveScript e raramente verifico parâmetros em funções "internas" ou "privadas".

"Quanto verificar" depende mais do projeto / idioma escolhido do que da sua habilidade com o código.

Michael Quad
fonte
1
Eu acho que foi tentar ... pegar ... ignorar. Você fez o contrário do que o OP está pedindo. O ponto de vista do IMHO é evitar inconsistências, enquanto o seu garante que o programa não exploda ao acertar um.
Maaartinus 20/0418
1
@maaartinus está correto. As linguagens de programação geralmente nos fornecem construções simples de usar para impedir a aplicação de explosões - mas as linguagens de programação construídas nos permitem evitar inconsistências parecem ser muito mais difíceis de usar: que eu saiba, refatore constantemente tudo e use as classes que melhor contentorizam fluxo de informações em seu aplicativo. É exatamente sobre isso que estou perguntando - existe uma maneira mais fácil de corrigir isso.
user7088941
@ user7088941 É por isso que evito idiomas fracamente digitados. Python é simplesmente fantástico, mas, para algo maior, não consigo acompanhar o que fiz em outro lugar. Portanto, prefiro o Java, que é bastante detalhado (não muito com os recursos do Lombok e do Java 8), possui digitação e ferramentas estritas para análise estática. Eu sugiro que você tente algum tipo de linguagem estritamente, pois não sei como resolvê-lo.
Maaartinus 20/0418
Não se trata de parâmetro de tipo estrito / fraco. É sobre saber que o parâmetro está correto. Mesmo se você usar (número inteiro de 4 bytes), pode ser necessário verificar se está em algum intervalo de 0 a 10, por exemplo. Se você sabe que esse parâmetro é sempre 0..10, não é necessário verificar. FoxPro não tem matrizes associativas, por exemplo, é muito difícil de operar com suas variáveis, seu alcance e assim por diante .. é por isso que você tem que verificar cheque ..
Michael Quad
1
@ user7088941 Não é OO, mas existe a regra "falhar rápido". Todo método não privado precisa verificar seus argumentos e lançar quando algo está errado. Sem tentativa de captura, sem tentativa de consertar, basta explodir no céu. Certamente, em algum nível superior, a exceção é registrada e manipulada. Como seus testes encontram a maioria dos problemas de antemão e nenhum problema é oculto, o código converge para uma solução livre de erros muito mais rapidamente do que quando é tolerante a erros.
Maaartinus 20/0418