Si vous deviez choisir vos techniques favorites (intelligentes) pour le codage défensif, quelles seraient-elles? Bien que mes langages actuels soient Java et Objective-C (avec un arrière-plan en C++), n'hésitez pas à répondre dans n'importe quel langage. L'accent serait mis ici sur intelligent techniques défensives autres que celles que 70% + d'entre nous connaissent déjà. Alors maintenant, il est temps de creuser profondément dans votre sac de trucs.
En d'autres termes, essayez de penser à autre chose que cet exemple sans intérêt:
if(5 == x)
au lieu deif(x == 5)
: pour éviter une affectation involontaireVoici quelques exemples de certaines intrigantes meilleures pratiques de programmation défensive (les exemples spécifiques au langage sont en Java):
Verrouillez vos variables jusqu'à ce que vous sachiez que vous devez les changer
Autrement dit, vous pouvez déclarer toutes les variables final
jusqu'à ce que vous sachiez que vous devrez les modifier, à quel moment vous pouvez supprimer le 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 */ }
En cas de problème, laissez une trace de preuves derrière vous
Il y a un certain nombre de choses que vous pouvez faire lorsque vous avez une exception: la journalisation et l'exécution d'un nettoyage en sont évidemment quelques-unes. Mais vous pouvez également 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 dépasseriez une exception catch
- block) .
En matière 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 borne inférieure inclusive et la borne supérieure exclusif . Cela le rendra cohérent avec des méthodes comme String.substring(start, end)
qui fonctionnent de la même manière. Vous trouverez tous ces types de méthodes dans le JDK Sun pour se comporter de cette façon car il effectue diverses opérations, y compris l'itération des éléments cohérents avec les tableaux, où les indices sont 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 à faire un carillon. Je donne la chance à plus de réponses de venir avant de choisir le officiel réponse.
En c ++, j'ai une fois aimé redéfinir le nouveau afin qu'il fournisse de la mémoire supplémentaire pour détecter les erreurs de clôture.
Actuellement, je préfère éviter la programmation défensive au profit de Test Driven Development . Si vous détectez des erreurs rapidement et en externe, vous n'avez pas besoin d'embrouiller votre code avec des manœuvres défensives, votre code est SEC - euh et vous enroulez- avec moins d'erreurs contre lesquelles vous devez vous défendre.
Comme WikiKnowledge l'a écrit :
Évitez la programmation défensive, échouez rapidement à la place.
Par programmation défensive, j'entends l'habitude d'écrire du code qui tente de compenser une défaillance des 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.
SQL
Quand je dois supprimer des données, j'écris
select *
--delete
From mytable
Where ...
Lorsque je l'exécuterai, je saurai si j'ai oublié ou raté 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 simplement *
Allouez une partie raisonnable de la mémoire au démarrage de l'application - je pense que Steve McConnell l'a appelé parachute de mémoire dans Code Complete.
Cela peut être utilisé en cas de problème grave 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:
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);
}
Lorsque vous gérez les différents états d'une énumération (C #):
enum AccountType
{
Savings,
Checking,
MoneyMarket
}
Puis, à l'intérieur d'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 switch. Alors le Debug.Fail
se bloque horriblement (en mode Debug) pour attirer mon attention sur ce fait. Lorsque j'ajoute le case AccountType.MyNewAccountType:
, l'horrible crash s'arrête ... jusqu'à ce que j'ajoute un autre type de compte et que j'oublie de mettre à jour les cas ici.
(Oui, le polymorphisme est probablement mieux ici, mais ce n'est qu'un exemple du haut de ma tête.)
Lors de l'impression de messages d'erreur avec une chaîne (en particulier celle 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 citations autour de %s
est vraiment mauvais, car disons que le nom de fichier est une chaîne vide ou simplement des espaces ou quelque chose. Le message imprimé serait bien sûr:
ERROR: Could not open file
Donc, toujours mieux faire:
fprintf(stderr, "ERROR: Could not open file '%s'\n", filename);
Ensuite, 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".
Sécurité SQL
Avant d'écrire un SQL qui modifiera les données, j'encapsule 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 une mauvaise suppression/mise à jour de façon permanente. Et, vous pouvez exécuter le tout et vérifier un nombre d'enregistrements raisonnable ou ajouter des instructions SELECT
entre votre SQL et le ROLLBACK 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
en COMMIT
et exécutez pour de vrai.
Pour toutes les langues:
Réduisez la portée des variables au moins possible. Eschew variables qui sont juste fournis pour les porter dans la prochaine instruction. Les variables qui n'existent pas sont des variables que vous n'avez pas besoin de comprendre et dont vous ne pouvez être tenu responsable. Utilisez Lambdas autant que possible pour la même raison.
En cas de doute, bombardez l'application!
Vérifiez le paramètre chacun au début de la méthode chacun (que vous le codiez explicitement vous-même ou que vous utilisiez une programmation basée sur un contrat n'a pas d'importance ici) et bombardez avec le bon exception et/ou message d'erreur significatif si aucune condition préalable au code n'est remplie.
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 se passe mal plus tard et que des piles de dizaines d'appels de méthode séparent l'occurrence. du symptôme et de l'emplacement réel où une condition préalable n'est pas remplie (= où le problème/bug est réellement).
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!
à Perl, tout le monde fait
use warnings;
J'aime
use warnings FATAL => 'all';
Cela provoque la mort du code pour tout avertissement de compilation/d'exécution. Ceci est surtout utile pour intercepter des chaînes non initialisées.
use warnings FATAL => 'all';
...
my $string = getStringVal(); # something bad happens; returns 'undef'
print $string . "\n"; # code dies here
C #:
string myString = null;
if (myString.Equals("someValue")) // NullReferenceException...
{
}
if ("someValue".Equals(myString)) // Just false...
{
}
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 aussi faire ce qui suit dans .NET 4.0, qui vérifie en outre si la valeur est juste un espace
string.IsNullOrWhiteSpace(myString)
Dans Java et C #, donnez à chaque thread un nom significatif. Cela inclut les threads du pool de threads. Cela rend les vidages de pile beaucoup 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 la pile (vous connaissez SendSignal.exe , n'est-ce pas?), Récupérez les journaux, et sans avoir à interrompre un système en cours d'exécution, je peux dire quels threads sont ... quoi que ce soit. Blocage, fuite, croissance, quel que soit le problème.
Avec VB.NET, les options Explicit et Option Strict sont activées par défaut pour l'ensemble de Visual Studio.
Avec Java, il peut être pratique d'utiliser le mot clé assert, même si vous exécutez du code de production avec des 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 des affirmations 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 membre d'une API publique. Cette méthode ne peut être appelée que par vous, il est donc acceptable de 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 lever une véritable exception pour les entrées non valides.
C++
#define SAFE_DELETE(pPtr) { delete pPtr; pPtr = NULL; }
#define SAFE_DELETE_ARRAY(pPtr) { delete [] pPtr; pPtr = NULL }
puis remplacez tous vos 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 l'erreur "violation d'accès". Il est beaucoup plus facile à corriger que les corruptions de mémoire aléatoires.
En Java, quand quelque chose se passe et je ne sais pas pourquoi, j'utilise 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 me montrant comment je suis entré dans une 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 véritable exception est levé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 quoi que ce soit d'autre. Je ne pas veux lever une exception et je n'en ai pas attrapé une. Je veux juste qu'une trace de pile soit enregistrée avec un message approprié pour me signaler ce qui se passe.
Je n'ai pas trouvé le mot clé readonly
avant d'avoir trouvé ReSharper, mais je l'utilise maintenant instinctivement, en particulier pour les classes de service.
readonly var prodSVC = new ProductService();
Si vous utilisez Visual C++, utilisez le mot-clé override chaque fois que vous contournez la méthode d'une classe de base. De cette façon, si quelqu'un modifie la signature de la classe de base, cela générera une erreur de compilation plutôt que la mauvaise méthode sera appelée en silence. 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 */ }
}
C #
sealed
pour les classes afin d'éviter d'introduire des dépendances là où je ne les voulais pas. Permettre l'héritage doit être fait de manière explicite et non par accident.Lorsque vous émettez un message d'erreur, essayez au moins de fournir les mêmes informations que le programme avait lorsqu'il a décidé de renvoyer 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 connaître la base sur laquelle la décision a été prise, même si elle est erronée - surtout si elle est incorrecte: nom de fichier incorrect? mal ouvert? autre erreur inattendue? - et vous permet de savoir où vous étiez lorsque vous avez eu le problème.
J'ai appris en Java to presque jamais attendre indéfiniment qu'un verrou se déverrouille, sauf si je m'attends vraiment à ce que cela prenne indéfiniment longtemps. Si, de façon réaliste, le le verrou doit se déverrouiller en quelques secondes, alors je n'attendrai qu'un certain temps. Si le verrou ne se déverrouille pas, alors je me plains et jette la pile dans les journaux, et selon ce qui est le mieux pour la stabilité du système, soit continuer comme si la serrure était déverrouillée ou continuer comme si la serrure n'avait jamais été déverrouillée.
Cela a permis d'isoler quelques conditions de course et des conditions de pseudo-impasse qui étaient mystérieuses avant de commencer.
En C #, utilisez le mot clé as
pour caster.
string a = (string)obj
lèvera une exception si obj n'est pas une chaîne
string a = obj as string
laissera un as null 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 blow up", auquel cas la syntaxe (string)obj
Est préférée.
Dans mon propre code, je constate que j'utilise la syntaxe as
environ 75% du temps et la syntaxe (cast)
Environ 25%.
Java
L'API Java n'a pas de concept d'objets immuables, ce qui est mauvais! Final peut vous aider dans ce cas. Marquez chaque classe qui est immuable avec final et préparez 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 boucles laides mais nécessaires. Il est juste trop facile de réutiliser accidentellement une variable même si elle est censée être une constante.
Utilisez copie de défense dans vos getters. À moins que vous ne retourniez 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 qu'il n'affecte pas votre code dans 99% des cas. Les gens écrasent égaux, mais ne se soucient pas du hashCode. Il existe des cas dans lesquels votre code peut se briser ou se comporter de manière étrange, par exemple utiliser des objets mutables comme clés dans une carte.
J'essaie d'utiliser l'approche Design by Contract. Il peut être émulé lors de l'exécution par n'importe quelle langue. Chaque langue 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 de précondition assert au début des méthodes est un bon moyen de s'assurer que les paramètres sont cohérents. À la fin des méthodes, j'écris des postconditions , qui vérifient que la sortie est ce qui est prévu.
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 macro pré-condition et post-condition.
J'évalue le Code Contract Library .
Soyez prêt pour tout entrée, et toute entrée que vous obtenez qui est inattendue, sauvegardez dans les journaux. (Dans des limites raisonnables. Si vous lisez les mots de passe de l'utilisateur, ne les sauvegardez pas dans les journaux! Et n'enregistrez pas des milliers de ces types de messages dans les journaux par seconde. Renseignez-vous sur le contenu, la probabilité et la fréquence avant de le connecter. .)
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 ma demande passe par un proxy transparent que je ne connaissais pas et que le client prétendait 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 câble, vous pouvez obtenir des formats de données inattendus sans aucune vilenie. Soyez prêt, codez de manière défensive et fournissez une sortie de diagnostic en cas d'entrée inattendue.
J'ai oublié d'écrire echo
en 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 ne m'en faisais pas l'écho! : -S J'ai donc créé une classe EchoMe
qui pourrait être enroulée autour de n'importe quelle valeur qui devrait être reproduite:
<?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 des 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 echo
lèverait maintenant une exception ...
Compilez toujours au niveau d'avertissement le plus élevé et traitez les avertissements comme des erreurs (build breakers).
Même si le code est "correct", corrigez la cause de l'avertissement sans désactiver l'avertissement si possible. Par exemple, votre compilateur C++ peut vous donner un avertissement pour un code légal comme celui-ci:
while (ch = GetNextChar()) { ... }
Il semble que vous ayez tapé =
au lieu de ==
. La plupart des compilateurs qui offrent cet avertissement (utile) se fermeront si vous ajoutez une vérification explicite.
while ((ch = GetNextChar()) != 0) { ... }
Être légèrement plus explicite non seulement fait taire l'avertissement, mais aide également le prochain programmeur qui doit comprendre le code.
Si vous DEVEZ désactiver un avertissement, utilisez un #pragma
dans le code, afin que vous puissiez (1) limiter la plage de code pour laquelle l'avertissement est désactivé et (2) utiliser des commentaires pour expliquer pourquoi l'avertissement doit être désactivé. Les avertissements désactivés dans les lignes de commande ou les makefiles sont des catastrophes qui attendent de se produire.
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 lorsque vous utilisez le 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érifiez par rapport à null. Même si vous pensez que ce ne sera pas nul, vérifiez quand même. J'ai été surpris.
Utilisez des classes sentinelles avec certains modèles basés sur l'interface OOP patterns au lieu de null
.
Par exemple. lors de l'utilisation de quelque chose comme
public interface IFileReader {
List<Record> Read(string file);
}
utiliser une classe sentinelle comme
public class NoReader : IFileReader {
List<Record> Read(string file) {
// Depending on your functional requirements in this case
// you will use one or more of any of the following:
// - log to your debug window, and/or
// - throw meaningful exception, and/or
return new List<Record>(); // - graceful fall back, and/or
// - whatever makes sense to you here...
}
}
et l'utiliser pour initialiser n'importe quelle variable IFileReader
IFileReader reader = new NoReader();
au lieu de simplement les laisser à null
(implicitement ou explicitement)
IFileReader reader; /* or */
IFileReader reader = null;
pour vous assurer de ne pas obtenir d'exceptions de pointeur nul inattendues.
Bonus: vous n'avez plus vraiment besoin d'encapsuler chaque variable IFileReader
avec une fonction if (var!=null) ...
car elles ne seront pas null
.
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 le rare état dans lequel le bogue s'est produit. Vous devez être en mesure d'activer davantage d'informations de journalisation sans arrêter le processus.
De plus, 'strace -p [pid]' sur linux montrera que vous voulez que les appels système soient effectués par un processus (ou un thread linux). Cela peut sembler étrange au début, mais une fois que vous vous serez habitué aux appels système généralement effectués par les appels libc, vous trouverez cela inestimable pour le diagnostic sur le terrain.
Utilisez une console, comme dans les jeux;
Pas complètement "défensif" mais je l'ai compris en le voyant dans beaucoup de matchs.
J'aime avoir une console complète pour toutes mes applications qui me permet de:
En C # si vous marquez les méthodes de la console avec l'attribut conditionnel, elles seront automatiquement supprimées de la version finale. Dans d'autres langues, la même chose peut être obtenue grâce aux directives du préprocesseur.
Je l'ai trouvé particulièrement utile pendant la phase de test car il permet au développeur de voir ce qui se passe et au testeur de fournir de meilleurs commentaires au développeur.
En plus:
En Python, si je stoppe (ou modifie une méthode) et que je n'ai pas le temps de le tester ce jour-là, j'entame un "assert False" afin que le code plante si la méthode est exécutée, créant des erreurs embarrassantes I le remarquerai le lendemain. Une erreur de syntaxe intentionnelle peut également être utile.
Exemple:
def somefunction(*args,**kwargs):
''' <description of function and args> '''
# finish this in the morning
assert False, "Gregg finish this up"
Pour C++: détection automatique de la taille des tableaux
char* mystrings[] = { "abc", "xyz" , "pqr" }
généralement alors pour est écrit comme
for (int i=0; i< 3; i++)
{
str= mystrings[i]
// somecode
}
Cependant, plus tard, vous pouvez ajouter de nouvelles chaînes à 'mystrings'. Dans ce cas, la boucle for ci-dessus peut introduire des bogues subtils dans le code.
la solution que j'utilise est
int mystringsize = sizeof(mystrings)/sizeof(char*)
for (int i=0; i< mystringsize; i++)
{
str= mystrings[i]
// somecode
}
Maintenant, si vous ajoutez plus de chaînes au tableau 'mystrings', la boucle sera automatiquement ajustée.
En C++ assert()
est un outil très pratique. Je ne lui donne pas seulement la condition à évaluer mais aussi un message indiquant ce qui ne va pas:
assert( isConditionValid && "ABC might have failed because XYZ is wrong." );
Lorsqu'il n'y a pas de variable réelle à vérifier ou que vous vous trouvez dans une situation qui n'aurait jamais dû se produire (gestionnaire "par défaut" de switch ()), cela fonctionne aussi:
assert( 0 && "Invalid parameter" );
Il affirme non seulement en mode débogage, mais vous indique également ce qui s'est mal passé en même temps.
Je l'ai obtenu des "normes de codage C++" si je me souviens bien.
N'oubliez pas que les exceptions sont les meilleurs amis d'un programmeur - ne les mangez jamais!
De plus, lorsque vos cours sont minuscules et généralement finaux, être sur la défensive est vraiment bon marché - autant le faire si vous y croyez ou non. Testez les valeurs transmises à vos constructeurs et (si vous DEVEZ vraiment les avoir) aux setters.
lors de l'obtention d'une table à partir d'un ensemble de données
if( ds != null &&
ds.tables != null &&
dt.tables.Count > 0 &&
ds.tables[0] != null &&
ds.tables[0].Rows > 0 )
{
//use the row;
}
Mes directives C++, mais je ne pense pas que ce soit intelligent:
class NonCopied {
private:
NonCopied(const NonCopied&);
NonCopied& operator=(const NonCopied&);
}
En C++
Je déploie des assertions sur toutes mes fonctions, en particulier au début et à la fin des fonctions pour intercepter toute entrée/sortie inattendue. Lorsque j'ajouterai plus de fonctionnalités dans une fonction, les assertions m'aideront à m'en souvenir. Il aide également d'autres personnes à voir l'intention de la fonction et ne sont actifs qu'en mode débogage.
J'évite les pointeurs autant que possible et j'utilise plutôt des références, de cette façon, je n'ai pas besoin de mettre des instructions if (NULL!=p)
- encombrantes dans mon code.
J'utilise également le mot const
aussi souvent que possible dans les déclarations et comme arguments de fonction/méthode.
J'évite également d'utiliser des POD et utilise plutôt STL/Boost autant que possible pour éviter les fuites de mem et autres choses désagréables. Cependant, j'évite d'utiliser trop de modèles définis personnalisés car je les trouve difficiles à déboguer, en particulier pour ceux qui n'ont pas écrit le code.
Rendez votre code aussi lisible que possible, notamment en utilisant des noms de fonction et de variable aussi évidents que possible. Si cela signifie que certains noms sont un peu longs, qu'il en soit ainsi.
Utilisez un analyseur statique autant que possible. Vous prenez rapidement l'habitude d'écrire du code conforme à ses règles.
Pendant le développement, activez facilement la sortie de diagnostic, mais désactivez-les en production.
Concevez votre stratégie de journalisation de sorte que lorsqu'une erreur se produit en production, la personne de support ou le développeur approprié reçoive automatiquement un e-mail. Cela vous permet de trouver proactivement des bogues, plutôt que d'attendre que les utilisateurs se plaignent.
Notez que cela doit être fait avec une certaine prudence. Un exemple que j'avais était qu'un développeur avait écrit du code de journalisation dans une boucle. Après quelques mois, une erreur dans le système a déclenché ce code. Malheureusement, l'application s'est retrouvée dans cette boucle, enregistrant la même erreur encore et encore. Nous sommes arrivés au bureau ce matin-là pour être informés que notre serveur de messagerie s'était planté après que notre infrastructure de journalisation ait envoyé 40 000 courriels entre 4 heures et 8 heures!
Ne passez pas autour de collections nues, même génériques. Ils ne peuvent pas être protégés et aucune logique ne leur est attachée.
Un bon parallèle serait d'avoir une variable publique au lieu de setter/getter. le setter/getter vous permet de changer votre implémentation sous-jacente sans affecter le monde extérieur.
Comment changez-vous votre structure de données sans affecter le monde extérieur si vous faites circuler une collection? Tous les accès pour votre collection sont répartis sur tout votre code !!
Au lieu de cela, enveloppez-le et donnez-vous un endroit pour mettre un peu de logique commerciale. Vous trouverez quelques refacteurs de Nice une fois que vous l'avez fait.
Souvent, vous trouverez qu'il est logique d'ajouter des variables et peut-être une deuxième collection - alors vous vous rendrez compte que cette classe a toujours été absente!
Retour à l'époque où RAM n'était pas gratuit, la plupart des ordinateurs étaient très limités et "NOT ENOUGH MEMORY!" Était un message d'erreur assez courant ...
Eh bien, la plupart des applications ont pu ensuite planter avec "élégance": les utilisateurs n'ont (presque) jamais perdu leurs œuvres.
(Presque, je l'ai dit! ^^).
Comment cela s'est-il fait? Très simple: lorsque votre application démarre, allouez une bulle de RAM (disons, 20 Ko!). Ensuite, lorsqu'un appel à malloc () échoue:
Et voilà. Votre application se bloque lentement sur l'utilisateur, la plupart du temps, peut sauver son travail.
Essayez de ne rien construire pendant quelques semaines. Souvent, d'autres scénarios se présentent à vous avant que les choses ne soient bloquées.
Lorsque vous effectuez une programmation C/C++ multithread, créez une série de macros qui affirment que votre fonction est appelée sur le thread auquel vous pensez qu'elle est appelée. Ensuite, utilisez-les généreusement.
Utilisez GetCurrentThreadId () sur Windows ou pthread_self () sur Posix lorsque le thread est initialisé, puis stockez-le dans des globaux. Les assertions se comparent à la valeur stockée.
M'a sauvé BEAUCOUP de débogage douloureux, surtout quand quelqu'un d'autre remodèle le code multithread existant.
En Perl, die () lorsque les sous-programmes ne reçoivent pas suffisamment de paramètres. Cela vous empêche d'obtenir des échecs que vous devez remonter de 10 niveaux à travers la pile.
sub foo {
my $param0 = shift or confess "param0 is a required param";
my $param1 = shift or confess "param1 is a required param";
my $param2 = shift or confess "param2 is a required param";
...
}
En C #, utilisez "using" pour vous assurer que l'objet est supprimé lorsqu'il sort de la portée. c'est à dire.
using(IDataReader results = DbManager.ExecuteQuery(dbCommand, transaction))
{
while (results.Read())
{
//do something
}
}
Vérifiez également les valeurs nulles après la conversion
MyObject obj = this.bindingSource.Current as MyObject;
if (MyObject != null)
{
// do something
}
En outre, j'utilise des énumérations chaque fois que possible pour éviter le codage en dur, les fautes de frappe et pour fournir un changement de nom facile si nécessaire, c'est-à-dire.
private enum MyTableColumns
{
UserID,
UserName
}
private enum StoredProcedures
{
usp_getMyUser,
usp_doSomething
}
public static MyUser GetMyUser(int userID)
{
List<SqlParameter> spParameters = new List<SqlParameter>();
spParameters.Add(new SqlParameter(MyTableColumns.UserID.ToString(), userID));
return MyDB.GetEntity(StoredProcedures.usp_getMyUser.ToString(), spParameters, CommandType.StoredProcedure);
}
Inclure la gestion des exceptions de haut niveau, comme décrit en détail ici
Gestion des exceptions de niveau supérieur dans les applications Windows Forms
Mon Program.cs ressemblerait alors à ceci
static class Program
{
[STAThread]
static void Main()
{
Application.ThreadException +=
new ThreadExceptionEventHandler(new ThreadExceptionHandler().ApplicationThreadException);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
public class ThreadExceptionHandler
{
public void ApplicationThreadException(object sender, ThreadExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
Certaines choses que je fais en PHP (où les erreurs sont faciles et souvent catastrophiques):
:help php
Pour les voir). Je pense à ajouter quelques éléments qui mettent en évidence mes erreurs ...php -l
) De chaque fichier modifié. Cela empêche seulement les erreurs de base d'entrer, mais c'est mieux que rien.$db->q1($sql, $param1, $param2)
pour récupérer une seule colonne de la première ligne, etc.N'utilisez pas de variables à un seul caractère pour les index de boucle. Par exemple:
for (int ii = 0 ; ii < someValue ; ii++)
// loop body
C'est une habitude simple, mais c'est très utile si vous devez utiliser un éditeur de texte standard pour trouver des références à la variable de boucle. Bien sûr, les boucles indexées ne devraient généralement pas être si longues que vous devez rechercher les références d'index ...
JavaScript:
Nous devons utiliser "==" et "===" de manière appropriée.
==: comparaison d'égalité de conversion de type
===: comparaison d'égalité stricte
Par exemple, "1" == 1 est vrai, mais "1" === 1 est faux.
Beaucoup de gens utilisent inconsciemment "==" au lieu de "===".
Alors, qui parmi nous n'a pas verrouillé accidentellement Visual Studio pendant un certain temps lors de l'écriture d'une méthode récursive?
int DoSomething(int value) // stupid example for illustrative purposes
{
if (value < 0)
return value;
value++; // oops
return DoSomething(value);
}
Pour éviter l'ennui de devoir attendre, et parfois d'avoir à tuer votre IDE avec le gestionnaire de tâches, incluez-le dans votre méthode récursive pendant que vous le déboguez:
int DoSomething(int value)
{
>> if (new StackTrace().FrameCount > 1000) // some appropriately large number
>> Debug.Fail("Stack overflow headed your way.");
if (value < 0)
// some buggy code that never fires
return DoSomething(value);
}
Cela peut sembler lent, mais en pratique, la vérification du FrameCount est assez rapide (moins d'une seconde sur mon PC). Vous pouvez supprimer cette sécurité intégrée (ou peut-être simplement la commenter, mais la laisser pour un débogage ultérieur) après avoir vérifié que la méthode fonctionne correctement.
logiciel Crash-Only , en bref au lieu d'exiger une procédure d'arrêt avec un code de récupération rarement utilisé (et donc probablement bogué), arrêtez toujours le programme en le "plantant" et exécutez donc toujours le code de récupération au démarrage.
Cela ne s'applique pas à tout, mais dans certains cas, c'est une idée très soignée.
S'il existe un type de valeur qui a certaines contraintes sur sa valeur, créez une classe où ces contraintes sont appliquées par du code. Quelques exemples:
public class SanitizedHtmlString
{
private string val;
public SanitizedHtmlString(string val)
{
this.val = Sanitize(val);
}
public string Val
{
get { return val; }
}
//TODO write Sanitize method...
}
public class CarSpeed
{
private int speedInMilesPerHour;
public CarSpeed(int speedInMilesPerHour)
{
if (speedInMilesPerHour > 1000 || speedInMilesPerHour < 0)
{
throw new ArgumentException("Invalid speed.");
}
this.speedInMilesPerHour = speedInMilesPerHour;
}
public int SpeedInMilesPerHour
{
get { return speedInMilesPerHour; }
}
}
En C #: Au lieu de cela:
if( str==null )
Faites ceci:
if( String.IsNullOrEmpty(str) )
Je fais beaucoup de calculs dans mon travail en testant des semi-conducteurs à signaux mixtes sur l'équipement de test automatique de Teradyne (c, vba), Advantest (c ++ .net), etc.
Deux manœuvres défensives que j'utilise sont:
empêcher la division par zéro, si (x! = 0) {z = y/x; } else {/ * donnez à z un faux numéro reconnaissable, continuez le programme * /}
ne passez pas zéro ou des nombres négatifs pour enregistrer les calculs. Ceci est courant pour les calculs de gain, CMRR et PSRR. si (x> 0) {psrr = 20 * log (x); } else {psrr = -999; /*faux numéro */ }
Certains peuvent s'opposer à l'utilisation de faux chiffres, mais ces programmes sont utilisés dans la fabrication de semi-conducteurs à très haut volume. Si une erreur se produit lors du test d'une mauvaise pièce, il est préférable de continuer le test et de conserver l'intégrité du format des données. Les faux chiffres sont facilement séparés en tant que valeurs aberrantes lors du post-traitement des données de test.
-- Mike
Indépendant du langage: ne vous fiez jamais aux compilateurs, machines virtuelles, etc. en ce qui concerne l'initialisation. Initialisez toujours vos variables explicitement en valeurs utiles.
Les assertions sont votre meilleur ami, bien que les tests unitaires puissent mieux convenir dans certains cas.
C/C++: utilisez autant que possible des objets basés sur la pile.
Dans les conditions, vérifiez explicitement la valeur que vous attendez ou n'attendez pas. Par exemple, si vous avez une variable booléenne appelée activated
au lieu d'écrire if (activated)
write if (true == activated)
. La raison en est que activated
peut contenir suffisamment de déchets pour que le conditionnel réussisse.
If (some really bad condition) Then
Throw New Exception("particular bad thing happened")
End If
Habituellement, cela prend la forme
SUb public nouveau (clé comme guide)
Dim oReturn As returnpacket = Services.TableBackedObjectServices.GetData (clé)
Si oReturn.ds.tables (0) .Rows.Count = 0 alors Throw New Exception ("TableBackedObject chargé à partir de la clé est introuvable dans la base de données.")
Fin si
Étant donné que ce constructeur particulier est uniquement supposé être appelé lors du chargement d'un objet particulier après l'avoir sélectionné dans les résultats d'une procédure de recherche, ne pas le trouver est soit un bug, soit une condition de concurrence (ce qui signifierait qu'un autre utilisateur a supprimé l'objet par clé).
En c #, l'utilisation de TryParse au lieu de Parse pour les types de valeur pour éviter les exceptions comme FormatException, OverflowException, etc., bien sûr pour éviter d'écrire le bloc try pour le même.
Mauvais code
string numberText = "123"; // or any other invalid value
public int GetNumber(string numberText)
{
try
{
int myInt = int.Parse(numberText);
return myInt;
}
catch (FormatException)
{
//log the error if required
return 0;
}
catch (OverflowException)
{
return 0;
}
}
Bon code (si vous ne voulez pas gérer les erreurs)
string numberText = "123"; // or any other invalid value
public int GetNumber(string numberText, int defaultReturnValue)
{
int myInt;
return ( int.TryParse(numberText, out myInt) ) ? myInt : defaultReturnValue;
}
Vous pouvez faire de même pour presque tous les types de valeurs, par exemple: Boolean.TryParse, Int16.TryParse, decimal.TryParse etc.
Plutôt alors var.equals ("que ce soit") dans Java je fais "quoi" .equals (var). De cette façon, si var est nul, je n'ai pas à me soucier d'une exception nullpointer Cela fonctionne très bien lorsqu'il s'agit de choses comme les paramètres d'URL, etc.
Indépendant de la langue: problème: rendre compte et traiter des parties d'un tout. Chaque fois que les calculs et les pourcentages sont affichés, je garde toujours un total cumulé et pour la dernière entrée, sa valeur n'est pas calculée comme les autres, mais en soustrayant le total cumulé de 100,00. De cette façon, si une partie intéressée choisit d'ajouter tous les pourcentages de composants, elle s'ajoutera exactement à 100,00.
Ne pas avoir à faire face aux limites du langage est la meilleure défense que je puisse utiliser dans la logique de mon programme. Parfois, il est plus facile de dire quand les choses devraient stop.
Par exemple, vous avez ce type de boucle:
while(1)
{
// some codes here
if(keypress == escape_key || keypress == alt_f4_key
|| keypress == ctrl_w_key || keypress == ctrl_q_key) break;
// some codes here
}
Si vous souhaitez placer la condition sur l'en-tête de boucle, au lieu de lutter contre le langage pour ne pas avoir de construction ntil, copiez simplement la condition textuellement et mettez un point d'exclamation:
while(! (keypress == escape_key || keypress == alt_f4_key
|| keypress == ctrl_w_key || keypress == ctrl_q_key) )
{
// some codes here
}
Il n'y a pas de construction jusqu'à sur les langages dérivés de C, alors faites juste ce qui précède, sinon faites cela (possible en C/C++, utilisez #define ;-)
until(keypress == escape_key || keypress == alt_f4_key
|| keypress == ctrl_w_key || keypress == ctrl_q_key)
{
// some codes here
}
DELETE
: DELETE FROM sometable WHERE name IS LIKE 'foo%' LIMIT 1
. De cette façon, vous n'effacerez pas toute la table en cas d'erreur.À peine intelligent, mais une pratique décente, peut-être. En C/C++:
Toujours revenir d'une fonction en bas, jamais au milieu. La seule exception à cela est une vérification de null sur les arguments requis; qui vient toujours en premier et revient immédiatement (sinon je serais juste en train d'écrire une grosse condition "si" en haut qui semble juste idiote).
int MyIntReturningFuction(char *importantPointer)
{
int intToReturn = FAILURE;
if (NULL == importantPointer)
{
return FAILURE;
}
// Do code that will set intToReturn to SUCCESS (or not).
return intToReturn;
}
J'ai vu beaucoup d'arguments pour expliquer pourquoi cela n'a pas vraiment d'importance, mais le meilleur argument pour moi est simplement l'expérience. Trop souvent, je me suis gratté la tête en demandant "Pourquoi diable mon point de rupture près du bas de cette fonction n'est-il pas atteint?" seulement pour constater que quelqu'un d'autre que moi avait mis un retour quelque part au-dessus (et changeant généralement une condition qui aurait dû être laissée seule).
J'ai également constaté que le fait d'avoir des règles très simples comme celle-ci fait de moi un codeur beaucoup plus cohérent. Je ne viole jamais cette règle en particulier, donc je dois parfois penser à d'autres façons de gérer les choses (comme nettoyer la mémoire, etc.). Jusqu'à présent, cela a toujours été pour le mieux.