Question

Disclaimer: Je suis en train d'apprendre à layperson programme. Jamais fait partie d'un projet, ni quoi que ce soit écrit plus de 500 lignes ~.

Ma question est: programmation défensive ne viole le Do not Repeat Yourself principe? Si l'on suppose ma définition de la programmation défensive est correcte (dont la fonction est d'appeler la validation d'entrée au lieu de l'inverse), ce ne serait pas préjudiciable à votre code?

Par exemple, est-ce mal:

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?
}   

par rapport à ceci:

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

Encore une fois, en tant que profane, je ne sais pas comment beaucoup de déclarations logiques simples contre vous dans la mesure du rendement va, mais sûrement la programmation défensive est pas bon pour le programme ou l'âme.

Était-ce utile?

La solution

Tout se résume à la contrat l'interface fournit. Il existe deux scénarios différents pour cela: les entrées et sorties

.

Entrées - et par que je essentiellement les paramètres moyens aux fonctions -. Doivent être vérifiées par la mise en œuvre en règle générale

Sorties - être de retour - résultats. Devraient être essentiellement confiance par l'appelant, au moins à mon avis

Tout cela est tempéré par cette question: ce qui se passe si l'une des parties rompt le contrat? Par exemple, permet de dire que vous aviez une interface:

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

et que le contrat précise qu'une chaîne vide ne sera jamais retourné (ce sera une chaîne vide au pire), il est sûr de faire ceci:

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

Pourquoi? Eh bien, si vous avez tort et retourne une valeur nulle callee alors le programme se bloque. C'est fait OK . Si un objet viole son contrat alors en général le résultat devrait être catastrophique.

Le risque que vous faites face à être trop sur la défensive est que vous écrivez beaucoup de code inutile (qui peut introduire plus de bugs) ou que vous pourrait en fait masquer un problème sérieux en avalant une exception que vous devriez vraiment pas.

Des circonstances de cours peuvent changer.

Autres conseils

Violer le principe DRY ressemble que:

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);
    }
}

comme vous pouvez le voir, le problème est que nous avons le même chèque deux fois dans le programme, donc si les changements d'état, il faut le modifier à deux endroits, et les chances sont que nous oublions l'un d'entre eux, ce qui provoque un comportement étrange. SEC ne signifie pas « ne pas exécuter le même code deux fois », mais « ne pas écrire le même code deux fois »

Laissez-moi tout d'abord que l'état de suivre aveuglément un principe est idéaliste et MAUVAIS. Vous devez obtenir ce que vous voulez réaliser (par exemple, la sécurité de votre application), qui est habituellement beaucoup plus important que la violation SEC. violations des principes obstinés sont le plus souvent nécessaire dans une bonne programmation.

Un exemple: je doubles contrôles à des étapes importantes (par exemple LoginService - d'abord valider une fois entrée avant d'appeler LoginService.Login, puis à nouveau à l'intérieur), mais parfois j'ai tendance à enlever l'externe plus tard après que je me suis assuré que tout fonctionne à 100%, en utilisant généralement des tests unitaires. Cela dépend.

Je ne l'avais jamais s'énerver sur le contrôle à double condition cependant. les OUBLIER entièrement d'autre part est généralement plusieurs grandeurs pire:)

Je pense que la programmation défensive obtient une sorte de mauvaise réputation, car il fait des choses qui sont un peu indésirables, qui comprennent le code verbeux, et plus important encore, dissimulant des erreurs.

La plupart des gens semblent convenir qu'un programme devrait échouer rapidement lorsqu'il rencontre une erreur, mais que systèmes critiques doivent de préférence ne manquent jamais, et plutôt aller très loin pour continuer à avancer face à des états d'erreur.

Il y a un problème avec cette déclaration, bien sûr, comment un programme, même mission critique, continuer quand il est dans un état incohérent. Bien sûr, il ne peut pas, vraiment.

Qu'est-ce que vous voulez pour le programme de prendre toutes les mesures raisonnables pour faire la bonne chose, même s'il y a quelque chose d'étrange se passe. En même temps, le programme devrait se plaindre, fort , à chaque fois qu'il rencontre un tel état bizarre. Et dans le cas où il rencontre une erreur qui est irrécupérable, il faut généralement éviter l'émission d'une instruction HLT, il devrait plutôt échouer avec grâce, d'arrêter ses systèmes en toute sécurité ou d'activer un système de sauvegarde si l'on est disponible.

Dans votre exemple simplifié, oui, le second format est sans doute préférable.

Toutefois, cela ne s'applique pas vraiment plus grands, des programmes plus complexes et plus réalistes.

Parce que vous ne savez jamais à l'avance où ou comment « foo » sera utilisé, vous devez protéger foo en validant l'entrée. Si l'entrée est validée par l'appelant (par exemple. « Principal » dans votre exemple) puis « principal » a besoin de connaître les règles de validation et de les appliquer.

Dans la programmation du monde réel, les règles de validation d'entrée peuvent être assez complexes. Il ne convient pas de faire l'appelant connaître toutes les règles de validation et de les appliquer correctement. Certains appelant, quelque part, va oublier les règles de validation, ou faire les mauvais. Ainsi son mieux pour mettre la validation à l'intérieur « foo », même si elle sera appelé à plusieurs reprises. Cela déplace la charge de l'appelant à l'appelé, ce qui libère l'appelant de penser moins sur les détails de « foo », et de l'utiliser plus comme une interface abstraite, fiable.

Si vous avez vraiment un modèle où « foo » sera appelé plusieurs fois avec la même entrée, je suggère une fonction d'emballage qui fait la validation une fois, et une version non protégée de ce côté-étapes de la validation:

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 */
}

Comme Alex a dit, cela dépend de la situation, par exemple, je valide presque toujours entrée à chaque étape du processus de connexion.

Dans d'autres endroits, vous n'avez pas besoin de tout ça.

Cependant, dans l'exemple que vous avez donné, je suppose, dans le second exemple, que vous avez plus d'une entrée, parce que sinon il sera inutile d'appeler la même fonction 3 fois pour la même entrée qui signifie que vous « ll doivent écrire la condition 3 fois. Maintenant redondante.

Si l'entrée doit toujours être vérifiée juste l'inclure dans la fonction.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top