Archives de catégorie : Vie de programmeur

Les liens de la semaine – Édition #209

Développement

.NET

Visual Studio 2017 et plus

Web

Technologie

Science et autres

Une petite introduction aux standards de programmation et à la revue de code

Comment devrait se dérouler une revue de code? Vous avez certainement eu à relire du code d’un autre programmeur afin de lui donner des commentaires. Qu’est-ce qu’on regarde? Quels sont les critères qui devraient être appliqués lorsque vous faites cette revue?

Tant de questions et une multitude de réponses. Je pense qu’il n’y a pas un programmeur qui va donner une réponse exactement identique. La programmation est tellement une activité libre pouvant être laissée au goût de chacun que ça varie énormément.

Par expérience, une revue systématique des changements qui sont produits avant la mise en production doit avoir lieu. Cette étape doit être obligatoire. Un collègue doit obligatoirement avoir jeté un coup d’œil à votre code et procédé aux tests de vos changements pour aller de l’avant.

Je me suis déjà prononcé dans le passé sur le sujet. À mon avis, tout débute par la qualité du code qui a été produit. La qualité de la solution réalisée sera directement liée à la qualité du code qui aura été développée. Cet impact est à plusieurs niveaux. Par exemple, une solution jetable sans égard à la séparation des responsabilités des différentes couches applicatives aura de la difficulté à évoluer avec le temps. Ou même, du code pas très propre avec, par exemple, des structures de données inappropriées sera difficile à comprendre à long terme.

À faire lors d’une revue de code

L’intention

La première question à se demander, en tant que reviewer, est la suivante: est-ce que, à la première lecture, êtes-vous en mesure de deviner l’intention de votre collègue dans sa résolution de problème? En aucun cas vous ne pouvez permettre de vous dire « Ah, oui, c’est bon c’est X qui a codé ça. Ce n’est pas beau, mais ça va aller ». Si c’est incohérent, incompréhensible ou simplement pas au niveau, ça doit retourner à la planche de dessin.

Première lecture

Il devrait être simple de faire une lecture rapide du code qui a été soumis et de comprendre le problème à résoudre. Une première lecture du code peut révéler certains détails que l’auteur n’a pas vus pendant le développement. Plusieurs éléments qui pourraient retenir votre attention ici.

Quelques exemples :

  • Est-ce que la convention de nommage utilisée représente bien le domaine d’affaires?
  • Y a-t-il est méthodes trop longues? En général une méthode doit faire quelques lignes, tout au plus.
  • Le code, qui a été ajouté (ou retiré), est-il au bon endroit?
  • Remarquez-vous de la duplication de code?

Signalement

Afin de ne rien oublier, notez et compilez tout ce que vous pouvez remarquer. Révisez par cette liste par la suite. Le travail de correction de votre collègue sera grandement simplifié si vous structurez vos remarques en sections logiques.

Par le fait même, considérez le facteur humain dans toute l’activité. Dans vos commentaires, mettez de l’emphase sur le code et vos suggestions pour améliorer ce qui a été produit. Les tournures de phrases comme « ton code » ou « tes changements » sont définitivement à éviter.

Une convention

Une convention de code permet d’émettre des lignes directrices qui servent à guider les développeurs dans leur prise de décision pour structurer leurs solutions. Il s’agit aussi de permettre d’unifier le design de l’application au goût de chacun selon l’humeur du moment.

Le plus important avec la convention de code est de promouvoir les meilleures pratiques avec votre langage de programmation. Il ne s’agit pas juste d’un exercice de style. Il faut tirer profit au maximum des fonctionnalités du langage que vous utilisez et aussi de vous protéger des moins bons côtés.

Cet aspect est à la base de tout. Cette convention doit être adoptée par tous et enseignée aux programmeurs se joignant à l’équipe.

D’ailleurs, si vous cherchez une base pour vous inspirer. Jetez un coup d’oeil à la section Framework Design Guidelines sur MSDN.

À valider

Indépendamment de la convention que vous employez, il y a quand même certains essentiels à regarder pour s’assurer que ce qui a été développé a un certain niveau de qualité.

  • Le code doit être clair et sans équivoque. Les variables sans acronymes. Les méthodes avec un nom concis, représentant ce qu’elle tente d’accomplir.
  • Les acronymes KISS, DRY et YAGNI ont leur raison d’être.
  • Viser un respect des principes SOLID. Ce n’est pas toujours évident, mais c’est un objectif à cibler.
  • Est-ce que les validations pour la valeur NULL ont été effectuées là où c’est nécessaire?
  • En cas d’erreur, l’application s’assure-t-elle de gérer ces erreurs convenablement. C’est à dire sans planter de façon catastrophique?
  • Toujours favoriser la lisibilité en faveur de la concision en ce qui a trait aux noms dans le code. (MSDN)
  • Valider le respect du rôle et responsabilité des différentes couches (Contrôleur, Vue, Service, Repository, DTO, Factory, etc)
  • Est-ce que le flow du code et la détection des erreurs est cohérent? Une méthode qui a une succession de if et/ou switch imbriqués ou plusieurs instructions return devrait générer une attention particulière.
  • Est-ce que les frameworks et librairies utilisées sont employés de la bonne façon et dans leur bon contexte?
  • Le choix des structures de données et algorithmes est-il approprié dans le contexte? Cela peut avoir un impact significatif sur la rapidité d’exécution de votre application.

Références

Vous ne déployez pas votre application en mode Release? Vous faites erreur!

Vous avez peut-être (ou pas du tout) remarqué cet article dans les liens de la semaine #201. Il y est question du déploiement d’une application en mode « Debug » en production. L’argument est bien simple. Le mode « Release » est synonyme d’application d’optimisations par le compilateur C# sur votre code. Ces optimisations sont, bien sûr, une façon très simple de donner un petit coup de turbo à votre application sans trop d’efforts.

À condition, bien évidemment, que vous pensiez à compiler votre application en mode « Release ». C’est aussi le point de l’article. Ce n’est pas tout. Il y a aussi le mode « debug » à désactiver. Cela se fait par l’entremise de l’attribut debug sur l’élément compilation qui doit être paramétré à « false ».

C’est à cet instant que ça m’a frappé. Si vous déployez votre application autrement qu’en mode debug et compilée en « Release », vous commettez un sérieux faux pas. C’est vraiment l’équivalent de passer « Go » sans réclamer son 200$ côté performance.

J’irais même plus loin. Il n’y a plus vraiment de bonnes excuses pour déployer manuellement une application de nos jours. Le déploiement devrait être une opération automatisée pouvant s’exécuter aussi fréquemment que possible. Les gens qui font des projets cloud n’ont, d’ailleurs, pas vraiment le choix de fonctionner ainsi. Bien souvent, l’accès aux environnements est assez limité.

Sommairement, les options qui s’offrent à nous sont :

  • Un script (Powershell ou équivalent) qui invoque MSBuild pour compiler votre application et préparer le déploiement. Avec .NET Core et le nouveau CLI, c’est vraiment un jeu d’enfant.
  • Utiliser un outillage comme Cake pour automatiser certaines tâches
  • Mettre en place un build server comme CCNet pour connecter votre gestionnaire de source à vos environnements de tests et production

Pour être sincère, bien souvent, la solution est à mi-chemin entre les deux. J’en conviens.

Pour les petites équipes/projets, investir du temps à configurer un serveur CCNet peut être difficile à trouver et à justifier. Je suis le premier à comprendre cela. Cependant, pour travailler dans un environnement où cet effort a été réalisé, le jeu en vaut réellement la chandelle.

Devoir repartir l’automatisation d’un projet demain matin, il est certain que je donnerais un sérieux coup d’oeil à Cake. Toutes les conditions de base de succès sont réunies dans cet outil pour permettre d’atteindre ce but. C’est-à-dire l’automatisation de la compilation et du déploiement d’une application en production.

Par le fait même, un processus de déploiement est simple et automatisé et aussi un synonyme de qualité d’environnement de développement. Dans mon équipe, il y a l’expression que « le build server a rarement tort ». Un déploiement réalisé par une machine tierce permet évidemment de mettre en évidence des pépins qui ne surviendraient pas sur un environnement de développement.

Un processus de déploiement bien rodé est celui où tous les développeurs d’une équipe peuvent déployer à leur guise et au moment qu’ils désirent. Pensez-y! Ne laissez rien au hasard côté déploiements. Automatisez le plus possible. Vous verrez, c’est payant à long terme.

Quelques leçons tirées d’optimisations de code

Il y a énormément de choses qu’un développeur doit se souvenir en développant. C’est loin d’être un exercice de tout repos. Respect des normes de programmation, mémorisation des bonnes pratiques de développement, connaissance de son framework. J’en passe. En même temps, c’est aussi ce qui fait la différence entre du code de qualité de celui qui ne l’est pas.

Ce qui est pernicieux avec la programmation est dans les choses que nous ne pouvons voir. Dans celles qu’il faut s’imaginer. L’effort dans l’action de la programmation réside principalement dans l’action cognitive de visualiser l’exécution du code qui est sous nos yeux. Là où ça se complique, c’est lorsqu’il y a des abstractions. Il est excessivement difficile de visualiser des abstractions. En supplément, plus il y en a, plus c’est compliqué à y voir clair.

Dernièrement, j’ai passé pas mal de temps à optimiser le temps de chargement d’une application. Dans cet exercice, j’y ai fait quelques constatations sur la façon d’intégrer quelques pratiques de programmation.

Avant tout, les métriques

Une boucle for au lieu d’un ForEach. Une cache par là. Mais qu’en est-il vraiment? Tenter d’optimiser son application sans avoir des données sur la performance de son code est un coup d’épée dans l’eau. Vraiment, c’est comme aller à la pêche sans hameçon.

Utilisez un outil comme dotTrace ou Ants afin d’être en mesure d’avoir visuellement une idée de ce qui se passe avec votre code. Sans cela, le reste ne vaut pas grand-chose. Avec ces outils, vous pouvez voir quelles sections de votre code sont exécutées, à quelle fréquence et leur temps d’exécution.

Comme dirait l’autre. On veut pas le savoir, on veut le voir!

Le volume et la fréquence

En ce qui concerne la performance, tout est une question de volume de données. C’est une perte de temps d’aborder le sujet d’optimisations si vous gérez un petit nombre de données. Tenter d’optimiser une recherche dans un tableau de dix éléments est un peu ridicule. L’intérêt de la notion de complexité d’algorithme varie proportionnellement selon le volume de données que vous avez à gérer.

Pour faire simple, plus une structure de données est utilisée fréquemment pour y faire des accès, plus l’intérêt pour l’optimiser est grand.

Base de données

Une des premières choses que j’ai l’habitude de regarder avec la performance c’est l’accès à la base de données. En général, accéder à la base de données c’est long et sujet à de la latence. Une situation que j’ai eu à optimiser est des appels à la base de données à l’intérieur d’une boucle. Des centaines de petites requêtes étaient exécutées unitairement. Pas cool.

Le calcul est très simple. Supposez que chaque appel à votre base de données nécessite 5 ms. Forcément, votre temps d’attente sera, au minimum, de 5 ms multiplié par votre nombre d’éléments à boucler. Au lieu de boucler un accès à la base de données, tentez de faire des opérations en lots. Soit l’équivalent d’une clause IN en SQL.

À ce sujet, il faut aussi être stratégique avec l’utilisation de la méthode .ToList() et l’invoquer le moins souvent possible. Par exemple, avec Entity Framework, l’utilisation de .ToList() signifie généralement l’instant où vous allez exécuter une requête à la base de données. Il faut éviter le plus possible des appels comme employees.Where(p=>p.Id < 3).ToList().Where(p=>p.Age>4).OrderBy(p=>p.Name).

Collections

Un dernier phénomène que j’ai eu l’occasion de revoir est la recherche d’éléments avec clé dans une liste. Par exemple, une liste d’employés où les éléments de la liste sont, à tous les coups, récupérés par leur identifiant d’employés.

À mon avis, la règle est simple, si la principale façon d’accéder aux éléments d’une liste ou un tableau est par une clé unique, forcément la liste doit être remplacée par un dictionnaire.

Par contre, l’utilisation d’un dictionnaire au lieu d’une liste n’est pas une solution miracle. Il faut être très bien conscient du contexte dans lequel utiliser un dictionnaire. La principale mise en garde est avec LINQ. Si vous utilisez LINQ avec un dictionnaire, sachez que vous perdez le principal intérêt de celui-ci. C’est-à-dire l’indexation.

Avec un dictionnaire, une simple condition LINQ .Where() fera en sorte que vous itérerez sur la totalité des clés. Soyez prudent.

Dans ma boîte à outils : Fingerprint pour versionner les ressources statiques

Un aspect primordial du développement est la mise en cache des ressources statiques qui sont utilisées par votre application web. De nos jours, cette pratique devient essentielle en raison du poids sans cesse augmenté des applications web. Il est donc essentiel d’évaluer ce qu’est un contenu dynamique et ce qui peut être mis en stockage permanent côté client.

Des applications plus lourdes à exécuter et des librairies CSS et JavaScript qui font de l’obésité. C’est un peu cela le web moderne.

À petite échelle, l’effort de s’assurer qu’un navigateur télécharge qu’une seule fois un fichier afin d’éviter de le réutiliser à travers vos différentes visites peut sembler exagéré. Cependant, lorsque votre application a un peu d’affluence, l’idée du caching a beaucoup de sens. Si vous payez pour votre bande passante, vous apprécierez tous les kilobits économisés.

Dans le même genre d’économies d’échelle, chaque requête que votre serveur web n’a pas à faire c’est des cycles de CPU qui peuvent être utilisés à faire d’autres traitements ou même vous permettre d’utiliser un serveur de plus petite capacité pour vos besoins. Considérant les coûts de certains hébergements dans le cloud, ce n’est pas si bête comme idée.

Mise en cache

Avec IIS (et les autres serveurs web), tout commence par l’activation de la cache des contenus statiques dans la configuration de votre application. Cela aura pour effet d’envoyer l’instruction aux navigateurs web n’éviter de télécharger inutilement les éléments image, CSS et JavaScript.

À ceci, j’ajouterais qu’en réalité, la configuration de la mise en cache des contenus est un peu plus complexe que cela. Ce que j’ai affirmé au paragraphe précédent va fonctionner pour une majorité de cas. Il s’agit uniquement d’une recette de base. Par exemple, il faut savoir quand et comment utiliser le etag, connaitre les différences entre les entêtes cache-control et expires.

Le web, c’est compliqué quand on s’attarde aux détails!

Fingerprint

Une fois que votre contenu est mis en cache pour les jours ou même les semaines à venir, que faire si vous venez de mettre à jour une partie de ceux-ci et désirez vous assurer qu’ils sont téléchargés par vos visiteurs?

Le meilleur et le plus simple mécanisme que je connaisse est d’introduire un paramètre d’URL indiquant un identifiant de version à vos fichiers statiques. Cet identifiant doit être incrémenté à chaque nouvelle révision de vos fichiers afin que le navigateur le télécharge à nouveau lorsqu’il sera modifié.

Ce que j’utilise depuis déjà un bon moment est l’utilitaire développé par Mads Kristensen nommé Fingerprint. Ce que cette classe fait est très simple. Elle permet la création d’URL convertissant la date de modification des fichiers désirés en un identifiant de version unique. Par exemple, la ressource /content/site.css devient /content/v-634933238684083941/site.css.

Cool, n’est-ce pas? En plus, c’est super simple à utiliser. Avec ASP.NET MVC, il suffit d’invoquer la classe Fingerprint de cette façon pour obtenir l’URL @Fingerprint.Tag(« /content/site.css »). Magie, magie.

La dernière chose à mentionner est l’ajout de cet élément de configuration dans votre web.config.

<rewrite>
  <rules>
    <rule name="fingerprint">
      <match url="([\S]+)(/v-[0-9]+/)([\S]+)" />
      <action type="Rewrite" url="{R:1}/{R:3}" />
    </rule>
  </rules>
</rewrite>

Parlant d’URL Rewrite, la seule vraie mise en garde est la configuration sur vos environnements de développement et de production. Fingerprint requiert l’installation de l’extension URL Rewrite sur vos serveurs IIS. Donc, si vous allez de l’avant avec ceci (je vous le conseille fortement), installez URL Rewrite avant tout. Sinon, votre site web sera simplement kaput à son démarrage.