C ++ au service de l'orthodontie: entretien avec Mikhail Matrosov, développeur CAO chez Align Technology


Mikhail Matrosov ( mmatrosov ) est un ingénieur de développement de premier plan au bureau de R & D de Moscou d'Align Technology. Sa spécialisation est très inhabituelle - il développe un système de CAO spécialisé pour la conception d'appareils orthodontiques.


Mikhail participe à C ++ Russia depuis la toute première conférence. Cette année, au C ++ Russia 2019 Piter, il fera une présentation sur «Qualifiers, Qualifiers and Templates» . Vous pouvez également le connaître dans les cours de Yandex "Fundamentals of C ++ Development: Brown Belt" et "Fundamentals of C ++ Development: Black Belt" à Coursera, dans la création desquels Michael a co-écrit.


La conférence est déjà proche, mais pour l'instant, prenez une interview avec Mikhail, où nous avons discuté de son travail chez Align Technology, de la migration du code hérité, de la préparation de cours et de rapports en ligne, ainsi que des fonctionnalités de C ++. Des questions ont été posées par Pavel Filonov (comité du programme C ++ Russie) et Oleg Chirukhin (journaliste du groupe JUG Ru).


Au croisement de la technologie et de la médecine


Pavel: Dites-moi ce que fait votre entreprise.


Michael: Align Technology est un pionnier et un leader du marché en orthodontie propre. C'est une chose qui se fait maintenant partout dans le monde au lieu des accolades. Si auparavant, un fil métallique a été placé pour le redressement des dents, et ils ont couru avec, maintenant au lieu de cela, vous mettez des protège-dents en plastique transparent (doublures), qui sont fabriqués individuellement pour chaque patient. Récemment, j'ai testé sur moi-même les produits de notre entreprise, une sensation très drôle!


Pavel: Vous avez donc un tel dogfooding, oui, il s'avère?


Michael: Oui, en quelque sorte. En fait, si vous ne le savez pas, il est très difficile de remarquer que la personne est dans les doublures. À moins qu'au début, par habitude, de petits défauts d'élocution apparaissent, mais même ils sont difficiles à remarquer.


Pavel: Et qu'est-ce que C ++ a à voir avec ce dont nous parlerons beaucoup aujourd'hui?


Michael: Excellente question. Lorsque la société en était à ses balbutiements, deux Pakistanais joyeux y ont pris du plastique spécial (je ne sais pas d'où ils l'ont trouvé), qui devrait être non toxique et inerte avec de la salive et d'autres substances dans la bouche.


Ensuite, ils ont pris un modèle de la mâchoire du patient, ce qu'ils ont su faire pendant très longtemps. Par exemple, pour fabriquer des couronnes, une personne a reçu une bouchée de quelque chose comme une pâte à modeler spéciale. Il y avait un moulage de dents rempli de matière. Ils ont donc obtenu une copie de la mâchoire, et ils ont tiré du plastique dessus (ils l'ont en quelque sorte réchauffé), et l'embout buccal est prêt. Autrement dit, tout ce marché il y a 20 ans était complètement analogique.


Maintenant, le volume de production dans la région de 300 à 400 000 doublures par jour (veuillez évaluer l'échelle). Ce sont des manufactures gigantesques, donc elles sont complètement automatisées.


Pour créer une doublure, nous imprimons sur un moule d'imprimante 3D spécial. Il s'agit en fait d'une copie de la mâchoire du patient pendant le traitement. Nous modélisons l'apparence de la mâchoire dans une semaine, deux, mois, année. Ensuite, nous commençons le processus de thermoformage - nous prenons un ruban en plastique, le chauffons et le tirons sur le moule. Coupez et obtenez une doublure en plastique.


Pour le moment, personne ne jette déjà des patients et ils utilisent des scanners intra-oraux tridimensionnels. Nous avons des gars qui font ça dans un bureau voisin. C'est probablement la partie la plus «wow» de notre production. Là, en temps réel, une buse spéciale mène le long de la mâchoire et construit sa forme. Les informations sont téléchargées sur les serveurs de l'entreprise, traitées et envoyées à nos spécialistes (techniciens). Ils peuvent modifier l'analyse, supprimer le bruit.


Ensuite, le traitement est prévu dans un système de CAO spécialisé. C'est une chose très compliquée, car personne ne sait quelle est la bonne bouchée. Chaque médecin a sa propre opinion à ce sujet. Nous travaillons actuellement à formaliser les approches de traitement, mais ce n'est pas encore dans la prod.


Mais revenons au C ++. Il y a un tel problème, par exemple. Les moules sont imprimés sur une imprimante 3D spéciale utilisant la technologie de stéréolithographie laser. Il y a une grande cuve avec un liquide photopolymère spécial. Ils brillent dessus avec un laser et ça durcit. D'abord, ils brillent tout en bas du modèle imprimé, puis un peu plus haut, encore plus haut, etc. Et donc, à l'intérieur du fluide, un modèle solide apparaît de bas en haut en couches.


L'impression d'un moule à la fois ne serait pas pratique. La cuve est assez grande et vous pouvez y mettre une centaine de moules. Mais ils doivent être disposés de manière compacte. La forme du moule est similaire à celle d'un fer à cheval - c'est-à-dire non convexe et plutôt complexe. Et ils sont tous différents. Il s'avère une tâche intéressante de carreler un rectangle avec des figures complexes.


Ici, dans cette vidéo de l'horodatage spécifié, vous pouvez voir à quoi cela ressemble.


Ce n'est qu'un exemple, il y a beaucoup de tâches.


Pavel: Vos descriptions sont comme des calculs de haute technologie. Des langues comme Fortran y ont historiquement bien performé. Au moins, vous pouvez toujours voir les échos. Pourquoi exactement C ++?


Michael: Eh bien, pas Fortran, ce serait très puissant. Au tout début du développement, le produit principal était ce système de CAO très spécialisé - une application de bureau pour Windows. Par conséquent, un langage était nécessaire pour un calcul efficace et en même temps pour développer l'application elle-même. Il n'y avait pas beaucoup de choix à l'époque - seulement C ++.


Nous avons maintenant un zoo de langages: JavaScript et TypeScript pour le Web, Java pour les applications mobiles, Go pour les serveurs, Python pour l'automatisation, et il y a beaucoup de petites choses. Fondamentalement, une pile standard. Et dans les applications à forte intensité scientifique et complexes en termes de calcul, le C ++ demeure.


Plus la position est élevée, plus les horizons sont larges.


Pavel: Vous avez mentionné votre position en tant qu'ingénieur principal. Et donc j'ai remarqué une telle étrangeté. Si on demandait au développeur C ++ novice de votre entreprise ce qu'il faisait, il dirait qu'il faisait tourner une sorte de super framework basé sur des modèles, qui basé sur booststage fait l'équilibrage des serveurs, et la technologie tomberait immédiatement.


Lorsqu'on vous demande ce que fait votre entreprise, toute une histoire commence sur l'entreprise: où est l'argent, pourquoi est-ce important, etc. Cela correspond-il d'une manière ou d'une autre à votre position? Quelle est cette vision des processus, des technologies, des outils?


Michael: Oui, peut-être. Mais en plus, si je commence immédiatement à raconter comment je traverse des arbres (même si je ne le fais pas moi-même), ce ne sera pas clair pour tout le monde. Par exemple, les mecs de Yandex, Kaspersky Lab peuvent commencer avec la technologie, car tout le monde connaît leurs produits. Nous parlons de doublures, que peu de gens connaissent. Par conséquent, il est nécessaire d'expliquer ce que c'est.


En ce qui concerne la façon dont cela est en corrélation avec le poste. Sur ma note, je dois au moins comprendre les éléments de notre flux de travail des systèmes actuels qui fonctionnent ensemble. Après tout, si quelque chose change, cela peut affecter la production, le site des médecins, l'interface avec laquelle les techniciens, les médecins et les analystes commerciaux interagissent. Autrement dit, il existe une corrélation. Avec la croissance de la note, vous devez mieux voir ce qui se passe dans l'entreprise.


Pavel: Pensiez-vous comment l'introduction de nouvelles technologies, par exemple celles liées au C ++, affecterait le flux de travail? Peu de gens regardent les nouvelles fonctionnalités de la langue. Et sinon, dites-nous comment vous avez récemment implémenté des outils?


Michael: Je ne me souviens pas du C ++ à la légère. Si nous prenons les outils, la dernière chose que nous avons ratée est un système de journalisation basé sur le cloud. Nous utilisons spécifiquement Splunk pour cela. Initialement, la plateforme était sous le bureau et la migration vers le cloud bat son plein. Par conséquent, nous accélérons, en particulier, la journalisation basée sur le cloud. Notre gestionnaire a rapidement appris à faire des demandes et à créer de magnifiques tableaux de bord, à afficher des graphiques en temps réel. J'étais très content. Je l'ai envoyé à tout le monde et j'ai dit, voyez comment nous faisons varier le temps de fonctionnement d'un algorithme aussi complexe, quelle est la raison? Ils ont commencé à comprendre, nous avons réalisé que nous n'avons presque pas de multithreading sur les agents de test. Quelque chose est constamment introduit.


C ++, Legacy, multiplateforme et tout, tout, tout


Oleg: Dois-je bien comprendre que vous avez rendu dans le navigateur?


Michael: Oui, il y en a.


Oleg: Les technologies des navigateurs sont en constante évolution. Cela affecte-t-il en quelque sorte? Par exemple, le nouveau JS, Shading Language , quelque chose comme ça.


Michael: Cela affecte, mais c'est un peu ennuyeux ici. En termes de rendu, la scène est assez simple. Nous n'avons pas d'effets spéciaux incroyables.


Pavel: Bien. Mais vous avez dit que vous avez commencé avec une application de bureau, où C ++ était la meilleure solution qui combinait tous les avantages. Lorsque l'entreprise a commencé à croître avec le produit, vous avez commencé à développer d'autres plateformes. Certains codes sont allés au navigateur, d'autres au backend, d'autres aux téléphones portables. Le noyau «positif» est-il resté sur toutes les plateformes?


Michael: Le passage à d'autres plateformes a commencé il y a deux ans, et pour notre échelle, c'est peu de temps. Par conséquent, notre première solution cloud était un monolithe de bureau, à partir duquel, en gros, l'interface graphique a été lancée. Il y a environ un an et demi, nous avons isolé le noyau conditionnel et la liaison qui l'entoure, nous avons donc pu le compiler et l'exécuter déjà sous Linux. Ce fut une grande percée. Linux était important pour nous principalement parce que les machines Linux cloud sont beaucoup moins chères.


Maintenant, nous voulons séparer le noyau informatique en C ++, où tous les calculs complexes auront lieu, de la logique métier qui décrit ce qu'il faut faire et dans quel ordre, d'où vient le cas, quel type de produit, ce que le médecin a le droit de faire et ce qui ne l'est pas, et c'est tout.


Pavel: Vous avez probablement déjà une feuille de route de ce processus, car vous en parlez si bien.


Michael: Nous avons essayé plusieurs fois de créer une feuille de route. Nous comprenons où nous semblons être en mesure de bouger, en tenant compte des 20 ans de Legacy (vous ne pouvez pas y arriver avec un demi-dollar, vous devez y penser).


L'interaction des projets positifs se fait désormais de manière simple. Parmi celles-ci, ce sont les interfaces C ++ qui ressortent, donc elles ne se réunissent que pour s'assurer qu'il n'y a qu'un seul compilateur. Un total d'environ 250 projets, chacun allant à sa propre bibliothèque dynamique. Et nous les combinons de différentes manières pour des tâches spécifiques.


Une dizaine d'équipes travaillent sur le système. Chaque équipe recrute un sous-ensemble de ces projets, généralement 50-70. Maintenant, nous allons dans le sens où certains services seront créés sur leur base. Nous définissons une API stricte pour le service (basée sur protobuf ou quelque chose d'autre), et faisons un schéma standard d'interaction entre les services. Il est difficile d'appeler le processus la séparation du calcul et de la logique, mais ce sont les premières tentatives de composante.


Mon équipe et plusieurs autres ont déjà commencé à le faire. Vous vous sentez immédiatement plus drôle quand vous collectez un monolithe non pas à partir de 250 projets, mais à partir de 70 seulement. Et il n'y a plus de situation où quelqu'un a changé un autre module qui interagit avec des choses apparemment complètement indépendantes, et quelque chose s'est cassé . Cela a un effet psychologique cool. Et pendant que nous essayons de nous éloigner d'un monolithe de bureau sain en allouant conditionnellement 10 petits monolithes à nos services.


Pavel: Et pendant ce processus, vous ne vous êtes pas demandé si les modules sur le point d'être promis pour être chargés sous une forme ou une autre pourraient l'aider à n'importe quel niveau?


Michael: C'est ce processus et le processus de migration Linux que Conan nous a aidés. Nous avons eu un sérieux problème avec la gestion de tiers. J'ai parlé de la façon dont nous avons utilisé Conan au C ++ Russia 2019 à Moscou.



Les modules «plus» aideraient à résoudre certains problèmes avec le temps de compilation, où, en fait, tout a commencé, pourquoi tout le monde veut tellement les modules. Réutiliser des modules positifs pour interagir avec le service est stupide, car ils communiqueront à un certain niveau indépendant du langage (protobuf), et à juste titre. Peut-être pourrions-nous effectuer la segmentation et ne pas construire nos 250 projets à partir de la source à chaque fois, mais les mettre dans les packages de Conan. Et si, par exemple, le pliage de DLL n'est pas pratique pour certaines raisons, l'assemblage en modules est une option.


Mais je ne peux pas dire que nous attendons une fonctionnalité qui changera notre approche du développement.


Pavel: Vous avez mentionné que Conan vous aide à résoudre les problèmes de gestion des packages. À mon avis, il y a environ 3 ans, la communauté C ++ n'en parlait que. Les premiers rapports sont apparus, les premiers prérequis. Et maintenant, vous dites que cela fonctionne déjà en production.


Parlez-nous de votre expérience d'évolution de projet. Le processus de transition était-il douloureux et en valait-il la peine?


Michael: Ça vaut vraiment le coup. Nous sommes satisfaits de Conan. Il y a quelques défauts, mais la gestion des dépendances en C ++ est une tâche très difficile. Par conséquent, il est évident qu'il n'y aura pas d'outil simple pour le résoudre.


Avec Conan et le processus, nous avons atteint un équilibre entre la complexité des tâches et des solutions. Nous avons d'abord dit: «D'accord, nous voulons mettre en évidence de nombreux ensembles de compilateurs et de configurations, puis créer des bibliothèques dynamiques (plutôt que statiques) par défaut, faire un certain petit ensemble de restrictions», et nous avons commencé à construire un système dans cet ensemble de restrictions. Nous avons une page wiki «Comment créer une recette Conan pour une bibliothèque», qui prend en compte toutes les spécificités de notre système. La page est assez grande et pas très simple. Mais, encore une fois, nous avons atteint un équilibre de complexité, donc je suis satisfait de la transition.


Pavel: Et ici, au fait, lors du prochain C ++ Russia 2019 Piter, Denis Panin de NVIDIA parlera de l'alternative représentée par vcpkg . Serait-il intéressant pour vous d'aller au rapport et d'écouter comment ils fonctionnent dans d'autres outils?


Michael: Oui, ce serait intéressant. J'ai un peu utilisé vcpkg, mais, à mon avis, il y a très peu de flexibilité dans vcpkg. Et je ne peux pas imaginer comment nous pourrions construire un système avec nos exigences basées sur vcpkg. Mais si je dois rapidement prendre et tester une sorte de bibliothèque, alors je ne la téléchargerai pas et je ne lirai pas les instructions de construction, que là vous devez spécifier comment enregistrer les dépendances, c'est tout ce qu'il y a dans la corbeille. Je vais voir si c'est dans les ports vcpkg. S'il y en a, mettez-le rapidement, expérimentez-le. Si tout va bien, je vais lui écrire une recette de conan.


Pavel: Continuons sur l'introduction de nouveaux outils et approches. Avez-vous rencontré le fait que les ingénieurs courent avec des yeux brûlants de la conférence, où ils ont été informés d'une nouvelle chose cool, et ils veulent l'insérer dans le projet? Comment réagissez-vous habituellement à de telles choses? Ou alors vous recourez après chaque conférence?


Michael: Il y a juste une belle histoire. Quand je viens de commencer à travailler dans l'entreprise, ils ont écrit une fonctionnalité, et il fallait en faire une configuration. De plus, une fonctionnalité peut avoir plusieurs versions différentes et les valeurs de propriétés spécifiques peuvent être recherchées entre différentes versions. Et je suis venu avec un schéma basé sur YAML cool où il y avait l'héritage et la redéfinition des propriétés. Il l'a écrit pendant environ une semaine, l'a couvert de tests. Et au tout début, le manager est venu vers moi et m'a dit: "Écoutez, Michael, peut-être que vous n'avez pas besoin de perdre du temps maintenant et de faire un plan si flexible?" Mais il n'a pas pu me convaincre. Mes yeux étaient trop brûlés.


Après quelques années, je regarde ce code et je ne comprends pas pourquoi il était si difficile à faire. Cela arrive, mais heureusement, depuis, je suis devenu plus intelligent. De plus, les développeurs sont généralement des gens très rationnels, vous pouvez leur parler.


Auparavant, quelqu'un prenait des décisions douteuses de tiers. On ne sait pas par qui ils se développent, la dernière mise à jour remonte à 5 ans, il n'a pas été vérifié si cela fonctionne sous Linux, ils génèrent des rapports dans un format propriétaire que vous ne pouvez convertir en rien. Et nous sommes assis et ne comprenons pas quoi faire ensuite. La plupart de ces tiers ont été ajoutés il y a de nombreuses années, mais il arrive que quelque chose de similaire se produise maintenant.


Par conséquent, nous avons trouvé une chose assez simple - une liste de contrôle tierce. Si vous souhaitez faire glisser un tiers, parcourez simplement les éléments de la liste de contrôle: multiplateforme, niveau de support, licence, quels analogues, niveau de support de développement (combien de mainteneurs prennent en charge, quelle communauté). Tous les points sont évidents, mais ils sont nombreux, et lorsque vous tapez tiers, vous ne vous souvenez pas de tout de suite. La situation s'est améliorée et nous regardons ce qui s'y ajoute.


Il est également intéressant de noter qu’auparavant, il était facile de faire glisser un tiers et de l’assembler, par exemple, uniquement dans une version sous «Windows» et 32 ​​bits. Maintenant, en principe, il n'y a pas une telle possibilité, car vous devez le mettre dans Conan. Et si vous faites glisser un tiers, vous devrez vous assurer ou indiquer explicitement que, dans certaines configurations, il ne le fera pas. Grâce à cela, le tiers gauche a commencé à être ajouté beaucoup moins.


Oleg: Et qu'en est-il du tiers, qui vit très peu? Par exemple, un pad gauche de JavaScript. Il va sur votre liste de contrôle. Il est souvent mis à jour, maintenu, réalisé par des packages.


Michael: Il n'y a rien de tel. En C ++, le tiers est généralement une chose épaisse. Et les solutions tierces qui exécutent de petites fonctions ne sont pas utilisées. En effet, en C ++, il n'est pas si simple d'ajouter des tiers. Et ce n'est pas JS, dans lequel chacun a son propre module.


Nous avons conditionnellement retiré 80 tiers, dont la moitié je n'ai jamais vu. Ou une sorte de géométrie infernale qui a été écrite il y a 15 ans dans une université, et nous l'avons.


Oleg: Avec Conan, il est facile d'ajouter un tiers.


Michael: Conan est plus facile, mais toujours loin du même Python où vous écrivez l'installation de pip et vous avez terminé. C ++ possède un écosystème très complexe: différents systèmes d'exploitation, compilateurs, chaîne d'outils, bibliothèques, normes. La plupart des bibliothèques ont un tas d'options.


Ils aiment nous demander: "Pourquoi écrivez-vous vos recettes de bibliothèque, mais ne les prenez-vous pas de Conan-centre?" J'en ai regardé périodiquement des recettes, mais elles ne nous conviennent pas. On va mieux. Par exemple, nos recettes prennent en charge les symboles de débit et ajoutent une liaison aux sources qui s'y trouvent. Ainsi, lorsque vous obtenez une bibliothèque tierce à partir du débogueur de Visual Studio, les sources sont chargées automatiquement.


Donc, avec Conan, c'est beaucoup mieux, mais encore loin de la commodité des autres langues.


Oleg: Y a-t-il des défauts dans Conan lui-même que je voudrais corriger?


Michael: Oui, il y a des inconvénients, mais techniques.


Conan dispose de méthodes qui sont conditionnellement responsables de la génération de packages et de la connexion des packages générés à votre solution. Idéologiquement, ce sont deux étapes différentes, et je peux en changer une sans changer l'autre. Lorsque je change la façon dont les packages sont connectés, je ne voudrais pas régénérer les packages binaires, car ils seront exactement les mêmes. Vous ne pouvez pas faire cela dans Conan, car la recette est une entité. Si vous avez modifié la recette, vous devez formellement changer la version et générer de nouveaux binaires, ce qui n'est pas pratique.


Caractéristiques de la transition vers de nouvelles normes


Pavel: Parlons de nouvelles normes. De plus, votre produit a déjà une histoire.


Nous arrivons maintenant à la conférence, où tous les trois ans, ils nous parlent de nouvelles fonctionnalités merveilleuses. Par exemple, au C ++ Russia 2019 Piter, il y aura trois rapports sur les changements à venir dans la langue. Avez-vous une expérience de la migration de l'ancienne base de code, qui correspondait aux anciennes normes, vers ce qui était ou sera proposé?


Mikhail: Oui, j'ai eu une telle expérience, j'en ai un peu parlé à C ++ Russia 2019. Nous avons eu une migration de Visual Studio 2013 vers Visual Studio 2017 et gcc, c'est-à-dire que nous avons simultanément ajouté le support Linux et mis à jour les compilateurs de Microsoft.


Les problèmes dans le code étaient beaucoup moins en comparaison avec les problèmes d'organisation, techniques, d'infrastructure, CI et le reste du réglage. Et nos problèmes dans le code étaient principalement liés à UB, qui, après la mise à jour du compilateur, a commencé à se déclencher. Bien que C ++ propage la pourriture pendant des années de compatibilité descendante, mais cela aide. Maintenant, nous compilons tout sous C ++ 17.


Les vorings ne sont pas synchronisés lorsque les développeurs collectent sous Windows, poussent dans CI et seulement après cela, ils commencent à s'assembler sous Linux. Mais il n'y a pas eu de problèmes importants.


En C ++ 17, certaines choses obsolètes ont été supprimées, mais nous n'avons passé que quelques heures à les supprimer. Par exemple, la bibliothèque log4cplus utilisait std :: auto_ptr dans les en-têtes, mais dans les nouvelles versions, elle était déjà remplacée par std :: unique_ptr, il suffisait donc de simplement mettre à jour la bibliothèque.


La migration vers la nouvelle norme a donc été relativement indolore. Et étant donné la taille de notre base de code, je suis surpris du peu de problèmes que nous avons rencontrés.


À propos de la ceinture marron et noire en C ++


Pavel: Vous et moi parlons déjà des processus existants, où travaillent déjà des développeurs expérimentés. Donnons matière à réflexion aux développeurs novices qui ne font qu'apprendre. Quelle norme C ++ recommanderiez-vous de commencer?


Michael: Nous devons prendre la dernière norme immédiatement. Vous n'apprendrez pas STL dans le même C ++ 98, car STL sans lambdas n'a aucun sens. Si vous regardez les cours "Fondamentaux du développement C ++: Brown Belt" et "Fondamentaux du développement C ++: Black Belt" sur Coursera, alors nous y écrivons déjà C ++ 17. Par exemple, nous utilisons des liaisons structurées.


: , . , . , ?


: , . , C++. , C++, , . , , . , , , , . , . , , , Align Technology.


: , , . Pourquoi?


: -, — . , . , . , , , . -, , , . . -, - ( ), -, . . . . . . , , , , . , , tutorial . -, , .



: , . , . ?


: , . , .


: , «» . , . «» , ?


: , , . . , - . , .


C++ . , . , . , , . . - , .


, . , , , . , . « ?». , .


. C++ Russia . ?


: . , , . , , . , .


: , . , ? 2015, 2019 2020 ?


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


CppCon 2018 . , , . C++ . , , .


. , — , .


: ?


: const, volatile, static, constexpr, inline, extern . , , ..


: consteval constinit?


: . , , . , 3-4 , .


: , , Visual Studio constexpr?


: Visual Studio . , . , constexpr-.


: , : , , .


: Visual Studio , . , 2010 : , . Microsoft .


: . : const, constexpr, constinit, consteval , , final. final , , constfinal?


: final, , . . , constfinal, .


: , , , , . ?


: , , . , , .


: ?


: , JUG Ru Group , ().


: . ?


: , — . , , , . .


: . , ?


: , . , ().


C++ Russia 2019 Piter «, » . , .

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


All Articles