
Le code facile à déboguer est un code qui ne vous trompe pas. Il est plus difficile de déboguer du code avec un comportement caché, avec une mauvaise gestion des erreurs, avec des incertitudes, insuffisamment ou excessivement structuré, ou en train de changer. Dans les projets suffisamment grands, vous vous retrouvez avec du code que vous ne pouvez pas comprendre.
Si le projet est relativement ancien, vous pouvez rencontrer du code que vous avez oublié du tout, et s'il ne s'agissait pas du journal de validation, vous jureriez que ces lignes n'ont pas été écrites par vous. À mesure que le projet se développe, il devient plus difficile de se rappeler ce que font les différents morceaux de code. Et la situation est aggravée si le code ne fait pas ce qu'il semble faire. Et lorsque vous avez besoin de changer du code que vous ne comprenez pas, vous devez le comprendre dur: le débogage.
La capacité à écrire du code facile à déboguer commence par la compréhension que vous ne vous souvenez de rien écrit précédemment.
Règle 0: Un bon code contient des erreurs évidentes.
Les fournisseurs de technologies largement utilisées affirment que «écrire du code clair» signifie «écrire du code propre». Le problème est que le degré de «pureté» est très sensible au contexte. Le code pur peut être codé en dur dans le système, et parfois un hack sale est écrit de sorte qu'il est facile de le désactiver. Parfois, le code est considéré comme propre, car toute la saleté est poussée quelque part. Un bon code n'est pas nécessairement propre.
La propreté caractérise le degré de fierté (ou de honte) ressenti par le développeur par rapport à ce code, plutôt que la facilité de maintenance ou de changement. Il vaut mieux nous donner un code ennuyeux au lieu d'un code propre, dont les changements sont évidents: j'ai trouvé que les gens sont plus disposés à affiner la base de code si le fruit est assez bas et facile à cueillir. Le meilleur code est peut-être celui que vous venez de regarder et avez immédiatement compris comment il fonctionne.
- Du code qui n'essaye pas de créer un problème laid pour avoir l'air bien, ou un problème ennuyeux pour avoir l'air intéressant.
- Code dans lequel les erreurs sont évidentes et le comportement est clair, contrairement au code sans erreurs évidentes et avec un comportement peu clair.
- Le code dans lequel il est documenté dans lequel il n'est pas idéal, contrairement au code qui aspire à la perfection.
- Code avec un comportement si évident que tout développeur peut proposer une myriade de façons différentes de modifier ce code.
Parfois, le code est si méchant que toute tentative de le rendre plus propre ne fait qu'exacerber la situation. L'écriture de code sans comprendre les conséquences de leurs actions peut également être considérée comme un rituel d'invocation de code bien entretenu.
Je ne veux pas dire que le code propre est mauvais, mais parfois le désir de propreté ressemble plus à balayer les ordures sous un tapis. Le débogage du code n'est pas nécessairement propre; et le code bourré de vérifications ou de gestion des erreurs est rarement lisible.
Règle 1: Il y a toujours des problèmes avec l'ordinateur
L'ordinateur a des problèmes et le programme s'est planté lors de la dernière exécution.
L'application doit d'abord s'assurer qu'elle part d'un état connu, bon et sûr avant d'essayer de faire quelque chose. Parfois, il n'y a tout simplement pas de copie de l'état, car l'utilisateur l'a supprimé ou a mis à niveau l'ordinateur. Le programme s'est écrasé lors de la dernière exécution et, paradoxalement, également lors de la première exécution.
Par exemple, lors de la lecture ou de l'écriture d'un état dans un fichier, les problèmes suivants peuvent se produire:
- Le fichier est manquant.
- Le fichier est corrompu.
- Fichier d'une version plus ancienne ou plus récente.
- La dernière modification du fichier n'est pas terminée.
- Le système de fichiers vous ment.
Ces problèmes ne sont pas nouveaux, les bases de données les rencontrent depuis l'Antiquité (1970-01-01). L'utilisation de quelque chose comme SQLite aidera à faire face à de nombreux problèmes similaires, mais si le programme s'est écrasé lors de la dernière exécution, le code peut fonctionner avec des données erronées et / ou de manière erronée.
Par exemple, avec des programmes planifiés, quelque chose de cette liste se produira:
- Le programme démarrera deux fois en une heure en raison de l'heure d'été.
- Le programme démarrera deux fois car l'opérateur a oublié qu'il est déjà en cours d'exécution.
- Le programme démarrera tard en raison d'un manque d'espace disque libre ou de mystérieux problèmes de cloud ou de réseau.
- Le programme durera plus d'une heure, ce qui peut entraîner un retard dans les appels ultérieurs au programme.
- Le programme démarrera au mauvais moment de la journée.
- Le programme sera inévitablement exécuté peu avant une heure limite, par exemple, minuit, la fin du mois ou de l'année, et échouera en raison d'erreurs de calcul.
La création d'un logiciel durable commence par l'écriture d'un logiciel qui pense qu'il est tombé la dernière fois et se bloque si vous ne savez pas quoi faire. La meilleure chose à propos de lever une exception et de laisser un commentaire dans le style «cela ne devrait pas arriver» est que lorsque cela se produit inévitablement, vous aurez une longueur d'avance pour déboguer votre code.
Un programme n'est même pas obligé de se remettre d'un échec, il suffit de le laisser abandonner et de ne pas aggraver la situation. De petites vérifications qui génèrent des exceptions peuvent économiser des semaines sur l'espionnage, et un simple fichier de verrouillage peut gagner des heures sur la récupération à partir de sauvegardes.
Le code facile à déboguer est:
- un code qui vérifie si tout va bien avant de faire ce qu'ils demandent;
- code qui permet de revenir facilement à un état connu et de réessayer;
- ainsi que du code avec des niveaux de sécurité qui provoquent des erreurs le plus tôt possible.
Règle 2: Votre programme se bat avec lui-même
La plus grande attaque DoS de l'histoire de Google est venue de nous-mêmes (car nos systèmes sont très volumineux). Bien que de temps en temps, quelqu'un essaie de nous tester pour la force, mais nous sommes toujours capables de nous faire du mal plus que les autres.
Cela s'applique à tous nos systèmes.
Astrid Atkinson, ingénieur du jeu long
Les programmes se bloquent toujours lors de la dernière exécution; il n'y a toujours pas assez de processeur, de mémoire ou d'espace disque. Tous les travailleurs martèlent dans une file d'attente vide, tout le monde essaie de répéter une demande échouée et obsolète, et tous les serveurs font une pause en même temps pendant la collecte des ordures. Le système n'est pas seulement brisé, il essaie constamment de se briser.
Une grande difficulté peut même provoquer la vérification du système.
L'implémentation d'une vérification du fonctionnement du serveur peut être facile, mais uniquement si elle ne traite pas les demandes. Si vous ne vérifiez pas la durée de disponibilité continue, il est possible que le programme tombe entre les vérifications. Les bugs peuvent également être déclenchés par des contrôles de santé: j'ai dû faire des contrôles qui ont conduit au crash du système, qu'ils devaient protéger. Deux fois, avec une différence de trois mois.
Le code de gestion des erreurs conduira inévitablement à la découverte d'erreurs encore plus qui doivent être traitées, dont beaucoup proviennent de la gestion des erreurs elle-même. De même, les optimisations de performances sont souvent à l'origine de goulots d'étranglement dans le système. Une application agréable à utiliser dans un onglet se transforme en problème, étant lancée en 20 exemplaires.
Autre exemple: un travailleur dans un pipeline s'exécute trop rapidement et consomme de la mémoire disponible avant que la partie suivante du pipeline n'y accède. Cela peut être comparé aux embouteillages: ils surviennent en raison d'une augmentation de la vitesse et, par conséquent, la congestion du trafic augmente dans la direction opposée. Les optimisations peuvent donc générer des systèmes soumis à des charges élevées ou lourdes, souvent de façon mystérieuse.
En d'autres termes: plus le système est rapide, plus la pression est forte, et si vous ne laissez pas le système contrecarrer un peu, alors ne soyez pas surpris s'il se fissure.
La contre-action est l'une des formes de rétroaction du système. Le programme, qui est facile à déboguer, engage l'utilisateur dans la boucle de rétroaction, vous permet de voir tous les comportements au sein du système, aléatoires, intentionnels, souhaités et non souhaités. Vous pouvez facilement inspecter ce code, voir et comprendre les modifications qui en découlent.
Règle 3: Si vous laissez quelque chose d'ambigu maintenant, vous devrez le déboguer plus tard
En d'autres termes, il devrait être facile pour vous de suivre les variables du programme et de comprendre ce qui se passe. Prenez n'importe quelle routine avec l'algèbre linéaire cauchemardesque, vous devez vous efforcer de présenter l'état du programme aussi clairement que possible. Cela signifie qu'au milieu d'un programme, vous ne pouvez pas changer le but d'une variable, car l'utilisation d'une variable à deux fins différentes est un péché mortel.
Cela signifie également que vous devez éviter soigneusement le problème des semi-prédicats, ne jamais utiliser une seule valeur (
count
) pour représenter une paire de valeurs (
boolean
,
count
). Il faut éviter de renvoyer un nombre positif pour le résultat et en même temps retourner
-1
si rien ne correspond. Le fait est que vous pouvez facilement vous retrouver dans une situation où vous avez besoin de quelque chose comme "
0, but true
" (en outre, une telle fonctionnalité est en Perl 5); ou lorsque vous créez du code difficile à combiner avec d'autres parties du système (
-1
pour la partie suivante du programme peut ne pas être une erreur, mais une valeur d'entrée valide).
En plus d'utiliser une variable à deux fins, il n'est pas recommandé d'utiliser deux variables dans le même but, surtout si elle est booléenne. Je ne veux pas dire qu'il est mauvais d'utiliser deux nombres pour stocker une plage, mais utiliser des nombres booléens pour indiquer l'état d'un programme est souvent une machine à états masquée.
Lorsqu'un état ne passe pas de haut en bas, c'est-à-dire dans le cas d'un cycle épisodique, il est préférable de fournir à l'état sa propre variable et d'effacer la logique. Si vous avez un ensemble de booléens à l'intérieur de l'objet, remplacez-les par une variable appelée
state
et utilisez enum (ou une chaîne si nécessaire quelque part).
if
expressions ressembleront à
if state == name
, pas à
if bad_name && !alternate_option
.
Même si vous créez une machine à états explicite, il y a une possibilité de confusion: parfois, le code peut avoir deux machines à états cachées à l'intérieur. Une fois, j'ai été torturé d'écrire des proxys HTTP, jusqu'à ce que j'explicite chaque machine, que je suive l'état de la connexion et que je l'analyse séparément. Lorsque vous combinez deux machines d'état en une seule, il peut être difficile d'ajouter un nouvel état ou de comprendre exactement quel état quelque chose devrait avoir.
Il s'agit davantage de créer du code qui ne doit pas être débogué plutôt que de déboguer facilement. Si vous développez une liste d'états corrects, il sera beaucoup plus facile d'éliminer les états incorrects sans en manquer accidentellement un ou deux.
Règle 4: Le comportement aléatoire est le comportement attendu.
Lorsque vous ne comprenez pas ce que fait la structure de données, ces lacunes de connaissances sont comblées par les utilisateurs: tout comportement du code, intentionnel ou accidentel, dépendra finalement de quelque chose. De nombreux langages de programmation populaires prennent en charge les tables de hachage qui peuvent être itérées et qui, dans la plupart des cas, maintiennent l'ordre après l'insertion.
Dans certaines langues, le comportement de la table de hachage répond aux attentes de la plupart des utilisateurs, itérant sur les clés dans l'ordre dans lequel elles ont été ajoutées. Dans d'autres langues, la table de hachage à chaque itération renvoie les clés dans un ordre différent. Dans ce cas, certains utilisateurs se plaignent que le comportement n'est
pas assez aléatoire.
Malheureusement, toute source de hasard dans votre programme sera éventuellement utilisée pour la simulation statistique, ou pire encore - la cryptographie; et toute source de tri sera utilisée pour le tri.
Dans les bases de données, certains identifiants contiennent un peu plus d'informations que d'autres. En créant une table, le développeur peut choisir entre différents types de clé primaire. Le bon choix est l'UUID, ou quelque chose qui ne se distingue pas de lui. L'inconvénient des autres options est qu'elles peuvent divulguer des informations de commande et d'identification. Autrement dit, non seulement
a == b
, mais
a <= b
, et d'autres options signifient des clés d'incrémentation automatique.
Lorsque vous utilisez une clé d'incrémentation automatique, la base de données attribue un numéro à chaque ligne du tableau, en ajoutant 1 lors de l'insertion d'une nouvelle ligne. Et le tri est vague: les gens ne savent pas quelle partie des données est canonique. En d'autres termes, triez-vous par clé ou horodatage? Comme pour une table de hachage, les personnes choisissent elles-mêmes la bonne réponse. Et un autre problème est que les utilisateurs peuvent facilement prédire les enregistrements voisins avec d'autres clés.
Mais toute tentative de déjouer l'UUID échouera: nous avons déjà essayé d'utiliser des codes postaux, des numéros de téléphone et des adresses IP, et chaque fois a échoué lamentablement. Un UUID peut ne pas faciliter le débogage de votre code, mais un comportement moins aléatoire signifie moins de problèmes.
À partir des clés, vous pouvez extraire des informations non seulement sur la commande. Si, dans la base de données, vous créez des clés basées sur d'autres champs, les utilisateurs supprimeront les données et les restaureront à partir de la clé. Et deux problèmes se poseront: lorsque l'état du programme est stocké à plusieurs endroits, il sera très facile pour les copies d'être en désaccord les unes avec les autres; et les synchroniser sera plus difficile si vous ne savez pas lequel doit être changé ou lequel a changé.
Quoi que vous autorisiez vos utilisateurs à faire, ils le feront. Écrire un code facile à déboguer signifie réfléchir à des moyens de l'utiliser à mauvais escient, ainsi qu'à la façon dont les gens peuvent interagir avec lui en général.
Règle 5: le débogage est une tâche sociale, tout d'abord technique.
Lorsqu'un projet est divisé en composants et systèmes, il peut être beaucoup plus difficile de trouver des bogues. En comprenant comment le problème survient, vous pouvez coordonner les modifications dans différentes parties pour corriger le comportement. Corriger des bugs dans de grands projets nécessite moins de les trouver que de convaincre les gens de l'existence de ces bugs, ou de la possibilité même d'exister.
Il y a des bogues dans le logiciel, car personne ne sait vraiment qui est responsable de quoi. Autrement dit, il est plus difficile de déboguer le code lorsque rien n'est écrit, vous devez vous renseigner sur tout dans Slack, et personne ne répond jusqu'à ce qu'un expert arrive.
Cela peut être résolu avec la planification, les outils, les processus et la documentation.
La planification est un moyen de se débarrasser du stress de rester en contact, la structure de gestion des incidents. Les plans vous permettent d'informer les acheteurs, de libérer les personnes qui sont en contact depuis trop longtemps, de suivre les problèmes et d'apporter des modifications pour réduire les risques futurs. Outils - un moyen de réduire les exigences pour effectuer certains travaux, afin qu'ils deviennent plus accessibles aux autres développeurs. Un processus est un moyen de supprimer des fonctions de gestion de participants individuels et de les transmettre à une équipe.
Les gens et les modes d'interaction changeront, mais les processus et les outils resteront à mesure que l'équipe se transformera. Ce n'est pas que l'un est plus important que l'autre, mais que l'un est conçu pour soutenir les changements dans l'autre. Le processus peut également être utilisé pour supprimer les fonctions de contrôle de l'équipe. Ce n'est pas toujours bon ou mauvais, mais il y a toujours
une sorte de processus, même s'il n'est pas précisé. Et le fait de le documenter est la première étape pour laisser d'autres personnes changer ce processus.
La documentation est plus que des fichiers texte. C'est un moyen de transférer la responsabilité, comment vous amenez les gens au travail, comment vous signalez les changements aux personnes affectées par ces changements. L'écriture de documentation nécessite plus d'empathie que lors de l'écriture de code, et plus de compétences: il n'y a pas de simples drapeaux de compilation ou de vérification de type, et vous pouvez facilement écrire beaucoup de mots sans rien documenter.
Sans documentation, on ne peut pas s'attendre à ce que d'autres prennent des décisions en connaissance de cause, ou même soient d'accord avec les conséquences de l'utilisation du logiciel. Sans documentation, outils ou processus, il est impossible de partager le fardeau de la maintenance ou au moins de remplacer les personnes qui résolvent maintenant le problème.
Le désir de faciliter le débogage est applicable non seulement au code lui-même, mais également aux processus liés au code, cela aide à comprendre dans quel skin vous devez entrer pour corriger le code.
Le code facile à déboguer est facile à expliquer.
Il y a une opinion que si vous expliquez un problème à quelqu'un pendant le débogage, vous le comprenez vous-même. Pour cela, vous n'avez même pas besoin d'une autre personne, l'essentiel est de vous forcer à expliquer la situation à partir de zéro, à expliquer l'ordre de lecture. Et souvent, cela suffit pour prendre la bonne décision.
Si seulement. Parfois, lorsque nous demandons de l'aide, nous ne demandons pas ce dont nous avons besoin. C'est tellement courant qu'on l'appelle Le problème XY: «
Comment puis-je obtenir les trois dernières lettres d'un nom de fichier? Hein? Non, je voulais dire l'expansion . "
Nous parlons d'un problème en termes de solution que nous comprenons et nous parlons d'une solution en termes de conséquences que nous craignons. Le débogage est difficile à comprendre des conséquences inattendues et des solutions alternatives, il nécessite la chose la plus difficile pour un programmeur: admettre qu'il a mal compris quelque chose.
Il s'avère que ce n'était pas une erreur de compilation.