Pergunta

Disclaimer: Eu sou um leigo atualmente aprender a programar. Nunca foi parte de um projeto, nem escreveu nada mais do que ~ 500 linhas.

A minha pergunta é: será que a programação defensiva violam o Do not Repeat Yourself princípio? Assumindo a minha definição de programação defensiva é correta (tendo a função de entrada de validação chamando em vez do oposto), que não iria ser prejudicial ao seu código?

Por exemplo, isso é ruim:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    foo(input); //doesn't the extra logic
    foo(input); //and potentially extra calls
    foo(input); //work against you?
}   

em comparação com isto:

int main()
{
    if (input == /*condition*/)
    {
        foo(input);
        foo(input);
        foo(input);
    }
}

Mais uma vez, como um leigo, eu não sei o quanto simples declarações lógica contar contra você, tanto quanto o desempenho vai, mas a programação certamente defensiva não é bom para o programa ou a alma.

Foi útil?

Solução

Tudo se resume ao contrato a interface fornece. Há dois cenários diferentes para isso: entradas e saídas

.

Entradas - e com isso quero dizer basicamente parâmetros para funções -. Deve ser verificado pela aplicação como regra geral

Saídas - sendo resultados de retorno -. Deve ser basicamente a confiança de que o chamador, pelo menos em minha opinião

Tudo isso é temperada por esta pergunta: o que acontece se quebra um partido do contrato? Por exemplo, digamos que você tinha uma interface:

class A {
  public:
    const char *get_stuff();
}

e que especifica contrato que uma cadeia nula nunca serão devolvidos (que vai ser uma cadeia vazia na pior das hipóteses), então é seguro fazer isso:

A a = ...
char buf[1000];
strcpy(buf, a.get_stuff());

Por quê? Bem, se você estiver errado e os callee retorna um valor nulo em seguida, o programa irá falhar. Isso é realmente OK . Se algum objeto viola seu contrato, em seguida, de um modo geral o resultado deve ser catastrófico.

O risco que enfrentam em ser excessivamente defensivo é que você escrever muito código desnecessário (que pode introduzir mais bugs) ou que você realmente pode mascarar um problema sério por engolir uma exceção que você realmente não deveria.

de circunstâncias curso pode mudar isso.

Outras dicas

Violar os olhares princípio DRY assim:

int foo(int bar)
{
    if (bar != /*condition*/)
    {
        //code, assert, return, etc.
    }
}

int main()
{
    int input = 10;
    if (input == /*condition*/)
    {
       foo(input);
       foo(input);
       foo(input);
    }
}

Como você pode ver, o problema é que temos a mesma verificação duas vezes no programa, por isso, se as mudanças de condição, temos de modificá-lo em dois lugares, e as chances são de que nos esquecemos de um deles, causando um comportamento estranho. DRY não significa "não executar o mesmo código duas vezes", mas "não escreva o mesmo código duas vezes"

Deixe-me dizer primeiro que seguir cegamente um princípio é idealista e errado. Você precisa alcançar o que você quer alcançar (digamos, a segurança de sua aplicação), que geralmente é muito mais importante que a violação DRY. violações intencionais de princípios são na maioria das vezes necessário em boa programação.

Um exemplo: eu faço duplo controlo nas fases importantes (por exemplo LoginService - primeira entrada validar uma vez antes de chamar LoginService.Login, e depois novamente no interior), mas às vezes eu tendem a remover o exterior novamente mais tarde depois que eu fiz certeza de que tudo funciona 100%, geralmente usando testes de unidade. Depende.

Eu nunca ter trabalhado ao longo de cheques dupla condição embora. Esquecê-los inteiramente, por outro lado é geralmente várias magnitudes piores:)

Eu acho programação defensiva fica meio de um mau rap, uma vez que ele faz algumas coisas que são tipo de indesejável, que incluem código prolixo e, mais significativamente, papering sobre erros.

A maioria das pessoas parecem concordar que um programa deve falhar rapidamente quando encontra um erro, mas que os sistemas de missão crítica deve, de preferência nunca falham, e em vez de ir para grandes comprimentos para manter em curso em face de estados de erro.

Há um problema com essa afirmação, é claro, Como pode um programa, mesmo de missão crítica, continuar quando ele está em um estado inconsistente. Claro que não pode, realmente.

O que você quer é para o programa de tomar todas as medidas razoáveis ??para fazer a coisa certa, mesmo se há algo estranho acontecendo. Ao mesmo tempo, o programa deve reclamar, alto , cada vez que encontra um estado tão estranho. E no caso de ele encontrar um erro que é irrecuperável, deve geralmente evitar emitir uma instrução HLT, em vez disso, deve falhar normalmente, desligar seus sistemas de segurança ou ativar algum sistema de backup se estiver disponível.

No seu exemplo simplificado, sim, o segundo formato é provavelmente preferível.

No entanto, isso não se aplica realmente a programas maiores, mais complexas e mais realistas.

Porque você nunca sabe com antecedência onde e como "foo" será usado, você precisa foo protect através da validação de entrada. Se a entrada é validada pelo chamador (ex. "Principal" no seu exemplo), em seguida, "principais" necessidades de saber as regras de validação, e aplicá-las.

Na programação do mundo real, as regras de validação de entrada pode ser bastante complexo. Não é apropriado para fazer o chamador sabe todas as regras de validação e aplicá-los corretamente. Alguns chamador, em algum lugar, vai esquecer as regras de validação, ou fazer as coisas erradas. Então é melhor colocar a validação dentro "foo", mesmo se ele será chamado repetidamente. Este transfere a carga do chamador para o receptor, o que libera o chamador a pensar menos sobre os detalhes de "foo", e usá-lo mais como uma interface abstrata, confiável.

Se você realmente tem um padrão onde "foo" será chamado várias vezes com a mesma entrada, sugiro uma função wrapper que faz a validação uma vez, e uma versão desprotegida que lado os passos a validação:

void RepeatFoo(int bar, int repeatCount)
{
   /* Validate bar */
   if (bar != /*condition*/)
   {
       //code, assert, return, etc.
   }

   for(int i=0; i<repeatCount; ++i)
   {
       UnprotectedFoo(bar);
   }
}

void UnprotectedFoo(int bar)
{
    /* Note: no validation */

    /* do something with bar */
}

void Foo(int bar)
{
   /* Validate bar */
   /* either do the work, or call UnprotectedFoo */
}

Como Alex disse, isso depende da situação, por exemplo, eu entrada quase sempre validar em cada etapa do processo de login.

Em outros lugares, você não precisa de tudo isso.

No entanto, no exemplo que você deu, eu estou supondo que, no segundo exemplo, que você tem mais de uma entrada, porque caso contrário ele vai ser redundante chamando a mesma função 3 vezes para a mesma entrada que significa que você terá que escrever a condição de 3 vezes. Agora que é redundante.

Se a entrada deve sempre ser verificado apenas incluí-lo na função.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top