La vie avant l'exécution. Rapport Yandex

Dans un grand projet, la tâche d'identifier les modifications pour l'utilisateur final par des différences dans le code frontal de l'application peut survenir. Le développeur de Yandex.Market Nikita Sidorov @nickshevr a expliqué comment nous avons résolu ce problème en utilisant la bibliothèque Diffector, comment créer et analyser le graphique du module dans les applications Node.js et comment trouver les défauts dans le code avant son lancement.



- Aujourd'hui, je vais essayer d'être aussi franc avec vous que possible. Je travaille sur Yandex.Market depuis un peu plus d'un an et demi. Je fais la même quantité de web, et j'ai commencé à remarquer des changements en moi-même, vous pouvez aussi les remarquer. Ma longueur moyenne de cheveux a augmenté et la barbe a commencé à apparaître. Et vous savez, aujourd'hui, j'ai regardé mes collègues: chez Sergei Berezhnoy veged , chez Vova Grinenko tadatuta , et j'ai réalisé - c'est un bon critère pour ce que j'ai presque mûri en tant que véritable développeur front-end.

Et en arrivant à cette prestation de serment, j'ai décidé de vous parler de la vie, de celle à laquelle nous participons tous. Surtout sur la vie avant l'exécution. Maintenant, je vais vous expliquer de quoi tout cela sera question.



Et la vie? À propos de la durée de vie du code, bien sûr. Le code est ce que nous faisons. Permettez-moi de vous rappeler que j'ai décidé d'être sincère avec vous ici, afin que la première diapositive soit aussi simple que possible. J'ai pris la vérité, la première étape - l'adoption, vous voyez, personne ne contestera cet axiome.



Et puis j'ai réalisé que je devrais le modifier, mais pour que ce soit clair. Que ce soit une sorte d'acceptation des exigences. Tout code commence par le fait que vous examinez la tâche et essayez d'accepter les exigences qui vous définissent.



Après cela, bien sûr, nous commençons la phase d'écriture - nous écrivons notre code. Ensuite, nous le couvrons avec des tests, nous vérifions nous-mêmes son efficacité. Après cela, nous vérifions déjà si notre application fonctionne avec notre code dans son ensemble. Après cela, nous le donnons au testeur - laissez-le vérifier. Que pensez-vous après ça? Je vous le rappelle, la vie avant l'exécution. Pensez-vous que le runtime suit cela? En fait, ça se passe comme ça. Et ce n'est pas une erreur dans la présentation. Très souvent, à n'importe quelle étape des vérifications - et il y en a peut-être beaucoup plus que je ne l'ai indiqué - vous pouvez avoir un coup de fil pour réécrire. D'accord, cela peut être un gros problème. Cela peut ralentir la livraison de certaines fonctionnalités en production et, en principe, vous ralentir en tant que développeur, car le ticket vous attendra. Et ici tout passe, passe. Il y a encore M fois pour N vérifications, et alors seulement le code parvient à l'utilisateur dans le navigateur. Mais c'est notre objectif. Notre objectif est d'écrire du code qui sera vraiment disponible pour l'utilisateur et qui fonctionnera vraiment à son avantage.

Aujourd'hui, nous allons parler de la première partie. Sur ce qui se passe avant, mais pas vraiment sur les tests.



Au fait, cela ressemble à quelque chose comme ça. J'ai pris notre tour dans le tracker, récupéré le mien, compté la médiane. Il s'avère que mes tickets sont beaucoup moins en développement qu'en vérification. Et comme vous le savez, plus le test est long, plus il y a de chances que goto apparaisse au début ou goto apparaisse à la fin - et je ne veux pas du tout le faire.

Et aussi, si vous faites attention, il y a deux mots sur la diapositive ici - développement (c'est ce que nous, les développeurs faisons) et vérification (nous le faisons, mais aussi les testeurs). Par conséquent, le problème est pertinent, en fait, pour les testeurs.



L'objectif est plutôt prosaïque. En général, j'aime dire que la vie doit être simplifiée: nous travaillons déjà beaucoup avec vous. L'objectif ressemble à ceci, mais vous devez admettre qu'il est plutôt éphémère, alors mettons en évidence certains critères de base dont l'objectif peut dépendre.

Bien sûr, moins il y a de code, plus c'est facile pour nous. Plus vite nous aurons des contrôles CI, plus vite nous réaliserons si nous avons raison ou non. Autrement dit, localement, il peut généralement commencer pour toujours. Vitesse de vérification - cela s'applique directement au testeur. Si notre application est volumineuse et doit être vérifiée dans son intégralité, c'est un temps énorme. La vitesse de libération dépend de tout cela. Y compris, il est impossible de libérer jusqu'à ce que nous ayons passé tous les contrôles et jusqu'à ce que nous comprenions que le code est exactement celui que nous voulons.

Pour résoudre certains des problèmes dont nous parlons, analysons le graphe de dépendance des modules de notre langage de programmation. Et, en fait, décrivons-le.



Le graphique est orienté: il a des arêtes avec des directions. Aux nœuds du graphe, nous n'aurons que les modules du langage dont nous parlons. Les côtes sont un type spécifique de liaison. Il existe plusieurs types de communication.



Regardons un exemple banal. Il y a le fichier A. Ici, quelque chose du fichier B est importé dedans, et c'est une telle relation entre les nœuds.



La mĂŞme chose se produira si vous remplacez l'importation par require. En fait, tout n'est pas si simple ici.



Je suggère, puisque nous parlons du type de dépendance, d'envisager au moins deux types - pour accélérer votre pipeline, pour accélérer la traversée du graphe. Il est nécessaire de surveiller non seulement le module dépendant, mais également le module dépendant. Je propose d'appeler le module A - le parent, B - l'enfant, et je vous conseille de garder les liens toujours comme une double liste chaînée. Cela vous simplifiera la vie, je vous informe à l'avance.

Une fois que nous avons en quelque sorte décrit le graphique, convenons de la façon dont nous allons le construire.



Il y a deux façons. Soit votre outil préféré dans votre langage de programmation préféré en utilisant le même AST (arbres de syntaxe abstraite) ou des habitués. Quel est le profit ici? Le fait qu'ici, vous n'êtes lié à personne, mais en même temps, vous devez tout mettre en œuvre vous-même. Vous devrez décrire tous les types de connexions de toutes ces choses et technologies que vous utilisez, que ce soit un collecteur CSS séparé, quelque chose d'autre comme ça. Mais vous avez une totale liberté de vol, pour ainsi dire.

De plus, la deuxième option, je vais aussi la promouvoir un peu, c'est une option juste pour la plupart des gens qui ont déjà un système de build configuré. Le fait est que le système d'assemblage collecte un graphique en fonction de la conception, par défaut.



Regardons l'un des systèmes d'assemblage les plus populaires de Yandex, c'est le webpack. Ici, j'ai donné un exemple de la façon dont vous pouvez collecter le résultat complet de webpack dans un fichier séparé, qui peut ensuite être envoyé à notre ou à un autre analyseur. Il le recueille avec l'aide d'AST, la bibliothèque de glands est utilisée. Vous l'avez peut-être remarquée quand quelque chose est tombé. J'ai remarqué.

Et quels sont les avantages. Le fait est que lorsque vous avez décrit votre système de construction, vous avez absolument honnêtement demandé à entrer. Ce sont les fichiers à partir desquels vos dépendances sont déroulées, les points de contournement initiaux. C'est bien, car vous n'avez pas besoin de les réenregistrer. En outre, webpack et babel, et tout cela, et gland, y compris, ce n'est toujours pas votre maintien. Et donc, toutes sortes de nouvelles fonctionnalités du langage, toutes sortes de bugs et tout le reste, sont corrigées plus rapidement que si vous l'avez fait, surtout si vous n'avez pas une très grande équipe. Oui, même si c'est gros, ce n'est pas aussi gros que l'open source.

C'est à la fois un plus et un moins, en fait. C'est comme si un double tranchant (épée à double tranchant) était obtenu. Le fait est que ce graphique est construit lors de l'assemblage. C’est plutôt bien, c’est-à-dire que nous pouvons assembler le projet et réutiliser immédiatement le résultat de l’assemblage. Mais que se passe-t-il si nous ne voulons pas assembler un projet, mais voulons simplement obtenir ce graphique?

Et un inconvénient majeur, en fait. Si vous avez des choses personnalisées connectées, nous parlerons beaucoup plus tard des connexions, alors le système de construction ne vous laissera pas faire cela. Ou, vous devrez l'intégrer, comme votre plugin webpack.



Prenons un exemple spécifique. J'ai exécuté une commande sur ma projection, où il n'y a que trois fichiers, et j'ai obtenu cette sortie. Et cela, je ne montre qu'une seule clé, qui s'appelle modules. Nous parlons juste avec vous du graphe de dépendance des modules, donc nous regardons les modules, tout est logique.



Beaucoup d'informations, mais nous n'avons pas besoin de tout. Laissez quelques points et parlons-en. Supposons que nous considérons le premier module. Il a un nom, il y a des raisons. Les raisons ne sont que la connexion, en fait, avec des modules «dépendants», il s'avère que ceux qui importent ce module pour eux-mêmes. Ce sont les données de base pour construire un graphique dessus.



De plus, veuillez prêter attention aux exportations utilisées et aux exportations fournies. Nous en parlerons un peu plus tard. Mais ce sont aussi des choses très importantes.





Et si vous décrivez votre décision, vous devez parler des types de connexions qui se produisent entre les modules. Autrement dit, nous avons, bien sûr, notre système de modules à l'intérieur de notre langage: que ce soit des modules cjs ou des modules esm. En outre, vous devez accepter que nous puissions avoir une connexion entre les fichiers dans le système de fichiers au niveau du système de fichiers lui-même. Ce sont une sorte de cadres: une sorte de cadre va, selon la façon dont les papas sont.



Et un exemple aussi banal - si vous avez écrit le côté serveur de Node, alors vous pouvez souvent voir un package npm populaire comme Config. Il vous permet de définir très facilement vos configurations.



Pour l'utiliser, vous devez obtenir le dossier config, où vous avez NODE_PATH, et spécifier plusieurs fichiers JavaScript - juste pour y présenter la configuration pour différents environnements. À titre d'exemple, j'ai créé un papa, spécifié par défaut, le développement et la production.



Et, en fait, toute la configuration fonctionne comme ceci. Autrement dit, lorsque vous écrivez require ('config'), il lit simplement le module à l'intérieur de lui-même et prend le nom du module de la variable d'environnement. Comme vous le comprenez, il n'était pas clair que ces fichiers soient utilisés d'une manière ou d'une autre, car il n'y a pas d'import / besoin direct, webpack ne le reconnaîtrait même pas.


Lien depuis la diapositive

Aujourd'hui, nous avons également parlé de l'injection de dépendance. Je n'étais pas quelque chose qui m'inspirait, mais à l'appui, j'ai regardé l'une des bibliothèques ici. Il est appelé inverser JS. Comme vous pouvez le voir, il fournit une syntaxe plutôt personnalisée: lazyInject, nameProvider, et le voici. Et, vous devez admettre, ce n'est pas clair de quel type de fournisseur il s'agit, quel type de module il injecte vraiment ici. Et nous en avons besoin et nous devons le comprendre. Autrement dit, le système de build ne sera pas résolu et nous devrons le faire nous-mêmes.

Supposons que nous ayons construit un graphique, et je vous suggère de commencer par le stocker quelque part. Qu'est-ce qui nous permettra de faire cela? Cela nous permettra de faire une sorte d'analyse heuristique, de jouer un peu de Data Science, et de le faire, en nous concentrant sur une tranche de temps.



Quelle est l'idée? Voici, en effet, directement nos données. Nous venons tout juste de mettre en œuvre notre système de conception dans Yandex.Market et, en particulier, nous avons implémenté une bibliothèque de composants dans le cadre de ce système de conception. Et ici, vous pouvez voir: nous considérons le nombre d'importations, le composant React de notre bibliothèque, le composant commun. Et vous pouvez distribuer dans des répertoires. Dans ce cas, nous avons un tel référentiel non mono, et donc nous avons platform.desktop, platform.touch et src.

Que pouvons-nous penser quand nous voyons ces chiffres? Nous pouvons émettre l'hypothèse que la commande tactile ne semble pas augmenter l'utilisation de composants communs. Cela signifie que les composants sont mauvais pour le mobile - de mauvaise qualité ou que la commande tactile est paresseuse. Mais est-ce vraiment le cas?



Si nous regardons dans une période plus longue, dans un laps de temps plus long, cela nous permet de ne faire que le stockage des graphiques après chaque version, alors nous comprendrons qu'en fait, tout est ok pour le toucher, l'indicateur y grandit. Pour src, c'est encore mieux, pour le bureau, il s'avère que non.



Il y avait encore une question de l'auditoire comment expliquer l'importance aux gestionnaires. Voici le nombre total d'importations de bibliothèques, également en fonction du temps. Quels managers n'aiment pas les graphiques? Vous pouvez créer un tel calendrier et voir que l'utilisation de la bibliothèque augmente, ce qui signifie que c'est au moins une chose utile.

Une de mes parties préférées. Je vais le couvrir assez brièvement. Il s'agit d'une recherche de défauts dans le graphique. Aujourd'hui, je voulais vous parler de deux types de défauts: il s'agit d'une dépendance cyclique des modules et de certains modules inutilisés, c'est-à-dire d'un problème d'élimination du code mort.



Commençons par la dépendance circulaire.



Tout semble assez simple ici. Vous avez déjà un graphe orienté, il vous suffit d'y trouver une boucle. Je vais vous expliquer pourquoi j'en parle. Le fait est qu'avant d'écrire, fondamentalement, le côté serveur sur Node.js, et nous n'utilisions, en principe, aucun webpack / babel, rien. Autrement dit, ils ont lancé en l'état. Et il y avait besoin. Qui se souvient de la différence entre l'importation et l'exigence? Tout est correct. Si vous avez mal écrit le code, mais je l'ai vraiment fait, vous pouvez découvrir sur votre serveur que votre module est dans une sorte de dépendance cyclique uniquement lorsqu'une demande vient des utilisateurs, ou qu'un autre événement fonctionnera. C'est un problème plutôt mondial. Jusqu'à l'exécution ne comprends pas. Autrement dit, l'importation est beaucoup mieux, il n'y aura pas un tel problème.



Ensuite, prenez tout algorithme que vous aimez. Ici, j'ai pris un algorithme assez simple. Nous devons trouver un sommet qui n'a qu'un seul type d'arĂŞtes - entrantes ou sortantes. S'il existe un tel sommet, nous le supprimons, supprimons les bords et, en fait, continuons ce processus, nous trouverons et prouverons qu'il y avait un cycle de cinq cycles dans ce graphique.

D'accord, si vous le regardez par code, c'est-à-dire que vous pouvez toujours trouver un cycle de deux ou trois longueurs, mais c'est plus irréaliste, et nous avions vraiment un cycle de sept dans le projet, mais pas en production.



À propos des modules inutilisés. Il existe également un algorithme plutôt trivial. Nous devons mettre en évidence les composants connectés dans notre graphique, et simplement regarder, trouver ces composants, qui n'incluent aucun des nœuds d'entrée. Dans ce cas, c'est cette composante de la connectivité, les deux sommets, il s'avère, les deux nœuds. Appelé ensuite entry.js. En fait, quel que soit son nom, c'est ce que vous avez décrit dans les moyens de configuration de l'assemblage d'entrée.



Mais il existe une autre approche. Si vous n'avez pas collecté le graphique et que vous avez juste un système de construction, alors comment est-ce le moyen le moins cher de le faire? Marquons simplement tous les fichiers entrés dans l'assemblage pendant l'assemblage. Marquez-les et créez-en plusieurs. Après cela, nous devrions obtenir une grande partie de tous les fichiers que vous avez dans le projet et simplement les soustraire. C'est une opération très simple.



Et maintenant, je ne vous dis pas seulement quelque chose de théorique, j'ai été inspiré, je suis venu à mon projet et je l'ai fait. Et attention! Je n'ai même pas supprimé node_modules. Je l'ai laissé comme point de croissance pour le prochain examen. Et, en bref, j'étais tellement inspiré par moi-même que j'ai décidé de faire en quelque sorte cette diapositive, de la réorganiser. Laisse ça comme ça, parce que c'est vraiment cool!

Bon nombre, pouvez-vous imaginer comment tout s'est bien passé? Et puis j'ai été conduit dans une telle steppe que je me suis senti comme un designer et j'ai pensé que c'était une réalisation que j'aimerais ajouter au cadre. Et, comme vous le savez, je me suis levé, j'ai regardé et j'ai réalisé que je n'étais probablement pas un designer, mais bien un développeur web. Mais je ne suis pas idiot. J'ai pris ce cadre, ajouté à mon site pour les amulettes SEO.



Vous pouvez utiliser, même le lien est. Et pour que vous ne pensiez pas que je vous trompe - nous sommes francs aujourd'hui - j'ai vraiment regardé les critiques. Je pense que vous pouvez les croire.



Eh bien, pour être honnête, cela ressemblait à quelque chose comme ça. J'ai vu une nouvelle bibliothèque d'hypothèques thanos-js, je l'ai prise, j'ai créé une demande de pool. En secret, j'ai des droits d'administrateur dans notre référentiel. Et j'ai pris et confondu le maître. Comment aimez-vous ça? Eh bien, vous et moi sommes francs, et en fait, tout cela ressemblait à ça. Si quelqu'un ne le sait pas, thanos-js est une bibliothèque qui supprime simplement 50% de votre code au hasard.



En fait, j'ai utilisé la bibliothèque de toute façon, mais la bibliothèque s'appelle différemment. C'est ce qu'on appelle un diffuseur, et maintenant nous allons en parler avec vous. Et ici, je voudrais noter que la demande de pool est assez importante, moins 44 000 lignes de code, et vous pouvez imaginer - elle a passé le test la première fois. Autrement dit, ce dont je parle peut vraiment fonctionner.



Diffuseur En fait, il est engagé non seulement dans la tâche de suppression des modules inutilisés, de recherche de défauts dans le graphe, mais également dans une tâche plus importante. Ce que j'ai initialement déclaré était d'aider le développeur et le testeur, nous allons maintenant en parler. Et cela fonctionne quelque chose comme ça.

Nous obtenons une liste des fichiers modifiés en utilisant le système de contrôle de version. Nous avons déjà construit un graphe - le diffecteur le construit. Et pour chacun de ces fichiers modifiés, nous recherchons le chemin d'accès à l'entrée et marquons l'entrée modifiée. Et l'entrée correspondra aux pages d'application que l'utilisateur verra. Mais c'est assez logique.

Et qu'est-ce que cela nous donne? Pour les tests - nous savons quelles pages de l'application ont changé. Nous pouvons dire au testeur que seuls eux valent la peine d'être testés. Nous pouvons également dire à notre ci-job, qui exécute des autotests, que seules ces pages valent la peine d'être testées. Et pour les développeurs, tout est beaucoup plus simple, car maintenant les testeurs ne vous écrivent pas et ne vous demandent pas: "Pourquoi avez-vous besoin de tester?"



Regardons un exemple de fonctionnement du diffecteur. Ici, nous avons un certain répertoire, pages.desktop / *. Il contient juste une liste des pages elles-mêmes. Et les pages sont également décrites par plusieurs fichiers. Le contrôleur est le côté serveur de la page. La vue est une sorte de réaction. Et deps, cela vient d'un autre système de build. Nous avons non seulement webpack, mais aussi ENB.



Et j'ai apporté quelques modifications au projet, à un fichier vide, dont vous avez vu la structure. C'est ce que le diffecteur me donne. Je viens de le démarrer, diffector est une application en ligne de commande. Je l'ai lancé, il me dit que j'ai changé une page, qui s'appelle BindBonusPage.



Je peux également l'exécuter en mode verbeux, voir un rapport plus détaillé et vraiment voir que cela fonctionne au moins dans un cas aussi simple. Comme nous le voyons, dans notre BindBonusPage, le fichier d'index et le contrôleur ont changé.

Mais voyons ce qui se passe si nous changeons autre chose.



J'ai changé autre chose. Et le diffecteur m'a dit que j'avais changé neuf pages. Et cela ne me fait plus plaisir, comme s’il ne voulait pas vraiment m’aider.



Voyons pourquoi? Il montre maintenant les raisons pour lesquelles cette page a été considérée comme modifiée. , . - uikit.



diff. . , diffector . , - , .



, , . , , entry, , , test-scope, . .

. , , , , .



. - , . — i18n, , . , , , . , , - .

? , , , , , .



- . , B , , -2 . . , esm.



.



.



, value, . , , . , .

, AST, , 250 , , . , , - , , .



, - GlobalContext - , . , modify, , ? , - GlobalContext. . . , side effects. , , webpack, , . , webpack sideEffects: true, . .



, - - . , . diffector, . , , . — , . , .

, , diff, expand, log, , , , .



, . D, diff. , , , . , , . , , . .

, , . . . , — , . . . , , , , , . . , .

— diffector ? , , , .



- , , .



, , entry. entry. diffector.



. -. , .



, entry -, .



. diffector. . , , - , -. , . : , BindBonusPage, -, . . , , - . .





— CI. . : , , .



, . 43 — testing, , .



. , , .



, . : , , . , , , , , . , , - .



. , , , . - , - , , , . .

, , , output . , . — . — . Je vous remercie!

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


All Articles