Question

  

Possible en double:    de programmation défensive

Nous avons eu une grande discussion ce matin sur le sujet de la programmation défensive. Nous avons eu un examen de code où un pointeur a été adopté et n'a pas été vérifié si elle était valide.

Certaines personnes ont estimé que seul un chèque de pointeur nul était nécessaire. J'ai demandé si elle pouvait être contrôlée à un niveau supérieur, plutôt que chaque méthode est passé à travers, et que la vérification de la valeur NULL était un contrôle très limité si l'objet à l'autre extrémité du point ne répondait pas à certaines exigences.

Je comprends et accepte qu'un chèque nul me vaut mieux que rien, mais il se sent à cette vérification que pour nul donne un faux sentiment de sécurité, car il est limité dans sa portée. Si vous voulez vous assurer que le pointeur est utilisable, vérifiez que le plus nulle.

Quelles sont vos expériences sur le sujet? Comment écrivez-vous les défenses pour votre code pour les paramètres qui sont passés aux méthodes subordonnées?

Était-ce utile?

La solution

Dans le code complet 2, dans le chapitre sur la gestion des erreurs, on m'a présenté l'idée de barricades. Essentiellement, une barricade est un code qui valide rigoureusement toutes les entrées à venir en elle. Code à l'intérieur de la barricade peut supposer que toute entrée non valide a déjà été traitée et que les entrées reçues sont bonnes. A l'intérieur de la barricade, le code ne doit se soucier des données sur invalides qui lui sont transmises par un autre code dans la barricade. Affirmant les conditions et les tests unitaires judicieux peut augmenter votre confiance dans le code barricadé. De cette façon, vous programme très défensive à la barricade, mais moins à l'intérieur de la barricade. Une autre façon de penser est que, à la barricade, vous gérez toujours correctement les erreurs, et à l'intérieur de la barricade que vous prétendez simplement les conditions dans votre version de débogage.

En ce qui concerne l'utilisation de pointeurs premières va, généralement le meilleur que vous pouvez faire est affirmer que le pointeur est non nul. Si vous savez ce qui est censé être dans cette mémoire, vous pouvez vous assurer que le contenu sont cohérents d'une certaine façon. Cela pose la question de savoir pourquoi cette mémoire n'est pas enveloppé dans un objet qui peut vérifier la cohérence est elle-même.

Alors, pourquoi vous utilisez un pointeur brut dans ce cas? Serait-il préférable d'utiliser une référence ou un pointeur intelligent? Est-ce que le pointeur contient des données numériques, et si oui, serait-il préférable de l'envelopper dans un objet qui a géré le cycle de vie de ce pointeur?

Répondre à ces questions peut vous aider à trouver un moyen d'être plus défensif dans la mesure où vous vous retrouverez avec un design qui est plus facile à défendre.

Autres conseils

La meilleure façon d'être sur la défensive est de ne pas vérifier les pointeurs NULL lors de l'exécution, mais évitez d'utiliser des pointeurs qui peuvent être nuls pour commencer

Si l'objet passe dans ne doit pas être nulle, utilisez une référence! Ou passer par la valeur! Ou utiliser un pointeur intelligent de quelque sorte.

La meilleure façon de faire de la programmation défensive est d'attraper vos erreurs à la compilation. Si elle est considérée comme une erreur pour un objet à nul ou d'un point d'ordures, alors vous devriez faire ces choses erreurs de compilation.

En fin de compte, vous avez aucun moyen de savoir si un pointeur vers un objet valide. Donc, plutôt que de vérifier pour un cas d'angle spécifique (ce qui est beaucoup moins fréquent que ceux qui sont vraiment dangereux, pointeurs pointant vers invalides objets), font l'erreur impossible à l'aide d'un type de données qui garantit la validité.

Je ne peux pas penser à une autre langue ordinaire qui vous permet d'attraper autant d'erreurs à la compilation tout comme C ++. utiliser cette capacité.

Il n'y a aucun moyen de vérifier si un pointeur est valide.

Dans tous les sérieux, cela dépend de combien de bugs que vous aimeriez avoir infligé à vous.

Vérification d'un pointeur NULL est certainement quelque chose que je considérerais nécessaire mais non suffisante. Il y a beaucoup d'autres principes solides, vous pouvez utiliser à partir de points d'entrée de votre code (par exemple, la validation d'entrée = fait ce point de pointeur vers quelque chose d'utile) et les points de sortie (par exemple, vous avez pensé que le pointeur a quelque chose utile, mais il est arrivé à la cause votre code pour lancer une exception).

En bref, si vous supposez que tout le monde appelle votre code va faire de leur mieux pour ruiner votre vie, vous trouverez probablement beaucoup des pires coupables.

EDIT pour plus de clarté: d'autres réponses parlent de tests unitaires. Je crois fermement que le code de test est parfois plus de valeur que le code qu'il teste (selon la personne qui mesure la valeur). Cela dit, je pense aussi que les tests unités sont aussi nécessaire mais non suffisante pour le codage défensif.

Exemple concret: envisager une 3ème méthode de recherche du parti qui est documenté pour retourner une collection de valeurs qui correspondent à votre demande. Malheureusement, ce qui était pas clair dans la documentation de cette méthode est que le développeur original a décidé qu'il serait préférable de retourner une valeur nulle et non une collection vide si rien ne correspond à votre demande.

Alors maintenant, vous appelez votre défensive et bien pensée méthode éprouvée unité (qui manque malheureusement un contrôle de pointeur NULL interne) et boum! NullPointerException que, sans un contrôle interne, vous avez aucun moyen de traiter:

defensiveMethod(thirdPartySearch("Nothing matches me")); 
// You just passed a null to your own code.

Je suis un grand fan de la « laisser planter » l'école de design. (Avertissement: Je ne travaille pas sur l'équipement médical, l'avionique ou des logiciels liés à la puissance nucléaire.) Si votre programme explose, vous lancez le débogueur et comprendre pourquoi. En revanche, si votre programme continue à fonctionner après les paramètres illégaux ont été détectés, au moment où il tombe en panne, vous aurez probablement aucune idée de ce qui a mal tourné.

Bon code se compose de nombreuses petites fonctions / méthodes, et l'ajout d'une douzaine de lignes de paramètres de vérification à chacun de ces extraits de code, il est plus difficile à lire et plus difficile à maintenir. Keep it simple.

Je peux être un peu extrême, mais je n'aime pas la programmation défensive, je pense qu'il est la paresse qui a introduit le principe.

Pour cet exemple, il n'y a pas de sens dans affirmer que le pointeur est non nul. Si vous voulez un pointeur NULL, il n'y a pas de meilleure façon de faire respecter réellement (et documenter clairement en même temps) que d'utiliser à la place une référence. Et il est la documentation qui sera effectivement appliquée par le compilateur et ne coûte pas un ziltch lors de l'exécution !!

En général, je tendance à ne pas utiliser les types « bruts » directement. Illustrons:

void myFunction(std::string const& foo, std::string const& bar);

Quelles sont les valeurs possibles de foo et bar? Eh bien, c'est à peu près limité que par ce qu'un std::string peut contenir ... ce qui est assez vague.

Par contre:

void myFunction(Foo const& foo, Bar const& bar);

est beaucoup mieux!

  • si les gens inverse par erreur l'ordre des arguments, il est détecté par le compilateur
  • chaque classe est seul responsable de vérifier que la valeur est juste, les utilisateurs ne sont pas burdenned.

J'ai tendance à favoriser typage fort. Si j'ai une entrée qui devrait être composé uniquement de caractères alphabétiques et jusqu'à 12 caractères, je préfère créer une petite classe envelopper un std::string, avec une méthode simple de validate utilisé en interne pour vérifier les affectations, et passer cette classe autour de la place . De cette façon, je sais que si je teste la routine de validation une fois, je n'ai pas à vous soucier réellement sur tous les chemins par lesquels cette valeur peut me faire> il sera validé quand il me parvient.

Bien sûr, cela ne me pas que le code ne doit pas être testé. Il est juste que je suis favorable à l'encapsulation forte, et la validation d'une entrée fait partie d'encapsulation de connaissances à mon avis.

Et comme aucune règle ne peut venir sans exception ... interface exposée est nécessairement pléthorique avec le code de validation, parce que vous ne savez jamais ce qui pourrait venir sur vous. Cependant, avec des objets d'auto-validation de votre nomenclature, il est tout à fait transparente en général.

« Les tests unitaires vérification du code fait ce qu'il doit faire »> « code de production essayant de vérifier son fait pas ce que son pas censé le faire ».

Je ne même vérifier nulle moi-même, à moins que sa part d'une API publiée.

Cela dépend beaucoup; est la méthode en question par le code déjà appelé externe à votre groupe, ou est-ce une méthode interne?

Pour les méthodes internes, vous pouvez tester assez pour faire un point discutable, et si vous le code du bâtiment où l'objectif est plus performant possible, vous voudrez peut-être de ne pas passer le temps sur les entrées de vérifier que vous êtes assez sûr sacrément sont à droite.

Pour les méthodes visibles de l'extérieur - si vous avez - vous devriez toujours vérifier vos entrées. Toujours.

Du point de vue du débogage, il est plus important que votre code est fail-rapide. Le plus tôt le code échoue, le plus facile de trouver le point d'échec.

Pour les méthodes internes, nous nous en tenons habituellement pour ce genre affirme de chèques. Cela ne se erreurs ramassées dans les tests unitaires (vous avez une bonne couverture de test, non?) Ou au moins dans les tests d'intégration qui sont en cours d'exécution avec des affirmations sur.

la vérification de pointeur NULL seulement la moitié de l'histoire , vous devez également attribuer une valeur nulle à chaque pointeur non affecté .
API la plus responsable fera de même.
pour vérifier un pointeur NULL est très pas cher dans les cycles de CPU, ayant une application s'écraser une fois sa livrée peut vous coûter et votre entreprise en argent et réputation.

vous pouvez ignorer les vérifications de pointeur nul si le code est dans une interface privée, vous avez le contrôle complet et / ou vous vérifiez null en exécutant un test unitaire ou un test de version de débogage (par exemple assert)

Il y a quelques petites choses au travail dans cette question que je voudrais aborder:

  1. directives de codage devraient spécifier que vous traitez avec soit une référence ou une valeur directement au lieu d'utiliser des pointeurs. Par définition, les pointeurs sont des types de valeur qui détiennent juste une adresse en mémoire - (. Plage de mémoire adressable, la plate-forme, etc.) la validité d'un pointeur est plate-forme spécifique et signifie beaucoup de choses
  2. Si vous vous trouvez avoir besoin jamais un pointeur pour une raison quelconque (comme pour les objets générés dynamiquement et polymorphes) envisager d'utiliser des pointeurs intelligents. pointeurs intelligents vous donnent de nombreux avantages avec la sémantique des pointeurs « normaux ».
  3. Si un type, par exemple, a un état alors le type « invalide » lui-même doit prévoir cela. Plus précisément, vous pouvez mettre en œuvre le modèle de NullObject qui spécifie comment un objet « mal défini » ou « non initialisé » se comporte (peut-être en lançant des exceptions ou par l'absence d'opération de fonctions membres).

Vous pouvez créer un pointeur intelligent qui fait la valeur par défaut NullObject qui ressemble à ceci:

template <class Type, class NullTypeDefault>
struct possibly_null_ptr {
  possibly_null_ptr() : p(new NullTypeDefault) {}
  possibly_null_ptr(Type* p_) : p(p_) {}
  Type * operator->() { return p.get(); }
  ~possibly_null_ptr() {}
  private:
    shared_ptr<Type> p;
    friend template<class T, class N> Type & operator*(possibly_null_ptr<T,N>&);
};

template <class Type, class NullTypeDefault>
Type & operator*(possibly_null_ptr<Type,NullTypeDefault> & p) {
  return *p.p;
}

Ensuite, utilisez le modèle de possibly_null_ptr<> dans les cas où vous les pointeurs éventuellement nulles à des types qui ont un défaut dérivé « comportement null ». Cela rend explicite dans la conception qu'il ya un comportement acceptable pour « objets nuls », et cela rend votre pratique défensive documentée dans le code - et plus concret -. Qu'une ligne directrice générale ou pratique

pointeur ne doit être utilisé si vous avez besoin de faire quelque chose avec le pointeur. Tels que l'arithmétique de pointeur pour traverser une structure de données. Ensuite, si possible, qui devrait être inscrite dans une classe.

Si le pointeur est passé dans la fonction de faire quelque chose avec l'objet auquel il pointe, puis passer dans une référence à la place.

Une méthode pour la programmation défensive est d'affirmer presque tout ce que vous pouvez. Au début du projet, il est ennuyeux, mais plus tard, il est un bon complément aux tests unitaires.

Un certain nombre d'adresse de réponse à la question de savoir comment écrire les défenses dans votre code, mais pas beaucoup a été dit sur « comment défensive si vous êtes? ». C'est quelque chose que vous devez évaluer en fonction de la criticité de vos composants logiciels.

Nous faisons des logiciels de vol et les impacts d'une gamme d'erreur de logiciel d'un ennui mineur à la perte d'avions / équipage. Nous classons des logiciels différents en fonction de leurs impacts négatifs potentiels qui affecte les normes de codage, tests, etc. Vous devez évaluer la façon dont votre logiciel sera utilisé et les effets des erreurs et définir quel niveau vous voulez de défensivité (et peut se permettre). norme DO-178B appelle cette "conception au niveau d'assurance".

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