Meilleures pratiques de programmation défensive (intelligentes) favorites [fermé]

148

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 de if(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 finaljusqu'à 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 catchpasseriez 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 .

Ryan Delucchi
la source
J'aimerais représenter ici les 30% ignorants d'entre nous qui ne connaissent peut-être pas les techniques simples . Quelqu'un a-t-il un lien avec les techniques «évidentes» que tout le monde devrait connaître comme base?
elliot42 le
En relation: stackoverflow.com/questions/163026/…
Bill the Lizard
Pourquoi choisiriez-vous une réponse officielle? Cela semble totalement onclever.
bzlm
Eh bien, quand il s'agit de programmation, même l'intelligence devrait passer au second plan en l'honneur de "la règle du moindre étonnement". Pour moi, rompre avec l'esprit de Stack Overflow consistant à marquer la meilleure réponse violerait le protocole de Stack Overflow (donc enfreindre ladite règle). A part ça: j'aime la fermeture :-)
Ryan Delucchi

Réponses:

103

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 :

Évitez la programmation défensive, échouez rapidement.

Par programmation défensive, j'entends l'habitude d'écrire du code qui tente de compenser un échec dans les données, d'écrire du code qui suppose que les appelants pourraient fournir des données qui ne sont pas conformes au contrat entre l'appelant et le sous-programme et que le sous-programme doit en quelque sorte faire face avec ça.

Joe Soul-bringer
la source
8
La programmation défensive tente de faire face aux conditions illégales introduites par d'autres parties d'un programme. Gérer une entrée utilisateur incorrecte est une chose complètement différente.
Joe Soul-bringer le
5
Euh ... notez que cette définition de la programmation défensive n'est même pas proche de la définition implicitement utilisée dans la question.
Sol
15
N'évitez jamais la programmation défensive. Il ne s'agit pas de «compenser» les défaillances des données, mais de se protéger des données malveillantes conçues pour faire faire à votre code des choses qu'il n'est pas censé faire. Voir Buffer Overflow, SQL Injection. Rien n'échoue plus vite qu'une page Web sous XSS mais ce n'est pas joli
Jorge Córdoba
6
Je dirais que les procédures «échouent rapidement» sont une forme de programmation défensive.
Ryan Delucchi le
6
@ryan a tout à fait raison, l'échec rapide est un bon concept défensif. Si l'état dans lequel vous vous trouvez n'est pas possible, n'essayez pas de continuer à boiter, échouez vite et fort! Très important si vous êtes axé sur les méta-données. La programmation défensive ne vérifie pas seulement vos paramètres ...
Bill K
75

SQL

Quand je dois supprimer des données, j'écris

select *    
--delete    
From mytable    
Where ...

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 *

John MacIntyre
la source
J'ai écrit ceci sur mon iPhone, où je ne peux pas sélectionner de texte. Si quelqu'un pouvait marquer mon code comme code, ce serait vraiment apprécié. Merci.
John MacIntyre
6
Je viens de l'envelopper dans une transaction pour que je puisse revenir en arrière si je me
trompe
36
COMMENCER LA TRANSACTION | TRANSACTION ROLLBACK
Dalin Seivewright
3
+1 Oui, je suppose que cela pourrait être remplacé par une transaction, mais j'aime la simplicité de le faire de cette façon.
Ryan Delucchi
3
Utiliser une transaction est mieux; cela signifie que vous pouvez l'exécuter des dizaines de fois et voir les effets réels , jusqu'à ce que vous soyez certain que vous avez bien fait les choses et que vous pouvez vous engager.
Ryan Lundy
48

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:

  • Sauvegardez toutes les données persistantes
  • Fermez tous les fichiers appropriés
  • Écrire des messages d'erreur dans un fichier journal
  • Présenter une erreur significative à l'utilisateur
LéopardPeauPillboxChapeau
la source
J'ai vu cela appelé un fonds pour les jours de pluie.
plinthe
42

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.

#define INVALID_SWITCH_VALUE 0

switch (x) {
case 1:
  // ...
  break;
case 2:
  // ...
  break;
case 3:
  // ...
  break;
default:
  assert(INVALID_SWITCH_VALUE);
}
Diomidis Spinellis
la source
2
Ou un jet dans une langue moderne aux crocs.
Tom Hawtin - tackline
2
Assert a l'avantage que ses effets peuvent être désactivés globalement au moment de la compilation. Pourtant, dans certaines situations, le lancer peut être plus approprié, si votre langue le prend en charge.
Diomidis Spinellis le
2
Assert a un autre avantage qui le rend utile pour le code de production: quand quelque chose ne va pas, il vous indique exactement ce qui a échoué et de quelle ligne du programme l'erreur provient. Un rapport de bogue comme celui-là est merveilleux!
Mason Wheeler
2
@Diomidis: Un autre angle est: Assert a l' inconvénient que ses effets peuvent être globalement désactivés au moment de la compilation.
Déçu le
1
lancer est en effet mieux. Et puis vous vous assurez que votre couverture de test est adéquate.
Dominic Cronin
41

Lorsque vous gérez les différents états d'une énumération (C #):

enum AccountType
{
    Savings,
    Checking,
    MoneyMarket
}

Ensuite, dans une routine ...

switch (accountType)
{
    case AccountType.Checking:
        // do something

    case AccountType.Savings:
        // do something else

    case AccountType.MoneyMarket:
        // do some other thing

    default:
-->     Debug.Fail("Invalid account type.");
}

À 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.Failplantages horriblement (en mode débogage) pour attirer mon attention sur ce fait. Quand j'ajoute le case 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.)

Kyralessa
la source
4
La plupart des compilateurs sont suffisamment intelligents pour émettre un avertissement si vous ne gérez pas certaines énumérations dans un bloc de cas. Mais définir la valeur par défaut pour échouer est toujours une bonne forme - une énumération n'est qu'un nombre et si vous obtenez une corruption de mémoire, une valeur non valide peut apparaître.
Adam Hawes
en c, j'avais l'habitude d'ajouter un invalidAccountType à la fin. cela est parfois utile.
Ray Tayek
1
@Adam - les compilateurs émettront un avertissement si vous recompilez tout . Si vous ajoutez de nouvelles classes et que vous ne recompilez que partiellement, vous ne remarquerez peut-être pas quelque chose comme ci-dessus, et le cas par défaut vous sauvera. Cela n'échouera pas silencieusement.
Eddie le
4
La rupture est implicite dans les commentaires, Slashene. : P
Ryan Lundy
2
Après le débogage, il devrait y avoir un 'throw new NotSupportedException ()' pour le code de production.
user7116
35

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:

FILE *fp = fopen(filename, "r");
if(fp == NULL) {
    fprintf(stderr, "ERROR: Could not open file %s\n", filename);
    return false;
}

Ce manque de guillemets %sest 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:

ERROR: Could not open file

Alors, il vaut toujours mieux faire:

fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);

Alors au moins l'utilisateur voit ceci:

ERROR: Could not open file ''

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".

Nik Reiman
la source
4
bon, j'ai vu ce problème aussi
Alex Baranosky
28

Sécurité SQL

Avant d'écrire un SQL qui modifiera les données, j'emballe le tout dans une transaction annulée:

BEGIN TRANSACTION
-- LOTS OF SCARY SQL HERE LIKE
-- DELETE FROM ORDER INNER JOIN SUBSCRIBER ON ORDER.SUBSCRIBER_ID = SUBSCRIBER.ID
ROLLBACK TRANSACTION

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 SELECTinstructions entre votre SQL et le ROLLBACK TRANSACTIONpour vous assurer que tout semble correct.

Lorsque vous êtes complètement sûr qu'il fait ce que vous attendiez, changez le ROLLBACKen COMMITet exécutez pour de vrai.

amsimmon
la source
Vous m'avez battu sur le coup sur celui-ci! :-)
Howard Pinsley
2
J'avais l'habitude de faire cela tout le temps, mais cela entraîne tellement de surcharge supplémentaire sur les clusters DB que j'ai dû arrêter.
Zan Lynx
25

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.

le dorfier
la source
5
Que signifie exactement la partie à éviter? J'introduis parfois des variables qui ne vivent que jusqu'à la ligne suivante. Ils servent de nom à une expression, ce qui rend le code plus lisible.
zoul
4
Oui, je ne suis pas d'accord aussi. Avec des expressions très complexes, c'est souvent une bonne idée de les décomposer en deux ou parfois plus courtes et plus simples à l'aide de variables temporaires. C'est moins sujet aux erreurs de maintenance et le compilateur optimisera les temps
Cruachan
1
Je conviens qu'il existe des cas exceptionnels où je déclare des variables pour plus de clarté (et parfois pour créer une cible pour le débogage). Mon expérience est que la pratique générale est de se tromper dans la direction opposée.
dkretz le
Je suis d'accord avec les deux points de vue; cependant, lorsque je décompose des expressions en variables temporaires, j'essaie de le faire dans une portée séparée. Les lambdas sont parfaits pour cela, tout comme les méthodes d'aide.
Erik Forbes le
19

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).

peSHIr
la source
Et bien sûr: utilisez du code générique (une petite bibliothèque, des méthodes d'extension en C #, peu importe) pour rendre cela facile. Donc, vous pouvez écrire quelque chose de glike param.NotNull("param")au lieu deif ( param == null ) throw new ArgumentNullException("param");
peSHIr
2
Ou utilisez la programmation basée sur des contrats, comme Spec #!
bzlm
Dépend de quelle application il s'agit. J'espère que les gens qui écrivent du code pour les avions fly-by-wire et les stimulateurs cardiaques ne pensent pas comme peSHIr.
MarkJ
6
@MarkJ: Vous ne comprenez pas vraiment, n'est-ce pas? S'il bombarde tôt (= pendant le développement et les tests), il ne devrait jamais bombarder lorsqu'il est en production. Alors j'espère vraiment qu'ils font des programmes comme ça!
peSHIr
Je dois avouer que je n'aime pas beaucoup ça, surtout pour les méthodes privées et protégées. Raisons: a) vous encombrez le code avec des vérifications qui ne ressemblent pas du tout aux exigences de l'entreprise b) ces vérifications sont difficiles à tester pour les méthodes non publiques c) c'est inutile dans de nombreux cas, par exemple, car une valeur nulle entraînera l'échec de la méthode de toute façon deux lignes plus tard
Erich Kitzmueller
18

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:

public List<T> getList() {
    return Collections.unmodifiableList(list);
}

Ne laissez rien échapper à votre classe dont vous n'avez pas besoin!

David Grant
la source
+1 En C #, il existe des collections en lecture seule pour cela.
tobsen le
1
+1 pour Java. Je l'utilise tout le temps
Fortyrunner
+1 ... Je fais beaucoup cela (même si je souhaite que plus de gens se souviennent de le faire).
Ryan Delucchi
Assurez-vous simplement que rien d'autre n'a une instance de la variable de liste sous-jacente, sinon cela ne vous fera pas beaucoup de bien ...
GreenieMeanie
5
Pour info, Collections.unmodifiableList renvoie une vue immuable de la liste, pas une copie immuable . Donc, si la liste d'origine est modifiée, la vue le sera aussi!
Zarkonnen
17

en Perl, tout le monde fait

use warnings;

J'aime

use warnings FATAL => 'all';

Cela provoque la mort du code pour tout avertissement du compilateur / runtime. Ceci est surtout utile pour intercepter les chaînes non initialisées.

use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens;  returns 'undef'
print $string . "\n";        # code dies here
Eric Johnson
la source
J'aimerais que ce soit un peu plus annoncé ...
DJG
16

C #:

string myString = null;

if (myString.Equals("someValue")) // NullReferenceException...
{

}

if ("someValue".Equals(myString)) // Just false...
{

}
CMS
la source
Idem en Java, et probablement dans la plupart des langages OO.
MiniQuark
C'est bien en Objective-C, où il est possible d'envoyer des messages à nil («appeler des méthodes d'un objet nul», avec une certaine licence). L'appel de [nil isEqualToString: @ "Moo"] renvoie false.
zoul
Je ne suis pas d'accord avec l'exemple C #. Une meilleure solution consiste à utiliser "if (myString ==" someValue ")". De plus, aucune exception de référence nulle, et certainement plus lisible.
Dan C. le
13
cet exemple vous permet simplement de cacher une situation potentiellement dangereuse dans votre programme. Si vous ne vous attendiez pas à ce qu'il soit nul, vous VOULEZ qu'il lève une exception, et si vous vous attendiez à ce qu'il soit nul, vous devez le gérer comme tel. C'est une mauvaise pratique.
rmeador le
2
En C #, j'utilise toujours simplement string.Equals (<string1>, <string2>). Peu importe si l'un d'eux est nul.
Darcy Casselman le
15

En c # vérification de string.IsNullOrEmpty avant de faire des opérations sur la chaîne comme length, indexOf, mid etc

public void SomeMethod(string myString)
{
   if(!string.IsNullOrEmpty(myString)) // same as myString != null && myString != string.Empty
   {                                   // Also implies that myString.Length == 0
     //Do something with string
   }
}

[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

string.IsNullOrWhiteSpace(myString)
Binoj Antony
la source
7
Ce n'est pas sur la défensive, c'est ignorer le problème. Devrait être if (!string.IsNullOrEmpty(myString)) throw new ArgumentException("<something>", "myString"); /* do something with string */.
enashnash
14

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.

Eddie
la source
Et - sous Windows - pour C ++ aussi! (activé avec une exception spéciale SEH et une capture suivante).
Brian Haak
12

Avec VB.NET, activez Option Explicit et Option Strict par défaut pour l'ensemble de Visual Studio.

ChrisA
la source
Bien que je travaille avec un code plus ancien, donc je n'ai pas l'option stricte activée (provoque beaucoup trop d'erreurs du compilateur à corriger), avoir ces deux options activées peut (et aurait dans mon cas) sauvé beaucoup de cœur mal.
Kibbee
1
À propos, pour quiconque convertit un ancien code qui n'a pas utilisé Option Strict pour l'utiliser, notez que dans le cas des éléments qui ont été convertis automatiquement en String, ils n'utilisent pas ToString (); ils utilisent un cast to string. À mes débuts .NET, je les changeais pour utiliser ToString (), et cela casserait les choses, en particulier avec les énumérations.
Ryan Lundy
10

C ++

#define SAFE_DELETE(pPtr)   { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }

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.

Nitin Bhide
la source
1
Mieux vaut utiliser un modèle. C'est cadré et surchargeable.
J'étais sur le point de le dire. Utilisez un modèle au lieu d'une macro. De cette façon, si jamais vous avez besoin de parcourir le code, vous le pouvez.
Steve Rowe
J'ai appris ce qui était plus facile à déboguer à la dure à l'école. C'est une erreur que je n'ai pas faite depuis. :)
Greg D
3
Ou utilisez des pointeurs intelligents ... Ou diable, évitez de créer / supprimer autant que vous le pouvez de toute façon.
Arafangion
1
@Arafangion: évitez simplement delete. L'utilisation newest correcte, tant que le nouvel objet est détenu par un pointeur intelligent.
Alexandre C.
10

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:

private Object someHelperFunction(Object param)
{
    assert param != null : "Param must be set by the client";

    return blahBlah(param);
}

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.

programmeur hors-la-loi
la source
Dans .NET, Debug.Assert fait la même chose. Chaque fois que vous avez un endroit où vous pensez "Cette référence ne peut pas être nulle ici, non?", Vous pouvez y placer un Debug.Assert, de sorte que s'il peut être nul, vous pouvez soit corriger le bogue, soit changer vos hypothèses.
Ryan Lundy
1
+1, les méthodes publiques devraient jeter sur les échecs de contrat, les privés devraient affirmer
user7116
9

Je n'ai pas trouvé le readonlymot - clé avant de trouver ReSharper, mais je l'utilise maintenant instinctivement, en particulier pour les classes de service.

readonly var prodSVC = new ProductService();
JMS
la source
J'utilise le mot-clé «final» équivalent de Java sur tous les champs que je peux. Cela vous évite vraiment de faire des mouvements de tête d'os, comme ne pas définir un champ / écrire des commutateurs déroutants qui peuvent définir des champs plusieurs fois. Je suis moins susceptible de marquer les variables / paramètres locaux mais je suppose que cela ne peut pas faire de mal.
Programmeur hors
C # ne vous permet pas de marquer les variables locales comme en lecture seule, donc nous n'avons même pas le choix ...
Jason Punyon
Je ne suis pas sûr de ce que signifie lecture seule ici, mais la finale de Java ne me suffit pas. Cela signifie simplement que le pointeur vers un objet est inchangé, mais il n'y a aucun moyen d'empêcher le changement sur l'objet lui-même.
Slartibartfast le
En C #, une structure en lecture seule l'empêcherait d'être modifiée.
Samuel
9

En Java, quand quelque chose se passe et que je ne sais pas pourquoi, j'utiliserai parfois Log4J comme ceci:

if (some bad condition) {
    log.error("a bad thing happened", new Exception("Let's see how we got here"));
}

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.

Eddie
la source
Hmm, c'est plutôt chouette mais je crains que cela ne provoque un mélange de code fonctionnel et de gestion des erreurs. Bien que le mécanisme try-catch puisse être maladroit, il oblige à effectuer une redirection d'exception d'un bloc (try) à un autre (catch), où se trouve toute la gestion des erreurs
Ryan Delucchi
1
Ceci n'est pas utilisé pour la gestion des erreurs, mais uniquement pour les diagnostics . Cela vous permet de voir le chemin par lequel votre code est entré dans une situation inattendue sans interrompre le flux de votre code. J'utilise ceci lorsque la méthode elle-même peut gérer la situation inattendue, mais cela ne devrait toujours pas arriver.
Eddie le
2
vous pouvez également utiliser une nouvelle exception ("message"). printStackTrace (); Aucun lancer ou attraper requis, mais vous obtenez toujours une belle trace de pile dans le journal. Évidemment, cela ne devrait pas être dans le code de production, mais cela peut être très utile pour le débogage.
Jorn
9

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:

class Foo
{
   virtual void DoSomething();
}

class Bar: public Foo
{
   void DoSomething() override { /* do something */ }
}
Steve Rowe
la source
pour Java, il s'agit de la classe Foo {void doSomething () {}} classe Bar {@Override void doSomething () {}} Donnera un avertissement s'il manque l'annotation (vous ne pouvez donc pas remplacer silencieusement une méthode que vous n'avez pas signifie à) et quand l'annotation est là mais elle ne remplace rien.
Jorn
Bien ... je ne savais pas pour celui-ci ... dommage que cela semble être spécifique à Microsoft ... mais quelques bons #defines peuvent aider à le rendre portable
e.tadeu
8

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.

Eddie
la source
1
les attentes avec des délais d'attente utilisés comme celui-ci sont le signe d'autres problèmes, pires.
dicroce
2
Dans un système qui doit avoir un temps de fonctionnement élevé, il est préférable au moins d'obtenir des informations de diagnostic afin que vous puissiez trouver le problème. Parfois, vous préférez quitter un thread ou renvoyer une réponse à l'appelant lorsque le système est dans un état connu, vous attendez donc sur un sémaphore. Mais vous ne voulez pas vous accrocher éternellement
Eddie
8

C #

  • Vérifiez les valeurs non nulles pour les paramètres de type de référence dans la méthode publique.
  • J'utilise sealedbeaucoup 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.
Brian Rasmussen
la source
8

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.

Joe McMahon
la source
1
Lorsque vous écrivez le code d'un message d'erreur, lisez le message et demandez ce que vous voudriez savoir ensuite , puis ajoutez-le. Répétez jusqu'à ce que cela ne soit pas raisonnable. Par exemple: "Hors de portée". Qu'est-ce qui est hors de portée? "Foo count out of range." Quelle était la valeur? "Foo count (42) out of range." Quelle est la gamme? "Foo count (42) hors de portée (549 à 666)."
HABO
7

En C #, utilisez le asmot clé pour effectuer un cast.

string a = (string)obj

lèvera une exception si obj n'est pas une chaîne

string a = obj as string

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 assyntaxe environ 75% du temps et la (cast)syntaxe environ 25%.

Matt Briggs
la source
Correct, mais maintenant vous devez vérifier null avant d'utiliser la référence.
Brian Rasmussen le
7
Je n'ai pas compris. Cela me semble une mauvaise décision, préférer le nul. Vous rencontrerez des problèmes quelque part pendant l'exécution sans aucune indication sur la raison d'origine.
Kai Huppmann le
Oui. Ceci n'est utile que si vous voulez réellement null alors que ce n'est pas le type correct. Utile dans certains cas: dans Silverlight où la logique de commande et la conception sont souvent séparées, la logique veut n'utiliser la commande «Up» que s'il s'agit d'un bouton. Sinon, c'est comme s'il n'existait pas (= null).
Sander
2
Cela semble à peu près aussi défensif que les grandes fenêtres d'une salle de bal dans une forteresse. Mais c'est une belle vue!
Pontus Gagge
1
Cela me rappelle quelqu'un qui n'a pas utilisé d'exceptions parce qu '«ils ont cassé des programmes». Si vous voulez un certain comportement lorsqu'un objet n'est pas une classe que vous attendez, je pense que je coderais cela d'une autre manière. is/instanceofviennent à l'esprit.
Kirschstein
6

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.

Eddie
la source
6

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 .

Zen
la source
6

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.

utilisateur45947
la source
5

J'ai oublié d'écrire echoen PHP une fois de trop:

<td><?php $foo->bar->baz(); ?></td>
<!-- should have been -->
<td><?php echo $foo->bar->baz(); ?></td>

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 EchoMeclasse qui pourrait être enroulée autour de n'importe quelle valeur qui devrait être renvoyée:

<?php
class EchoMe {
  private $str;
  private $printed = false;
  function __construct($value) {
    $this->str = strval($value);
  }
  function __toString() {
    $this->printed = true;
    return $this->str;
  }
  function __destruct() {
    if($this->printed !== true)
      throw new Exception("String '$this->str' was never printed");
  }
}

Et puis pour l'environnement de développement, j'ai utilisé un EchoMe pour envelopper les choses qui devraient être imprimées:

function baz() {
  $value = [...calculations...]
  if(DEBUG)
    return EchoMe($value);
  return $value;
}

En utilisant cette technique, le premier exemple manquant le echoferait maintenant lever une exception ...

trop de php
la source
Le destructeur ne devrait-il pas vérifier $ this-> Printed! == true?
Karsten
Vous devriez envisager d'utiliser un système de modèle, l'intégration de php dans html est sous-optimale sur presque tous les systèmes.
Cruachan le
Peut-être que je manque quelque chose? Mais il me semble que cela essaie de compenser le codage: AnObject.ToStringau lieu de Writeln(AnObject.ToString)?
Désillusionné le
Oui, mais l'erreur est beaucoup plus facile à faire en PHP
trop de php
4

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.

mmr
la source
J'aime votre premier point. Je fais des choses similaires lorsque, par exemple, j'écris une méthode qui renvoie une collection de quelque chose. Je crée la collection sur la première ligne et j'écris immédiatement l'instruction return. Il ne reste plus qu'à indiquer comment la collection est peuplée.
Outlaw Programmer
5
En C ++, lorsque vous tapez new, vous devez immédiatement affecter ce pointeur à un conteneur AutoPtr ou compté par référence. C ++ a des destructeurs et des modèles; utilisez-les à bon escient pour gérer automatiquement la suppression.
An̲̳̳drew
4

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.

dicroce
la source