La création d'un nouveau système est un processus en plusieurs étapes: élaboration du concept et de la conception, conception de l'architecture, mise en œuvre, test, publication. La conception et la mise en œuvre d'une architecture sont les étapes qui intéressent principalement les développeurs.
La plupart des développeurs aiment se lancer dans l'architecture, pour réfléchir à la façon dont le système ou une partie de celui-ci sera organisé à partir de zéro. Si quelqu'un qui a réfléchi à l'architecture du système et va le mettre en œuvre, il n'y a pas de problème de motivation: le programmeur recevra la satisfaction de la réalisation de ses idées. Mais si une pensée de l'architecture et une autre seront engagées dans la mise en œuvre, alors cette dernière peut avoir une indignation naturelle: ils ont tout pensé pour moi, mais puis-je simplement faire ce qui est écrit?

Comment éviter de telles situations, pourquoi l'implémentation peut être non moins intéressante que l'élaboration de l'architecture, et parfois plus, sera discutée dans cet article.
Présentation
Une architecture bien pensée peut être utilisée comme base pour les tâches de ventilation : la mise en œuvre de chaque composant suffisamment séparé devient une sous-tâche distincte.
Par exemple, s'il existe un pipeline de traitement des requêtes conçu dans le style des tuyaux et des filtres , les sous-tâches seront l'implémentation d'étapes de traitement individuelles (chaque étape a sa propre sous-tâche) et une autre sous-tâche pour connecter toutes les étapes ensemble.
Bien qu'une architecture bien pensée et une répartition en sous-tâches donnent une idée générale de la façon de créer le système et vous permettent d'évaluer les coûts de main-d'œuvre, elles ne sont pas suffisantes pour mettre en œuvre le plan. La description de la sous-tâche indiquera ce que le composant doit faire, il peut contenir des exigences de vitesse et de consommation de mémoire, mais il ne donnera pas d'instructions détaillées sur la façon de le faire.
Le fait est qu'il existe de nombreuses options pour fabriquer un composant qui répond aux exigences spécifiées. Cela dépend beaucoup de la manière dont il sera implémenté: flexibilité du code, extensibilité, facilité de support, etc. Nous approchons du concept de conception de code .
Concept de conception de code
Parfois, la conception de code est appelée architecture ou organisation de code, parfois même simplement architecture. Je m'en tiens au terme de conception de code car il le contraste avec l'architecture du système et trace une ligne claire entre eux. Pour être plus précis, considérons un exemple.
Disons que nous développons le backend d'un site qui gagne en popularité. Le nombre de serveurs a déjà dépassé plusieurs dizaines, l'audience augmente et nous décidons de vouloir collecter des analyses sur le comportement des utilisateurs sur le site: popularité des pages de visite, fréquence d'utilisation des fonctionnalités en fonction du profil utilisateur, etc.
Un certain nombre de problèmes architecturaux et technologiques se posent: où stocker les métriques, comment les transférer sur le réseau, que faire si le magasin de métriques n'est pas disponible, comment le service backend enregistrera les métriques, etc. L'architecture a juste besoin de répondre à ces questions, de déterminer les composants de la solution et de définir leurs exigences.
Supposons que nous ayons développé une architecture: nous allons utiliser InfluxDB comme stockage, transférer des métriques sur le réseau en utilisant UDP vers telegraf , et pour contourner l'inaccessibilité du stockage, nous stockerons les métriques dans Kafka répliquées sur plusieurs serveurs. Toutes les métriques iront dans le sens du service backend -> telegraf -> Kafka -> InfluxDB. Pour écrire les métriques, le backend a décidé d'écrire un module qui implémente la fonction de transfert des métriques dans telegraf en utilisant UDP.
Le module d'enregistrement des métriques est un composant distinct du système, son écriture est une sous-tâche distincte qui peut être confiée au développeur. Cette sous-tâche comporte de nombreuses solutions et questions auxquelles il faut répondre: les mesures seront envoyées de manière synchrone ou asynchrone; comment l'accès simultané de plusieurs threads backend sera synchronisé, quelles seront les principales classes / fonctions.
Ces questions dépassent la description de l'architecture de la solution, mais les réponses à celles-ci ont des conséquences profondes. Par exemple, si pendant le fonctionnement d'une solution, il devient clair que la pile technologique n'est pas optimale et que vous devez remplacer telegraf par une solution alternative, alors une division incorrecte du module en classes ne le permettra pas sans réécrire tout le module. Les réponses à ces questions relèvent du domaine de la conception de code .
Le développement de la conception de code est une étape de conception distincte , qui se situe entre le développement de l'architecture du système et le codage. Tracer une ligne entre l'architecture et la conception de code vous permet de concevoir un système sans prendre en compte tous les détails et d'évaluer les coûts de main-d'œuvre en un temps limité. D'un autre côté, mettre en évidence le développement de la conception de code en tant qu'étape d'implémentation distincte vous permet d'augmenter la qualité de l'implémentation du système, de réduire le coût des améliorations supplémentaires et d'augmenter la facilité de prise en charge.
La nécessité de réfléchir à la conception du code au stade de l'implémentation avant de coder rend l'implémentation intéressante : les tâches de conception d'une conception de code peuvent être tout aussi intéressantes que la conception d'un système complet au niveau de l'architecture. Cette idée a été exprimée par Brooks dans le mois-homme mythique .
Bien sûr, tracer une ligne entre l'architecture et la conception de code peut ne pas être si facile, examinons de plus près ce problème.
La frontière entre architecture et conception de code
Sur le plan idéologique, l'architecture et la conception de code sont à différents niveaux de conception: l'architecture est pensée au tout début, lorsqu'il y a peu de certitude, et la réflexion sur la conception de code ajoute des détails. En conséquence, ils sont exécutés à différents moments: l'architecture est plus proche du tout début et la conception du code lors de la mise en œuvre des sous-tâches.
Tracer une frontière entre ces deux étapes de la conception dépend d'un certain nombre de facteurs, en voici les principaux:
- Le degré auquel le composant affecte le système. Parfois, le périphérique de l'ensemble du système peut dépendre de manière significative du périphérique de son composant individuel. Dans ce cas, vous devez concevoir le composant au stade du développement architectural et non au stade de la mise en œuvre.
- La présence d'une interface claire pour le composant. Il est possible d'isoler la conception d'un composant en tant que sous-tâche uniquement s'il est clairement défini ce que ce composant doit faire et comment il interagira avec le reste du système.
- Estimations réalistes de l'effort pour terminer la sous-tâche. La tâche peut être trop importante pour pouvoir évaluer les coûts de main-d'œuvre avec une précision suffisante. Dans ce cas, il est préférable de concevoir la tâche plus en détail et de la décomposer en ses propres sous-tâches afin de donner une évaluation plus adéquate des coûts de main-d'œuvre.
Il existe plusieurs cas particuliers dans lesquels vous pouvez tracer une bonne ligne entre la conception architecturale et la conception de code.
Le composant a une API stricte.
Par exemple, dans ma pratique, il y avait une tâche: implémenter au-dessus d'une API socket UNIX pour capturer / libérer les ressources du système d'exploitation utilisées par un démon existant. Cette tâche est née dans le cadre de l'architecture choisie pour la nouvelle fonctionnalité épique . Dans le cadre de l'architecture, il était plutôt de haut niveau de décrire l'API, et la conception détaillée a été faite plus tard, lors de la mise en œuvre.
Module / classe avec une interface donnée
La façon la plus simple de déléguer la conception d'une partie d'un système monolithique est de mettre en évidence un module ou une classe, de décrire son interface et ses tâches. Un module alloué en tant que sous-tâche distincte ne doit pas être trop volumineux. Par exemple, la bibliothèque cliente pour l'accès à la base de données des fragments est sans aucun doute un module distinct, mais la tâche de mise en œuvre de cette bibliothèque sera difficile à évaluer par les coûts de main-d'œuvre sans une conception plus détaillée. En revanche, l'implémentation d'une classe trop petite sera triviale. Par exemple, si la sous-tâche «implémenter une fonction qui vérifie l'existence d'un dossier donné par un chemin donné» apparaît, alors l'architecture est clairement pensée avec trop de détails.
Petit composant avec des exigences fixes
Si le composant est suffisamment petit et que le problème qu'il résout est strictement défini, les coûts de main-d'œuvre pour la mise en œuvre peuvent être estimés avec une précision suffisante, et la mise en œuvre du composant lui-même laissera de la place à la conception. Exemple: un processus s'exécutant sur une couronne et supprimant récursivement d'anciens fichiers et répertoires sur un chemin donné.
Antipatterns
Il existe des scénarios où la répartition entre la réflexion sur l'architecture et la mise en œuvre n'est pas correcte, certains d'entre eux sont discutés ci-dessous.
Tout est conçu dans les moindres détails.
Des diagrammes UML détaillés ont été construits, la signature de chaque méthode de chaque classe a été spécifiée, les algorithmes pour implémenter des méthodes individuelles ont été décrits ... Selon une description si détaillée, vous pouvez implémenter le système le plus rapidement, vraiment, car tout est planifié avec des détails tels qu'il n'y a pas de place pour la créativité, prenez-le et faites-le écrit. Si l'objectif est que le développeur code le plus rapidement possible ce qu'il lui dit, alors oui, vous le pouvez.
Cependant, si vous creusez un peu plus profondément, il deviendra clair un certain nombre de lacunes dans l'organisation du travail dans cette veine. Tout d'abord, pour tout concevoir dans de tels détails, vous devrez consacrer beaucoup de temps à la conception elle-même. Ce que le développeur pense généralement avant la mise en œuvre, l'architecte réfléchira à ce schéma: toute la conception se rapproche du début du projet, ce qui peut augmenter sa durée. Après tout, si vous ne divisez pas le travail de conception en plusieurs parties, vous ne pourrez pas le paralléliser. Deuxièmement, le manque de travail de conception pendant la mise en œuvre réduira considérablement la motivation des développeurs: faire exactement ce qu'ils disent peut être utile pour les débutants, mais les développeurs expérimentés s'ennuieront. Troisièmement, cette approche peut généralement réduire la qualité des résultats: le système, qui n'est pas divisé en composants suffisamment indépendants, sera plus difficile à maintenir et à développer.
L'architecture est toujours conçue par un développeur, le reste fumer de côté réaliser seulement
Tout d'abord, plusieurs cas sont à noter lorsque cela peut être utile. Tout d'abord, c'est une équipe dans laquelle il y a beaucoup de débutants et un seul programmeur expérimenté. Dans ce cas, les débutants n'ont pas assez d'expérience pour concevoir l'architecture pour faire le travail avec une qualité suffisante, tandis que dans le même temps, la mise en œuvre d'une architecture bien pensée les aidera à élever leur niveau. Deuxièmement, il s'agit de grands projets dans lesquels plusieurs équipes sont impliquées. Ensuite, la conception de l'architecture du projet est divisée en deux niveaux: l'architecte y réfléchit dans son ensemble et chaque équipe - l'architecture des composants dans son domaine de responsabilité.
Mais considérons une équipe composée de spécialistes suffisamment expérimentés. Si les tâches architecturales seront toujours attribuées à un seul, par exemple, le développeur le plus expérimenté, alors les autres développeurs ne seront pas en mesure de révéler pleinement leurs capacités. L'architecture du système sera unilatérale, car chacun a un ensemble de techniques qu'il applique. Si différents développeurs pensaient à l'architecture de différents composants / sous-systèmes, cela faciliterait l'échange d'expérience et le développement des membres de l'équipe. Même des membres de l'équipe pas trop expérimentés devraient parfois se voir confier des tâches architecturales: cela augmentera leur niveau et leur implication dans le projet.
Conclusion
La présence de la phase de conception dans l'implémentation est le principal facteur qui rend les tâches d'implémentation intéressantes. Bien sûr, il y en a d'autres: l'utilisation des nouvelles technologies, les tâches de recherche, mais elles sont, en règle générale, beaucoup moins courantes. Si les tâches d'implémentation ne nécessitent pas de conception et consisteront en un simple codage, cela affectera grandement la motivation des développeurs et ne permettra pas l'utilisation de leurs compétences.
La conception d'une conception de code au stade de la mise en œuvre vous permet de faire rapidement des estimations adéquates des coûts de main-d'œuvre, de paralléliser plus efficacement le travail et d'améliorer généralement la qualité du système.
La nécessité de concevoir une conception de code pendant l'implémentation est exactement ce qui rend le travail d'implémentation intéressant aux yeux des développeurs.
Cela ne vaut pas la peine de faire des erreurs, excluant le travail de conception de la mise en œuvre des sous-tâches, tout comme vous ne devriez pas toujours confier les tâches architecturales uniquement au développeur le plus expérimenté.