Tu vas détester ça ou un conte sur le bon code

Combien de copies sont cassées et seront toujours cassées à la recherche du code parfait.  Apparemment, le moment est venu et je devrais y participer :)


Bonne journée à tous. Il y a quelque temps, j'ai parlé aux étudiants du sujet «Qu'attendons-nous d'un bon code» et j'ai décidé de le reproduire ici. En cours de traduction, le texte a quelque peu changé, mais l'essence est restée la même. L'article s'est avéré simple (et certainement pas complet), mais il y a un grain rationnel ici.


Le code devrait fonctionner


Les choses évidentes n'apportent pas moins de problèmes du fait qu'elles sont évidentes. De nombreux projets s'effondrent uniquement parce que le développement s'est complètement détourné de la résolution de problèmes réels des utilisateurs


Parlons de ce que nous attendons du code. Eh bien, pour commencer, cela devrait fonctionner.


Cela semble évident, bien sûr, mais chacun de nous a déjà essayé ou lancé avec succès du code qui n'allait même pas, alors ne riez pas. Le deuxième point - le code devrait fonctionner correctement avec des situations incorrectes. C'est pour détecter les erreurs. Mais revenons au premier point et en parlons un peu.


Périodiquement, je reçois une tâche que je ne sais pas comment faire. C'est-à-dire, en général (j'essaie de regarder autour et d'essayer constamment quelque chose de nouveau). Et là, j'ai tout de suite été amené à écrire quelques abstractions, une sorte d'infrastructure, afin de retarder le moment du vrai travail. Donc, c'est faux. Le code devrait fonctionner. Je sais que je me répète, mais c'est un point important. Si vous ne savez pas comment résoudre le problème, ne vous précipitez pas pour créer des interfaces, des modules et c'est tout. C'est une mauvaise idée, qui se terminera à la fin de votre temps, et vous n'irez nulle part. Souvenez-vous qu'un code qui fonctionne mal est souvent mieux qu'un bon, mais qu'il ne fonctionne pas.


Il y a une vieille parabole sur deux éditeurs de logiciels qui fabriquaient le même produit. Le premier l'a fait de toute façon, mais le premier est entré sur le marché, et le second a tout fait parfaitement et était en retard. En conséquence, la première campagne a réussi à conquérir le marché et a acheté la deuxième entreprise. Il s'agit un peu d'un autre, mais l'idée principale est toujours la même. Nous résolvons d'abord le problème, puis nous rendons le code magnifique.


En général, faites d'abord un prototype fonctionnel. Qu'il soit boiteux, tordu et malheureux, mais lorsqu'on lui demande, on peut dire que la solution est déjà là, il reste à l'intégrer. Et réécrivez-le comme il se doit. Vous pouvez essayer d'exprimer cela avec une telle maxime - si vous savez comment faire la tâche - faites-la bien. Si vous ne le savez pas, résolvez-le d'abord d'une manière ou d'une autre.


Et il y a un point important. J'aimerais que vous compreniez. Ce n'est pas un appel pour écrire un mauvais code. Le code devrait être bon. Ceci est un appel à First Thing First - d'abord le code fonctionne, puis il refacture.


Parlons maintenant de Shit Happens. Donc, nous avons le code, cela fonctionne même. Au contraire, cela "fonctionne". Regardons un exemple simple:


public string Do(int x) { using (WebClient xx = new WebClient()) { return xx.DownloadString("https://some.super.url"); } } 

Ceci est un excellent exemple de code "fonctionnel". Pourquoi? Parce qu'il ne tient pas compte du fait que tôt ou tard, notre point de terminaison va tomber. Cet exemple ne prend pas en compte le cas dit de bord - borderline, "bad cases". Lorsque vous commencez à écrire du code, réfléchissez à ce qui pourrait mal tourner. En fait, je ne parle pas seulement des appels distants, mais de toutes les ressources qui sont hors de votre contrôle - entrée utilisateur, fichiers, connexions réseau, même la base de données. Tout ce qui peut se briser se brisera au moment le plus inopportun et la seule chose que vous puissiez faire à ce sujet est de vous y préparer autant que possible.


Malheureusement, tous les problèmes ne sont pas aussi évidents. Il existe un certain nombre de problèmes qui sont presque garantis pour générer des bogues. Par exemple, travailler avec des paramètres régionaux, avec des fuseaux horaires. C'est de la douleur et des cris "tout fonctionne sur ma machine". Ils ont juste besoin de les connaître et de travailler avec eux avec soin.


Soit dit en passant sur l'entrée utilisateur. Il existe un très bon principe, qui dit que toute entrée d'utilisateur est considérée comme incorrecte jusqu'à preuve du contraire. En d'autres termes, validez toujours ce que l'utilisateur a entré. Et oui, sur le serveur aussi.


Total:


  • Faites d'abord fonctionner le code,
  • Alors fais-le bien
  • N'oubliez pas les cas de bord et la gestion des erreurs.

Parlons maintenant de la prise en charge du code


Le support est un concept complexe, mais j'inclurais trois composants ici - le code devrait être facile à lire, facile à changer et cohérent.


Qui écrit les commentaires en russe? Personne n'écrit? Super En général, l'un des problèmes est le code non anglais. Ne fais pas ça. J'avais un morceau de code avec des classes en norvégien, et je ne pouvais tout simplement pas prononcer leurs noms. C'était triste. De toute évidence, la prise en charge d'un tel code (pour les non-Norvégiens) ne sera pas une tâche triviale. Mais c'est rare.


En général, la facilité de lecture concerne la dénomination et la structure. Les noms des entités - classes, méthodes, variables, doivent être simples, lisibles et porteurs de sens. Prenons notre exemple précédent.


 public string Do(int x) { using (WebClient xx = new WebClient()) { return xx.DownloadString("https://some.super.url"); } } 

Pouvez-vous comprendre ce que fait la méthode Do malgré l'implémentation? À peine. De même pour les noms de variables. Afin de comprendre quel type d'objet xx vous devez rechercher sa déclaration. Cela prend notre temps, nous empêche de comprendre ce qui, en termes généraux, se passe dans le code. Par conséquent, les noms doivent refléter l'essence de l'action ou du sens. Par exemple, si vous renommez la méthode Do en GetUserName, le code devient un peu plus clair et dans certains cas, nous n'avons plus à examiner son implémentation. De même avec les noms de variables sous la forme x et xx. Certes, il existe des exceptions généralement acceptées sous la forme de e pour les erreurs, i, k pour les compteurs de cycles, n pour les dimensions et quelques autres.


Encore une fois, pour un exemple, prenez votre code que vous avez écrit il y a un mois et essayez de le lire couramment. Comprenez-vous ce qui se passe là-bas? Si oui, je vous félicite. Sinon, vous avez un problème avec la lisibilité du code.


En général, il y a une telle citation intéressante:


"Il n'y a que deux choses difficiles en informatique: l'invalidation du cache et le nommage." © Phil Karlton

Il n'y a que deux choses complexes en informatique: l'invalidation du cache et le nommage.


Souvenez-vous d'elle lorsque vous donnez des noms à vos entités.


Le deuxième composant du code lisible est sa complexité ou sa structure. Je parle de ceux qui aiment écrire six ifas imbriqués, ou écrire un rappel dans un rappel de rappel à l'intérieur du rappel. JavaScript a même un terme appelé Callback Hell .


Parler de code parfait est facile, mais l'écrire est un peu plus difficile. La chose la plus importante ici est au moins de ne pas vous mentir. Si votre code est mauvais - ne l'appelez pas des bonbons, mais prenez-le et terminez-le


Ayez pitié de vos collègues et de vous-même. Après une semaine, vous devrez littéralement parcourir ce code pour corriger ou ajouter quelque chose. Éviter cela n'est pas si difficile:


  • Écrire des fonctions courtes,
  • Évitez beaucoup de ramification ou de nidification,
  • Séparez les blocs logiques de code en fonctions distinctes même si vous n'allez pas les réutiliser,
  • Utilisez le polymorphisme au lieu de if

Parlons maintenant d'une chose plus compliquée: ce bon code est facile à changer. Qui connaît le terme grosse boule de boue? Si quelqu'un n'est pas familier - regardez l'image.


En général, à cet égard, j'aime vraiment l'open source. Lorsque votre code est ouvert au monde entier, je veux en quelque sorte le rendre au moins normal.


Chaque module dépend de chaque module et les modifications du contrat en un seul endroit sont susceptibles de conduire à l'avènement du renard polaire ou, au moins, à un très long débogage. Théoriquement, gérer cela est assez simple - réduisez la dépendance de votre code à votre propre code. Moins votre code connaît les détails de l'implémentation, mieux ce sera pour lui. Mais en pratique, c'est beaucoup plus compliqué et conduit à une re-complication du code.


Sous forme de conseils, je dirais ceci:


  • Cachez votre code le plus profondément possible. Imaginez que demain, vous devrez le supprimer manuellement du projet. Combien d'endroits devrez-vous réparer et quelle sera la difficulté? Essayez de minimiser ce montant.
  • Évitez les dépendances circulaires. Séparez le code en couches (logique, interface, accès aux données) et assurez-vous que les couches du niveau "inférieur" ne dépendent pas des couches du niveau "supérieur". Par exemple, l'accès aux données ne doit pas dépendre de l'interface utilisateur.
  • Regroupez les fonctionnalités en modules (projets, dossiers) et masquez les classes à l'intérieur, ne laissant que la façade et les interfaces.

Et dessine. Il vous suffit de dessiner sur un morceau de papier comment vos données sont traitées par l'application et quelles classes sont utilisées pour cela. Cela vous aidera à comprendre les endroits trop compliqués avant que tout ne devienne irréparable.


Et enfin sur l'uniformité. Essayez toujours de respecter le style uniforme adopté par l'équipe, même si cela vous semble incorrect. Cela s'applique au formatage et aux approches pour résoudre le problème. N'utilisez pas ~~ pour l'arrondi, même s'il est plus rapide. L'équipe ne l'appréciera pas. Et lorsque vous commencez à écrire du nouveau code, regardez toujours le projet, peut-être que quelque chose dont vous avez besoin a déjà été implémenté et que vous pouvez le réutiliser.


Total:


  • Dénomination correcte
  • Bonne structure
  • Uniformité.

Le code devrait être assez productif


Ayons un peu froid. La prochaine exigence que nous considérerons est que le code soit assez productif.


Qu'est-ce que je veux dire par le mot "assez"? Tout le monde a probablement entendu dire que les optimisations prématurées sont mauvaises, elles tuent la lisibilité et compliquent le code. C'est vrai. Mais il est également vrai que vous devez connaître votre outil et ne pas écrire dessus pour que le client de messagerie Web charge Core I7 de 60%. Vous devez connaître les problèmes typiques qui entraînent des problèmes de performances et les éviter même au stade de l'écriture de code.


Revenons à notre exemple:


 public string GetUserName(int userId) { using (WebClient http = new WebClient()) { return http.DownloadString("https://some.super.url"); } } 

Ce code a un problème: le téléchargement synchrone sur le réseau. Il s'agit d'une opération d'E / S qui gèle notre flux jusqu'à son exécution. Dans les applications de bureau, cela conduira à une interface pendante, et dans les applications de serveur, à une réservation de mémoire inutile et à l'épuisement du nombre de requêtes vers le serveur. Connaissant simplement ces problèmes, vous pouvez déjà écrire du code plus optimisé. Et dans la plupart des cas, cela suffira.


Mais parfois, non. Par conséquent, avant d'écrire du code, vous devez savoir à l'avance quelles exigences sont définies en termes de performances.


Parlons maintenant des tests.


Ce sujet n'est pas moins holivny que le précédent, et peut-être même plus. Tout est compliqué avec des tests. Commençons par la déclaration - je pense que le code devrait être couvert par un nombre raisonnable de tests.


Pourquoi avons-nous même besoin d'une couverture de code et de tests? Dans un monde idéal, ils ne sont pas nécessaires. Dans un monde idéal, le code est écrit sans bogues et les exigences ne changent jamais. Mais nous vivons dans un monde loin d'être idéal, nous avons donc besoin de tests pour être sûr que le code fonctionne correctement (il n'y a pas de bogues) et que le code fonctionne correctement après avoir changé quelque chose. C'est l'avantage que les tests nous apportent. En revanche, même 100% (en raison des spécificités du calcul des métriques) couvertes par les tests ne garantit pas que vous avez tout couvert. De plus, chaque test supplémentaire ralentit le développement car après avoir changé la fonctionnalité, vous devrez également mettre à jour les tests. Par conséquent, le nombre de tests doit être raisonnable et la principale difficulté consiste à trouver un compromis entre la quantité de code et la stabilité du système. Trouver cette facette est assez difficile et il n'y a pas de recette universelle pour le faire. Mais il y a quelques astuces qui peuvent vous y aider.


  • Couvrir la logique d'application métier. La logique métier est tout ce pour quoi une application est créée, et elle doit être aussi stable que possible.
  • Couvrez des choses complexes et calculées. Calculs, transformations, fusion de données complexes. Celui où il est facile de se tromper.
  • Couvrir les bugs. Un bug est un indicateur qui nous indique que le code était vulnérable ici. Et c'est un bon endroit pour écrire un test ici.
  • Couvrir le code fréquemment réutilisé. Il est très probable qu'il sera mis à jour fréquemment et nous devons être sûrs que l'ajout d'une chose ne cassera pas l'autre.

Ne pas couvrir sans trop de besoins


  • Bibliothèques étrangères - recherchez les bibliothèques dont le code est déjà couvert par des tests.
  • Infrastructure - DI, mappage automatique (s'il n'y a pas de mappage compliqué) et ainsi de suite. Il existe des tests e2e ou d'intégration pour cela.
  • Choses triviales - attribution de données à des champs, renvoi d'appels, etc. Vous trouverez presque certainement des endroits beaucoup plus utiles pour les couvrir de tests.

Eh bien, c'est tout.


Pour résumer. Un bon code est


  • Code de travail
  • Facile à lire
  • Facile à changer
  • Assez rapide
  • Et couvert de tests dans la bonne quantité.

Bonne chance dans cette voie difficile. Et très probablement, tu vas détester ça. Eh bien, sinon, bienvenue.


En fait, c'est un monde étonnant de découvertes, d'opportunités et de créativité.  Ici seulement dans le quartier, il y a l'ennui de la fessée, la morosité de plus de 20 ans de code hérité et un champ de béquilles pour des termes durables.  Je préfère donc l'appeler le pont capillaire (C).  Recherchez les projets que vous allez graver.  Essayez de faire quelque chose de bien choisi.  Simplement, faites du monde un meilleur endroit et tout ira bien.

Source: https://habr.com/ru/post/fr429732/


All Articles