Objectif du chapitre
Le style de programmation ne concerne pas seulement l'apparence du code. Un bon style doit permettre à un développeur de comprendre rapidement :
- ce que fait le programme ;
- pourquoi il le fait ;
- quelles données sont manipulées ;
- quelles fonctions ont quelles responsabilités ;
- quelles ressources sont possédées ou empruntées ;
- où se trouvent les opérations potentiellement dangereuses.
1. L'importance d'écrire du bon code
Du code correct n'est pas forcément du bon code.
Un programme peut compiler et produire le résultat attendu tout en étant difficile à comprendre, modifier ou déboguer. Le code professionnel doit donc viser plusieurs qualités :
- correction ;
- lisibilité ;
- simplicité ;
- cohérence ;
- maintenabilité ;
- facilité de débogage.
Le code source est lu beaucoup plus souvent qu'il n'est écrit. Le lecteur peut être :
- un collègue ;
- un mainteneur plusieurs années plus tard ;
- toi-même quelques mois après avoir écrit le programme ;
- une personne chargée de rechercher un bug.
Thinking Ahead
Lorsqu'on écrit une fonction, il faut penser à son utilisation future. Une fonction claire, correctement nommée et ayant une responsabilité précise sera beaucoup plus facile à réutiliser et à modifier.
2. Documenter son code
Les commentaires sont utiles lorsqu'ils apportent une information que le code ne communique pas suffisamment clairement.
Un commentaire ne doit normalement pas traduire mécaniquement une instruction C++ en français.
Mauvais principe :
Code: Select all
// Incrémente x
++x;
Un commentaire beaucoup plus utile explique une intention :
Code: Select all
// Ignore l'entrée réservée afin qu'elle ne soit pas traitée
// comme une entrée utilisateur normale.
Le code explique généralement COMMENT quelque chose est fait. Le commentaire doit surtout expliquer POURQUOI.
3. Raisons d'écrire des commentaires
Expliquer l'utilisation d'une fonction
Une fonction publique ou complexe peut nécessiter une documentation indiquant :
- son objectif ;
- le sens de ses paramètres ;
- ses préconditions ;
- la signification de sa valeur de retour ;
- les erreurs ou exceptions possibles ;
- les effets de bord importants.
Préconditions et postconditions
Une précondition décrit ce qui doit être vrai avant l'appel.
Exemples conceptuels :
- un pointeur doit être valide ;
- une taille doit être supérieure à zéro ;
- une ressource doit déjà avoir été initialisée.
Dans du code système, ces informations peuvent être très importantes lorsque plusieurs composants se partagent des ressources ou des buffers.
Expliquer les choix non évidents
Un algorithme peut sembler inutilement compliqué alors qu'il répond à une contrainte précise. Le commentaire doit alors expliquer cette contrainte.
Par exemple :
Code: Select all
// Cette copie intermédiaire est nécessaire car la zone source
// peut être invalidée par l'opération suivante.
4. Commenter du code compliqué
Lorsqu'un algorithme est complexe, des commentaires peuvent servir de repères.
Il est préférable de commenter les grandes étapes :
Code: Select all
// 1. Recherche de la position d'insertion.
// 2. Décalage des éléments.
// 3. Insertion du nouvel élément.
Mais il existe une meilleure solution lorsque le code devient vraiment difficile à suivre : le décomposer.
Une énorme fonction accompagnée de dizaines de commentaires est souvent le signe que plusieurs responsabilités devraient être séparées.
5. Les commentaires doivent rester corrects
Un commentaire faux peut être plus dangereux qu'une absence de commentaire.
Exemple :
Code: Select all
// Buffer de 256 octets.
std::byte buffer[512];
Lorsqu'on modifie du code, il faut donc vérifier les commentaires associés.
Un bon commentaire :
- apporte une information utile ;
- reste proche du code concerné ;
- est concis ;
- décrit l'intention plutôt que la syntaxe ;
- est mis à jour lorsque le comportement change.
Avant d'ajouter un commentaire pour expliquer une instruction incompréhensible, il faut se demander si le code pourrait simplement être rendu plus clair.
Au lieu d'une expression opaque :
Code: Select all
if (s && n > 0 && f)
Le meilleur commentaire est parfois un meilleur nom de variable, une constante correctement nommée ou une fonction extraite.
Le code auto-documenté
Un code est dit auto-documenté lorsque ses noms et sa structure communiquent naturellement son intention.
Cela ne signifie pas qu'il ne faut jamais commenter. Cela signifie qu'on ne doit pas utiliser les commentaires comme remplacement d'un code lisible.
7. Commentaires ligne par ligne
Commenter chaque ligne est généralement une mauvaise pratique.
Exemple :
Code: Select all
// Déclare le compteur.
int count = 0;
// Incrémente le compteur.
++count;
// Retourne le compteur.
return count;
Ils augmentent également la quantité d'informations qu'il faudra maintenir.
Un commentaire ligne par ligne est justifié lorsque l'instruction possède une signification qui n'est réellement pas évidente.
8. Commentaires de bloc et documentation
Un commentaire placé avant une fonction ou un bloc peut documenter son rôle général.
Pour une fonction importante, la documentation peut contenir :
- description ;
- paramètres ;
- retour ;
- exceptions ;
- conditions particulières.
Il faut néanmoins éviter de produire une documentation gigantesque pour des fonctions dont l'utilisation est évidente.
9. Commentaires ad hoc
Les commentaires temporaires servent parfois de marqueurs pendant le développement.
Exemples courants :
Code: Select all
// TODO: gérer le cas où la taille est nulle.
// FIXME: cette logique doit être revue.
Un commentaire temporaire doit idéalement être :
- précis ;
- actionnable ;
- facile à retrouver.
10. Commentaires de copyright
Certains projets placent une notice de copyright ou de licence en tête des fichiers.
Il s'agit d'une information juridique ou organisationnelle, pas d'une explication technique du code.
La convention utilisée dépend du projet et de l'organisation.
11. Décomposition
La décomposition consiste à transformer un gros problème en plusieurs problèmes plus petits.
Supposons qu'un programme doive :
- ouvrir un fichier ;
- lire son contenu ;
- analyser les données ;
- calculer des statistiques ;
- afficher le résultat.
Une meilleure conception sépare les responsabilités conceptuellement :
Code: Select all
ouvrir le fichier
lire les données
analyser les données
calculer les statistiques
afficher le résultat
12. Décomposition par fonctions
Une fonction devrait généralement représenter une opération cohérente.
Une bonne fonction possède :
- un objectif clair ;
- un nom précis ;
- des entrées compréhensibles ;
- une sortie identifiable ;
- une quantité raisonnable de logique.
L'objectif n'est pas d'obtenir le plus grand nombre de fonctions possible. L'objectif est de créer des unités logiques.
Signe qu'une fonction devrait être décomposée
Une fonction qui peut être décrite par :
« elle ouvre le fichier, vérifie son format, analyse les données, calcule des statistiques et affiche les erreurs »
contient probablement plusieurs responsabilités.
À l'inverse :
« elle vérifie la validité de l'en-tête »
correspond à une responsabilité beaucoup plus précise.
13. Avantages de la décomposition
La décomposition apporte plusieurs bénéfices :
- lecture plus rapide ;
- tests plus simples ;
- réutilisation ;
- débogage plus localisé ;
- modifications moins risquées ;
- réduction de la complexité mentale.
Une fonction appelante peut simplement demander une opération sans connaître toutes les étapes internes nécessaires pour la réaliser.
14. Décomposition dans du code système
Cette technique devient particulièrement intéressante en programmation système.
Imagine une opération qui nécessite conceptuellement :
- obtenir une ressource ;
- vérifier son état ;
- allouer un buffer ;
- effectuer une opération ;
- valider le résultat ;
- libérer les ressources.
C'est particulièrement utile avec :
- descripteurs de fichiers ;
- handles Windows ;
- sockets ;
- allocations mémoire ;
- mappings ;
- threads ;
- objets de synchronisation.
Le nom d'une variable ou d'une fonction constitue une forme de documentation.
Un bon nom permet de comprendre le rôle de l'élément sans rechercher immédiatement son origine.
Mauvais :
Code: Select all
int x;
int tmp;
int a2;
Code: Select all
int retryCount;
std::size_t bufferSize;
bool connectionClosed;
16. Choisir un bon nom
Un bon nom décrit l'intention et non simplement le type.
Par exemple :
Code: Select all
std::size_t fileSize;
Code: Select all
std::size_t number;
Code: Select all
readHeader()
validateBuffer()
calculateSize()
Code: Select all
isValid
hasData
shouldRetry
Les noms d'une lettre ne sont pas toujours mauvais.
Dans une petite boucle :
Code: Select all
for (int i = 0; i < count; ++i)
Mais une variable utilisée pendant 80 lignes et appelée `x` oblige le lecteur à mémoriser sa signification.
Plus la portée et la durée de vie d'une variable sont grandes, plus son nom devrait être explicite.
18. Éviter les noms trompeurs
Le pire nom n'est pas forcément un nom court : c'est un nom qui fait croire quelque chose de faux.
Une variable appelée :
Code: Select all
bufferSize
De même, une fonction appelée :
Code: Select all
checkFile()
Le nom doit correspondre au comportement réel.
19. Conventions de nommage
Plusieurs conventions existent.
Exemples :
Code: Select all
bufferSize
BufferSize
buffer_size
Le point essentiel est la cohérence.
Dans un projet existant, il est généralement préférable de suivre la convention du projet plutôt que d'imposer sa préférence personnelle.
Cela concerne :
- variables ;
- fonctions ;
- types ;
- constantes ;
- membres ;
- namespaces.
Certaines conventions utilisent des préfixes pour communiquer une information supplémentaire.
Historiquement, différents projets C/C++ ont utilisé des préfixes indiquant :
- un membre ;
- une variable statique ;
- une constante ;
- un type particulier.
Il ne faut cependant pas accumuler des préfixes uniquement par habitude. Les outils modernes et le système de types fournissent déjà beaucoup d'informations.
21. Utiliser les fonctionnalités du langage pour exprimer l'intention
Le C++ possède des mécanismes qui permettent au compilateur et au lecteur de comprendre certaines intentions.
Au lieu d'écrire une intention uniquement dans un commentaire, il est souvent préférable de l'exprimer directement dans le langage lorsque c'est possible.
C'est une idée fondamentale :
Une contrainte vérifiée par le compilateur est généralement plus fiable qu'une contrainte écrite uniquement dans un commentaire.
22. Utiliser const
`const` indique qu'une valeur ne doit pas être modifiée par l'intermédiaire considéré.
Exemple :
Code: Select all
const int maxRetries = 5;
Le compilateur peut également empêcher certaines modifications accidentelles.
`const` sert donc à la fois :
- à documenter l'intention ;
- à renforcer les contraintes du programme.
La const-correctness consiste à utiliser `const` de manière cohérente lorsque les données ne doivent pas être modifiées.
Pour un pointeur :
Code: Select all
const std::byte* data;
Dans du code manipulant des buffers, cette information est très précieuse.
Le lecteur peut immédiatement distinguer conceptuellement :
Code: Select all
const std::byte* input;
std::byte* output;
24. Les constantes nommées
Les valeurs littérales dispersées dans le code peuvent rendre le programme difficile à comprendre.
Exemple :
Code: Select all
if (retryCount >= 5)
Une constante nommée communique l'intention :
Code: Select all
constexpr int maxRetryCount = 5;
Code: Select all
if (retryCount >= maxRetryCount)
25. Les nombres magiques
Un nombre magique est une valeur apparaissant dans le code sans que sa signification soit évidente.
Exemple :
Code: Select all
buffer.resize(4096);
- une taille de page ;
- une taille de bloc ;
- une limite arbitraire ;
- une taille imposée par un format.
Par exemple :
Code: Select all
constexpr std::size_t blockSize = 4096;
Un code bas niveau rempli de :
Code: Select all
0x20
0x40
0x1000
0x2000
26. constexpr et les constantes modernes
Lorsque la valeur peut être connue à la compilation, `constexpr` permet d'exprimer cette propriété.
Exemple :
Code: Select all
constexpr std::size_t headerSize = 64;
Toutes les constantes n'ont pas besoin d'être globales. Une constante utilisée uniquement dans une fonction peut rester locale afin de limiter sa portée.
27. Références et pointeurs
Les références et pointeurs communiquent des intentions différentes.
Une référence :
Code: Select all
void process(Buffer& buffer);
Un pointeur :
Code: Select all
void process(Buffer* buffer);
- qu'une valeur nulle est possible ;
- qu'une sémantique particulière est nécessaire ;
- qu'on travaille naturellement avec une adresse.
En programmation système, les pointeurs restent indispensables.
La question est plutôt :
Quelle représentation exprime le mieux le contrat réel de l'interface ?
28. Références const
Une référence constante est particulièrement utile lorsqu'on veut recevoir un objet sans le copier et sans le modifier :
Code: Select all
void inspect(const Header& header);
- l'objet doit exister ;
- la fonction travaille sur l'objet original ;
- elle n'est pas censée le modifier par cette référence.
29. Pointeurs en programmation bas niveau
Dans le code système, les pointeurs sont naturels lorsqu'on manipule :
- buffers ;
- mémoire brute ;
- structures fournies par une API ;
- zones mappées ;
- adresses ;
- interfaces C ;
- structures de données internes.
Il faut pouvoir répondre rapidement à :
- le pointeur peut-il être nul ?
- qui possède la mémoire ?
- quelle est la taille accessible ?
- la mémoire est-elle modifiable ?
- combien de temps reste-t-elle valide ?
30. Ownership et durée de vie
L'ownership désigne la responsabilité de gérer la durée de vie d'une ressource.
Lorsqu'une fonction reçoit une adresse, le lecteur doit idéalement comprendre si elle :
- emprunte simplement la ressource ;
- prend possession de la ressource ;
- peut conserver l'adresse après son retour ;
- doit libérer quelque chose.
Le style, les types, les noms et la structure du programme doivent donc rendre ces relations aussi évidentes que possible.
31. Exceptions personnalisées
Le C++ permet de créer des types d'exceptions représentant des erreurs spécifiques.
Conceptuellement, une erreur précise est plus informative qu'une erreur générique.
Le lecteur peut comprendre immédiatement la catégorie de problème rencontrée.
Cependant, en programmation système, l'utilisation des exceptions dépend fortement du projet.
Certains environnements C++ les utilisent largement. D'autres, notamment certains composants très bas niveau, préfèrent :
- codes d'erreur ;
- valeurs de retour ;
- types représentant explicitement succès ou erreur.
32. Formatage
Le formatage n'affecte généralement pas le comportement du programme, mais influence fortement sa lisibilité.
Exemple compact :
Code: Select all
if(valid){process(data);}
Code: Select all
if (valid) {
process(data);
}
33. Indentation
L'indentation montre visuellement l'imbrication.
Exemple :
Code: Select all
if (connected) {
if (hasData) {
process();
}
}
Une indentation incohérente peut faire croire qu'une instruction appartient à un bloc alors que ce n'est pas le cas.
34. Accolades
Plusieurs styles d'accolades existent.
Exemple :
Code: Select all
if (condition) {
action();
}
Code: Select all
if (condition)
{
action();
}
Le choix du style est beaucoup moins important que sa cohérence dans le projet.
Pour les structures de contrôle, utiliser des accolades même lorsqu'une seule instruction est présente peut éviter certaines erreurs lors de modifications ultérieures.
Exemple :
Code: Select all
if (condition) {
action();
}
35. Espaces et parenthèses
Les espaces doivent faciliter la lecture.
Exemple :
Code: Select all
result = a + b * c;
Les parenthèses peuvent également être utilisées pour rendre l'ordre logique plus évident, même lorsque les règles de priorité des opérateurs suffiraient techniquement.
Le but n'est pas de montrer qu'on connaît par cœur toute la table de précédence : le but est que le lecteur comprenne immédiatement l'expression.
36. Tabs et espaces
Tabs contre espaces est principalement une convention de projet.
Le véritable problème apparaît lorsque plusieurs développeurs utilisent des configurations incompatibles et produisent un code dont l'alignement change selon l'éditeur.
Une équipe doit donc définir une règle commune ou utiliser un outil de formatage automatique.
Le choix précis est secondaire.
37. Retours à la ligne
Les systèmes utilisent historiquement différentes représentations des fins de ligne.
Cela peut provoquer des différences inutiles dans les outils de versionnement lorsqu'un fichier entier semble avoir changé uniquement à cause de son format de fin de ligne.
Les éditeurs et systèmes de contrôle de version modernes permettent généralement de gérer cette situation.
Pour un projet multiplateforme, il faut éviter de modifier accidentellement toutes les fins de ligne d'un fichier.
38. Formatage automatique
Les outils de formatage permettent d'appliquer automatiquement une convention.
C'est souvent préférable à des discussions interminables sur :
- l'emplacement des accolades ;
- le nombre d'espaces ;
- l'indentation ;
- les retours à la ligne.
39. Les défis stylistiques dans les vrais projets
Dans un projet réel, il est rarement possible d'obtenir un code parfaitement uniforme immédiatement.
On peut rencontrer :
- du vieux code ;
- plusieurs générations de développeurs ;
- des bibliothèques tierces ;
- des conventions historiques ;
- différents compilateurs ;
- différents systèmes d'exploitation.
40. Ne pas réécrire inutilement du code uniquement pour le style
Modifier énormément de lignes uniquement pour changer une convention peut :
- rendre les revues de code difficiles ;
- polluer l'historique du projet ;
- créer des conflits ;
- masquer les véritables changements fonctionnels.
Mais reformater arbitrairement une énorme codebase sans raison doit être évité.
41. Respecter le style d'une codebase existante
Lorsqu'on contribue à un projet, la cohérence collective est généralement plus importante que sa préférence personnelle.
Si un projet utilise une convention donnée, il est préférable de la suivre.
Un développeur professionnel doit pouvoir travailler dans plusieurs styles.
Le style est un outil au service de la compréhension, pas une identité personnelle.
42. Application directe au C++ système et bas niveau
Dans le développement système, la lisibilité est une mesure de sécurité et de fiabilité.
Un code peut manipuler simultanément :
- adresses mémoire ;
- tailles ;
- offsets ;
- flags ;
- handles ;
- buffers ;
- threads ;
- états de ressources.
43. Toujours associer un buffer à sa taille
Lorsqu'on manipule de la mémoire brute, l'adresse seule ne suffit souvent pas.
Conceptuellement :
Code: Select all
data + dataSize
Dans une fonction travaillant sur un buffer, il faut toujours savoir :
- où commence le buffer ;
- combien d'octets sont valides ;
- combien d'octets sont alloués ;
- si le buffer peut être modifié.
44. Distinguer taille en octets et nombre d'éléments
C'est une source classique de bugs.
Une variable appelée :
Code: Select all
size
Des noms comme :
Code: Select all
byteCount
elementCount
bufferCapacity
bytesRead
Dans du code bas niveau, l'unité d'une valeur fait partie de sa signification.
45. Les offsets doivent être explicites
Lorsqu'on analyse un format binaire, plusieurs valeurs numériques peuvent représenter :
- une adresse ;
- un offset depuis le début du fichier ;
- un offset depuis une structure ;
- une taille ;
- un index.
Exemple :
Code: Select all
headerOffset
sectionOffset
fileOffset
virtualAddress
rawSize
46. Isoler les opérations dangereuses
Une opération utilisant :
- casts bas niveau ;
- arithmétique de pointeurs ;
- mémoire brute ;
- API système complexe ;
- conversion de représentation ;
Cela permet de concentrer l'audit et les vérifications.
Au lieu de répandre des conversions partout, une petite interface bien définie peut centraliser les hypothèses nécessaires.
47. Vérifier les limites avant l'accès
Lorsqu'un programme analyse des données externes, il ne faut jamais supposer qu'elles sont valides.
Avant de lire une structure ou un champ, il faut conceptuellement vérifier que la zone correspondante appartient bien au buffer disponible.
Par exemple, avant de lire N octets à partir d'un offset, il faut s'assurer que cette plage est contenue dans les données disponibles.
Cette règle est fondamentale pour :
- parseurs binaires ;
- protocoles réseau ;
- formats de fichiers ;
- IPC ;
- données provenant d'une API non fiable.
Les flags numériques doivent être représentés par des noms significatifs lorsque cela est possible.
Mauvais :
Code: Select all
if (flags & 0x20)
Meilleur conceptuellement :
Code: Select all
if (flags & executableFlag)
49. Gérer clairement les ressources
Une fonction système peut acquérir plusieurs ressources.
Le lecteur doit pouvoir déterminer rapidement :
- où chaque ressource est acquise ;
- qui en est responsable ;
- quand elle est libérée ;
- ce qui arrive en cas d'erreur.
Réduire les portées et utiliser des abstractions de durée de vie appropriées simplifie énormément le raisonnement.
50. Limiter la portée des variables
Une variable devrait généralement être déclarée dans la portée la plus petite qui permet son utilisation.
Cela réduit :
- le nombre d'endroits pouvant la modifier ;
- la durée pendant laquelle son état doit être mémorisé ;
- les risques de réutilisation accidentelle.
51. Éviter les états implicites
Un programme devient difficile à comprendre lorsque le comportement d'une fonction dépend de nombreuses variables globales ou états cachés.
Plus les dépendances sont explicites, plus le code est facile à raisonner.
Une fonction dont les données nécessaires sont visibles dans son interface est généralement plus facile à tester et déboguer.
Dans le code système, certains états globaux sont parfois nécessaires, mais ils doivent rester contrôlés.
52. Commenter les hypothèses bas niveau
Certains commentaires sont extrêmement utiles en programmation système.
Exemples conceptuels :
Code: Select all
// La structure est garantie valide jusqu'au retour de cette fonction.
// L'offset est relatif au début du mapping.
// Cette opération doit être exécutée sous le verrou.
// La mémoire est fournie par l'appelant et ne doit pas être libérée ici.
C'est exactement le type de documentation qui possède une forte valeur.
53. Commenter le pourquoi des contraintes système
Lorsqu'une opération inhabituelle est nécessaire à cause d'une API, d'un ABI ou d'un format, documenter la raison évite qu'un futur développeur ne la supprime.
Exemple conceptuel :
Code: Select all
// Conserve cet alignement : le format binaire impose une frontière
// de 8 octets pour cette structure.
54. Le style et le débogage
Un programme correctement décomposé facilite énormément le débogage.
Si une opération est divisée en étapes clairement nommées, il devient plus facile de déterminer :
- quelle étape échoue ;
- quelles données étaient valides avant l'erreur ;
- quelle ressource était détenue ;
- quel invariant a été violé.
55. Le style et la performance
Écrire du code lisible ne signifie pas abandonner les performances.
Il faut d'abord exprimer clairement l'algorithme et les invariants.
Lorsqu'une optimisation est réellement nécessaire, elle peut rendre le code moins évident. Dans ce cas, un commentaire expliquant :
- pourquoi l'optimisation existe ;
- quelle hypothèse elle utilise ;
- quelle mesure a justifié son introduction ;
Une optimisation obscure sans justification risque d'être supprimée ou modifiée incorrectement.
56. Style C++ et interfaces C / système
Le C++ système communique fréquemment avec des interfaces C.
Cela implique souvent :
- pointeurs ;
- structures ;
- buffers ;
- codes d'erreur ;
- ressources représentées par des handles.
L'objectif n'est pas de cacher aveuglément le système, mais de rendre explicites :
- les durées de vie ;
- les erreurs ;
- les invariants ;
- les responsabilités.
1. Le code est écrit pour être lu.
Un programme qui fonctionne mais que personne ne comprend devient rapidement difficile à maintenir.
2. Les commentaires expliquent surtout le pourquoi.
Ne répète pas simplement l'instruction.
3. Un commentaire faux est dangereux.
La documentation doit évoluer avec le code.
4. Préfère rendre le code clair avant de le commenter.
Un bon nom ou une meilleure décomposition peut supprimer le besoin d'un commentaire.
5. Décompose les gros problèmes.
Chaque fonction devrait représenter une responsabilité compréhensible.
6. Les noms portent de l'information.
`byteCount` est plus précis que `n`.
7. Utilise const lorsque l'intention est la lecture seule.
Le compilateur peut ainsi faire respecter une partie du contrat.
8. Évite les nombres magiques.
Donne un nom aux valeurs possédant une signification métier ou technique.
9. Référence et pointeur ne communiquent pas exactement la même chose.
Choisis la représentation correspondant au contrat réel.
10. En bas niveau, ownership et durée de vie doivent être évidents.
Il faut savoir qui possède une ressource et combien de temps elle reste valide.
11. Pour chaque buffer, connais l'adresse et la taille.
Une adresse sans limites connues est dangereuse.
12. Précise les unités.
Octets, éléments, offsets et adresses ne doivent pas être confondus.
13. La cohérence du projet est plus importante que tes préférences de formatage.
Tabs contre espaces ou emplacement des accolades sont secondaires.
14. Isole les opérations dangereuses.
Les casts, calculs d'adresses et manipulations de mémoire brute doivent rester faciles à identifier et auditer.
15. En programmation système, le bon style contribue directement à la robustesse.
Quand le programme manipule directement mémoire et ressources système, rendre les hypothèses explicites réduit les erreurs humaines.
Résumé simplifié
Si tu ne devais retenir que la logique générale de ce chapitre :
Code: Select all
Code clair
|
+-- noms précis
|
+-- fonctions avec une responsabilité claire
|
+-- commentaires = pourquoi
|
+-- const pour exprimer l'immuabilité
|
+-- constantes nommées
|
+-- ownership et durée de vie explicites
|
+-- buffers accompagnés de leurs tailles
|
+-- formatage cohérent
|
+-- opérations dangereuses isolées
- Est-ce que je comprends immédiatement ce que représente cette variable ?
- Cette taille est-elle en octets ou en éléments ?
- Ce pointeur peut-il être nul ?
- Qui possède cette mémoire ?
- Combien de temps cette adresse reste-t-elle valide ?
- Cette fonction modifie-t-elle les données ?
- Cette valeur numérique possède-t-elle un nom ?
- Cette fonction fait-elle trop de choses ?
- Ce commentaire explique-t-il réellement quelque chose ?
- Un autre développeur pourrait-il modifier ce code sans casser une hypothèse invisible ?
