Blogue
Savoir-faire et technologie
Histoires, idées et perspectives sur la stratégie, la technologie et les solutions d’affaires.

Articles à la une

Nouvelles
5 min de lecture
Annonce : Spiria est certifiée SOC 2 Type 2
<h2>Qu'est-ce que la certification SOC 2 ?</h2><p>La certification SOC 2 (Service Organization Control 2) est une norme élaborée par l'American Institute of Certified Public Accountants (AICPA) qui évalue la capacité d'une organisation à gérer les risques liés à la sécurité, à la disponibilité, à l'intégrité du traitement, à la confidentialité et à la protection de la vie privée des données qu'elle traite pour le compte de ses clients.</p><p>La certification SOC 2 repose sur cinq principes, appelés critères de confiance, qui définissent les exigences minimales que doit respecter une organisation pour assurer la sécurité et la qualité de ses services. Ces critères sont les suivants :</p><ul> <li><strong>Sécurité</strong> : l'organisation protège les données contre les accès non autorisés, les modifications, les divulgations, les dommages ou la perte.</li> <li><strong>Disponibilité</strong> : l'organisation assure la disponibilité et le fonctionnement continu de ses services conformément aux accords conclus avec ses clients.</li> <li><strong>Intégrité du traitement</strong> : l'organisation traite les données de manière complète, valide, exacte, opportune et autorisée.</li> <li><strong>Confidentialité</strong> : l'organisation respecte les engagements et les obligations de confidentialité envers ses clients et les tiers concernant les données qu'elle traite.</li> <li><strong>Protection de la vie privée</strong> : l'organisation respecte les principes de protection de la vie privée définis par l'AICPA et les lois applicables en matière de collecte, d'utilisation, de conservation, de divulgation et d'élimination des données personnelles.</li></ul><p>« Obtenir et maintenir la certification SOC 2, je le vois comme un ultramarathon et non un sprint sur 100 mètres. C'est une première étape, dans un long processus en constante évolution. La cybersécurité, dans son ensemble, nécessite une rigueur et une attention aux détails constante auquel notre équipe est prête à s’attarder. »</p><p>– Vincent Huard, Vice-Président, gestion et analyse des données</p><p>Pour obtenir la certification SOC 2, une organisation doit faire l'objet d'un audit indépendant réalisé par un cabinet comptable qualifié qui vérifie qu’elle respecte les critères de confiance applicables à ses services. L'audit porte sur la conception et l'efficacité des contrôles mis en place par l'organisation pour assurer la conformité aux critères de confiance.</p><h2>Quelle est la différence entre la certification SOC 2 Type 1 et Type 2 ?</h2><p>Il existe deux types de certification SOC 2. C’est entre autres la durée de l’audit qui les distingue. SOC 2 Type 2 est couvert par l’audit le plus long et rigoureux.</p><ul> <li>La certification SOC 2 Type 1 atteste que l'organisation respecte les critères de confiance à une date donnée à une date précise. Elle évalue la conception des contrôles, mais pas leur efficacité dans le temps.</li> <li>La certification SOC 2 Type 2 atteste que l'organisation respecte les critères de confiance sur une période de temps définie, généralement de trois à douze mois. Elle évalue la conception, mais également l'efficacité des contrôles, en tenant compte de leur fonctionnement réel et de leur évolution.</li></ul><p>En d’autres mots, la certification SOC 2 Type 2 répond à des critères plus exigeants et rigoureux, car elle implique un suivi continu et une vérification régulière des contrôles. Elle offre une assurance plus élevée sur la qualité et la sécurité des services fournis par l'organisation.</p><h2>Quels sont les bénéfices pour nos clients ?</h2><p>En obtenant la certification SOC 2 Type 2, Spiria réaffirme sa posture de partenaire de confiance dans la réalisation de projets de développement de solutions numériques pour ses clients. Voici quelques bénéfices principaux qui permettent à nos clients de se lancer la tête tranquille dans des projets d’envergure avec Spiria :</p><ul> <li>La garantie que nous respectons les normes les plus élevées en matière de sécurité de l'information</li> <li>La garantie que nous protégeons les données de nos clients contre les menaces internes et externes.</li> <li>La confiance que nous assurons la disponibilité et la performance de nos services</li> <li>La confiance que nous sommes capables de réagir rapidement et efficacement en cas d'incident.</li> <li>La certitude que nous traitons vos données avec intégrité, en respectant les règles de validation, d'exactitude, de traçabilité et d'autorisation.</li> <li>La tranquillité d'esprit que nous respectons vos obligations de confidentialité et que nous ne divulguons pas vos données à des tiers non autorisés.</li> <li>La sécurité que nous respectons les principes de protection de la vie privée et que nous nous conformons aux lois applicables en matière de données personnelles.</li></ul><p>La certification SOC 2 Type 2 est un gage de confiance et de sécurité pour nos clients qui témoigne de notre engagement à fournir des services de qualité et à respecter les meilleures pratiques du secteur. Elle représente l’excellence en matière de sécurité des données dans le marché tout en étant de plus en plus prisée pour les projets de développement logiciels. Il était donc tout naturel pour Spiria d’être parmi les quelques firmes d’experts à s’y conformer en Amérique du Nord. Nous sommes fiers d’arborer cette certification et d'assurer à la fois l'excellence, la fiabilité et la rigueur de nos pratiques d’affaires.</p><p>Démarrez un projet en toute confiance : <a href="mailto:nouveauprojet@spiria.com">nouveauprojet@spiria.com</a>.</p>

Stratégie
5 min de lecture
Temps et matériel ou forfaitaire, que choisir ?
<p>Les équipes de Spiria ont une longue et riche expérience avec les deux types de contrats, et nous vous dévoilons ici ce que nous avons appris au fil du temps sur le sujet et quels sont les critères de succès pour chaque option.</p><p>Clarifions tout d’abord ce que sont ces deux types de projets :</p><h3>Projets temps & matériel</h3><p>Projets dont la portée (activités, livrables, inclusions comme exclusions, etc.) peut être plus ou moins clairement définie. L’évaluation initiale des coûts présente une fourchette de prix probable pour la réalisation du dit projet. Les coûts sont facturés selon les heures réelles exécutées et le matériel/ressources (autres coûts, par exemple des licences logicielles ou des services infonuagiques) nécessaire. Cette approche est plus flexible, car elle permet des changements de spécifications tout au long du processus de développement. L’agilité est encouragée et les contrôles de gestion de projets sont mis de l’avant.</p><h3>Projets forfaitaires ou fixes</h3><p>Projets dont la portée est plus souvent bien ou très bien définie. Le niveau de confiance de l’évaluation initiale des coûts repose sur des informations plus claires que le précédent type de projet. Comme son nom l’indique, les coûts sont fixés au départ, peu importe les heures réellement exécutées et le coût en matériel et ressources. Par conséquent, les notions de risques et de profitabilité sont des considérations plus critiques à évaluer dans ce type de projet. Toute modification des spécifications est encadrée par un processus de demande de changement et est facturée en tant que travail supplémentaire.</p><p>Dans un premier scénario, pour un projet préalablement qualifié, le type de projet (temps/matériel vs fixe) peut être imposé par le client, les exigences internes des organisations ou encore des réglementations, par exemple dans le cas des appels d’offres (majoritairement fixes). Lorsque possible, Spiria peut proposer une approche pour mitiger les risques et mieux saisir la portée du projet, comme proposer au client un investissement initial dans une phase découverte, en mode temps/matériel ou forfaitaire, dans l’intention de pouvoir proposer par la suite les phases de développement et de déploiement en mode forfaitaire. Ceci n’empêche bien sûr pas le client de changer de priorité ou de modifier la portée à la suite de la phase de découverte. Notre flexibilité doit nous permettre de négocier avec le client la portée définie en variant les inclusions/exclusions, dans l’objectif de rester dans l’enveloppe budgétaire forfaitaire contractuelle entendue.</p><p style="text-align: center;"><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/11800/process-fr.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/11800/process-fr.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/11800/process-fr.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/11800/process-fr.webp" style="width: 60%; border: none;" alt="Un cycle projet type." title="Un cycle projet type."></source></source></source></picture></p><p style="text-align: center; font-style: italic;">Figure 1. Un cycle projet type.</p><p>Dans un deuxième scénario, si le type de projet n’est pas imposé, ceci nous donne la latitude du choix de la stratégie. Habituellement, les clients prévoient des sessions de rencontres avec les différents fournisseurs pour répondre à leurs questions. Une réflexion interne s’impose ensuite pour bien évaluer les facteurs décisionnels menant à la meilleure stratégie. À cet effet, le tableau ci-dessous présente une liste non exhaustive de points qui éclairent les équipes dans cette réflexion. Ces points sont pondérables (facilement identifiables, quantifiables ou mesurables) ou impondérables, en fonction des informations fournies lors des rencontres initiales, dans les cahiers de charge, ou pouvant être obtenues par des demandes au client. Les annotations des deux colonnes de droite sont simplement des suggestions de poids relatifs aux deux types de projets.</p><table cellpadding="0" cellspacing="0" style="width:100%"> <tbody> <tr> <td style="width:76%"><strong>Points</strong></td> <td style="width:12%"><strong>Fixe</strong></td> <td style="width:12%"><strong>T&M</strong></td> </tr> <tr> <td>Le plan d’affaires, les requis, les besoins et les attentes sont claires.</td> <td>➕➕</td> <td>➕</td> </tr> <tr> <td>Les processus et règles d’affaires sont nombreux et complexes.</td> <td>➕</td> <td>➕➕</td> </tr> <tr> <td>Le budget client est identifié et la planification budgétaire est cadrée.</td> <td>➕</td> <td>➖</td> </tr> <tr> <td>L’échéancier est strict ou critique en raison du contexte client ou d’affaires.</td> <td>➕</td> <td>➖</td> </tr> <tr> <td>Les expertises nécessaires sont identifiables.</td> <td>➕</td> <td>➕</td> </tr> <tr> <td>La structure organisationnelle et décisionnelle est grande et complexe.</td> <td>➖</td> <td>➕</td> </tr> <tr> <td>Les aspects légaux sont complexes.</td> <td>➖</td> <td>➕</td> </tr> <tr> <td>Les relations sont déjà établies (historique) ou des contacts sont nos promoteurs.</td> <td>➕</td> <td>➕</td> </tr> <tr> <td>Le calcul de risques, les incertitudes et la contingence sont élevés.</td> <td>➖</td> <td>➕</td> </tr> <tr> <td>Les risques de dérives sont probables.</td> <td>➖</td> <td>➕</td> </tr> <tr> <td>Le client détient une capacité en effectifs ou en connaissances internes<br> (designer, équipe de développement, AQ, etc.).</td> <td>➕</td> <td>➕</td> </tr> <tr> <td>L’environnement technologique est connu.</td> <td>➕</td> <td>➕</td> </tr> <tr> <td>Les contraintes technologiques sont importantes (ex. : système hérité).</td> <td>➖</td> <td>➕</td> </tr> <tr> <td>Les défis d’intégration sont nombreux et complexes.</td> <td>➖</td> <td>➕</td> </tr> <tr> <td>Les choix technologiques sont imposés.</td> <td>➕</td> <td>➕</td> </tr> <tr> <td>Les données sont disponibles pour faire l’assurance qualité fidèlement.</td> <td>➕</td> <td>➕</td> </tr> <tr> <td>La solution est assujettie à des certifications spéciales.</td> <td>➖</td> <td>➕</td> </tr> </tbody></table><p><br>Le résultat de cette réflexion peut amener vers différentes approches représentées dans le diagramme suivant :</p><p><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/11800/strategies-fr.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/11800/strategies-fr.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/11800/strategies-fr.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/11800/strategies-fr.png" style="width: 100%; border-style:solid; border-width:1px;" alt="Les différentes stratégies (approches)." title="Les différentes stratégies (approches)."></source></source></source></picture></p><p style="text-align: center; font-style: italic;">Figure 2. Les différentes stratégies. (Cliquer pour agrandir.)</p><p>La stratégie sélectionnée dicte la façon donc les ententes contractuelles sont conclues. Ce choix d’approche a des incidences sur tout le déroulement du projet et son succès final. La transparence du processus de choix et la justification des motifs auprès du client permettent de démarrer la relation sur des bases saines. Les objectifs ultimes sont de livrer un projet qui respecte nos valeurs spiriennes et qui apporte la valeur attendue au client.</p>
Tous les articles
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Culture
5 min de lecture
Spiria Montréal va s’installer chez Fabrik8 !
<h2>Le concept Fabrik8</h2><p>Cofondé par Pierre-Antoine Fernet et Vanessa Brochu, Fabrik8 est un espace de travail partagé qui peut accueillir des travailleurs autonomes comme des PME de toutes tailles et de tous horizons, favorisant la création de synergies entre les entrepreneurs qui s’y installent et proposant de nombreux services destinés à faciliter le bien-être des travailleurs.</p><p>Initialement installé sur la rue Saint-Urbain, Fabrik8 a vite rencontré le succès en répondant aux besoins de jeunes entreprises dynamiques en quête d’un environnement de travail stimulant. Afin de répondre à la demande croissante, elle a par la suite déménagé dans un ancien bâtiment de style industriel de la rue Waverly, ayant une superficie de plus de 3 000 m<sup>2</sup> (32 000 pi<sup>2</sup>). L’espace offre plusieurs configurations flexibles de bureaux fermés, pour des équipes de 1 à 35 employés, et de nombreux espaces communs comme des salles de conférence, une cafétéria, une salle de jeux, des salles de détente, etc.</p><p>L’ascension fulgurante de Fabrik8 n’allait pas s’arrêter là : ses confondateurs ont mis sur pied le projet de construire un ambitieux complexe de 18 600 m<sup>2</sup> (200 000 pi<sup>2</sup>) sur le même site, afin d’offrir des prestations hors du commun aux entreprises qu’elle accueille. Les plans des nouveaux bâtiments ont été confiés au cabinet d’architecture de Rocio H. Venegas, à qui l’on doit dans le même quartier la restructuration certifiée LEED du 7250 Marconi qui abrite les bureaux de Gameloft Montréal. La construction s’effectue en deux phases. La première, du côté de la rue Jean-Talon, a été achevée cet hiver et accueillera Spiria dans quelques mois, et la seconde, du côté de la rue de Castelnau, débutera dès ce printemps, une fois que toutes les entreprises logées par Fabrik8 auront pu déménager dans le nouvel immeuble de la première phase.</p><p><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_lounge.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_lounge.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_lounge.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/6353/fabrik8_lounge.webp" style="width: 100%; border-style:solid; border-width:1px;" alt="Lounge and café at Fabrik8." title="Lounge and café at Fabrik8."></source></source></source></picture></p><p>L’espace bar/salon. © Fabrik8.</p><p><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_sport.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_sport.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_sport.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/6353/fabrik8_sport.webp" style="width: 100%; border-style:solid; border-width:1px;" alt="The gym at Fabrik8." title="The gym at Fabrik8."></source></source></source></picture></p><p>La salle de sport. © Fabrik8.</p><p>Le nouveau complexe offre trois niveaux de bureaux pour les entreprises de 1 à 50 personnes et trois niveaux pour de plus grandes entreprises qui ont la possibilité de louer un plateau au complet (1 765 m<sup>2</sup>, 19 000 pi<sup>2</sup>) ou seulement une partie qu’elles peuvent aménager selon leurs souhaits. Sa particularité est d’obéir aux conditions de la certification WELL, la première norme de construction qui se concentre sur l’amélioration de la santé et du bien-être des occupants. Pour obtenir ce label d’excellence, Fabrik8 et son architecte ont dû travailler sur de nombreux aspects de l’environnement : le confort thermique, l’acoustique, la qualité des espaces, de l’éclairage, de l’air et de l’eau, etc. Et tout le projet se doit de mettre l’accent sur l’intégration de l’activité physique et de l’alimentation saine au quotidien. C’est ainsi qu’on y trouve un centre sportif tout équipé sur le toit ainsi qu’une spacieuse cafétéria-santé au rez-de-chaussée, mis à la disposition de tous les locataires. Et la mobilité active n’est pas oubliée avec un grand espace dédié au stationnement sécurisé des vélos.</p><p><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_hockey.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_hockey.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/fabrik8_hockey.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/6353/fabrik8_hockey.webp" style="width: 100%; border-style:solid; border-width:1px;" alt="The rooftop ice rink at Fabrik8." title="The rooftop ice rink at Fabrik8."></source></source></source></picture></p><p>La patinoire sur le toit. © Fabrik8.</p><p>Enfin, il y a une cerise sur le sundae… Une particularité tout à fait unique qui distingue Fabrik8 de tous les autres immeubles de bureaux : la présence d’une patinoire sur le toit. Oui, vous avez bien lu, une vraie patinoire, surfaceuse à glace incluse, qui l’été venu, se transforme en plateau multisport pouvant accueillir des parties de basketball, de soccer ou de handball entre collègues.</p><h2>Pourquoi avoir choisi Fabrik8 ?</h2><p>Spiria a toujours été extrêmement attachée au bien-être de ses employés et s’est reconnue dans le concept et les valeurs portées par Fabrik8. En prenant un étage au complet, il s’agit d’offrir un environnement de travail fonctionnel, plaisant et inspirant qui dépasse ce que la plupart des entreprises peuvent offrir. L’aménagement du plateau a été confié à Ædifica, un cabinet de design et d’architecture spécialisé dans les environnements de travail, qui a déjà collaboré avec d’innombrables grandes entreprises comme L’Oréal Canada, IBM, Air Transat, Bell, CN, Sanofi, WB Games, etc. Les choix sur la distribution de l’espace, le mobilier, les matériaux et les couleurs se font en collaboration avec un comité d’employés de Spiria qui veille à ce que nos futurs bureaux soient vraiment à notre image, que chacun s’y sente bien et qu’on puisse y avoir du fun.</p><p><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/aedifica_1.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/aedifica_1.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/aedifica_1.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/6353/aedifica_1.webp" style="width: 100%; border-style:solid; border-width:1px;" alt="Spiria office at Fabrik8." title="Spiria office at Fabrik8."></source></source></source></picture></p><p>© Ædifica.</p><p>Tous les Spiriens et Spiriennes vont ainsi pouvoir bénéficier de toute une nouvelle gamme de services que notre actuelle implantation, dans les anciens ateliers de confection <i>Kiddies Togs</i>, ne pouvait offrir. Stationnement couvert, salle de sport avec cours, patinoire, cafétéria, terrasses, environnement ultramoderne, autant d’atouts qui contribueront à la qualité de vie de tous, tout en gardant tous les avantages du quartier auxquels nous sommes déjà habitués : la proximité des transports en commun avec les stations Parc et De Castelnau, du parc Jarry pour s’aérer l’esprit et pour les pique-niques entre collègues, du plus beau marché alimentaire de la ville, des microbrasseries dont nous connaissons la carte par cœur, des merveilleuses soupes de Soupson, entre autres choses.</p><p><picture><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/aedifica_2.400x0.webp" media="(max-width: 599px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/aedifica_2.760x0.webp" media="(max-width: 999px)"><source type="image/webp" srcset="https://mirror.spiria.com/site/assets/files/6353/aedifica_2.1039x0.webp" media="(min-width: 1000px)"><img src="https://mirror.spiria.com/site/assets/files/6353/aedifica_2.webp" style="width: 100%; border-style:solid; border-width:1px;" alt="Spiria office at Fabrik8." title="Spiria office at Fabrik8."></source></source></source></picture></p><p>© Ædifica.</p><p>Tout cela vous fait-il envie ? Souhaiteriez-vous travailler dans un environnement d’exception au sein d’une entreprise pas moins exceptionnelle ? Sachez que nous avons de très nombreux postes actuellement offerts, alors n’hésitez pas à <a href="https://www.spiria.com/fr/carriere/">les consulter</a> et peut-être aurez-vous la chance de profiter vous aussi du style de vie proposé par Spiria et Fabrik8.</p>

Coin des développeurs
5 min de lecture
C++ : le pire des deux mondes
<p>En contraste extrême, Python est souvent décrit comme l’un des langages de programmation les plus simples à apprendre et à lire. Il est également très dynamique, permettant de passer facilement n’importe quelle donnée à n’importe quelle fonction. Mieux, les fonctions peuvent être définies et redéfinies à tout moment. Lorsque vous appelez une fonction, vous ne savez jamais d’avance quel code sera <i>vraiment</i> exécuté.</p><p>Alors, pourquoi ne pas combiner les deux ? La complexité et la syntaxe obscure des templates du C++ avec les surprises dynamiques de Python ? Si ce genre de mélange peu ragoûtant vous intéresse, alors vous êtes à la bonne place !</p><h2>Sérieusement…</h2><p>En fait, mon but est de déplacer la résolution des fonctions surchargées (<i>overloaded functions</i> en anglais) à l’exécution du code plutôt qu’au moment de la compilation.</p><p>Je voulais créer un système dynamique d’appel de fonctions qui pourrait appeler une fonction avec n’importe quelle donnée. Je voulais que ce système soit dynamique et extensible, afin de permettre de nouvelles surcharges de la fonction pour de nouveaux types de données. Je voulais également pouvoir appeler ces fonctions avec des données concrètes ou un tas de <code>std::any</code>. Enfin, je voulais aussi que tout cela soit raisonnablement efficace.</p><p>Pour atteindre tous ces objectifs, je me suis tourné vers les templates. Pas de simples templates, mais la version plus complexe des templates variadiques.</p><h2>Syntaxe des templates variadiques</h2><p>Qu’est-ce qu’un template variadique ? Les templates normaux prennent en argument un nombre de types fixe. C’est bien quand on sait à l’avance combien de types on doit utiliser. En revanche, un template variadique prend en argument un nombre variable de types. Il peut en recevoir zéro, un, deux, ou n’importe combien.</p><p>En plus de recevoir ces types, le template doit être capable de s’en servir. Comme vous le savez peut-être déjà, les templates sont entièrement générés à la compilation. Ils doivent donc fonctionner sans modifier aucune donnée. Ainsi, pour manipuler un nombre variable de types, une nouvelle syntaxe a dû être ajoutée au C++. La nouvelle syntaxe pour la réception des types et leur utilisation a été créée avec l’ellipse : <code>…</code>.</p><p>L’astuce de base est que chaque fois que l’ellipse est utilisée, elle indique au compilateur C++ de générer autant de fois que nécessaire le code qui entoure l’ellipse. Par exemple, les types du template sont reçus avec une ellipse. Dans l’exemple suivant, l’argument <code>VARIA</code> du template représente un nombre quelconque de types.</p><pre><code>template <class... VARIA>struct example{ // le code du template serait ici.};</code></pre><p>Par la suite, dans le code du template, les arguments variadiques peuvent être utilisés avec une ellipse. Par exemple, le template variadique présenté ci-haut pourrait avoir une fonction qui recevrait des arguments pour les transmettre à une autre fonction, comme ceci :</p><pre><code>// Réception d'un nombre variable d'arguments...void foo(VARIA... function_arguments){ // ... passation à une autre fonction. other_bar_function(function_arguments...);}</code></pre><p>Ces exemples ne font qu’effleurer la surface de ce qui est possible avec modèles variés, mais ils seront suffisants pour notre objectif dans cet article.</p><h2>Design d’appels dynamiques</h2><p>Avant de nous lancer dans le design de notre système d’appels dynamique, exposons plus concrètement nos exigences. J’ai dit qu’il devrait imiter la surcharge de fonctions du C++. Qu’est-ce que cela signifie concrètement ? Voici nos exigences :</p><ul> <li>La fonction elle-même est déclarée et référencée par son nom, comme une fonction normale.</li> <li>Le nombre d’arguments de cette fonction peut varier.</li> <li>Chaque surcharge de cette fonction peut avoir un type de valeur de retour différent.</li> <li>Cette fonction peut être surchargée pour n’importe quel type.</li> <li>Une nouvelle surcharge de fonction peut être ajoutée dynamiquement, au moment de l’exécution, pour tout type.</li></ul><p>Bien que ces exigences soient suffisantes pour atteindre notre objectif, je désirais quelques ajouts. Le premier ajout est de supporter des arguments de fonction ayant un type fixe. Par exemple, une fonction pour écrire recevrait toujours en argument un <code>std::ostream</code>. Le deuxième est de permettre sélectionner la surcharge d’une fonction sans avoir à passer d’argument à la fonction. Par exemple, ceci permet de spécifier le type de retour de la fonction ou bien d’écrire une fonction ne prenant aucun argument.</p><p>Pour supporter tout cela, nous ajoutons deux exigences à la liste :</p><ul> <li>Tous les arguments n’ont pas à jouer un rôle dans la sélection de la fonction.</li> <li>Des types supplémentaires peuvent jouer un rôle dans la sélection de la fonction sans en être un argument.</li></ul><p>Le résultat doit ressembler à la surcharge de fonctions normale C++. Par exemple, voici à quoi ressemble un appel à la fonction <code>to_text</code> dans le système d’appels dynamique :</p><pre><code>std::wstring resultat = to_text(7);// resultat == "7"std::any seven(7);std::wstring resultat = to_text(seven);// resultat == "7"</code></pre><p>Pour parvenir à une telle similitude comparée à la surcharge de fonction, un grand nombre d'éléments complexes travaillent en arrière-scène.</p><h2>Smooth Operator</h2><p>Avant de montrer comment les appels dynamique fonctionnent, nous allons voir comment ils se présentent du point de vue du programmeur voulant créer une nouvelle opération.</p><p>Pour créer une nouvelle fonction appelée <code>foo</code>, il faut déclarer une classe pour la représenter. Pour notre exemple, nous l'avons nommée <code>foo_op_t</code> et l'avons dérivée de <code>op_t</code>. Cette classe <code>foo_op_t</code> ne sert qu'à identifier notre fonction. Elle peut être entièrement vide! Ensuite, nous pouvons écrire la fonction <code>foo</code> elle-même, le vrai point d'entrée. Cette fonction est très simple: elle ne fait qu'appeler la fonction <code>call<>::op()</code> (pour des valeurs de type concret) ou bien <code>call_any<>::op()</code> (pour des valeurs de type <code>std::any</code>), toutes deux contenue dans <code>foo_op_t</code>, qui fera tout le travail:</p><pre><code>struct foo_op_t : op_t<foo_op_t> { /* empty! */ };inline std::any foo(const std::any& arg_a, const std::any& arg_b){ return foo_op_t::call_any<>::op(arg_a, arg_b);}template<class A, class B, class RET>inline RET foo(const A& arg_a, const A& arg_b){ std::any result = foo_op_t::call<>::op(arg_a, arg_b); // Notez: nous pourrions aussi vérifier que le std::any // contient bien un RET, plutôt qu'en être sûr. return any_cast<RET>(result);}</code></pre><p>Notez que la classe de base de la nouvelle opération prend l’opération elle-même comme paramètre de template. Il s’agit d’une astuce bien connue dans la programmation de templates. Elle est si connue qu’elle a même un nom : curiously recursive template pattern. Dans notre cas, cette astuce est utilisée pour que le <code>op_t</code> puisse se référer à l’opération spécifique utilisée.</p><p>Maintenant, nous pouvons créer des surcharges de la fonction <code>foo</code>. Ceci est fait en appelant <code>make<>::op</code> avec une fonction qui implémente la surcharge. Pour créer une surcharge prenant les types <code>A</code> et <code>B</code> et retournant le type <code>RET</code>, nous appellerions <code>make<>::op<RET, A, B></code>. Cela enregistre la surcharge dans la classe <code>foo_op_t</code>. Par exemple, implémentons notre <code>foo</code> pour les types <code>int</code> et <code>double</code>, retournant un <code>float</code> :</p><pre><code>// Code de la surcharge.float foo_for_int_and_double(int i, double d){ return float(i + d);}// Enregistrement de la surcharge.foo_op_t::make<>::op<float, int, double>(foo_for_int_and_double);</code></pre><p>Bien sûr, nous pourrions raccourcir et simplifier ceci en rédigeant le code dans l’appel à <code>make<>::op</code> lui-même, avec un lambda. C’est même le style que vous je suggère :</p><pre><code>foo_op_t::make<>::op<float, int, double>( [](int i, double d) -> float { return float(i + d); });</code></pre><p>Si vous vous demandez pourquoi les appels <code>call<></code> et <code>make<></code> ont les sigils de templates, c’est bien sûr parce qu’ils sont des templates variadiques. Les arguments optionnels de template sont les types supplémentaires qui permettent de choisir une surcharge de fonction plus précisément, sans que ces types soient passés en argument à la fonction <code>foo</code>. Nous verrons cela plus en détail plus loin.</p><p>Nous sommes maintenant prêts à entrer dans le vif du sujet : le design des appels dynamiques.</p><h2>C’est le sélecteur</h2><p>Le premier problème à résoudre est la manière dont chaque surcharge est identifiée au sein d’une famille de fonction. La solution évidente est de l’identifier par les types des arguments et les types supplémentaires optionnels de sélection. C++ fournit les classes <code>std::type_info</code> et <code>std::type_index</code> pour identifier un type. Ce dont nous avons besoin, c’est d’un <code>tuple</code> de ces <code>type_index</code>. Pour ce faire, nous utilisons deux templates : le convertisseur et le sélecteur.</p><p>Le convertisseur convertit (eh ben…) n’importe quel type vers le type <code>std::type_index</code>. Écrire ainsi une classe pour implémenter une étape d’un algorithme est une astuce très idiomatique des templates. Ceci permet d’exécuter l’algorithme pendant la compilation. Voici donc le convertisseur, qui convertit tout type <code>A</code> vers les types <code>type_index</code> et <code>std::any</code> :</p><pre><code>template <class A>struct type_converter_t{ using type_index = std::type_index; using any = std::any;};</code></pre><p>Le sélecteur de type peut alors être écrit comme un template variadique en appliquant le convertisseur à tous les types donnés comme argument et en déclarant un type <code>tuple</code> nommé <code>selector_t</code> avec le résultat. Ce tuple contient les types des arguments de la fonction que l’on est en train de créer, <code>N_ARY</code>, et les types supplémentaires de sélection, <code>EXTRA_SELECTORS</code>, afin d’avoir un sélecteur complet.</p><pre><code>template <class... EXTRA_SELECTORS>struct op_selector_t{ template <class... N_ARY> struct n_ary_t { // Le type selector_t est un tuple de type_index. using selector_t = std::tuple< typename type_converter_t<EXTRA_SELECTORS>::type_index..., typename type_converter_t<N_ARY>::type_index...>; };};</code></pre><p>Notez comment l’ellipse est appliquée à la ligne :</p><pre><code>typename type_converter_t<EXTRA_SELECTORS>::type_index...</code></pre><p>La façon dont le langage C++ applique l’ellipse est un peu la magie noire des templates variadiques. Parfois, vous devrez faire plusieurs essais pour trouver ce qui fonctionne et ce qui ne fonctionne pas.</p><p>Nous avons maintenant un sélecteur, mais comment l’utiliser ? Pour cela, nous fournissons quelques fonctions. L’objectif est d’avoir une fonction qui construit un sélecteur rempli avec les types concrets. Naturellement, nous appelons notre fonction <code>make</code> :</p><pre><code>template <class... EXTRA_SELECTORS>struct op_selector_t{ template <class... N_ARY> struct n_ary_t { template <class A, class B> static selector_t make() { return selector_t( std::type_index(typeid(EXTRA_SELECTORS))..., std::type_index(typeid(N_ARY))...); } };};</code></pre><p>Puisque je veux supporter les appels de fonction avec <code>std::any</code>, nous devons fournir une fonction <code>make_any</code> prenant des <code>std::any</code> en arguments. (En tant qu’optimisation, une version avec les sélecteurs supplémentaires déjà convertis en <code>type_index</code> est fournie sous le nom <code>make_extra_any</code>, mais elle n’est pas montrée ici.)</p><pre><code>static selector_t make_any(const typename type_converter_t<N_ARY>::any&... args){ return selector_t( std::type_index(typeid(EXTRA_SELECTORS))..., std::type_index(args.type())...);}</code></pre><h2>Plongée mécanique</h2><p>Enfin, nous pouvons nous plonger dans les détails mécaniques de l’enregistrement et de l’appel des fonctions. La classe de base des opérations est déclarée comme un template prenant l’opération elle-même et la liste des arguments immuables supplémentaires, <code>EXTRA_ARGS</code>, qui auront donc des types fixes. (Rappelez-vous notre précédent exemple d’opération d’écriture, qui reçoit toujours un <code>std::ostream</code> en argument.)</p><pre><code>template <class OP, class... EXTRA_ARGS>struct op_t{ // Détails internes décrit ci-bas...};</code></pre><p>Les premiers détails internes que nous verrons sont quelques types utilisés à plusieurs reprises : la classe sélecteur (<code>op_sel_t</code>), le tuple sélecteur (<code>selector_t</code>) et la représentation interne des fonctions (<code>op_func_t</code>).</p><pre><code>using op_sel_t = typename op_selector_t<EXTRA_SELECTORS...>::template n_ary_t<N_ARY...>;using selector_t = typename op_sel_t::selector_t;using op_func_t = std::function<std::any(EXTRA_ARGS ..., typename type_converter_t<N_ARY>::any...)>;</code></pre><p>Ce code montre une partie de la complexité inhérente à la programmation des templates. Il y a plusieurs éléments du code qui seraient normalement totalement superflus. Mais, dans le contexte particulier des templates, ces éléments sont nécessaires. Par exemple, Le <code>typename</code> est nécessaire pour dire au compilateur que ce qui suit vraiment est un type. Cela arrive quand un template fait référence à des éléments d’un autre template. La syntaxe C++ est trop ambiguë pour que le compilateur puisse déduire que nous utilisons un type. Un autre élément très particulier est le mot-clé <code>template</code> se trouve juste avant l’accès à <code>n_ary_t</code>. Cet ajout est nécessaire pour dire au compilateur qu’il s’agit réellement d’un template.</p><p>Nous sommes donc prêts à décrire l’ensemble du système, construit à partir de quelques fonctions :</p><ul> <li>Appeler l’opération : <code>call<>::op</code></li> <li>Créer une nouvelle surcharge : <code>make<>::op</code></li> <li>Rechercher la surcharge correcte : <code>get_ops</code></li></ul><p>Nous nous attaquerons à chacune d’entre elles dans l’ordre inverse, en partant des tréfonds du design jusqu’à notre but final : appeler une surcharge.</p><h2>Gardien des merveilles</h2><p>Le tréfonds du design est la fonction qui détient les surcharges déjà enregistrées. Il y a une raison très importante pour laquelle <code>get_ops</code> doit exister. En effet, les surcharges doivent bien être conservées dans un conteneur, mais que notre classe d’opération est un template. Nous ne pouvons pas garder toutes les surcharges pour toutes les opérations ensemble. Heureusement, en C++, nous avons la garantie qu’une variable statique contenue dans une fonction d’un template est unique pour chaque instanciation du template. Donc, <code>get_ops</code> peut contenir en toute sécurité notre liste de surcharge :</p><pre><code>template <class SELECTOR, class OP_FUNC>static std::map<SELECTOR, OP_FUNC>& get_ops(){ static std::map<SELECTOR, OP_FUNC> ops; return ops;}</code></pre><p>Le fait que <code>get_ops</code> soit un template pour <code>SELECTOR</code> et pour <code>OP_FUNC</code> permet supporter l’enregistrement de surcharges avec un nombre d’arguments différents.</p><h2>Création d’opérations</h2><p>La fonction <code>make<>::op</code> est un template qui prend une surcharge de fonction que vous avez écrite pour des types concrets. Elle enveloppe la surcharge dans la représentation interne de la fonction et l’enregistre. L’enveloppe se charge de convertir les <code>std::any</code> en des types concrets. C’est sans danger, puisque la surcharge pour ces types concrets n’est appelée que lorsque les types correspondent. C’est ici que les types de sélection supplémentaires facultatifs peuvent être fournis en arguments du template, sous le nom <code>EXTRA_SELECTORS</code>.</p><pre><code>template <class... EXTRA_SELECTORS>struct make{ template <class RET, class... N_ARY> static void op( std::function<RET(EXTRA_ARGS... extra_args, N_ARY... args)> a_func) { // Enveloppe gardée sous la forme d’un lambda qui // convertit la représentation interne de la fonction // vers la signature réelle de la surcharge. op_func_t op( [a_func]( EXTRA_ARGS... extra_args, const typename type_converter_t<N_ARY>::any&... args) -> std::any { // Conversion vers les types concrets. return std::any(a_func(extra_args..., *std::any_cast<N_ARY>(&args)...)); } ); // Enregistrement. auto& ops = get_ops<selector_t, op_func_t>(); ops[op_sel_t::make()] = op; }};</code></pre><h2>Appels à la pelle</h2><p>Nous arrivons enfin à la fonction utilisée pour envoyer un appel. Il y a trois versions de la fonction. Les seules différences entre elles sont si les arguments sont déjà convertis en <code>std::any</code> et si les sélecteurs supplémentaires facultatifs sont déjà convertis en <code>std::type_index</code>. Voici ce que la fonction <code>call<>::op</code> doit faire :</p><ul> <li>Créer un sélecteur à partir des types de ses arguments, plus les sélecteurs supplémentaires optionnels.</li> <li>Aller chercher la liste des surcharges disponibles.</li> <li>Trouver la surcharge de fonction à l’aide du sélecteur.</li> <li>Retournez une valeur vide si aucune surcharge ne correspond aux arguments.</li> <li>Appeler la fonction trouvée si une surcharge correspond aux arguments.</li></ul><pre><code>template <class... EXTRA_SELECTORS>struct call{ template <class... N_ARY> static std::any op(EXTRA_ARGS... extra_args, N_ARY... args) { // Les surcharges disponibles. const auto& ops = get_ops<selector_t, op_func_t>(); // Trouver une surcharge correspondante. const auto pos = ops.find(op_sel_t::make()); // Résultat vide si auncune surcharge n'est trouvée. if (pos == ops.end()) return std::any(); // Appel à la bonne surcharge trouvée. return pos->second(extra_args..., args...); }};</code></pre><h2>Point final</h2><p>Cela complète la description du design du système dynamique d’appels surchargés. Le repo du code source contient de multiples exemples d’opérations avec une suite complète de tests.</p><p>Les opérations données en exemple sont :</p><ul> <li><code>compare</code>, une opération binaire pour comparer deux valeurs.</li> <li><code>convert</code>, une opération unaire pour convertir une valeur vers autre type. Cet exemple montre une opération avec un argument de sélection supplémentaire, le type final de la conversion.</li> <li><code>is_compatible</code>, une opération nullaire prenant deux types supplémentaires pour sélectionner la surcharge, et qui vérifie si l’un peut être converti en l’autre.</li> <li><code>size</code>, une opération unitaire retournant le nombre d’éléments dans un conteneur, ou retournant zéro si aucune surcharge n’a été trouvée.</li> <li><code>stream</code>, une opération unaire pour écrire une valeur dans un flux de texte. C’est un exemple d’une opération avec un argument immuable, la destination <code>std::ostream</code>.</li> <li><code>to_text</code>, une opération unitaire de conversion d’une valeur en texte.</li></ul><p>Tout le code se trouve dans la bibliothèque <code>any_op</code> qui fait partie de <a href="https://github.com/pierrebai/dak_utility">mon repo dak_utility</a>.</p>

Stratégie
5 min de lecture
Établir une stratégie de réduction de la dette technique
<p>J’ai eu l’occasion de travailler ces derniers mois sur deux applications souffrant de lourdes dettes techniques, et pour l’une d’elles, il a fallu mettre en place une stratégie de paiement progressif, plus aisée à appliquer pour la plupart des <a href="https://www.spiria.com/fr/services/developpement-axe-performance/developpement-logiciel-sur-mesure/">projets de développement</a>.</p><p>À première vue, évaluer le coût d’une dette technique est très complexe. Il faut également se rappeler que si on n’y consacre qu’un minimum d’efforts, on finira toujours par ne payer que les intérêts. Chaque nouvelle fonctionnalité ne sera alors qu’un nouveau prêt, s’ajoutant dans le meilleur des cas au principal. Afin de rattraper le retard et de poursuivre en payant ses mensualités à temps, il faut évaluer la dette actuelle et revoir certaines définitions et pratiques au sein de l’équipe.</p><p>Est-ce que des librairies pas à jour ou l’utilisation d’une API obsolète constituent une dette technique ? Qu’en est-il des bogues connus ? Et des problèmes de performance qui découlent de ces précédents points ? Et de chaque raccourci pris dans le passé simplement pour pouvoir livrer à temps sans réellement régler le problème ? Oui, ils sont tous constitutifs de la dette globale. Est-ce qu’une architecture non optimale fait partie de cette dette technique ? Probablement non, et le changement d’architecture serait un thème à traiter à part. Et le déficit en tests unitaires ? Ce n’est pas non plus une dette en soi, mais l’implémentation de ceux-ci fera sans doute partie des bonnes habitudes qui permettront de ne pas faire croître la dette à nouveau.</p><p>La première étape inévitable est de disséquer l’application et de regrouper par thème chaque élément de la dette. Le temps nécessaire à cette analyse dépendra directement de l’implication antérieure de l’équipe dans le projet. Une fois une vue d’ensemble acquise, les différentes parties de la dette peuvent être regroupées en trois catégories principales :</p><ul> <li>celles qui sont d’ordre général, qui peuvent être payées sans conséquences directes pour les fonctionnalités, et qui sont donc chacune candidates à leur propre récit (par exemple : “mettre à jour React à la dernière version”) ;</li> <li>celles qui sont d’ordre général, mais qui devront être répétées pour de multiples fonctionnalités, et qui sont donc candidates à devenir des sous-tâches (par exemple, effectuer la transition de classes à des composantes fonctionnelles) ;</li> <li>celles qui sont spécifiques à un récit et qui ne seront pas dupliquées (typiquement, un bogue concret).</li></ul><p>Avant de commencer l’évaluation des efforts à attribuer à chaque élément de la dette, il est important de redéfinir la notion d’achèvement. À quel moment considère-t-on un récit comme “fini”, que la “story” peut être fermée ? Avant que les tests d’assurance qualité ne débutent ? Après les tests d’AQ et le “merge” dans la branche de développement principale, mais avant les tests de contrôle qualité (CQ) ? Ou encore, seulement au moment où la fonctionnalité est livrée aux utilisateurs ?</p><p>Dans le cas de mon équipe, l’approche qui s’est avérée la plus bénéfique fut de considérer un récit comme “fini” une fois que les tests d’AQ étaient réussis et que notre code se trouvait dans notre branche de développement. Toutefois, notre entente stipulait que cette branche devrait être livrable à tout moment. En conséquence, aucun “merge” incomplet ou problématique n’était en aucun cas permis.</p><p>Lors du premier “grooming”, nous avons dû mettre en place une méthodologie permettant une réduction systématique de la dette. Cette méthodologie se révéla en fin de compte assez simple, tout en étant méticuleuse et très efficace.</p><p>Il a été convenu qu’un maximum de 20 % du sprint serait consacré au paiement de la partie de la dette d’ordre général (la première catégorie de notre liste plus haut), celle qui ne nous créerait pas de bogues affectant les fonctionnalités individuelles.</p><p>Pour la deuxième catégorie de dette, c’est-à-dire les parties qui sont globales, mais qui doivent être répétées, il a été décidé d’y aller progressivement. Chaque fois que l’on toucherait à une fonctionnalité existante, deux sous-tâches seraient créées automatiquement pour le récit, l’une pour la refactorisation du code et l’autre pour les tests unitaires. Ces deux sous-tâches représentaient dans la majorité des cas de 15 à 30 % des points du récit (“Story Points”).</p><p>La troisième catégorie de la dette, qui porte sur des bogues spécifiques à une fonctionnalité, serait payée selon les priorités établies dans le carnet de produit (“Backlog”).</p><p>Au bout de trois sprints avec cette approche, le résultat fut sans appel. Chaque refactorisation du code nous a permis d’éliminer beaucoup de raccourcis qui avaient été pris auparavant, d’améliorer la modularité et de tester chaque ligne de code, ce qui a eu un impact direct sur la facilité de maintenance et d’implémentation, la fiabilité et les performances. Les bénéfices étaient palpables des deux côtés de l’application : pour les développeurs et, tout aussi important, pour les utilisateurs. Au final, la taille du paquet de l’application a été réduite de 70 % (de 12 à 3,5 Mo), le temps de chargement de 80 % et la couverture en tests unitaires est passée de 2 à 60 % (ce qui représente près de 380 nouveaux tests).</p><p>Même si au départ tout ce travail semblait représenter un effort relativement flou et un coût supplémentaire inconnu, il s’est avéré finalement que les bénéfices obtenus justifiaient largement l’investissement, et ce sur long terme. Il n’est parfois pas aisé d’être complètement transparent sur la dette existante, et d’exposer celle-ci à toute l’équipe par le biais du carnet de produit, mais en mettant en place des métriques pour chaque objectif, l’avancée est plus que gratifiante pour toutes les personnes impliquées.</p>

Bonnes pratiques
5 min de lecture
Quelles sont les qualités d’un bon chef de développement logiciel?
<p>Ainsi, qu’est-ce qu’un bon chef de développement logiciel? Avant de répondre à cette question, demandons-nous <i>pourquoi</i> il faut un chef de développement logiciel.</p><h2>Un chef est-il vraiment nécessaire?</h2><p>S’il est admis que les bons logiciels sont développés par les les bonnes équipes, alors, il est logique d’avoir un bon chef d’équipe.</p><p>D’abord, un bon chef d’équipe <b>écarte les problèmes</b> qui empêchent l’équipe de travailler libre de distractions ou d’obstacles. Une équipe qui doit s’écarter du développement pour régler des problèmes accessoires est une équipe dont la performance en développement est diminuée.</p><p>Ensuite, un bon chef d’équipe <b>motive et rallie l’équipe</b> tout au long du cycle de vie du projet, surtout lorsque les choses vont mal. Une équipe de développement logiciel se compose (normalement) d’êtres humains ; or, il faut comprendre ce qui anime chacun de ses membres pour guider l’équipe vers son but commun.</p><p>Enfin, un bon chef d’équipe <b>cerne et gère les lignes de fracture</b>. Katerina Bezrukova, professeure adjointe de dynamique de groupe à l’Université de Santa Clara, a étudié des entreprises de haute technologie de la Silicon Valley pour déterminer si la « chimie d’équipe » peut être prédite, et si celle-ci est essentielle au succès. Les « lignes de fracture » sont les caractéristiques personnelles qui peuvent produire des clivages au sein d’un groupe, par exemple l’âge, le sexe, l’ethnicité, les motivations professionnelles, les passe-temps ou les intérêts personnels. Ces clivages permettent de comprendre les effets de la composition d’un groupe, soit la « chimie d’équipe ». La diversité peut creuser les lignes de fracture, mais si celles-ci peuvent être comblées, par exemple grâce aux intérêts chevauchants des membres d’un groupe, les conflits éventuels peuvent être surmontés.</p><p>Il existe d’autres excellentes raisons pour lesquelles un bon chef d’équipe vaut son pesant d’or, mais nous nous fonderons sur les trois raisons ci-dessus pour examiner les qualités qui vous permettront d’en être un.</p><h2>Empathie</h2><p>Les bonnes équipes ne se créent pas par hasard : il faut comprendre chaque membre de l’équipe et, surtout, comprendre que leur vision du monde et perspective sont différentes des vôtres. Un bon chef d’équipe observe et retient les comportements et réactions des membres de l’équipe. Ceci dit, rien ne remplace <b>l’écoute active</b>. Souvent, les chefs d’équipe frustrés ne comprennent pas pourquoi leur technique de ralliement ne fonctionne pas (par exemple, but commun et valeurs culturelles partagées, ennemi commun, système de récompense). Ces chefs d’équipe ont tendance à se rabattre sur la technique « c’est moi qui commande ».</p><p>Le manque d’empathie pour les membres de l’équipe peut creuser les lignes de fracture et créer des clivages au sein de l’équipe.</p><h2>Anticipation</h2><p>Les clients comme les développeurs de logiciels sont unanimes : tous apprécient les chefs d’équipe qui <b>anticipent les réactions et les résultats</b>. Ces chefs d’équipe ont pris le temps de prévoir la façon par laquelle un résultat pourrait infléchir divers comportements et ont <b>fait un plan</b>. Ce genre de leadership valorise les membres de l’équipe dans toute leur diversité de personnalités et d’expériences, sans essayer de les homogénéiser et d’en faire une armée de robots commandés.</p><p>La capacité d’un chef d’équipe à prévoir et anticiper conforte le sentiment qu’il <b>s’occupe de tout et de tous</b> – équipe et client – pour que les membres de l’équipe puissent travailler l’esprit tranquille et se concentrer sur leur mission.</p><h2>Communication</h2><p>L’on n’insiste jamais assez sur l’importance de <b>l’écoute active</b> comme élément clé de la communication. Un bon chef d’équipe doté d’une bonne écoute active mène par l’exemple, ce qui encourage les membres de l’équipe de faire preuve à leur tour d’écoute active en cas de conflits entre eux. Mais l’inverse est vrai aussi : un chef d’équipe qui n’est pas à l’écoute sème la zizanie entre les membres de l’équipe, qui se concurrencent pour se faire entendre.</p><p>Les équipes fonctionnent le mieux lorsque la confiance règne. Pour faire régner la confiance, un bon chef d’équipe doit savoir quel message transmettre (« le <b>quoi</b> »), <b>quand</b> le transmettre (le choix du moment) et <b>comment</b> (le ton). Le bon choix du « quoi », « quand » et « comment » s’appuie sur l’empathie et l’anticipation qui nous ont aidé à bien comprendre la dynamique du groupe.</p><p>Toujours dans l’optique de « connaître son auditoire », attention aux effets pervers de la sur-communication. Parfois, il vaut mieux éviter la précipitation afin de ne pas semer la panique ou aggraver l’anxiété au sein de l’équipe. Accordez à votre équipe le temps, et la confiance, de régler les problèmes.</p><h2>Prise de décision</h2><p>Peu importe que vous soyiez le genre de chef d’équipe qui mène de l’arrière ou de l’avant : votre équipe s’attend à ce que vous fassiez preuve de décision et de responsabilité. Ce « courage décisionnel » ne signifie pas la prise de risques, mais plutôt la fermeté, la conviction et la prise de responsabilité. Si votre compétence et votre titre appuient votre crédibilité, le manque de courage la saborde.</p><h2>Compétence</h2><p>Enfin, la compétence est toujours importante pour un chef d’équipe. Il ne s’agit pas de savoir coder ou de connaître votre clients ou leur secteur mieux que quiconque, mais plutôt, de posséder les qualités déclinées plus haut : compétence au niveau de l’empathie, de l’anticipation et de la planification, de la communication et de la prise de décision.</p><p>Soyez honnête avec votre équipe quant à votre niveau de compétence : cela lui permettra d’ajuster tout naturellement sa dynamique jusqu’à ce que tout le monde atteigne son plein potentiel dans chaque domaine.</p><p>D’aucuns diront que les développeurs de logiciels n’ont pas besoin de chef d’équipe. Certes, une équipe peut toujours se débrouiller tant bien que mal en l’absence de direction ; mais les problèmes sous-jacents ne se résolvent pas tout seuls. En fait, ils empêchent les développeurs de se concentrer sur leur mission, puisque ceux-ci doivent maintenant s’attarder sur la dynamique du groupe et les autres problèmes inhérents à la collaboration humaine. Les mauvais chefs d’équipe, en revanche, imposent leur volonté à l’équipe et divisent le groupe pour se rendre indispensables au progrès.</p><p>C’est pourquoi il est de l’intérêt de toute l’équipe – client et développeurs – d’avoir un bon chef d’équipe pour guider votre projet de développement logiciel. Si la valeur d’un bon chef d’équipe est difficile à isoler et à quantifier, elle éclate au grand jour dès que l’on constate l’amélioration de la performance des membres de son équipe.</p>

Stratégie
5 min de lecture
Comment établir le budget de mon projet de développement logiciel ?
<p>Bien entendu, le facteur coût est important, mais ce coût ne dicte pas la <b>valeur</b> de votre idée. Dans cet article, nous examinerons la façon d’établir un budget qui appuiera votre idée, qui lui donnera un élan plutôt que de la freiner, pour que rien n’entrave vos rêves et votre inspiration.</p><h2>Faites vos devoirs</h2><p>Eh oui, les mots « rêves » et « inspiration » sont suivis du mot « devoirs ». En effet, préparation et information sont les prérequis d’une décision budgétaire éclairée.</p><h3>Préparation : comparaisons et leçons</h3><p>Les devoirs se composent de deux éléments complémentaires : préparation et philosophie. La <b>préparation</b> vous permet de faire des <b>comparaisons</b> pour vous situer. Idéalement, vous comparez des pommes avec des pommes, mais parfois, il n’existe pas deux pommes comparables. Gardez cela à l’esprit lorsque vous comparerez des « projets semblables », et évitez de présumer qu’un résultat en garantit un autre. De plus, demandez-vous <b>comment votre entreprise maintiendra ses activités malgré la surcharge d’un projet additionnel</b> (logiciel ou autre), puisqu’un projet de développement logiciel pourrait perturber vos activités quotidiennes. Les projets réussis reposent sur une robuste collaboration — ce qui, à son tour, exigera du temps de votre personnel. Enfin, tirez des leçons de vos projets antérieurs (et ceux des autres), et gardez-les toujours à l’esprit.</p><h3>Philosophie : assurance-coût et assurance-valeur</h3><p>Le second élément complétant vos devoirs consiste à définir votre <b>philosophie</b> budgétaire. L’on croit à tort que l’existence d’un budget représente une <b>assurance-coût</b> (sans parler de son incidence sur la certitude des délais !) Demandez-vous si vous recherchez réellement une assurance-coût, ou plutôt une <b>assurance-valeur</b>. La valeur est cultivée et affinée dans le temps — voulez-vous positionner votre équipe pour rechercher la valeur ? L’orientation valeur ne signifie pas un chèque en blanc ; elle signifie tout simplement que vous êtes disposé à revoir vos priorités et à affiner votre budget au fur et à mesure de l’évolution de la situation.</p><p>Un bon plan de <a href="https://www.spiria.com/fr/services/developpement-axe-performance/developpement-logiciel-sur-mesure/">développement logiciel</a> ne suit jamais le plan. Par contre, si vous faites bien vos devoirs, vous pourrez prendre des décisions en cours de projet et apporter des ajustements en fonction des faits nouveaux, d’un raisonnement sûr et de nouveaux objectifs, plutôt que de constamment éteindre des feux.</p><h2>Liste de vérification</h2><p>Cernons maintenant les éléments d’un projet de développement logiciel :</p><p><b>Travail, dont frais de déplacement</b> : Ceci inclut le travail de votre entreprise ou celui d’un consultant externe. N’oubliez pas que vous payez pour de l’expertise en plus de codage : les conseils techniques ne sont pas gratuits, et représentent en fait la plus importante partie de votre projet.</p><p><b>Équipement, licences de logiciels</b> : Tant d’options s’offrent à vous que vous devez clairement définir les objectifs et comprendre le contexte. Par exemple, un logiciel gratuit open source peut convenir dans certains cas, mais pas dans d’autres.</p><p><b>Impact sur les activités quotidiennes</b> : Votre projet enlèvera votre personnel de vos activités quotidiennes. Et, une fois le projet livré, vous devrez prévoir des coûts de formation et d’adoption du nouveau système jusqu’à ce qu’il soit pleinement intégré à vos activités.</p><p><b>Coûts de maintenance récurrents</b> : Vous devriez prévoir entre 10 % et 30 % du coût de projet initial par année. Au moment de dresser votre liste de vérification, évaluez les nombreux facteurs qui infléchiront le budget que vous devrez allouer à chacun des éléments suivants :</p><ul> <li>Coûts directs et indirects</li> <li>Coûts fixes et variables</li> <li>Coûts uniques et récurrents</li> <li>Désirs et nécessités</li></ul><p>Dresser une liste de vérification est facile ; l’assortir d’estimations réalistes est difficile. C’est là que votre préparation paie des dividendes : vos comparaisons vous aideront à estimer la dépense initiale, et les leçons vous aideront à faire votre évaluation de risques et d’imprévus. Enfin, le plus important reste l’acceptation, par tous, du budget et de la valeur intrinsèque du projet. Il faudra faire front commun au moment de recueillir des devis.</p><h2>Recueillir des devis</h2><p>Maintenant que vous avez un budget préliminaire pour votre projet de développement logiciel — pour votre idée ! — vous pouvez commencer à recueillir des devis des entreprises et fournisseurs.</p><p>Vous recevrez toute une fourchette de devis, dont quelques surprises. Examinez ces surprises : certaines témoignent peut-être d’éléments que vous avez oubliés ou sous-estimés, tandis que d’autres ne sont rien de plus que des « surclassements » ou des arnaques. Ou bien, elles pourraient indiquer que ces entreprises ou fournisseurs ont mal compris votre philosophie, ou qu’ils fondent leur devis non pas sur vos besoins, mais bien les leurs. Profitez-en pour revoir votre philosophie.</p><p>Enfin, n’oubliez pas que ces devis ne représentent qu’une partie du budget total — c’est pourquoi nous avons calculé les coûts indirects et l’impact de votre projet sur vos activités quotidiennes.</p><h2>Continuez à rêver !</h2><p>Nous avons commencé cet article en parlant de votre grande idée. L’énergie et la passion que vous avez ressenties au moment de la visite de votre idée doivent être mises en bouteille et partagées tout au long du projet. Le train-train quotidien de la gestion d’un budget risque d’obscurcir votre vision originale, ce qui pourrait la distordre.</p><p>Il existe un juste équilibre entre, d’une part, respecter le budget — même s’il est coulé dans le béton — et, d’autre part, donner vie à votre idée ; votre état de préparation et votre leadership vous aideront à réussir ce tour de funambule.</p><p>Et <a href="https://www.spiria.com/fr/">Spiria</a> est là pour vous aider. 😊</p>

Développement sur mesure
5 min de lecture
Le PMV, la stratégie gagnante pour être rapidement sur le marché !
<p>Le PMV, le « Produit Minimum Viable », est essentiellement une version du logiciel présentant juste assez de fonctionnalités pour satisfaire les premiers clients et obtenir d’utiles retours d’information pour les futurs développements du produit. Mais à qui est-ce de déterminer ce qui compose le PMV ? La réponse est « à tous les acteurs ». Le créateur lui-même, mais aussi et surtout l’utilisateur final, ainsi que le développeur, qui pourra aider à trancher entre finalité et éventuelle difficulté technique.</p><p>Il est important de se rappeler qu’on pourra éventuellement en faire des itérations (des versions 2, 3, etc.), et qu’il sera possible d’ajouter de nouveaux incréments à chaque sprint. L’itération et le perfectionnement continu sont un modèle éprouvé depuis plusieurs années pour de nombreux types de produits, et c’est lui qui permet de sortir la création le plus rapidement possible !</p><p>La nature même du <a href="https://www.spiria.com/fr/services/developpement-axe-performance/developpement-logiciel-sur-mesure/">développement logiciel</a> s’y prête parfaitement avec des méthodologies adaptées. L’objectif est de recevoir des commentaires et opinions le plus rapidement possible et ainsi d’encore mieux cerner les désirs des utilisateurs et ce qui fera pencher la balance dans les options constitutives du produit. Conséquemment, ces retours fourniront également la « liste de souhaits » des versions 2, 3, et ainsi de suite.</p><p>Plus concrètement, c’est avec des maquettes, des démonstrations de faisabilité (<em>Proof of Concept</em>), des “spikes” et finalement un <a href="https://www.spiria.com/fr/services/design-centre-sur-utilisateur/prototypage-produit-logiciel/">prototype</a> fonctionnel, créés conjointement par des <a href="https://www.spiria.com/fr/services/design-centre-sur-utilisateur/design-experience-utilisateur/">designers/experts UX</a> et des développeurs, qu’on peut arriver au meilleur PMV possible. Une boucle de rétroaction fréquente (par exemple à chaque démo de sprint, ou encore à chaque étape importante que l’on s’est donnée) est la clé du succès.</p><p>En développement logiciel, il est possible que certaines fonctionnalités soient plus complexes à réaliser ou encore contiennent plusieurs incertitudes techniques. On doit se demander — et confirmer l’hypothèse — si cette fonctionnalité est essentielle au lancement initial, sinon elle ne fera que retarder la mise en marché.</p><p>La première impression d’un nouveau produit est cruciale, et on se doit d’avoir potentiellement au moins une fonctionnalité qui le différencie de la concurrence, qui innove ou atteint particulièrement bien son but. On recommande d’en sélectionner quelques-unes et de laisser les autres pour les versions à suivre. Le juste milieu, le PMV créant un engouement tout en n’offrant pas tout d’un coup, est un mélange magique qu’on peut atteindre à l’aide d’une équipe méticuleuse épaulée d’expert logiciel qui pourra calculer la faisabilité/complexité, la mesurer aux autres fonctionnalités et émettre des recommandations techniques.</p><p>Généralement, un produit minimum viable contient chaque strate d’un produit, mais en version incomplète (par exemple, on veut qu’il y ait un forum ou un blogue, mais au début, il n’y aura pas la possibilité de faire de recherche dans ledit forum/blog). Parfois, une fonctionnalité entière n’est pas vraiment essentielle et fera un ajout très excitant pour l’utilisateur lorsqu’elle sera proposée.</p><p>Attention, incomplet ne signifie pas « qui contient encore des anomalies ! » Si l’utilisateur expérimente trop d’anicroches, il peut vivre une telle frustration qu’il ne reviendra pas pour les versions subséquentes.</p>

Coin des développeurs
5 min de lecture
Résoudre les problèmes des “futures” en C++
<p>De nombreux langages de programmation modernes vous permettent de parvenir à cette accélération grâce au code asynchrone et aux valeurs futures. Le principe de base de l’asynchronisation et des valeurs futures est que les fonctions appelées sont exécutées sur un autre fil, et les valeurs de retour sont converties en ce que l’on appelle une valeur future. De telles valeurs futures ne possèdent pas de valeur réelle tant que la fonction asynchrone n’est pas terminée. La fonction s’exécute simultanément et, lorsqu’elle finit par retourner une valeur, la variable de la valeur future est mise à jour en arrière-plan pour conserver cette valeur de retour. Il n’est pas nécessaire d’utiliser des mutex ou un système de messages inter-fil (“interthread”) explicite : toute la synchronisation entre l’appel initial et le deuxième fil d’exécution est effectuée en arrière-plan. Lorsque le fil initial accède à la valeur future, il est automatiquement mis en pause jusqu’à ce que la valeur soit prête.</p><p>Le principal avantage de ce système est qu’il est très facile d’avoir un fonctionnement asynchrone. Bien sûr, le programmeur doit s’assurer que la fonction peut réellement être exécutée dans un autre fil en toute sécurité, qu’il n’y a pas de course aux données avec d’autres fils d’exécution. Les fonctions asynchrones et les valeurs futures ne font que créer des fils d’exécution pour générer un résultat.</p><h2>Les problèmes écartés</h2><p>Mon but ici n’est pas de discuter de la manière de concevoir des algorithmes sans course entre fils, ni de la manière d’arranger des données pour faciliter l’exécution d’algorithmes multi-fils. Je me contenterai de mentionner qu’un moyen possible d’y parvenir consiste à éviter toute valeur globale et à transmettre toutes les données à la fonction asynchrone par valeur. De cette façon, rien n’est partagé entre les fils et donc aucune concurrence ne peut se produire.</p><h2>Les problèmes abordés</h2><p>Si l’asynchronisme et les valeurs futures permettent de transformer facilement une fonction en un fil conducteur, c’est cette simplicité même qui pose problème. Elle signifie une absence totale de contrôle. Plus précisément, vous n’avez aucun contrôle sur :</p><ul> <li>combien de fonctions asynchrones sont exécutées,</li> <li>combien de fils sont créés pour exécuter ces fonctions,</li> <li>combien de fils attendent des résultats.</li></ul><p>Cela nécessite un équilibre délicat entre le désir de maximisation de l’utilisation du processeur et le maintien d’un certain contrôle. D’une part, vous voulez qu’un maximum de fonctions asynchrones soit exécuté, afin que le processeur soit toujours entièrement occupé ; d’autre part, vous ne voulez pas arriver à une surcharge du processeur avec trop de fils.</p><h2>La solution</h2><p>La meilleure solution à ce problème est d’introduire une certaine complexité. La complexité ajoutée vous permet de reprendre le contrôle de tous les éléments énumérés ci-dessus.</p><h3>Première étape : le pool de fils</h3><p>La première étape consiste à renoncer aux fonctions asynchrones et aux valeurs futures pour maximiser l’utilisation du processeur. Ceux-ci peuvent toujours être utilisés pour exécuter des algorithmes parallèles, mais pas pour créer des fils multiples. Il est préférable d’utiliser un pool de fils (“thread pool”).</p><p>Un pool de fils vous permet de contrôler le nombre de fils créés pour exécuter des algorithmes parallèles parallèles. Vous pouvez créer exactement autant de fils qu’il y a de cœurs dans le processeur, ce qui garantit un débit maximal exact sans surcharge du processeur.</p><h3>Deuxième étape : la file d’attente des travaux</h3><p>Si le pool de fils contrôle le nombre de fils utilisés, il ne contrôle pas la manière dont les fonctions sont gérées par ces fils. C’est le travail de la file d’attente des travaux. Les fonctions à exécuter de manière asynchrone sont ajoutées à la file, et le pool prend des fonctions de cette file pour les exécuter et produire des résultats.</p><h3>Troisième étape : les résultats</h3><p>Alors que la file d’attente des travaux s’occupe de l’entrée des algorithmes parallèles, nous avons besoin d’une autre fonction pour gérer l’attente des résultats. Alors qu’on aurait pu utiliser une file d’attente de résultats, nous avons une meilleure option : les valeurs futures ! La synchronisation entre le producteur d’un résultat et son consommateur est exactement l’utilité des valeurs futures. La principale différence par rapport au problème initial est que les résultats sont ici créés par le pool de fils.</p><h3>Quatrième étape : le vol de fils</h3><p>Un problème est alors de savoir ce qui se passe si l’algorithme parallèle soumet des sous-algorithmes à la file d’attente des travaux et attend leurs résultats. Nous pourrions nous retrouver à court de fils ! Chaque fil pourrait attendre que les résultats soient produits alors qu’aucun fil n’est disponible pour produire ces résultats.</p><p>La solution à ce problème est le concept de vol de fil en attente d’un résultat. Essentiellement, vous fournissez une fonction qui peut travailler sur la file d’attente des travaux tant qu’un résultat précis n’est pas disponible. Nous évitons donc d’accéder directement aux valeurs futures produites, car accéder à une valeur future non disponible bloquerait le fil. Au lieu de cela, nous passons la valeur future à la file d’attente, qui peut alors exécuter des travaux en attendant que la valeur soit prête.</p><h2>Exemple de code concret</h2><p>J’ai mis en place un tel système à plusieurs reprises dans le passé. Je l’ai récemment réimplémenté dans une application open-source, écrite en C++. Cette application s’appelle <i>Tantrix Solver</i> et comme son nome l’indique, elle solutionne les puzzles Tantrix. Le code de l’application est disponible sur GitHub et contient plusieurs branches Git :</p><ul> <li>Une branche utilise des fonctions asynchrones et les valeurs futures.</li> <li>Une autre branche montre le même algorithme en utilisant le design suggéré.</li></ul><p>Le repo sur GitHub est disponible <a href="https://github.com/pierrebai/Tantrix">ici</a>.</p><h3>Version asynchrone avec valeurs futures</h3><p>La branche Git contenant la version asynchrone avec valeurs futures est appelée “thread-by-futures”.</p><p>Le design du code dans cette branche est simple. C’est son principal avantage. Il utilise la fonction C++ <code>std::async</code> avec le mode <code>std::launch::async</code> pour créer des fils. Cependant, les problèmes que nous avons mentionnés se manifestent comme prévu, avec un nombre incontrôlé de fils. Un puzzle Tantrix simple peut engendrer la création de quelques dizaines de fils, ce qui est probablement trop, mais reste encore gérable. En revanche, un puzzle Tantrix complexe peut créer plusieurs <b>centaines</b> de fils, ce qui peut saturer la plupart des ordinateurs.</p><h3>Pool de fils et file d’attente des travaux</h3><p>La branche Git contenant la version avec pool de fils et file d’attente est appelée “thread-pool”. Je vais décrire plus en détail la conception du code, car elle est plus complexe, bien que j’aie essayé de la garder aussi simple que possible.</p><h3>Conception du code : les parties faciles</h3><p>Dans cette section, je présenterai les éléments les plus simples du design.</p><p>Le premier élément du design est la classe du pool de fils. Il suffit de lui donner un “fournisseur de travail” et le nombre de fils à créer :</p><pre><code style="white-space: pre;"> // A pool of threads of execution. struct thread_pool_t { // Create a thread pool with a given number of threads // that will take its work from the given work provider. thread_pool_t(work_provider_t& a_work_provider, size_t a_thread_count = 0); // Wait for all threads to end. ~thread_pool_t(); private: // The internal function that execute queued functions in a loop. static void execution_loop(thread_pool_t* self); };</code></pre><p>Le fournisseur de travail (“work provider”) indique aux fils ce qu’ils doivent faire. Il contrôle l’arrêt et l’exécution des algorithmes. Il dispose d’une fonction d’attente et d’exécution qui encapsule entièrement l’exécution d’un travail ou l’attente d’une fonction à exécuter. Les détails de la cette fonction seront présentés ultérieurement, dans son implémentation concrète. Pour l’instant, voici la conception du fournisseur :</p><pre><code style="white-space: pre;"> // The provider of work for the pool. struct work_provider_t { // Request that the threads stop. virtual void stop() = 0; // Check if stop was requested. virtual bool is_stopped() const = 0; // The wait-or-execute implementation, called in a loop // by the threads in the thread =s pool. virtual void wait_or_execute() = 0; };</code></pre><p>Ces deux classes précédentes sont cachées dans la file d’attente de travail. Pour cette raison, elles peuvent en fait être totalement ignorées par les utilisateurs de la conception. C’est pourquoi nous ne les aborderons pas plus avant.</p><h3>Conception du code : les parties intermédiaires</h3><p>La queue de travail est la pièce la plus complexe. Sa mise en œuvre est un template C++ pour faciliter l’utilisation d’un algorithme donné qui produit un type spécifique de résultats.</p><p>Comme il s’agit de la partie centrale du design, je la présenterai dans son intégralité. Je diviserai la présentation de la classe en plusieurs parties pour la rendre plus facile à comprendre.</p><p>La première partie de la conception est constituée des paramètres du template C++ :</p><pre><code style="white-space: pre;"> template <class WORK_ITEM, class RESULT> struct threaded_work_t : work_provider_t { using result_t = typename RESULT; using work_item_t = typename WORK_ITEM; using function_t = typename std::function<result_t(work_item_t, size_t)>;</code></pre><p>Le <code>work_item_t</code> (WORK_ITEM) est la donnée d’entrée de l’algorithme. Le <code>result_t</code> (RESULT) est la sortie de l’algorithme. La <code>function_t</code> est l’algorithme. En utilisant une fonction, nous pouvons prendre en charge une <i>famille</i> d’algorithmes ayant les mêmes types en entrée et en sortie. Lorsqu’un <code>work_item_t</code> est ajouté, l’appelant fournit également la fonction à appliquer.</p><p>La deuxième partie de la conception de la file d’attente des travaux est l’ensemble des types de données de mise en œuvre et les variables membres. Les voici :</p><pre><code style="white-space: pre;"> using task_t = std::packaged_task<result_t(work_item_t, size_t)>; // How the function, work item and recursion depth is kept internally. struct work_t { task_t task; work_item_t item; }; std::mutex my_mutex; std::condition_variable my_cond; std::atomic<bool> my_stop = false; std::vector<work_t> my_work_items; const size_t my_max_recursion; // Note: the thread pool must be the last variable so that it gets // destroyed first while the mutex, etc are still valid. thread_pool_t my_thread_pool;</code></pre><p>Le type <code>task_t</code> contient la fonction dans un type C++ spécial qui peut l’appeler tout en produisant un <code>std::future</code> en C++. C’est ainsi que les valeurs futures sont créées. Le <code>work_t</code> est l’unité de travail qui peut être exécutée par un fil.</p><p>Les deux premières variables membres de la file d’attente sont le mutex et la variable de condition qui servent toutes deux à protéger les données partagées entre les fils d’exécution et le fil principal.</p><p>La variable atomique <code>my_stop</code> est utilisée pour signaler que toute exécution doit s’arrêter (quelle surprise !). Le vecteur de <code>work_t</code> contient les unités de travail à exécuter. Il s’agit donc de la file d’attente elle-même. La variable <code>max_recursion</code> est un détail de l’implémentation utilisé pour éviter la récursion trop profonde due au vol de fils. Ce sera expliqué plus en détail plus tard. Le <code>thread_pool</code> est évidemment l’endroit où les fils d’exécution sont gardés.</p><p>La troisième partie du design est la création de la file d’attente des travaux et l’implémentation de l’interface <code>work_provider_t</code>. Tout cela est relativement simple. Nous créons le pool de fils internes avec le nombre exact de cœurs du processeur. Nous faisons également passer la file d’attente de travail elle-même en tant que “fournisseur de travail” (<code>work_provider_t</code>) du pool de fils.</p><pre><code style="white-space: pre;"> // Create a threaded work using the given thread pool. threaded_work_t(size_t a_max_recursion = 3) : my_max_recursion(a_max_recursion), my_thread_pool(*this, std::thread::hardware_concurrency()) {} ~threaded_work_t() { stop(); } // Stop all waiters. void stop() override { my_stop = true; my_cond.notify_all(); } // Check if it is stopped. bool is_stopped() const override { return my_stop; } // Wait for something to execute or execute something already in queue. void wait_or_execute() override { std::unique_lock lock(my_mutex); return internal_wait_or_execute(lock, 0); }</code></pre><p>La mise en œuvre des fonctions de destruction et d’arrêt (<code>stop</code>) utilise simplement le drapeau d’arrêt (<code>my_stop</code>) et la variable de condition pour signaler l’arrêt à tous les fils d’exécution. La fonction <code>wait_or_execute</code> appelle simplement une autre fonction interne, plus complexe, qui sera décrite dans la section suivante.</p><h3>Conception du code : les parties complexes</h3><p>Dans cette section, nous arrivons enfin au cœur du design, les détails les plus complexes du code.</p><p>Tout d’abord, la fonction d’attente d’un résultat donné. Cette partie est encore facile à comprendre. Tant que la valeur future n’est pas prête, nous continuons à attendre l’arrivée de nouveaux résultats ou de nouveaux travaux à exécuter. C’est ici que nous travaillons pour d’autres algorithmes en file d’attente au lieu de dormir bêtement et de perdre un fil d’exécution. Si nous recevons le signal de tout arrêter, nous sortons rapidement avec un résultat vide.</p><pre><code style="white-space: pre;"> // Wait for a particular result, execute work while waiting. result_t wait_for(std::future<result_t>& a_token, size_t a_recusion_depth) { while (!is_stopped()) { std::unique_lock lock(my_mutex); if (a_token.wait_for(std::chrono::seconds(0)) == std::future_status::ready) return a_token.get(); internal_wait_or_execute(lock, a_recusion_depth); } return {}; }</code></pre><p>Deuxièmement, la fonction qui exécute réellement l’unité de travail. Elle attend quand il n’y a rien à exécuter. En revanche, lorsqu’il <i>y a</i> au moins une unité de travail en file d’attente, elle exécute son algorithme, qui produira un nouveau résultat.</p><pre><code style="white-space: pre;"> private: // Wait for something to execute or execute something already in queue. void internal_wait_or_execute(std::unique_lock<std::mutex>& a_lock, size_t a_recursion_depth) { if (my_stop) return; if (my_work_items.size() <= 0) { my_cond.wait(a_lock); return; } work_t work = std::move(my_work_items.back()); my_work_items.pop_back(); a_lock.unlock(); work.task(work.item, a_recursion_depth + 1); my_cond.notify_all(); }</code></pre><p>La seule chose subtile à observer, c’est que si la fonction était en attente et est réveillée, alors elle retourne immédiatement au lieu d’essayer d’exécuter un travail quelconque. Il y a une bonne raison pour un retour immédiat : le réveil peut être dû à un résultat devenant disponible ou à une unité de travail ajoutée. Comme nous ne savons pas de quel cas il s’agit et comme l’appelant pourrait être intéressé par ces nouveaux résultats, nous revenons à l’appelant pour qu’il puisse vérifier. Peut-être que la valeur future qu’il attendait est prête !</p><p>Enfin, voici la fonction de soumission des travaux à exécuter :</p><pre><code style="white-space: pre;"> // Queue the the given function and work item to be executed in a thread. std::future<result_t> add_work(work_item_t a_work_item, size_t a_recusion_depth, function_t a_function) { if (my_stop) return {}; // Only queue the work item if we've recursed into the threaded work only a few times. // Otherwise, we can end-up with too-deep stack recursion and crash. if (a_recusion_depth < my_max_recursion) { // Shallow: queue the function to be called by any thread. work_t work; work.task = std::move(task_t(a_function)); work.item = std::move(a_work_item); auto result = work.task.get_future(); { std::unique_lock lock(my_mutex); my_work_items.emplace_back(std::move(work)); } my_cond.notify_all(); return result; } else { // Too deep: call the function directly instead. std::promise<result_t> result; result.set_value(a_function(a_work_item, a_recusion_depth + 1)); return result.get_future(); } }</code></pre><p>La principale chose inattendue à remarquer est la vérification de la profondeur de récursivité. Le problème subtil que ceci vise à éviter est lié à la mise en œuvre des fonctions <code>wait_for()</code> et <code>wait_or_execute()</code>. Comme l’attente peut provoquer l’exécution d’une autre unité de travail et cette unité de travail pourrait à son tour finir par attendre et exécuter une autre unité de travail, cela pourrait faire boule de neige et devenir une récursion très profonde.</p><p>Malheureusement, nous ne pouvons pas refuser d’exécuter un travail, car cela pourrait signifier que tous les fils cesseraient d’exécuter du travail. Le système cesserait de travailler et s’arrêterait ! Ainsi, au lieu de cela, lorsque le maximum de profondeur est atteint à l’intérieur d’un fils d’exécution, tout travail supplémentaire mis en file d’attente par ce fils est exécuté immédiatement.</p><p>Bien que cela semble équivalent à mettre en file d’attente le travail à accomplir, ce n’est pas le cas. Comme vous voyez, la quantité de travail nécessaire pour évaluer une branche d’un algorithme est limitée. En contraste, le nombre d’unités de travail qui peuvent être mises en attente en raison de <b>toutes</b> les branches des algorithmes peut être extrêmement grand. En effet, on peut supposer que l’algorithme a été conçu de manière à ce <b>qu’une</b> branche ne cause pas de récursion excessive. Nous ne pouvons pas supposer la même chose sur le total de tous les travaux mis en attente par plusieurs branches indépendantes de l’algorithme.</p><p>Pour cette raison, il est également judicieux de vérifier la profondeur de récurrence dans l’algorithme lui-même et ne pas même mettre en file d’attente ces éléments de travail, une fois la profondeur maximale de récursion atteinte. Il devrait plutôt appeler leur fonction directement dans l’algorithme, plutôt que passer par la file d’attente, pour rendre le tout plus efficace.</p><p>En dehors de cette subtilité, le reste du code ne fait que mettre en file d’attente l’unité de travail et réveiller tout fil qui voulait exécuter un travail.</p><h2>Conclusion</h2><p>Comme on le voit, cette mise en place d’une file d’attente de travail remplace les fonctions asynchrones et les valeurs futures par une réserve de fils. Pour l’appelant, il suffit de connaître deux fonctions : <code>add_work()</code> et <code>wait_for()</code>. L’interface est donc assez simple à utiliser, mais il donne qu’un contrôle supplémentaire sur le multithreading pour éviter de surcharger le processeur.</p><p>J’espère qu’un jour, le standard C++ sera doté d’un design intégré pour les files d’attente et les pools de fils, afin de ne pas avoir à les faire à la main comme ici. D’ici là, n’hésitez pas à réutiliser mon design.</p>

Coin des développeurs
5 min de lecture
Emballé pour le multithreading
<h2>La duplication</h2><p>Cette première approche est la plus simple. Il suffit de dupliquer les données pour chaque thread d’exécution. Pour que cela fonctionne, les données doivent répondre à quelques critères :</p><ul> <li>être faciles à identifier,</li> <li>ne pas avoir de parties cachées,</li> <li>être faciles à reproduire,</li> <li>ne pas avoir d’exigences essentielles pour être partagées en continu.</li></ul><p>Si les données répondent à tous ces critères, la duplication est alors la solution la plus rapide et la plus sûre. Habituellement, les données qui peuvent être utilisées de cette manière sont essentiellement un groupe de valeurs, comme une structure pure en C++, contenant des valeurs simples.</p><h2>L’emballage</h2><p>Si vos données ne répondent pas au critère de duplication, l’approche de l’emballage des données peut être utilisée. Un cas courant est celui où on a une interface qui devrait être partagée entre plusieurs threads d’exécution. Voici les étapes de la création d’un emballage :</p><ul> <li>Identifier l’interface qui doit être isolée.</li> <li>Écrire un simple protecteur multithread sur l’interface.</li> <li>Écrire une implémentation simple de l’interface pour chaque thread.</li></ul><p>Pour illustrer la technique, je vais vous montrer un exemple d’emballage que j’ai récemment fait en C++. Le code fait partie de l’application <i>Tantrix Solver</i> que j’ai écrite. L’élément particulier que je devais convertir pour une utilisation multithread était l’interface du rapport d’avancement.</p><p>Le code pour cette application est <a href="https://github.com/pierrebai/Tantrix">disponible sur GitHub</a>.</p><h3>Identifier l’interface</h3><p>La première étape consiste à identifier ce qui sera utilisé par les threads. Cela peut nécessiter un certain remaniement dans le cas d’un groupe disparate d’éléments sans cohésion. Dans l’exemple de code, il s’agissait déjà d’une interface appelée <code>progress_t</code>. À noter qu’il n’y a qu’une seule fonction virtuelle qui doit être protégée : <code>update_progress()</code>.</p><pre><code> // Report progress of work. // // Not thread safe. Wrap in a multi_thread_progress_t if needed. struct progress_t { // Create a progress reporter. progress_t() = default; // Force to report the progress tally. void flush_progress(); // Clear the progress. void clear_progress(); // Update the progress with an additional count. void progress(size_t a_done_count); size_t total_count_so_far() const; protected: // Update the total progress so far to the actual implementation. virtual void update_progress(size_t a_total_count_so_far) = 0; };</code></pre><h3>Protecteur multithread</h3><p>La deuxième étape consiste à créer un protecteur multithread. La conception de tous les protecteurs est toujours la même :</p><ul> <li>Ne <b>pas</b> dériver de l’interface à protéger.</li> <li>Conserver l’implémentation originale de l’interface non protégée.</li> <li>Fournir une protection multithread, généralement avec un mutex.</li> <li>Fournir un accès interne à l’implémentation par thread.</li></ul><p>La raison de ne pas implémenter l’interface souhaitée est que le protecteur multithread n’est pas destiné à être utilisé directement. En le rendant non compatible, il ne peut pas être utilisé accidentellement.</p><p>Sa mise en œuvre imitera toujours très fidèlement l’interface. La différence est que chaque fonction correspondante verrouillera le mutex et appellera l’interface originale, non sécurisée pour les threads. C’est ainsi qu’il est protégé contre le multithread.</p><p>Voici un exemple pour l’interface <code>progress_t</code> :</p><pre><code> // Wrap a non-thread-safe progress in a multi-thread-safe progress. // // The progress can only be reported by a per-thread-progress referencing // this multi-thread progress. struct multi_thread_progress_t { // Wrap a non-threas safe progress. multi_thread_progress_t() = default; multi_thread_progress_t(progress_t& a_non_thread_safe_progress) : my_non_thread_safe_progress(&a_non_thread_safe_progress), my_report_every(a_non_thread_safe_progress.my_report_every) {} // Report the final progress tally when destroyed. ~multi_thread_progress_t(); // Force to report the progress tally. void flush_progress() { report_to_non_thread_safe_progress(my_total_count_so_far); } // Clear the progress. void clear_progress() { my_total_count_so_far = 0; } protected: // Receive progress from a per-thread progress. (see below) void update_progress_from_thread(size_t a_count_from_thread); // Propagate the progress to the non-thread-safe progress. void report_to_non_thread_safe_progress(size_t a_count); private: progress_t* my_non_thread_safe_progress = nullptr; size_t my_report_every = 100 * 1000; std::atomic<size_t> my_total_count_so_far = 0; std::mutex my_mutex; friend struct per_thread_progress_t; };</size_t></code></pre><p>Les fonctions importantes sont : <code>update_progress_from_thread()</code> et <code>report_to_non_thread_safe_progress()</code>. La première reçoit les progrès de chaque thread, dont le code sera présenté plus tard. La fonction accumule le total dans une variable multithread-safe et ne l’envoie que lorsque ce total franchit un seuil donné. La deuxième fonction envoie les progrès réalisés à la version originale de l’interface, sous la protection d’un mutex. Voici le code pour les deux fonctions :</p><pre><code> void multi_thread_progress_t::update_progress_from_thread(size_t a_count_from_thread) { if (!my_non_thread_safe_progress) return; const size_t pre_count = my_total_count_so_far.fetch_add(a_count_from_thread); const size_t post_count = pre_count + a_count_from_thread; if ((pre_count / my_report_every) != (post_count / my_report_every)) { report_to_non_thread_safe_progress(post_count); } } void multi_thread_progress_t::report_to_non_thread_safe_progress(size_t a_count) { std::lock_guard lock(my_mutex); my_non_thread_safe_progress->update_progress(a_count); }</code></pre><h3>Code pour chaque thread</h3><p>La dernière partie du modèle est le code de l’interface originale pour chaque thread. Dans ce cas, nous voulons dériver de l’interface. Cela permettra de remplacer la version originale de l’interface qui ne supportait pas le multithreading ! Ce code par thread est destiné à être utilisé par un seul thread. La protection multithread se fait dans le protecteur multithread que nous avons montré auparavant.</p><p>Cette division du travail entre le protecteur et les parties par thread simplifie grandement le raisonnement sur le code et simplifie le code lui-même.</p><p>Voici le code de <code>progress_t</code> par thread de notre exemple :</p><pre><code> // Report the progress of work from one thread to a multi-thread progress. // // Create one instance in each thread. It caches the thread progress and // only report from time to time to the multi-thread progress to avoid // accessing the shared atomic variable too often. struct per_thread_progress_t : progress_t { // Create a per-thread progress that report to the given multi-thread progress. per_thread_progress_t() = default; per_thread_progress_t(multi_thread_progress_t& a_mt_progress) : progress_t(a_mt_progress.my_report_every / 10), my_mt_progress(&a_mt_progress) {} per_thread_progress_t(const per_thread_progress_t& an_other) : progress_t(an_other), my_mt_progress(an_other.my_mt_progress) { clear_progress(); } per_thread_progress_t& operator=(const per_thread_progress_t& an_other) { progress_t::operator=(an_other); // Avoid copying the per-thread progress accumulated. clear_progress(); return *this; } // Report the final progress tally when destroyed. ~per_thread_progress_t(); protected: // Propagate the progress to the multi-thread progress. void update_progress(size_t a_total_count_so_far) override { if (!my_mt_progress) return; my_mt_progress->update_progress_from_thread(a_total_count_so_far); clear_progress(); } private: multi_thread_progress_t* my_mt_progress = nullptr; };</code></pre><h2>Conclusion</h2><p>J’ai utilisé ce design pour résoudre plusieurs fois des problèmes multithread. Cela m’a toujours été utile. N’hésitez pas à réutiliser ce design là où vous en avez besoin !</p><p>L’exemple particulier utilisé ici se trouve dans la bibliothèque “utility” du projet <i>Tantrix Solver</i> <a href="https://github.com/pierrebai/Tantrix">disponible sur GitHub</a>.</p>

Développement sur mesure
5 min de lecture
Les aventures d’un développeur Flask au pays de Django
<p>Je suis développeur back-end Python depuis plus de 7 ans. La majorité de mon expérience professionnelle s’est faite avec le cadre de développement web Flask qui m’a toujours satisfait. Autant que je sache, les deux options concurrentes en matière de frameworks web en Python sont <a href="https://www.djangoproject.com/">Django</a> et <a href="https://flask.palletsprojects.com/en/1.1.x/">Flask</a> (à dire ça, je vais sans doute recevoir des messages hargneux de la part des communautés Pyramid, Bottle et autres !). Dans ma tête, j’ai toujours pensé que Flask était objectivement supérieur, car il permet une plus grande liberté de choix, est plus récent et est moins surchargé. J’avais l’idée erronée que les gens choisissaient Django simplement à cause de sa popularité. Puis, après avoir débuté dans un nouvel emploi chez <a href="https://www.spiria.com/">Spiria</a>, on m’a confié quelques projets qui avaient été développés avec Django. L’expérience m’a ouvert les yeux, et je me suis mis de façon inattendue à apprécier ce framework. Ci-après, je vais présenter mon évaluation de Django, du point de vue d’un développeur Flask aguerri. Vous lisez peut-être cet article en vous posant la question “Quel framework dois-je utiliser ?”. Comme souvent, la réponse à cette question est… cela dépend !</p><h3>Structure de projet</h3><p>Le framework Django impose une structure de projet spécifique. Vos modèles, vues et routages sont tous placés dans des endroits prévus. Les projets Django dont j’ai hérité à Spiria ont tous été initialement créés par des développeurs Rails et, bien que la base de code ait été parsemée de particularismes, je me suis quand même senti chez moi en tant que développeur Python.</p><p>Je pense que si les développeurs avaient commencé un projet avec Flask, j’aurais eu beaucoup de difficultés. Je me souviens d’une conversation que j’ai eue avec un développeur Django il y a quelques années, au sujet de l’expérience de son équipe avec un projet Flask. Ils n’arrivaient pas comprendre comment quelqu’un peut arriver à réaliser un projet d’envergure avec Flask. Après avoir approfondi la question, il m’est apparu clairement que leur application Flask contenait absolument tout le code dans un seul fichier. Dix mille lignes dans toute leur gloire. Pas étonnant qu’ils aient trouvé ça impossible à maintenir ! Flask n’impose aucune structure inhérente, ce qui peut être une arme à double tranchant. Au mieux, vous adoptez et appliquez une structure stricte dès le début de votre projet. Au pire, aucune structure n’est imposée, et vous héritez d’une très grosse application à fichier unique, ce qui donne un tout nouveau sens au concept d’application web monopage ! Cette liberté veut dire que chaque application Flask que vous rencontrerez sera probablement structurée de manière différente.</p><h3>L’ORM de Django face à SQLAlchemy</h3><p>Ceux d’entre vous qui sont des habitués de Flask auront très probablement utilisé <a href="https://www.sqlalchemy.org/">SQLAlchemy</a> pour leurs besoins en matière d’ORM (mapping objet-relationnel). SQLAlchemy est un cadre extrêmement puissant qui vous donne tout le contrôle dont vous avez besoin sur votre base de données. L’un des avantages de SQLAlchemy est que vous pouvez vous approcher du bas niveau dans les domaines où vous avez vraiment besoin d’un contrôle très fin (par exemple, dans les domaines à faible performance) en allant directement dans le <a href="https://docs.sqlalchemy.org/en/13/core/">noyau SQLAlchemy</a> et en écrivant des requêtes dans le langage d’expressions SQL. Toutefois, cette flexibilité a un coût ; comme on dit, un grand pouvoir s’accompagne de grandes responsabilités. Un peu plus bas, je vous parlerai de certaines fonctionnalités vraiment cool de l’ORM de Django qui sont probablement beaucoup plus difficiles à atteindre avec SQLAlchemy en raison de cette flexibilité.</p><p>En regard, l’ORM de Django est beaucoup plus proche de Rail, probablement parce que l’ORM de Django utilise une implémentation d’enregistrement actif (“active record”). Cela signifie essentiellement que les objets de votre modèle Django ressembleront beaucoup aux lignes SQL dont ils sont dérivés. Cela présente des avantages aussi bien que des inconvénients : du côté positif, la simplicité et l’élégance du système de migration de Django (voir plus bas), sans avoir à gérer la session de base de données ; du côté négatif, le fait de ne pas pouvoir déclarer automatiquement les jointures sur les relations directement sur le modèle lui-même. Dans SQLAlchemy, cela se fait facilement grâce aux techniques de chargement des relations (“Relationship Loading Techniques”) de SQLAlchemy (lazy loading, eager loading, etc.). Pour éviter le <a href="https://thenewstack.io/finding-and-fixing-django-n1-problems/">problème du N+1</a>, vous devrez appeler <code>select_related()</code> ou <code>prefetch_related()</code> partout où vous faites une requête pour ce modèle. Cela peut devenir fastidieux, surtout lorsque vous avez deux modèles qui sont presque toujours utilisés ensemble.</p><pre><code>‘‘‘Example of eagerloading taken from the SQLAlchemy docs’’’class Parent(Base): __tablename__ = ‘parent’ id = Column(Integer, primary_key=True) children = relationship("Child", lazy=‘joined’)</code></pre><p>Comme vous pouvez voir dans l'exemple ci-dessus, vous pouvez faire vos jointures directement sur les classes modèles de votre base de données, au lieu d'ajouter la jointure à la requête chaque fois que vous voulez optimiser votre requête.</p><pre><code>parents_and_children = Parent.objects.select_related(‘children’).all()</code></pre><h3>Django Migrations face à Alembic</h3><p>La migration des bases de données est un domaine où Django surpasse vraiment Flask — le système de migration de base de données intégré à Django est un pur plaisir à utiliser. J’ai été particulièrement impressionné lorsqu’un coéquipier et moi-même avons simultanément engagé des migrations de bases de données dans le projet : Django a immédiatement identifié le problème, et il a été simple de lancer une fusion par le biais de la commande Python <code>manage.py makemigrations --merge</code>. Techniquement, Alembic dispose de la <a href="https://alembic.sqlalchemy.org/en/latest/branches.html">fonction branches</a> pour traiter ce problème, mais elle est en “bêta” depuis des années, ce qui n’inspire pas confiance pour une utilisation en production.</p><h3>Django Admin face à Flask-Admin</h3><p>Django Admin est un avantage majeur de Django. Dès la première utilisation, vous obtenez une interface web <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> instantanée pour tous vos modèles de base de données, protégée derrière un écran de connexion utilisateur. Le panneau d’administration de Django est très performant, et un certain nombre de bibliothèques tierces facilitent la vie avec des fonctionnalités supplémentaires.</p><p>Une bibliothèque tierce pour Flask, la bien nommée <a href="https://flask-admin.readthedocs.io/en/latest/">Flask-Admin</a>, occupe la même place que Django Admin. En phase avec le reste, Flask-Admin est livré avec beaucoup moins de choses préconfigurées pour vous. Vous devrez inclure vous-même un “boilerplate” pour chaque modèle de base de données que vous souhaitez ajouter au panneau Flask-Admin.</p><p>Un bémol autant pour Django Admin que Flask-Admin : bien qu’ils peuvent vous faire gagner beaucoup de temps au départ, à mesure que votre projet mûrit et que vos utilisateurs commencent à demander (ou exiger) des fonctionnalités supplémentaires dans vos panneaux d’administration, vous vous retrouverez à vous battre avec ces bibliothèques pour obtenir ce que vous voulez. J’ai trouvé que ces bibliothèques d’admin étaient mieux adaptées à l’usage des développeurs uniquement. Si vous avez l'intention d'ouvrir des fonctionnalités d'administration à votre base d'utilisateurs, il est préférable d'en écrire une admin à partir de zéro, en utilisant un template côté serveur ou une API avec un framework front-end, comme <a href="https://reactjs.org/">React</a>.</p><h3>Framework Django REST</h3><p>En parlant d’API, j’ai trouvé que travailler avec le framework <a href="https://www.django-rest-framework.org/">Django REST framework</a> était un rêve absolu. Les vues génériques basées sur les classes rendent la vie pas mal plus simple et la base de code bien plus légère. En gros, vous avez vos vues, vos sérialiseurs et vos permissions. “Subclassez” la vue générique appropriée (par exemple, une <a href="https://www.django-rest-framework.org/api-guide/generic-views/#listcreateapiview">ListCreateAPIView</a>), attachez une requête par défaut, un sérialiseur (quelque chose qui définit votre requête/réponse aux endpoints et gère votre conversion entre JSON et Python) et toutes les autorisations qui sont pertinentes pour cette vue. Et c’est tout.</p><pre><code>‘‘‘Example ListCreateAPIView class’’’class BlogPostListCreate(ListCreateAPIView): permission_classes = [IsAuthenticated, BlogPostPermission] serializer_class = BlogPostSerializer queryset = BlogPost.objects.all()</code></pre><p>En revanche, contruire ça sans Django REST ressemblerait davantage à ceci :</p><pre><code>from django.http import JsonResponsefrom django.views import Viewclass BlogPostListCreateView(View): def get(self, request): blog_posts = BlogPost.objects.all() blog_post_dicts = [] for blog_post in blog_posts: blog_post_dicts.append({ ‘id’: blog_post.id, ‘title’: blog_post.title, ‘body’: blog_post.body }) return JsonResponse({‘blog_post_dicts’: blog_post_dicts}) def post(self, request): new_blog_post = BlogPost.objects.create(title=request.POST[‘title’], body=request.POST[‘body’]) return JsonResponse({‘message’: ‘Blog Post Created’})</code></pre><p>L’exemple ci-dessus n’inclut pas la vérification des autorisations, la validation ou la pagination, ce que vous aurez en bonus avec le framework Django REST.</p><p>Un atout supplémentaire est toutes les bibliothèques additionnelles qui ont été développées autour de Django REST et qui rendent votre vie de développeur tellement plus facile. Deux bibliothèques remarquables que j’ai utilisées sont <a href="https://pypi.org/project/django-rest-framework-camel-case/">Django-Rest-Framework-Camel-Case</a> et <a href="https://drf-yasg.readthedocs.io/en/stable/">drf-yasg</a>. La première garantit que vos API endpoints peuvent accepter à la fois les clés en CamelCase et celles en snake_case pour la demande et utilisent le CamelCase pour la réponse, tout en conservant les noms de variables en snake_case dans tout votre code Python. Cela permet à vos développeurs front-end d'utiliser les conventions de nommage auxquelles ils sont habitués, et à vos développeurs back-end Python d'utiliser le snake_case et de se conformer à <a href="https://www.Python.org/dev/peps/pep-0008/">PEP8</a>. La seconde bibliothèque, drf-yasg, fonctionne bien avec la première et aide à générer automatiquement la documentation <a href="https://swagger.io/">swagger</a> en analysant vos vues génériques et leurs sérialiseurs. Rien ne vaut une documentation générée automatiquement !</p><p>Pour être juste à l’égard de Flask, un certain nombre de bibliothèques tierces font probablement beaucoup de ce que Django REST apporte à Django. Cependant, pour une raison ou une autre, je n’ai jamais eu recours à l’un de ces outils pendant mes développements dans Flask, ce qui mériterait en soi une enquête approfondie.</p><h3>Conclusion</h3><p>Je suis bien heureux d’avoir eu l’occasion d’essayer Django dans un cadre professionnel. Avant de rejoindre Spiria, j’étais un développeur Flask acharné et je ne voyais pas l’autre côté de la médaille de ce framework. Flask est un outil étonnant, et la liberté qu’il offre permet de faire beaucoup de choses. Cependant, cette liberté exige beaucoup de discipline, et une mauvaise décision au stade de l’architecture peut vous coûter cher à mesure que votre projet mûrit. Passer à Django m’a ouvert les yeux, me révélant de nombreuses façons de faciliter ma vie dont je ne soupçonnais même pas la possibilité en travaillant avec Flask. Bien sûr, il y a quelques éléments de Flask qui m’ont manqué (les charges jointes automatiques, par exemple), mais, pour les projets sur lesquels j’ai travaillé jusqu’à présent, j’ai vraiment eu le sentiment que Django était le bon choix.</p><p>Ma recommandation serait de faire appel à Flask pour la réalisation d’un prototype rapide, ou pour la production de quelque chose de tellement spécifique que les rails imposés par Django seraient trop contraignants. D’un autre côté, il faut faire appel à Django pour les projets qui peuvent bénéficier d’une telle structure, surtout lorsqu’on travaille avec une grosse équipe. Il y a certainement une place pour Flask et Django dans le monde, et la communauté Python dans son ensemble est bien nantie d’avoir de tels choix. Merci à tous ceux qui ont contribué aux deux projets et à tous les projets satellites open-source qui contribuent à faire de ces deux frameworks un rêve avec lequel travailler.</p>

Culture
5 min de lecture
Au téléphone avec Spiria
<h2>Pour commencer, un courriel d’invitation</h2><p>Peu de temps après avoir soumis avec succès votre CV et votre lettre de motivation (voyez notre <a href="https://www.spiria.com/fr/blogue/environnement-de-travail/la-recette-spiria-pour-un-cv-reussi/">recette pour un CV réussi</a>), vous êtes censé recevoir une confirmation automatique de notre part accusant bonne réception de votre candidature. Ensuite, dans un délai de 2 à 3 jours ouvrables, notre équipe de recrutement étudiera votre CV, et si vos compétences coïncident avec nos besoins actuels, nous vous enverrons un courriel d’invitation pour un appel téléphonique. Nous ne vous appellerons jamais sans votre consentement. Surveillez aussi votre dossier de courrier indésirable, car il y a des risques que notre message y atterrisse.</p><h2>Ensuite, vous choisissez l’heure et le jour de notre appel</h2><p>Après avoir reçu notre invitation à passer un appel téléphonique, assurez-vous de vérifier l’offre d’emploi à laquelle vous avez postulé et le CV que vous avez envoyé, cela nous aidera à parler dès le départ des mêmes choses. Vous pouvez aussi consulter le <a href="https://www.spiria.com/fr/">site web de Spiria</a> pour mieux comprendre l’entreprise. Voyez l’emplacement du bureau et s’il correspond bien à vos attentes. Indiquez-nous le jour et l’heure qui vous conviennent le mieux. En cas de besoin, n’hésitez pas à nous appeler à l’heure du déjeuner, après vos cours, avant ou après votre travail. Notre équipe de recrutement fera de son mieux pour vous satisfaire. Nous ne voulons pas vous causer de désagréments ni de stress. Et, bien sûr, préparez toutes vos questions pour nous.</p><h2>Puis, nous discutons</h2><p>Nos questions d’entretien téléphonique sont simples et directes.</p><p>Nous vous demanderons des détails sur :</p><ul> <li>votre situation actuelle,</li> <li>comment vous avez découvert l’offre d’emploi de Spiria,</li> <li>pourquoi vous aimeriez travailler avec nous,</li> <li>la raison pour laquelle vous cherchez un changement de carrière, si c’est le cas,</li> <li>sur votre expérience et vos compétences,</li> <li>sur votre vie de tous les jours au travail,</li> <li>sur les technologies que vous connaissez le mieux,</li> <li>et sur les langages et les technologies que vous aimeriez apprendre à l’avenir.</li></ul><p>Si nécessaire, nous pouvons être amenés à tester vos compétences linguistiques, et si vous êtes bilingue, nous pouvons passer du français à l’anglais lors de la conversation.</p><p>Nous vous demanderons également quelles sont vos attentes en matière de salaire. N’ayez pas peur de cette question, faites simplement quelques recherches au préalable et donnez-nous la fourchette dans laquelle vous vous sentiriez à l’aise. Spiria a également une grille de salaires complète pour tous ses postes. Pour chaque poste, une fourchette salariale inclut un point de départ, plusieurs points médians et un maximum possible. Cette grille permet de s’assurer que tous les employés de l’entreprise sont payés équitablement en fonction de leurs compétences, de leur progression dans le poste, de leurs collègues, des nouvelles embauches et, bien sûr, du marché canadien.</p><p>À la fin, nous vous demanderons vos disponibilités pour un éventuel entretien en personne. Pensez donc à vérifier à l’avance votre emploi du temps pour les jours à venir. Et bien sûr, nous serons là pour répondre à toutes vos questions. L’entretien téléphonique avec Spiria ne dure pas plus de 25-30 minutes. Son but est de mieux vous connaître et de nous présenter. L’équipe d’acquisition des talents est réellement intéressée par votre embauche potentielle et est là pour représenter vos intérêts auprès des responsables de l’embauche.</p><h2>Enfin, nous prenons une décision</h2><p>En général, vous aurez de nos nouvelles dans un délai d’une semaine. Si vous ne recevez aucune réponse dans ce délai, n’hésitez pas à nous envoyer un courriel de suivi. Si les responsables du recrutement sont intéressés par vous, l’équipe d’acquisition de talents vous contactera pour fixer un rendez-vous en personne ou une réunion Zoom. Vous recevrez également un court test de personnalité de 10 minutes et, si nécessaire pour le poste offert, un test technique qui peut prendre jusqu’à une heure.</p><p>Si vous souhaitez connaître les étapes suivantes du processus d’embauche de Spiria, comment vous préparer à un entretien et quelles sont les questions à attendre de nos gestionnaires recruteurs, restez avec nous, suivez Spiria, et nous vous raconterons tout dans nos prochains articles.</p><h3>Voir aussi :</h3><ul> <li><a href="https://www.spiria.com/fr/blogue/environnement-de-travail/comment-preparer-une-entrevue-gagnante-chez-spiria">Comment préparer une entrevue gagnante chez Spiria</a>, par Amanjot Kaur.</li> <li><a href="https://www.spiria.com/fr/blogue/environnement-de-travail/la-recette-spiria-pour-un-cv-reussi">La recette Spiria pour un CV réussi</a>, par Lidiia Meleshchenko.</li></ul>

Culture
5 min de lecture
Comment préparer une entrevue gagnante chez Spiria
<h2>Renseignez-vous sur Spiria</h2><p>Comment Spiria se définit-elle comme entreprise? Quelle est sa mission? Quels types de services offre-t-elle?</p><p>Préparez des questions sur tout élément que vous ne comprenez pas, ou que vous voulez approfondir. Cherchez, sur notre site web, les réponses à des questions comme les suivantes :</p><p>1. Où se trouvent les bureaux de Spiria?</p><p>2. Spiria a-t-elle une présence unique, nationale, internationale?</p><p>3. Quelles qualités Spiria recherche-t-elle chez ses employés?</p><p>Prenez note des éléments qui vous intéressent ou vous motivent tout particulièrement.</p><p>« Pourquoi voulez-vous travailler chez Spiria plutôt qu’ailleurs? » est une question souvent posée en entrevue (à bon entendeur, salut).</p><p>Vous trouverez également de l’information utile sur Spiria ailleurs que sur notre site web. Par exemple, Glassdoor contient des évaluations d’employés et de l’information utile sur le processus d’entrevue.</p><h2>Renseignez-vous sur votre intervieweur</h2><p>Si vous connaissez le nom de votre intervieweur, allez voir son profil LinkedIn. Recherchez des intérêts en commun, qui vous donneront l’occasion d’établir un bon rapport interpersonnel. Vous y trouverez également une indication de son niveau d’expertise et d’expérience dans son rôle actuel chez Spiria.</p><h2>Préparez une réponse aux questions d’entrevue les plus souvent posées</h2><p>Tous les interviewés devraient avoir des réponses bien rodées à un certain nombre de questions typiques d’entrevue : questions sur eux-mêmes, leurs objectifs de carrière, leurs motivations et leur intérêt pour leur rôle futur au sein de l’entreprise.</p><p>Par exemple :</p><p><i>Parlez-nous de vous. </i></p><p><i>Parlez-nous de votre expérience jusqu’à ce jour.</i></p><p><i>Pourquoi voulez-vous travailler chez Spiria?</i></p><p><i>L’emplacement de nos bureaux vous est-il facilement accessible au quotidien?</i></p><p>Nous pourrions aussi vous poser des questions techniques! Préparez-vous à des <b>exercices de codage ou de résolution de problèmes</b> sur tableau blanc.</p><p>Enfin, nous pourrions vous poser des questions sur votre expérience en relation avec la clientèle, par exemple :</p><p><i>Comment traitez-vous un <b>client</b> peu raisonnable ?</i></p><p><i>À votre avis, qu’est-ce qu’un bon service à la <b>clientèle</b>?</i></p><p><i>Que feriez-vous si un <b>client</b> avait un problème que vous ne saviez pas résoudre?</i></p><p>Pour répondre à ces questions, nous vous recommandons d’employer la méthode <b>STAR</b> :</p><p><b>Situation</b> : Quelle était la situation?</p><p><b>Tâche</b> : Quelles étaient ses tâches, responsabilités et rôle?</p><p><b>Action</b> : Quelle action a-t-il ou a-elle prise?</p><p><b>Résultat</b> : Quel était le résultat?</p><h2>Comment préparer une entrevue Zoom</h2><p>Les entrevues par vidéoconférence sont de plus en plus usitées.Grâce aux améliorations de la technologie, la plupart des gens peuvent se faire interviewer par Skype, Zoom, ou Google Hangouts sans aucun problème. De plus, une entrevue vidéo vous donne l’aspect visuel que n’autorise pas le téléphone – et ce, sans avoir à s’absenter du travail, ou à voyager à l’autre bout du pays pour se rencontrer en personne! Spiria utilise normalement la plateforme Zoom.</p><p>Voici comment vous préparer :</p><ul> <li>Choisissez un lieu qui vous permet de maîtriser votre environnement.</li> <li>Profitez de la lumière du jour en vous installant près d’une fenêtre.</li> <li>Assurez-vous que la camera se trouve au niveau des yeux.</li> <li>Ayez sous la main un exemplaire de votre CV, un bloc-notes et un stylo, et un verre d’eau.</li> <li>Habillez-vous en tenue de travail, de la tête aux pieds.</li> <li>Testez votre audio et connexion internet à l’avance.</li> <li>Assurez-vous que votre arrière-plan est neutre, sans distractions.</li> <li>Assurez-vous que la tête et les épaules sont visibles.</li> <li>Les écouteurs avec micro intégré évitent l’écho.</li> <li>Souriez et hochez de la tête pour indiquer que vous écoutez bien.</li></ul><h2>La veille de l’entrevue</h2><p>La veille de l’entrevue au plus tard, demandez à votre contact chez Spiria où stationner et où l’attendre dans l’immeuble.</p><p>Si vous vous interrogez sur le code vestimentaire chez Spiria, posez-nous la question! Règle générale, mieux vaut en faire trop que pas assez; mais il est encore mieux de demander, plutôt que de deviner. Préparez votre ensemble la veille et profitez-en pour le repasser, au besoin, afin de vous éviter du stress le lendemain.</p><p>À apporter absolument à l’entrevue : plusieurs exemplaires de votre CV, portfolio, plusieurs cartes d’affaires (si vous en avez), bloc-notes et stylo, et liste de références, au cas où. Bref, tout ce qui peut appuyer votre expertise et soutenir ce que vous avancez.</p><h2>Le jour de l’entrevue</h2><ul> <li>Donnez-vous amplement de temps pour vous préparer, afin d’éviter la panique de dernière minute. Donnez-vous également 15 ou 30 minutes de jeu pour votre déplacement, en cas d’embouteillages.</li> <li>Faites ce qui vous détend le mieux, que ce soit 15 minutes de yoga ou prendre le temps de siroter votre café.</li> <li>Mangez un copieux déjeuner, ça aiguise les neurones!</li> <li>Récitez des affirmations positives, et dites-vous que vous êtes à la hauteur. La confiance en soi compte pour beaucoup en entrevue.</li></ul><h2>Après l’entrevue</h2><p>Communiquez avec nous pour nous dire comment vous avez trouvé votre expérience tout au long du processus. Nous voulons que l’expérience de nos candidats soit la meilleure! Nous recherchons toujours des moyens de nous améliorer.</p><p>Vous êtes maintenant fin prêt à épater votre intervieweur avec vos connaissances du poste et de l’entreprise.</p><p>Bonne chance! ☺</p>

Culture
5 min de lecture
La recette Spiria pour un CV réussi
<p>Après tout, il représente votre occasion de mettre le lecteur en appétit, ce qui peut être déterminant pour l’obtention d’un poste. C’est pourquoi votre CV doit jouer le rôle d’entrée avant le plat de résistance (vous). En cette conjoncture de pandémie, alors que nombre d’entre vous ont perdu leur emploi, ont été mis à pied, ou se retrouvent dans des circonstances pour lesquelles vous n’étiez pas préparé, l’équipe d’acquisition de talent de Spiria veut vous aider à mitonner votre CV pour qu’il délecte notre équipe de recrutement.</p><p>Ci-dessous, vous trouverez notre recette pour un CV de professionnel des TI à point.</p><p>1. <b>Soyez bref et concis. </b>Idéalement, votre CV devrait faire 1 ou 2 pages, ou bien une page par décennie d’expérience. Dans ce monde compétitif, nous recevons des dizaines de CV chaque semaine. Le vôtre devrait nous mettre en appétit dès le premier coup d’œil.</p><p>2. <b>Vous êtes bilingue ? Dites-le tout de suite</b>. Nous avons des clients de langue française et anglaise et, souvent, ils recherchent des spécialistes bilingues.</p><p>3. <b>Où êtes-vous ? </b>Indiquez votre ville de résidence, et si vous pouvez et voulez travailler à distance. En cette conjoncture de COVID-19, un nombre croissant d’entreprises font travailler leurs employés à distance.</p><p>4. <b>Qui êtes-vous ? </b>Juste après votre nom et vos coordonnées, insérez une ou deux phrases qui résument qui vous êtes et quelle est votre arme secrète. Indiquez aussi vos deux-trois technologies de prédilection. Par exemple : « Développeur back-end principal, spécialisé en Python, 9 ans d’expérience » ; ou bien, « Développeur full stack intermédiaire, 5 ans d’expérience avec JavaScript, C#, .Net ».</p><p>5. <b>Les informations personnelles sont personnelles.</b> Nous n’avons pas besoin de savoir votre date de naissance, état civil, nombre d’enfants, sexe, religion, race, couleur, ou nationalité ; même votre photo est de trop.</p><p>6. <b>Antécédents professionnels</b>. Pour chacun de vos employeurs, indiquez le nom de l’entreprise, le lieu, votre rôle et dates d’emploi. Résumez chacune de ces expériences en 3 ou 4 points.</p><p>7. <b>Technologies.</b> N’indiquez que les technologies avec lesquelles vous avez de l’expérience, et êtes à l’aise de travailler. N’incluez pas toutes celles dont vous avez entendu parler en classe. Vous pouvez aussi omettre les technologies de base qui vont sans dire, par exemple HTML pour les développeurs Web. ;)</p><p>8. <b>Vous avez de l’expérience en relation avec la clientèle</b> ? Hourrah ! Spiria est une entreprise de développement sur mesure, de sorte que ses employés ont souvent l’occasion d’interagir avec ses clients. Un atout à inclure absolument dans votre CV !</p><p>9. <b>Expliquez les trous dans votre parcours.</b> Par exemple, vous avez obtenu votre diplôme dès 2010, mais votre premier emploi ne débute qu’en 2015 ? Ne laissez aucune place à l’imagination. Expliquez plutôt ce que vous faisiez, par exemple : vous travailliez à la pige, vous lanciez votre propre entreprise, vous avez sillonné le monde ou avez travaillé comme vendeur en magasin (n’oubliez pas que nous valorisons toute expérience en relation avec la clientèle !).</p><p>10. <b>Études</b>. Incluez vos distinctions éducatives, votre participation à des <i>hackathons</i> et autres événements de type « sprint ». Mais de grâce, épargnez-nous vos relevés de notes ; vos titres suffiront.</p><p>11. <b>Réalisations personnelles.</b> Si vous avez une réalisation digne de mention à votre actif, ou si vous faites du bénévolat, nous voulons le savoir. Nous nous intéressons à votre personnalité et à vos valeurs.</p><p>12. <b>“Références fournies sur demande.”</b> Inutile et superflu. Cela n’ajoute rien à votre CV, et distrait de ce qui précède. Ne vous inquiétez pas, nous n’hésiterons pas à vous demander vos références si besoin est.</p><p>13. <b>PDF plutôt que Word</b>. Ne nous envoyez pas votre CV sous forme de document Word. En effet, toute votre soigneuse mise en page sautera dès que nous l’ouvrirons sur notre plateforme.</p><p>Que se passe-t-il après cette mise en bouche ? Eh bien, si nous avons goûté votre CV, nous vous enverrons un courriel pour vous inviter à une entrevue téléphonique. Ainsi, une fois <a href="https://www.spiria.com/fr/carriere/">votre CV transmis</a>, vérifiez vos courriels régulièrement, y compris votre boîte de pourriels ; puisque nous sommes un expéditeur inconnu, nos courriels pourraient ne pas être reconnus. Pour en savoir davantage sur ce qui suivra, suivez le blogue Spiria. Nous vous en dirons davantage dans le prochain article.</p><p>Bonne chance, et au plaisir de faire connaissance ! :)</p>