Une histoire sur la façon dont une équipe de pigistes écrit des applications JavaScript à pile complÚte

L'auteur du matériel, dont nous publions la traduction aujourd'hui, dit que le référentiel GitHub , sur lequel il a travaillé et plusieurs autres pigistes, a reçu, pour diverses raisons, environ 8 200 étoiles en 3 jours. Ce référentiel est arrivé en premiÚre place sur HackerNews et GitHub Trending, et 20 000 utilisateurs de Reddit l'ont voté.



Ce référentiel reflÚte la méthodologie de développement d'applications full-stack à laquelle cet article est consacré.

Contexte


J'allais écrire ce matériel pendant un certain temps. Je pense que je ne peux pas trouver de meilleur moment que celui-ci lorsque notre référentiel est trÚs populaire.


N ° 1 sur les tendances GitHub

Je travaille dans une équipe de pigistes . Nos projets utilisent React / React Native, NodeJS et GraphQL. Ce matériel est destiné à ceux qui veulent en savoir plus sur la façon dont nous développons des applications. De plus, il sera utile à ceux qui rejoindront notre équipe à l'avenir.

Je vais maintenant parler des principes de base que nous utilisons lors du développement de projets.

Plus c'est simple, mieux c'est.


«Plus c'est simple, mieux c'est» est plus facile à dire qu'à faire. La plupart des développeurs sont conscients que la simplicité est un principe important dans le développement de logiciels. Mais ce principe n'est pas toujours facile à suivre. Si le code est simple, cela facilite le support du projet et simplifie le travail d'équipe sur ce projet. De plus, le respect de ce principe aide à travailler avec du code écrit, disons, il y a six mois.

Voici les erreurs que j'ai rencontrées concernant ce principe:

  • DĂ©sir injustifiĂ© de respecter le principe DRY. Parfois, copier et coller du code est tout Ă  fait normal. Pas besoin de rĂ©sumer tous les 2 fragments de code qui sont quelque peu similaires les uns aux autres. J'ai moi-mĂȘme fait cette erreur. Tous l'ont peut-ĂȘtre commis. DRY est une bonne approche de programmation, mais le choix d'une abstraction ayant Ă©chouĂ© ne peut qu'aggraver la situation et compliquer la base de code. Si vous voulez en savoir plus sur ces idĂ©es, je vous recommande de lire la programmation AHA de Kent A Dodds.
  • Refus d'utiliser les outils disponibles. Un exemple de cette erreur est l'utilisation de reduce au lieu de map ou de filter . Bien sĂ»r, l'utilisation de reduce peut reproduire le comportement de la map . Mais cela est susceptible de conduire Ă  une augmentation de la taille du code, et au fait qu'il sera plus difficile pour d'autres personnes de comprendre ce code, Ă©tant donnĂ© que la «simplicitĂ© du code» est un concept subjectif. Parfois, il peut ĂȘtre nĂ©cessaire d'utiliser simplement reduce . Et si vous comparez la vitesse de traitement de l'ensemble de donnĂ©es Ă  l'aide des appels de map et de filter combinĂ©s dans une chaĂźne, et Ă  l'aide de reduce , il s'avĂšre que la deuxiĂšme option fonctionne plus rapidement. Dans l'option avec reduce vous devez regarder l'ensemble des valeurs une fois, pas deux. Nous avons devant nous un dĂ©bat sur la productivitĂ© et la simplicitĂ©. Dans la plupart des cas, je prĂ©fĂ©rerais la simplicitĂ© et j'Ă©viterais une optimisation prĂ©maturĂ©e du code, c'est-Ă -dire que je choisirais une paire map / filter au lieu de reduce . Et s'il s'avĂ©rait que la construction de la map et du filter devenait le goulot d'Ă©tranglement du systĂšme, cela traduirait le code Ă  reduce .

De nombreuses idées qui seront discutées ci-dessous visent à rendre la base de code aussi simple que possible et à la maintenir dans cet état.

Gardez des entités similaires proches les unes des autres


Ce principe, le «principe de colocation», s'applique à de nombreuses parties de la demande. C'est la structure des dossiers dans lesquels le code client et serveur est stocké, c'est le stockage du code de projet dans un référentiel, c'est aussi la prise de décision concernant le code dans un certain fichier.

▍ RĂ©fĂ©rentiel


Il est recommandĂ© de conserver le code client et serveur dans le mĂȘme rĂ©fĂ©rentiel. C'est simple. Ne compliquez pas ce qui n'est pas nĂ©cessaire pour compliquer. Avec cette approche, il est pratique d'organiser un travail d'Ă©quipe coordonnĂ© sur un projet. J'ai travaillĂ© sur des projets utilisant diffĂ©rents rĂ©fĂ©rentiels pour stocker des matĂ©riaux. Ce n'est pas un dĂ©sastre, mais les mono-dĂ©pĂŽts facilitent la vie.

▍ Structure du projet de la partie client de l'application


Nous écrivons des applications complÚtes. Autrement dit, le code client et le code serveur. La structure de dossiers d'un projet client typique fournit des répertoires distincts pour les composants, les conteneurs, les actions, les réducteurs et les itinéraires.

Des actions et des rĂ©ducteurs sont prĂ©sents dans les projets qui utilisent Redux. Je m'efforce de me passer de cette bibliothĂšque. Je suis sĂ»r qu'il existe des projets de haute qualitĂ© qui utilisent la mĂȘme structure. Certains de mes projets ont des dossiers sĂ©parĂ©s pour les composants et les conteneurs. Un dossier de composants peut stocker quelque chose comme des fichiers avec du code pour des entitĂ©s telles que BlogPost et Profile . Dans le dossier du conteneur, il existe des fichiers qui stockent le code du conteneur BlogPostContainer et ProfileContainer . Le conteneur reçoit des donnĂ©es du serveur et les transmet au composant enfant "stupide", dont la tĂąche est d'afficher ces donnĂ©es Ă  l'Ă©cran.

Il s'agit d'une structure de travail. C'est au moins homogÚne, et c'est trÚs important. Cela conduit au fait que le développeur, qui a rejoint le travail sur le projet, comprendra rapidement ce qui s'y passe et quel rÎle jouent ses différentes parties. L'inconvénient de cette approche, en raison de laquelle j'ai récemment essayé de ne pas l'utiliser, est qu'elle oblige le programmeur à se déplacer constamment dans la base de code. Par exemple, les BlogPostContainer ProfileContainer et BlogPostContainer n'ont rien en commun, mais leurs fichiers sont cÎte à cÎte et loin des fichiers dans lesquels ils sont réellement utilisés.

Depuis quelque temps, je m'efforce de placer des fichiers dont le contenu est prĂ©vu d'ĂȘtre partagĂ© dans les mĂȘmes dossiers. Cette approche de la structuration des projets repose sur le regroupement des fichiers en fonction de leurs capacitĂ©s. GrĂące Ă  cette approche, vous pouvez grandement vous simplifier la vie si, par exemple, vous placez le composant parent et son composant enfant «stupide» dans le mĂȘme dossier.

Habituellement, nous utilisons le dossier routes / screens et le dossier des components . Le dossier des composants stocke gĂ©nĂ©ralement du code pour des Ă©lĂ©ments tels que Button ou Input . Ce code peut ĂȘtre utilisĂ© sur n'importe quelle page de l'application. Chaque dossier du dossier des itinĂ©raires est une page d'application distincte. Dans le mĂȘme temps, les fichiers avec le code du composant et le code logique d'application liĂ©s Ă  cette route se trouvent dans le mĂȘme dossier. Et le code des composants utilisĂ©s sur plusieurs pages tombe dans le dossier des components .

Dans le dossier de routage, vous pouvez crĂ©er des dossiers supplĂ©mentaires dans lesquels le code responsable de la formation des diffĂ©rentes parties de la page est regroupĂ©. Cela est logique dans les cas oĂč l'itinĂ©raire est reprĂ©sentĂ© par une grande quantitĂ© de code. Ici, cependant, je voudrais avertir le lecteur qu'il ne vaut pas la peine de crĂ©er des structures Ă  partir de dossiers avec un niveau d'imbrication trĂšs Ă©levĂ©. Cela complique le mouvement du projet. Les structures de dossiers imbriquĂ©es profondes sont l'un des signes de la complexitĂ© excessive d'un projet. Il convient de noter que l'utilisation d'outils spĂ©cialisĂ©s, tels que les commandes de recherche, donne au programmeur des outils pratiques pour travailler avec le code du projet et pour trouver ce dont il a besoin. Mais la structure des fichiers du projet affecte Ă©galement sa convivialitĂ©.

En structurant le code du projet, vous pouvez regrouper des fichiers en fonction non pas de l'itinĂ©raire, mais des capacitĂ©s du projet implĂ©mentĂ©es par ces fichiers. Dans mon cas, cette approche se montre parfaitement sur des projets d'une seule page qui implĂ©mentent de nombreuses fonctionnalitĂ©s sur leur seule page. Mais il convient de noter que le regroupement des matĂ©riaux du projet par itinĂ©raire est plus facile. Cette approche ne nĂ©cessite pas d'efforts mentaux particuliers pour dĂ©cider quelles entitĂ©s doivent ĂȘtre placĂ©es les unes Ă  cĂŽtĂ© des autres et pour rechercher quelque chose.

Si nous allons plus loin dans le regroupement du code, nous pouvons dĂ©cider que le code des conteneurs et des composants sera Ă  juste titre placĂ© dans le mĂȘme fichier. Et vous pouvez aller encore plus loin: mettez le code de deux composants dans un seul fichier. Je suppose que vous pensez peut-ĂȘtre maintenant que recommander de telles choses est vraiment un blasphĂšme. Mais en rĂ©alitĂ©, tout est loin d'ĂȘtre si mauvais. En fait, cette approche est pleinement justifiĂ©e. Et si vous utilisez des hooks React, ou du code gĂ©nĂ©rĂ© (ou les deux), je recommanderais cette approche.

En fait, la question de savoir comment dĂ©composer le code en fichiers n'est pas d'une importance capitale. La vraie question est de savoir pourquoi vous devrez peut-ĂȘtre diviser les composants en intelligents et stupides. Quels sont les avantages de cette sĂ©paration? Il y a plusieurs rĂ©ponses Ă  cette question:

  1. Les applications ainsi conçues sont plus faciles à tester.
  2. Le développement de telles applications facilite l'utilisation d'outils comme le Storybook.
  3. Les composants stupides peuvent ĂȘtre utilisĂ©s avec de nombreux composants intelligents diffĂ©rents (et vice versa).
  4. Les composants intelligents peuvent ĂȘtre utilisĂ©s sur diffĂ©rentes plateformes (par exemple, sur les plateformes React et React Native).

Tous ces arguments sont rĂ©els en faveur de la division des composants en «intelligent» et «stupide», mais ils ne sont pas applicables Ă  toutes les situations. Par exemple, nous utilisons souvent Apollo Client avec des crochets lors de la crĂ©ation de projets. Afin de tester de tels projets, vous pouvez crĂ©er des simulations de rĂ©ponse Apollo ou des simulateurs de crochet. Il en va de mĂȘme pour le livre de contes. Si nous parlons de mĂ©langer et de partager des composants «intelligents» et «stupides», alors, en fait, je ne l'ai jamais rencontrĂ© dans la pratique. Concernant l'utilisation multiplateforme du code, il y avait un projet dans lequel j'allais faire quelque chose de similaire, mais je ne l'ai jamais fait. C'Ă©tait censĂ© ĂȘtre le mono-rĂ©fĂ©rentiel Lerna. De nos jours, au lieu de cette approche, vous pouvez opter pour React Native Web.

En consĂ©quence, nous pouvons dire que la sĂ©paration des composants en «intelligent» et «idiot» a une certaine signification. Il s'agit d'un concept important qui mĂ©rite d'ĂȘtre connu. Mais souvent, vous n'avez pas Ă  vous en prĂ©occuper, surtout compte tenu de l'apparition rĂ©cente des crochets React.

Le point fort de la combinaison des capacitĂ©s des composants «intelligents» et «idiots» dans une mĂȘme entitĂ© est qu'elle accĂ©lĂšre le dĂ©veloppement et simplifie la structure du code.

De plus, si un tel besoin se fait sentir, un composant peut toujours ĂȘtre divisĂ© en deux composants distincts - «intelligent» et «stupide».

Stylisation


Nous utilisons des composants émotion / style pour les applications de style. Il y a toujours la tentation de séparer les styles dans un fichier séparé. J'ai vu certains développeurs faire cela. Mais, aprÚs avoir essayé les deux approches, je n'ai finalement pas trouvé de raison de déplacer les styles vers un fichier séparé. Comme dans le cas de beaucoup d'autres choses, dont nous parlons ici, un développeur peut lui faciliter la vie en combinant les styles et les composants auxquels ils se rapportent dans un seul fichier.

Structure Structure du projet de la partie serveur de l'application


Tout ce qui précÚde est vrai en ce qui concerne la structuration du code cÎté serveur de l'application. Une structure typique que j'essaie personnellement d'éviter pourrait ressembler à ceci :

 src │ app.js #     └───api #   Express      └───config #      └───jobs #    agenda.js └───loaders #     └───models #    └───services # - └───subscribers #      └───types #    (d.ts)  Typescript 

Nous utilisons gĂ©nĂ©ralement GraphQL dans nos projets. Par consĂ©quent, ils utilisent des fichiers qui stockent des modĂšles, des services et des outils de reconnaissance. Au lieu de les disperser Ă  diffĂ©rents endroits du projet, je les rassemble dans un dossier. Le plus souvent, ces fichiers seront partagĂ©s, et il sera plus facile de travailler avec eux s'ils sont stockĂ©s dans le mĂȘme dossier.

N'écrasez pas les définitions de type plusieurs fois


Nous utilisons de nombreuses solutions dans nos projets qui sont en quelque sorte liĂ©es aux types de donnĂ©es. Ce sont TypeScript, GraphQL, les schĂ©mas de base de donnĂ©es et parfois MobX. En consĂ©quence, il peut s'avĂ©rer que les types pour les mĂȘmes entitĂ©s sont dĂ©crits 3-4 fois. De telles choses devraient ĂȘtre Ă©vitĂ©es. Nous devons nous efforcer d'utiliser des outils qui gĂ©nĂšrent automatiquement des descriptions de type.

Sur le serveur, une combinaison de TypeORM / Typegoose et TypeGraphQL peut ĂȘtre utilisĂ©e Ă  cet effet. Cela suffit pour dĂ©crire tous les types utilisĂ©s. TypeORM / Typegoose vous permet de dĂ©crire le schĂ©ma de base de donnĂ©es et les types TypeScript correspondants. TypeGraphQL vous aidera Ă  crĂ©er les types de GraphQL et TypeScript.

Voici un exemple de détermination des types de TypeORM (MongoDB) et TypeGraphQL dans un fichier:

 import { Field, ObjectType, ID } from 'type-graphql' import { Entity, ObjectIdColumn, ObjectID, Column, CreateDateColumn, UpdateDateColumn, } from 'typeorm' @ObjectType() @Entity() export default class Policy { @Field(type => ID) @ObjectIdColumn() _id: ObjectID @Field() @CreateDateColumn({ type: 'timestamp' }) createdAt: Date @Field({ nullable: true }) @UpdateDateColumn({ type: 'timestamp', nullable: true }) updatedAt?: Date @Field() @Column() name: string @Field() @Column() version: number } 

Le générateur de code GraphQL peut également générer de nombreux types différents. Nous utilisons cet outil pour créer des types TypeScript sur le client, ainsi que des hooks React qui accÚdent au serveur.

Si vous utilisez MobX pour contrĂŽler l'Ă©tat de l'application, puis en utilisant quelques lignes de code, vous pouvez obtenir des types TS gĂ©nĂ©rĂ©s automatiquement. Si vous utilisez Ă©galement GraphQL, vous devriez jeter un Ɠil au nouveau package - MST-GQL , qui gĂ©nĂšre une arborescence d'Ă©tat Ă  partir du schĂ©ma GQL.

L'utilisation de ces outils ensemble vous évitera de réécrire de grandes quantités de code et vous aidera à éviter les erreurs courantes.

D'autres solutions, telles que Prisma , Hasura et AWS AppSync , peuvent également aider à éviter les déclarations de type en double. Bien entendu, l'utilisation de tels outils a ses avantages et ses inconvénients. Dans les projets que nous créons, de tels outils ne sont pas toujours utilisés, car nous devons déployer le code sur nos propres serveurs d'organisations.

Dans la mesure du possible, recourir à des moyens de génération automatique de code


Si vous regardez le code que vous crĂ©ez sans utiliser les outils ci-dessus pour gĂ©nĂ©rer automatiquement du code, il s'avĂšre que les programmeurs doivent constamment Ă©crire la mĂȘme chose. Le principal conseil que je peux donner Ă  ce sujet est que vous devez crĂ©er des extraits pour tout ce que vous utilisez souvent. Si vous entrez souvent la commande console.log , crĂ©ez un extrait comme cl , qui se transforme automatiquement en console.log() . Si vous ne le faites pas et que vous me demandez de vous aider avec le dĂ©bogage du code, cela me dĂ©rangera beaucoup.

Il existe de nombreux packages avec des extraits, mais il est facile de créer vos propres extraits. Par exemple, en utilisant le générateur d'extraits .

Voici le code qui me permet d'ajouter certains de mes extraits préférés à VS Code:

 { "Export default": {   "scope": "javascript,typescript,javascriptreact,typescriptreact",   "prefix": "eid",   "body": [     "export { default } from './${TM_DIRECTORY/.*[\\/](.*)$$/$1/}'",     "$2"   ],   "description": "Import and export default in a single line" }, "Filename": {   "prefix": "fn",   "body": ["${TM_FILENAME_BASE}"],   "description": "Print filename" }, "Import emotion styled": {   "prefix": "imes",   "body": ["import styled from '@emotion/styled'"],   "description": "Import Emotion js as styled" }, "Import emotion css only": {   "prefix": "imec",   "body": ["import { css } from '@emotion/styled'"],   "description": "Import Emotion css only" }, "Import emotion styled and css only": {   "prefix": "imesc",   "body": ["import styled, { css } from ''@emotion/styled'"],   "description": "Import Emotion js and css" }, "Styled component": {   "prefix": "sc",   "body": ["const ${1} = styled.${2}`", "  ${3}", "`"],   "description": "Import Emotion js and css" }, "TypeScript React Function Component": {   "prefix": "rfc",   "body": [     "import React from 'react'",     "",     "interface ${1:ComponentName}Props {",     "}",     "",     "const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = props => {",     " return (",     " <div>",     " ${1:ComponentName}",     " </div>",     " )",     "}",     "",     "export default ${1:ComponentName}",     ""   ],   "description": "TypeScript React Function Component" } } 

En plus des extraits, les gĂ©nĂ©rateurs de code peuvent aider Ă  gagner du temps. Vous pouvez les crĂ©er vous-mĂȘme. J'aime utiliser plop pour cela.

Angular a ses propres gĂ©nĂ©rateurs de code intĂ©grĂ©s. À l'aide des outils de ligne de commande, vous pouvez crĂ©er un nouveau composant, composĂ© de 4 fichiers, qui prĂ©sente tout ce que vous pouvez espĂ©rer trouver dans le composant. Il est dommage que React ne dispose pas d'une telle fonctionnalitĂ© standard, mais quelque chose de similaire peut ĂȘtre créé indĂ©pendamment en utilisant plop. Si chaque nouveau composant que vous crĂ©ez doit ĂȘtre prĂ©sentĂ© sous la forme d'un dossier contenant un fichier avec le code du composant, un fichier de test et un fichier Storybook, le gĂ©nĂ©rateur vous aidera Ă  crĂ©er tout cela avec une seule commande. Dans de nombreux cas, cela facilite considĂ©rablement la vie du dĂ©veloppeur. Par exemple, lorsque vous ajoutez une nouvelle fonctionnalitĂ© au serveur, exĂ©cutez simplement une commande sur la ligne de commande. AprĂšs cela, des fichiers de l'entitĂ©, des services et des modules de reconnaissance seront automatiquement créés contenant toutes les structures de base nĂ©cessaires.

Une autre force des gĂ©nĂ©rateurs de code est qu'ils contribuent Ă  l'uniformitĂ© du dĂ©veloppement d'Ă©quipe. Si tout le monde utilise le mĂȘme gĂ©nĂ©rateur de plop, alors le code sera trĂšs uniforme pour tout le monde.

Formatage automatique du code


Le formatage du code est une tùche simple, mais, malheureusement, il n'est pas toujours résolu correctement. Ne perdez pas de temps à aligner manuellement le code ou à y insérer des points-virgules. Utilisez Prettier pour formater automatiquement le code lors de la validation .

Résumé


Dans cet article, je vous ai parlé de certaines choses que nous avons apprises au fil des années de travail, au cours des années d'essais et d'erreurs. Il existe de nombreuses approches pour structurer la base de code des projets. Mais parmi eux, il n'y a personne que l'on puisse appeler le seul juste.

La chose la plus importante que je voulais vous transmettre est que le programmeur doit s'efforcer de simplifier l'organisation des projets, leur homogénéité, d'utiliser une structure compréhensible et facile à travailler. Cela simplifie les projets de développement d'équipe.

Chers lecteurs! Que pensez-vous des idées de développement d'applications JavaScript à pile complÚte décrites dans cet article?



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


All Articles