Si vous deviez choisir vos techniques préférées (intelligentes) pour le codage défensif, quelles seraient-elles? Bien que mes langages actuels soient Java et Objective-C (avec une formation en C ++), n'hésitez pas à répondre dans n'importe quel langage. L'accent serait mis ici sur des techniques défensives intelligentes autres que celles que plus de 70% d'entre nous connaissons déjà. Alors maintenant, il est temps de creuser profondément dans votre sac d'astuces.
En d'autres termes, essayez de penser à autre chose que cet exemple inintéressant :
if(5 == x)
au lieu deif(x == 5)
: pour éviter une affectation involontaire
Voici quelques exemples des meilleures pratiques de programmation défensives intéressantes (des exemples spécifiques à un langage sont en Java):
- Verrouillez vos variables jusqu'à ce que vous sachiez que vous devez les modifier
Autrement dit, vous pouvez déclarer toutes les variables final
jusqu'à ce que vous sachiez que vous devrez la modifier, auquel cas vous pouvez supprimer le fichier final
. Un fait généralement inconnu est que cela est également valable pour les paramètres de méthode:
public void foo(final int arg) { /* Stuff Here */ }
- Quand quelque chose de mauvais se produit, laissez une trace de preuves derrière
Il y a un certain nombre de choses que vous pouvez faire lorsque vous avez une exception: évidemment, la consigner et effectuer un nettoyage en serait quelques-unes. Mais vous pouvez aussi laisser une trace de preuves (par exemple, définir des variables sur des valeurs sentinelles comme "UNABLE TO LOAD FILE" ou 99999 serait utile dans le débogueur, au cas où vous catch
passeriez un bloc d' exception ).
- Quand il s'agit de cohérence: le diable est dans les détails
Soyez aussi cohérent avec les autres bibliothèques que vous utilisez. Par exemple, en Java, si vous créez une méthode qui extrait une plage de valeurs, rendez la limite inférieure inclusive et la limite supérieure exclusive . Cela le rendra cohérent avec des méthodes comme celles String.substring(start, end)
qui fonctionnent de la même manière. Vous trouverez que tous ces types de méthodes dans le Sun JDK se comportent de cette façon car il effectue diverses opérations, y compris l'itération d'éléments cohérents avec des tableaux, où les indices vont de zéro ( inclus ) à la longueur du tableau ( exclusif ).
Alors, quelles sont vos pratiques défensives préférées?
Mise à jour: si vous ne l'avez pas déjà fait, n'hésitez pas à intervenir. Je donne une chance à plus de réponses avant de choisir la réponse officielle .
la source
Réponses:
En C ++, j'ai aimé redéfinir le nouveau pour qu'il fournisse de la mémoire supplémentaire pour attraper les erreurs de clôture.
Actuellement, je préfère éviter la programmation défensive en faveur du développement piloté par les tests . Si vous détectez des erreurs rapidement et en externe, vous n'avez pas besoin de brouiller votre code avec des manœuvres défensives, votre code est DRY -er et vous vous retrouvez avec moins d'erreurs contre lesquelles vous devez vous défendre.
Comme l'écrit WikiKnowledge :
la source
SQL
Quand je dois supprimer des données, j'écris
Quand je l'exécuterai, je saurai si j'ai oublié ou bâclé la clause where. J'ai une sécurité. Si tout va bien, je surligne tout après les jetons de commentaire «-» et je l'exécute.
Edit: si je supprime beaucoup de données, j'utiliserai count (*) au lieu de juste *
la source
Allouez une quantité raisonnable de mémoire au démarrage de l'application - je pense que Steve McConnell a appelé cela un parachute de mémoire dans Code Complete.
Cela peut être utilisé au cas où quelque chose de grave se passe mal et que vous devez résilier.
L'allocation de cette mémoire à l'avance vous offre un filet de sécurité, car vous pouvez la libérer puis utiliser la mémoire disponible pour effectuer les opérations suivantes:
la source
Dans chaque instruction switch qui n'a pas de cas par défaut, j'ajoute un cas qui abandonne le programme avec un message d'erreur.
la source
Lorsque vous gérez les différents états d'une énumération (C #):
Ensuite, dans une routine ...
À un moment donné, j'ajouterai un autre type de compte à cette énumération. Et quand je le ferai, j'oublierai de corriger cette instruction de commutation. Donc, les
Debug.Fail
plantages horriblement (en mode débogage) pour attirer mon attention sur ce fait. Quand j'ajoute lecase AccountType.MyNewAccountType:
, l'horrible crash s'arrête ... jusqu'à ce que j'ajoute encore un autre type de compte et que j'oublie de mettre à jour les cas ici.(Oui, le polymorphisme est probablement meilleur ici, mais ce n'est qu'un exemple qui me vient à l'esprit.)
la source
Lors de l'impression de messages d'erreur avec une chaîne (en particulier une qui dépend de l'entrée de l'utilisateur), j'utilise toujours des guillemets simples
''
. Par exemple:Ce manque de guillemets
%s
est vraiment mauvais, car disons que le nom de fichier est une chaîne vide ou juste un espace ou quelque chose. Le message imprimé serait bien sûr:Alors, il vaut toujours mieux faire:
Alors au moins l'utilisateur voit ceci:
Je trouve que cela fait une énorme différence en termes de qualité des rapports de bogues soumis par les utilisateurs finaux. S'il y a un message d'erreur drôle comme celui-ci au lieu de quelque chose de générique, alors ils sont beaucoup plus susceptibles de le copier / coller au lieu d'écrire simplement "cela n'ouvrirait pas mes fichiers".
la source
Sécurité SQL
Avant d'écrire un SQL qui modifiera les données, j'emballe le tout dans une transaction annulée:
Cela vous empêche d'exécuter définitivement une mauvaise suppression / mise à jour. Et, vous pouvez exécuter le tout et vérifier le nombre d'enregistrements raisonnables ou ajouter des
SELECT
instructions entre votre SQL et leROLLBACK TRANSACTION
pour vous assurer que tout semble correct.Lorsque vous êtes complètement sûr qu'il fait ce que vous attendiez, changez le
ROLLBACK
enCOMMIT
et exécutez pour de vrai.la source
Pour toutes les langues:
Réduisez au minimum la portée des variables . Évitez les variables qui viennent d'être fournies pour les transporter dans l'instruction suivante. Les variables qui n'existent pas sont des variables que vous n'avez pas besoin de comprendre et dont vous ne pouvez pas être tenu responsable. Utilisez Lambdas chaque fois que possible pour la même raison.
la source
En cas de doute, bombardez l'application!
Vérifiez chaque paramètre au début de chaque méthode (que ce soit le codage explicite vous-même ou l'utilisation de la programmation basée sur un contrat n'a pas d'importance ici) et bombardez avec l'exception correcte et / ou le message d'erreur significatif si une condition préalable au code est pas rencontré.
Nous connaissons tous ces conditions préalables implicites lorsque nous écrivons le code , mais si elles ne sont pas explicitement vérifiées, nous créons des labyrinthes pour nous-mêmes lorsque quelque chose ne va pas plus tard et des piles de dizaines d'appels de méthodes séparent l'occurrence du symptôme et l'emplacement réel où une condition préalable n'est pas remplie (= où se trouve réellement le problème / bogue).
la source
param.NotNull("param")
au lieu deif ( param == null ) throw new ArgumentNullException("param");
En Java, en particulier avec les collections, utilisez l'API, donc si votre méthode renvoie le type List (par exemple), essayez ce qui suit:
Ne laissez rien échapper à votre classe dont vous n'avez pas besoin!
la source
en Perl, tout le monde fait
J'aime
Cela provoque la mort du code pour tout avertissement du compilateur / runtime. Ceci est surtout utile pour intercepter les chaînes non initialisées.
la source
C #:
la source
En c # vérification de string.IsNullOrEmpty avant de faire des opérations sur la chaîne comme length, indexOf, mid etc
[Modifier]
Maintenant, je peux également faire ce qui suit dans .NET 4.0, qui vérifie en outre si la valeur n'est que des espaces
la source
if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */
.En Java et C #, donnez à chaque thread un nom significatif. Cela inclut les threads de pool de threads. Cela rend les vidages de pile beaucoup plus significatifs. Il faut un peu plus d'efforts pour donner un nom significatif aux threads du pool de threads, mais si un pool de threads a un problème dans une application de longue durée, je peux provoquer un vidage de pile (vous connaissez SendSignal.exe , n'est-ce pas? ), saisissez les journaux, et sans avoir à interrompre un système en cours d'exécution, je peux dire quels threads sont ... peu importe. Blocage, fuite, croissance, quel que soit le problème.
la source
Avec VB.NET, activez Option Explicit et Option Strict par défaut pour l'ensemble de Visual Studio.
la source
C ++
puis remplacez tout votre ' appels delete pPtr ' et ' delete [] pPtr ' par SAFE_DELETE (pPtr) et SAFE_DELETE_ARRAY (pPtr)
Maintenant, par erreur, si vous utilisez le pointeur «pPtr» après l'avoir supprimé, vous obtiendrez une erreur de «violation d'accès». C'est beaucoup plus facile à corriger que les corruptions de mémoire aléatoires.
la source
delete
. L'utilisationnew
est correcte, tant que le nouvel objet est détenu par un pointeur intelligent.Avec Java, il peut être pratique d'utiliser le mot-clé assert, même si vous exécutez du code de production avec les assertions désactivées:
Même avec les assertions désactivées, au moins le code documente le fait que param devrait être défini quelque part. Notez qu'il s'agit d'une fonction d'assistance privée et non d'un membre d'une API publique. Cette méthode ne peut être appelée que par vous, vous pouvez donc faire certaines hypothèses sur la façon dont elle sera utilisée. Pour les méthodes publiques, il est probablement préférable de lancer une véritable exception pour une entrée non valide.
la source
Je n'ai pas trouvé le
readonly
mot - clé avant de trouver ReSharper, mais je l'utilise maintenant instinctivement, en particulier pour les classes de service.la source
En Java, quand quelque chose se passe et que je ne sais pas pourquoi, j'utiliserai parfois Log4J comme ceci:
de cette façon, j'obtiens une trace de pile qui me montre comment je suis entré dans la situation inattendue, disons un verrou qui n'a jamais été déverrouillé, quelque chose de nul qui ne peut pas être nul, et ainsi de suite. Évidemment, si une vraie exception est lancée, je n'ai pas besoin de le faire. C'est à ce moment que j'ai besoin de voir ce qui se passe dans le code de production sans vraiment déranger rien d'autre. Je ne veux pas lancer d'exception et je n'en ai pas attrapé. Je veux juste une trace de pile enregistrée avec un message approprié pour me signaler ce qui se passe.
la source
Si vous utilisez Visual C ++, utilisez le mot clé override chaque fois que vous la méthode d'une classe de base. De cette façon, si quelqu'un change la signature de la classe de base, cela générera une erreur de compilation plutôt que la mauvaise méthode appelée silencieusement. Cela m'aurait sauvé plusieurs fois s'il avait existé plus tôt.
Exemple:
la source
J'ai appris en Java à ne presque jamais attendre indéfiniment le déverrouillage d'un verrou, à moins que je ne m'attends vraiment à ce que cela prenne un temps indéfiniment long. Si de manière réaliste, la serrure doit se déverrouiller en quelques secondes, alors je n'attendrai qu'un certain temps. Si le verrou ne se déverrouille pas, je me plains et je vide la pile dans les journaux, et en fonction de ce qui est le mieux pour la stabilité du système, continuez comme si le verrou était déverrouillé ou continuez comme si le verrou ne s'était jamais déverrouillé.
Cela a aidé à isoler quelques conditions de course et de pseudo-impasse qui étaient mystérieuses avant que je ne commence à le faire.
la source
C #
sealed
beaucoup pour les classes pour éviter d'introduire des dépendances là où je ne les voulais pas. Autoriser l'héritage doit être fait explicitement et non par accident.la source
Lorsque vous émettez un message d'erreur, essayez au moins de fournir les mêmes informations que le programme avait lorsqu'il a pris la décision de générer une erreur.
"Autorisation refusée" vous indique qu'il y a eu un problème d'autorisation, mais vous ne savez pas pourquoi ni où le problème s'est produit. "Impossible d'écrire le journal des transactions / mon / fichier: système de fichiers en lecture seule" vous permet au moins de savoir sur quelle base la décision a été prise, même si elle est fausse - surtout si elle est fausse: nom de fichier erroné? mal ouvert? autre erreur inattendue? - et vous permet de savoir où vous étiez lorsque vous avez eu le problème.
la source
En C #, utilisez le
as
mot clé pour effectuer un cast.lèvera une exception si obj n'est pas une chaîne
laissera a comme nul si obj n'est pas une chaîne
Vous devez toujours prendre en compte null, mais c'est généralement plus simple que de rechercher des exceptions de cast. Parfois, vous voulez un comportement de type "cast or exploser", auquel cas
(string)obj
syntaxe est préférée.Dans mon propre code, je trouve que j'utilise la
as
syntaxe environ 75% du temps et la(cast)
syntaxe environ 25%.la source
is/instanceof
viennent à l'esprit.Soyez prêt pour toute entrée et toute entrée inattendue que vous obtenez, sauvegardez dans les journaux. (Dans la limite du raisonnable. Si vous lisez les mots de passe de l'utilisateur, ne les videz pas dans les journaux! Et ne consignez pas des milliers de ces types de messages dans les journaux par seconde. Raison du contenu, de la probabilité et de la fréquence avant de les enregistrer .)
Je ne parle pas seulement de la validation des entrées utilisateur. Par exemple, si vous lisez des requêtes HTTP qui devraient contenir du XML, préparez-vous à d'autres formats de données. J'ai été surpris de voir des réponses HTML où je n'attendais que du XML - jusqu'à ce que je regarde et que je voie que ma demande passait par un proxy transparent dont je n'étais pas au courant et que le client affirmait ignorer - et le proxy a expiré en essayant de terminer le demande. Ainsi, le proxy a renvoyé une page d'erreur HTML à mon client, déroutant le client qui n'attendait que des données XML.
Ainsi, même lorsque vous pensez contrôler les deux extrémités du fil, vous pouvez obtenir des formats de données inattendus sans qu'aucune méchanceté ne soit impliquée. Soyez prêt, codez de manière défensive et fournissez une sortie de diagnostic en cas d'entrée inattendue.
la source
J'essaye d'utiliser l'approche Design by Contract. Il peut être émulé à l'exécution par n'importe quelle langue. Chaque langage prend en charge «assert», mais il est facile et pratique d'écrire une meilleure implémentation qui vous permet de gérer l'erreur de manière plus utile.
Dans le top 25 des erreurs de programmation les plus dangereuses la «validation d'entrée incorrecte» est l'erreur la plus dangereuse dans la section «Interaction non sécurisée entre les composants».
L'ajout d' assertions de précondition au début des méthodes est un bon moyen de s'assurer que les paramètres sont cohérents. A la fin des méthodes j'écris des postconditions , qui vérifient que la sortie est ce qui est censé être.
Afin d'implémenter des invariants , j'écris une méthode dans n'importe quelle classe qui vérifie la "cohérence de classe", qui devrait être appelée automatiquement par la macro de précondition et de postcondition.
J'évalue la bibliothèque de contrats de code .
la source
Java
L'API java n'a aucun concept d'objets immuables, ce qui est mauvais! Final peut vous aider dans ce cas. Marquer chaque classe immuable avec final et préparer la classe en conséquence .
Parfois, il est utile d'utiliser final sur des variables locales pour s'assurer qu'elles ne changent jamais leur valeur. J'ai trouvé cela utile dans les constructions de boucle laides, mais nécessaires. Il est juste trop facile de réutiliser accidentellement une variable même si elle est en passe de devenir une constante.
Utiliser la copie de défense dans vos getters. Sauf si vous retournez un type primitif ou un objet immuable, assurez-vous de copier l'objet pour ne pas violer l'encapsulation.
N'utilisez jamais de clone, utilisez un constructeur de copie .
Apprenez le contrat entre equals et hashCode. Ceci est violé si souvent. Le problème est que cela n'affecte pas votre code dans 99% des cas. Les gens écrasent les égaux, mais ne se soucient pas de hashCode. Il y a des instances dans lesquelles votre code peut se casser ou se comporter de manière étrange, par exemple utiliser des objets mutables comme clés dans une carte.
la source
J'ai oublié d'écrire
echo
en PHP une fois de trop:Cela me prendrait une éternité pour essayer de comprendre pourquoi -> baz () ne retournait rien alors qu'en fait je n'en faisais pas écho! : -S J'ai donc créé une
EchoMe
classe qui pourrait être enroulée autour de n'importe quelle valeur qui devrait être renvoyée:Et puis pour l'environnement de développement, j'ai utilisé un EchoMe pour envelopper les choses qui devraient être imprimées:
En utilisant cette technique, le premier exemple manquant le
echo
ferait maintenant lever une exception ...la source
AnObject.ToString
au lieu deWriteln(AnObject.ToString)
?C ++
Lorsque je tape new, je dois immédiatement taper delete. Surtout pour les tableaux.
C #
Vérifiez la valeur null avant d'accéder aux propriétés, en particulier lors de l'utilisation du modèle Mediator. Les objets sont passés (et doivent ensuite être convertis en utilisant as, comme cela a déjà été noté), puis vérifiés par rapport à null. Même si vous pensez qu'il ne sera pas nul, vérifiez quand même. J'ai été surpris.
la source
Utilisez un système de journalisation qui permet des ajustements dynamiques du niveau de journalisation au moment de l'exécution. Souvent, si vous devez arrêter un programme pour activer la journalisation, vous perdrez l'état rare dans lequel le bogue s'est produit. Vous devez pouvoir activer plus d'informations de journalisation sans arrêter le processus.
De plus, 'strace -p [pid]' sous Linux montrera que vous voulez que les appels système qu'un processus (ou un thread Linux) effectue. Cela peut paraître étrange au début, mais une fois que vous vous serez habitué à ce que les appels système sont généralement effectués par les appels de la libc, vous trouverez cela inestimable pour le diagnostic sur le terrain.
la source