
Le sujet du code idéal provoque souvent une controverse parmi les programmeurs chevronnés. Il était d'autant plus intéressant d'obtenir l'avis du directeur du développement de Parallels RAS Igor Marnat. Sous la coupe, l'opinion de son auteur sur le sujet déclaré. Profitez-en!

En guise d'introduction, je voudrais m'attarder sur la question de savoir pourquoi j'ai décidé d'écrire ce court article. Avant de l'écrire, j'ai posé une question du titre à plusieurs développeurs. J'ai travaillé avec la plupart des gars pendant plus de cinq ans, avec un peu moins, mais je fais confiance à leur professionnalisme et à leur expérience sans condition. Toute expérience en développement industriel depuis plus de dix ans, tout le monde travaille dans des entreprises russes et internationales, des éditeurs de logiciels.
Certains collègues ont eu du mal à répondre (certains pensent encore), d'autres ont cité un ou deux exemples à la fois. Pour ceux qui ont donné des exemples, j'ai posé une question de clarification - "Qu'est-ce qui a provoqué cette admiration?" Les réponses étaient cohérentes avec les résultats de la phase suivante de ma petite recherche. J'ai cherché sur le Web des réponses à cette question dans différentes formulations proches du titre de l'article. Tous les articles ont répondu à peu près de la même manière que mes camarades ont répondu.
Les réponses des développeurs, ainsi que le libellé des articles trouvés, concernaient la lisibilité et la structure du code, l'élégance des constructions logiques, l'utilisation de toutes les fonctionnalités des langages de programmation modernes et le respect d'un certain style de conception.
Quand j'ai posé la question sur le "code divin" pour moi-même, la réponse est apparue immédiatement, du subconscient. J'ai immédiatement pensé à deux exemples de code avec lesquels je travaillais depuis longtemps (il y a plus de dix ans), mais je ressens toujours un sentiment d'admiration et une certaine vénération. Après avoir examiné les raisons de l'admiration pour chacun d'eux, j'ai formulé plusieurs critères, qui seront discutés ci-dessous. Je m'attarderai sur le premier exemple en passant, mais je voudrais analyser le second plus en détail. Soit dit en passant, à des degrés divers, tous ces critères sont pris en compte dans le manuel de chaque développeur «
Perfect Code » de Steve McConnell, mais cet article est sensiblement plus court.
Exemple des années 90
Le premier exemple que je mentionnerai concerne la mise en œuvre du protocole modem v42bis. Ce protocole a été développé à la fin des années 80 - début des années 90. Une idée intéressante, incarnée par les développeurs du protocole, est la mise en œuvre de la compression de flux d'informations pendant la transmission sur une ligne de communication instable (téléphonique). La différence entre la compression de flux et la compression de fichiers est fondamentale. Lors de la compression de fichiers, l'archiveur a la capacité d'analyser complètement l'ensemble de données, de déterminer l'approche optimale de compression et de codage des données et d'écrire l'intégralité des données dans le fichier sans se soucier des éventuelles pertes de données et de métadonnées. Lors de la décompression, à son tour, l'ensemble de données est à nouveau entièrement accessible, l'intégrité est assurée par une somme de contrôle. Avec la compression en ligne, l'archiveur ne peut accéder qu'à une petite fenêtre de données, il n'y a aucune garantie de perte de données, la nécessité de réinstaller la connexion et d'initialiser le processus de compression est courante.
Les auteurs de l'algorithme ont trouvé une solution élégante, dont la description prend
littéralement plusieurs pages . De nombreuses années ont passé, mais je suis toujours impressionné par la beauté et la grâce de l'approche proposée par les développeurs de l'algorithme.
Cet exemple ne fait toujours pas référence au code en tant que tel, mais plutôt à l'algorithme, nous ne nous attarderons donc pas sur lui plus en détail.
Linux est à la tête de tout!
Je voudrais analyser plus en détail le deuxième exemple de code parfait. Ceci est le code du noyau Linux. Le code qui, au moment de la rédaction, contrôle le travail de 500 supercalculateurs parmi les
500 premiers , le code qui fonctionne sur un téléphone sur deux dans le monde et qui contrôle la plupart des serveurs sur Internet.
Par exemple, considérez le fichier memory.c du
noyau Linux , qui appartient au sous-système de gestion de la mémoire.
1. Les sources sont faciles à lire. Ils sont écrits en utilisant un style très simple, facile à suivre et difficile à confondre. Les majuscules sont utilisées uniquement pour les directives et les macros du préprocesseur, tout le reste est écrit en petites lettres, les mots dans les noms sont séparés par des traits de soulignement. C'est peut-être le style de codage le plus simple possible, à l'exception de l'absence de style du tout. En même temps, le code est parfaitement lisible. L'approche d'indentation et de commentaire est visible à partir de n'importe quel morceau de n'importe quel fichier du noyau, par exemple:
static void tlb_remove_table_one(void *table) { smp_call_function(tlb_remove_table_smp_sync, NULL, 1); __tlb_remove_table(table); }
2. Il n'y a pas trop de commentaires dans le code, mais ceux qui sont généralement utiles. En règle générale, ils ne décrivent pas une action qui est déjà évidente dans le code (un exemple classique d'un commentaire inutile est «cnt ++; // increment counter»), mais le contexte de cette action - pourquoi ici on fait ce qui est fait, pourquoi est-ce fait ainsi, pourquoi ici, avec quelles hypothèses il est utilisé, avec quels autres endroits du code il est connecté. Par exemple:
void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long start, unsigned long end)
Une autre utilisation des commentaires dans le noyau est de décrire l'historique des modifications, généralement au début d'un fichier. L'histoire du noyau existe depuis près de trente ans, et c'est juste intéressant de lire certains endroits, vous sentez une partie de l'histoire:
3. Le code du noyau utilise des macros spéciales pour vérifier les données. Ils sont également utilisés pour vérifier le contexte dans lequel le code fonctionne. La fonctionnalité de ces macros est similaire à l'assertion standard, à la différence près que le développeur peut remplacer l'action qui est effectuée si la condition est vraie. Une approche générale du traitement des données dans le noyau - tout ce qui provient de l'espace utilisateur est vérifié, en cas de données erronées, la valeur correspondante est retournée. Dans ce cas, WARN_ON peut être utilisé pour émettre un enregistrement dans le journal du noyau. BUG_ON est généralement très utile lors du débogage de nouveau code et du démarrage du noyau sur de nouvelles architectures.
La macro BUG_ON provoque généralement l'impression du contenu des registres et de la pile et arrête tout le système ou le processus dans le contexte duquel l'appel correspondant s'est produit. La macro WARN_ON envoie simplement un message au journal du noyau si la condition est vraie. Il existe également des macros WARN_ON_ONCE et un certain nombre d'autres, dont la fonctionnalité ressort clairement du nom.
void unmap_page_range(struct mmu_gather *tlb, …. unsigned long next; BUG_ON(addr >= end); tlb_start_vma(tlb, vma); int apply_to_page_range(struct mm_struct *mm, unsigned long addr, … unsigned long end = addr + size; int err; if (WARN_ON(addr >= end)) return -EINVAL;
L'approche dans laquelle les données obtenues à partir de sources non fiables sont vérifiées avant utilisation, et la réponse du système aux situations «impossibles» est prévue et déterminée, simplifie considérablement le débogage et le fonctionnement du système. Vous pouvez considérer cette approche comme une implémentation du principe d'échec précoce et fort.
4. Tous les principaux composants du noyau fournissent aux utilisateurs des informations sur leur état via une interface simple, le système de fichiers virtuel / proc /.Par exemple, les informations sur l'état de la mémoire sont disponibles dans le fichier / proc / meminfo
user@parallels-vm:/home/user$ cat /proc/meminfo MemTotal: 2041480 kB MemFree: 65508 kB MemAvailable: 187600 kB Buffers: 14040 kB Cached: 246260 kB SwapCached: 19688 kB Active: 1348656 kB Inactive: 477244 kB Active(anon): 1201124 kB Inactive(anon): 387600 kB Active(file): 147532 kB Inactive(file): 89644 kB ….
Les informations ci-dessus sont collectées et traitées dans plusieurs fichiers sources du sous-système de gestion de la mémoire. Ainsi, le premier champ MemTotal est la valeur du champ totalram de la structure sysinfo, qui est rempli avec la fonction
si_meminfo du fichier page_alloc.c .
De toute évidence, organiser la collecte, le stockage et fournir à l'utilisateur l'accès à ces informations nécessite des efforts de la part du développeur et des frais généraux du système. Dans le même temps, les avantages d'avoir un accès pratique et facile à ces données sont inestimables à la fois dans le processus de développement et dans le fonctionnement du code.
Le développement de presque tous les systèmes doit commencer par un système de collecte et de fourniture d'informations sur l'état interne de votre code et de vos données. Cela facilitera grandement le processus de développement et de test et, plus tard, le fonctionnement.
Comme l'a
dit Linus , «Les mauvais programmeurs s'inquiètent du code. Les bons programmeurs se soucient des structures de données et de leurs relations. "
5. Tout le code est lu et discuté par plusieurs développeurs avant de s'engager. Un historique des modifications du code source est enregistré et disponible. Les modifications apportées à n'importe quelle ligne peuvent être retracées jusqu'à son occurrence - ce qui a changé, par qui, quand, pourquoi, quels problèmes ont été discutés par les développeurs. Par exemple, la modification de https://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2ef dans le code memory.c est inspirée du https://buggshb_b. Une petite optimisation du code a été effectuée (un appel pour activer la protection en écriture de la mémoire ne se produit pas si la mémoire est déjà protégée en écriture).
Il est toujours important pour un développeur travaillant avec du code de comprendre le contexte autour de ce code, avec quelles hypothèses le code a été créé, quoi et quand il a changé, afin de comprendre quels scénarios pourraient être affectés par les changements qu'il va apporter.
6. Tous les éléments importants du cycle de vie du code du noyau sont documentés et accessibles , en commençant
par le style de codage et en terminant par le
contenu et le calendrier de publication des versions stables du noyau . Chaque développeur et utilisateur qui souhaite travailler avec le code du noyau dans une capacité ou une autre dispose de toutes les informations nécessaires pour cela.
Ces moments m'ont paru importants, au fond, ils ont déterminé mon enthousiasme pour le code de base. De toute évidence, la liste est très courte et peut être étendue. Mais les points énumérés ci-dessus, à mon avis, se rapportent aux aspects clés du cycle de vie de tout code source du point de vue du développeur travaillant avec ce code.
Ce que je voudrais dire en conclusion. Les développeurs principaux sont intelligents et expérimentés, ils ont réussi. Prouvé par des milliards d'appareils Linux
Soyez un développeur de noyau, utilisez les meilleures pratiques et lisez le code complet!
Z.Y. Soit dit en passant, quels critères d'un code idéal avez-vous personnellement? Partagez vos pensées dans les commentaires.