• chevron_right

      Scaling stories - comment les startups se sont plantées

      news.movim.eu / PutainDeCode · Tuesday, 2 June, 2020 - 00:00 · 9 minutes

    • "Ca ne fonctionne pas, mes enfants n'arrivent pas à se connecter à votre service !"
    • "Nous sommes désolé notre équipe technique est mobilisée à 100% sur ce problème c'est une situation exceptionnelle… Nous vous tenons au courant."

    C'est le genre de tweet ou de message LinkedIn qu'on a pu voir se multiplier avec le confinement récent dû au Covid-19. En effet les services web, et en particulier les outils de communication ou les programmes du secteur de l'EdTech (education technology), ont dû faire face à un usage particulièrement intense et soutenu ces derniers temps. Il n'y en a que peu à ma connaissance qui ont été capables de maintenir une qualité de service acceptable (Slack et Zoom ont particulièrement brillé sur ce sujet). Si la plupart ont mis en place des solutions minutes comme des queues de connexion,d'autres encore ont été forcés de désactiver leur service pour une "maintenance" à durée indéterminée…

    Même en étant compréhensifs, face à un service planté, on a vite fait de changer. Après tout, avec le choix plus que fourni de services dont on dispose, pourquoi se priver ? Le problème c'est qu'après la crise, il est fort probable que nous ne reviendrons pas au service qu'on avait pourtant trouvé si cool au premier abord… Aïe, pour une startup c'est beaucoup de clients perdus, peut-être assez pour ne jamais retrouver de la traction. Une mort pénible donc pour un business, victime lui aussi du virus.

    Mais 🧐

    • Ces entreprises n'auraient-elles pas pu mieux anticiper ?
    • N'auraient-elles pas pu avoir une meilleure tech à la base, crise ou pas crise ?
    • Et une fois devant le problème, n'auraient-elles pas pu mieux s'adapter et plus rapidement ?

    le modèle de "scaling maturity"

    modèle de scaling maturity

    En premier lieu j'aimerais introduire le concept de scaling maturity . "To scale" c'est l'art d'adapter (automatiquement ou non) sa stack technique afin de répondre à la demande en entrée. Et reconnaissons déjà que Zoom et Slack sont beaucoup plus matures que (par exemple) de jeunes startups de l'EdTech.
    Analysons les à l'aide du modèle de scaling maturity .

    1 - Volume d'usage nominal : Slack ou Zoom avaient déjà un trafic (très) important, le pic d'activité représente un pourcentage plus petit que pour une startup pour qui c'est peut-être un boost de 100 ou 1000 fois l'activité habituelle.
    2 - Maturité du produit : Ils ont eu le temps de connaître les spécificités de leur usage, les caractéristiques d'accès aux données, les points de fragilité de leur système, …3 - Compétences techniques : Ils ont probablement une équipe tech plus grande et plus expérimentée.

    Pour synthétiser, ils en savent beaucoup sur la façon dont leur produit est utilisé et quelle est leur roadmap, et donc savent bien quel type d'effort concentrer pour s'adapter à la demande supplémentaire. En plus, leur infrastructure actuelle peut déjà encaisser un volume conséquent.

    De l'autre côté, les jeunes services web se sont retrouvés submergés, cherchant de l'aide désespérement et des solutions pour sharder et répliquer leur base de donnée relationnelle existante (plus à ce sujet un peu plus loin).
    Je prends à présent l'exemple hypothétique d'une startup EdTech offrant un service de classe en ligne innovant.

    1 - Volume d'usage nominal : Quelques clients aiment leur produit, "c'est le futur", ils croient au potentiel de croissance et l'ajout de fonctionnalités avec le temps. Il y a donc un faible volume d'utilisation pour le moment et une croissance mesurée attendue, ils ont opté pour quelques serveurs OVH économiquement intéressants.
    2 - Maturité du produit : Leur produit est très jeune, ils misent sur l'innovation et des boucles de feedback rapides pour l'étoffer.
    3 - Compétences techniques : Des stagiaires, peut-être de jeunes employés, parfois des fondateurs qui font eux-mêmes les premiers prototypes. A ce niveau les salaires pèsent beaucoup dans la balance.

    Je m'autorise ici une conclusion préliminaire à la première question : les entreprises n'auraient pas pu anticiper, et même j'irai plus loin pour les plus petites d'entre elles, elles ne devaient pas le faire… En effet, si on souhaite créer un produit avec du scaling "infini" dès le début, ça implique d'investir beaucoup en temps et en argent . Deux ressources précieuses que l'on préfère rationnellement investir sur d'autres sujets quand on est un business en phase de démarrage (comme trouver sa place sur le marché, ajouter des fonctionnalités, croître, …).

    cygne noir Covid-19 est un très bon exemple de ce qu'on appelle un évènement "cygne noir" Un évènement qui est très rare, a des répercussions massives, que les entreprises n'avaient donc pas prévu.
    En effet ce point est assez évident, néanmoins je trouve cet "interlude du cygne" bienvenu 😉

    J'ai pu parler récemment avec quelques startups EdTech qui recherchaient des solutions urgemment… Elles en étaient au même point : elles ne pouvaient plus doper les ressources de leur base de donnée relationnelle ( scale up ). Après avoir essayé d'ajouter du cache, déployer de nouveau nœuds, de refactorer leurs applications, le point limitant final restait la base de donnée… La seule solution était donc de sharder ( scale out ) afin de répartir les écritures sur plusieurs instances en parallèle. Ils cherchaient donc des solutions intelligentes (dans le sens autonomes) à ajouter en amont de leur base de donnée afin de pouvoir continuer le scale. Pas si évident, et plutôt cher…Et je ne parlerai même pas de la gestion de la migration dans ce contexte !

    Pour avoir une idée de la difficulté de sharder une base de donnée existante, je dirais que plus le requêtage des données est global et complexe (par exemple de l'agrégation cross-compte), le plus intelligent et cher devra être le proxy en amont. Ca peut aller d'une simple hash-distribution à un "query planner" distribué complexe, et difficile à scale lui aussi par ailleurs.

    A la lumière du modèle de scaling maturity , il est assez clair qu'on ne peut les blâmer de ne pas avoir eu ces mécanismes de scale déjà en place auparavant, mais elles auraient pu au moins avoir mieux planifié leur prochaine étape de scale .

    Comment on scale efficacement quand on est une startup?

    Déconstruisons déjà ce qui doit être "scaled" :

    • la capacité serveur : La maîtrise des coûts implique un dimensionnement adapté en terme de taille CPU/RAM/storage. Par serveur, j'entends noeuds physiques, virtuels ou containers.
    • les patterns d'accès aux données : C'est à dire connaître son usage, éviter de gérer des états partagés, des requêtes globales, préférer l'immutabilité, …
    • l'intervention humaine et la maintenance : Plus on automatise, plus vite on peut itérer, des outils comme Github, CircleCI ou terraform sont précieux.
    • le refactoring de code : "scale" veut souvent dire pré-calculer des états, utiliser du cache, plus de synchronisation, tout ça doit être codé et maintenu également…

    Note : si votre produit n'a pas encore de traction réelle, se préoccuper de ce sujet est probablement prématuré et inutile, un rapide prototype MVC avec la techno que vous connaissez déjà fera tout à fait l'affaire.

    Mais si vous avez de l'usage et une première idée de la direction du produit, alors il y a plusieurs possibilités. Si vous avez avec vous un fondateur au profil technique il sait certainement quoi faire. Sinon, et en particulier si vous avez levé des fonds, c'est une bonne idée de se faire accompagner sur le design de l'infrastructure dès à présent.

    Explorons différentes stratégies.

    La stratégie de scaling infini

    Dissipons immédiatement les nuages de fumée, il est théoriquement possible d' approcher une telle architecture mais ce sera très coûteux (en temps et en argent – encore une fois deux choses précieuses pour une startup), et potentiellement assez rigide.
    La clé ici serait d'utiliser au maximum des services gérés de haut niveau qui tournent sur de grosses infrastructures clouds. Les services choisis devraient être 100% dynamiques, c'est à dire scale de manière autogérée : on ne devrait pas avoir à gérer de ressources physiques ou même virtuelles. Idéalement ces services intègreraient de base de la réplication (pour scale en lecture) et du sharding (pour scale en écriture) et pourraient être répartis dans différentes régions sur la planète.

    Voici quelques exemples de services gérés de cet ordre :

    • Base de donnée relationnelle : Google Cloud Spanner
    • NoSQL synchronisation temps réel : Google Firestore
    • Cluster de cache cross-region : AWS Elasticache
    • Stockage de fichier distributé : AWS S3
    • Data streaming : Google Pub/Sub
    • Data warehouse : Google BigQuery

    Hé oui, ça fait beaucoup de services Google, tout simplement parce qu'ils ont un train d'avance !

    courbe de scaling dynamique

    Ici je me dois de nuancer cette courbe

    • Le prix est en fait très bas au démarrage : ces services offrent des niveaux d'utilisation gratuit pour un volume limité et/ou pendant une période limitée.
    • Une fois un usage conséquent atteint, un tarif dégressif ou des techniques d'optimisation des coûts liées au provider viendront réduire la facture.

    La stratégie de scaling step-by-step

    courbe de scaling step by step

    C'est la stratégie classique et probablement la plus efficace, on fait avec ce qu'on a à disposition au début (compétences, personnes) mais on essaie d'avoir toujours un coup d'avance. On reste conscient des points de fragilité du système, et on sait comment y remédier. On s'attache à planifier les prochaines migrations.
    Ca nécessite en particulier :

    • De développer une pipeline solide de monitoring et d'alerting. Il y a beaucoup d'outils pour faire ça aujourd'hui.
    • De tester chaque migration. Car plus le système est distribué, le moins prévisibles seront les problèmes. Il est plus simple de tester avant de migrer.

    Par exemple, les prochaines étapes de scale planifiées pourraient être :

    • Sharder la base de donnée ou le stream de données.
    • Conserver les données en silos isolés logiquement, afin de pouvoir scaler plus simplement le reste.
    • Introduire un scaling automatique des services HTTP basé sur le trafic.

    Le principal est de rester économique et intelligent : pour scale efficacement il faut scale dans les bonnes proportions et au bon moment. Trop tôt et ça coûtera trop cher, trop tard et la qualité de service tombera… C'est pourquoi avoir un feedback pertinent (automatisé) sur l'architecture est un réel plus.

    Les clouds proposent souvent des metrics de monitoring intégrées, par exemple si on utilise les environnements AWS Beanstalk pour des services web il est fourni automatiquement les métriques suivantes :

    • le nombre de requêtes
    • la latence moyenne
    • l'indice de charge du serveur (load)
    • le CPU
    • le réseau entrant/sortant

    Il est très facile alors de les visualiser en dashboards, de configurer de l'alerting, ou même d'activer de l'auto-scaling basé sur le pourcentage de CPU utilisé… Autant d'outils que l'on peut utiliser directement pour scaler ses web services correctement. Après tout, le plus d'informations (métriques) on a à la base, le mieux on peut prendre les bonnes décisions au final.

    Bien entendu, scaler efficacement c'est scaler de manière appropriée, à chaque situation sa réponse adaptée !

    Pour conclure, la bonne solution se situe probablement entre les deux stratégies. Ca dépendra du budget, de la complexité technique du produit, et des compétences à disposition, …Au moins j'espère que vous avez une meilleure idée de comment anticiper le scale !

    Scale safe 👋

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /articles/scaling-stories-comment-les-startups-se-sont-plantees

    • chevron_right

      S03E01 Open-Source et organisation

      news.movim.eu / PutainDeCode · Monday, 11 May, 2020 - 00:00

    Comment se gère un projet open-source ? Comment répondre aux demandes, les arbitrer sans crouler sous la pression. On aborde ces problématiques avec :

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /podcasts/s03e01-open-source-organisation

    • chevron_right

      Est-il possible de sécuriser une application front-end ?

      news.movim.eu / PutainDeCode · Thursday, 2 April, 2020 - 00:00 · 8 minutes

    Je suis toujours très surpris d'entendre parler de la sécurité des applications frontend parce que précisément une application frontend s'exécute sur le périphérique de l'utilisateur et ne peut donc pas être sécurisée. Elle doit même être considérée comme un client potentiellement malveillant .

    En effet, le code source de l'application étant à la disposition de l'utilisateur, il est possible de l'étudier et de le modifier à volonté afin d'en comprendre les mécanismes internes ou de récupérer toutes les données stockées sur le périphérique.

    Je suis tombé sur de nombreux articles de diverses sources ( Callstack , Jscrambler , Tabris , Nativescript , Reactnativecode ) qui détaillaient les "techniques" pour sécuriser une application frontend en utilisant l'obfuscation, du chiffrement custom (XOR avec réutilisation de clé, etc...), et ainsi de suite.

    Ce n'est pas la première fois que j'entends parler de recettes sur "l'écriture d'une application frontend sécurisée": Il peut être dangereux et contre-productif d'essayer de sécuriser une application avec des techniques inefficaces . Voici pourquoi.

    TLDR;

    Authentification

    L’envoi des informations d’authentification se fait de manière sécurisée au travers d’une connexion SSL, la confidentialité des communications est donc assurée dans la plupart des cas.

    Ajouter une couche de chiffrement basique pour le mot de passe avec un simple XOR et une clé réutilisée pour chaque authentification de chaque client est inutile pour plusieurs raisons:

    • la réutilisation de la clé pour chaque client et chaque requête rend le chiffrement vulnérable car il est possible de deviner le message avec une analyse de fréquence .
    • étant donné que la clé est stockée dans le device , il suffit de télécharger l’application pour la connaître
    • si la clé était stockée dans le localstorage du navigateur , n'importe quel code Javascript executé sur la page pourrait y accéder

    Rajouter une couche de chiffrement peut s’avérer être une bonne idée pour éviter la compromission des données dans le cas d’une attaque Man In The Middle avec un faux certificat SSL .

    Pour un chiffrement solide, il sera nécessaire à minima:

    • D’utiliser une clé de la taille du mot de passe
    • D’utiliser une clé différente pour chaque utilisateur et chaque requête
    • De négocier cette clé avec un algorithme de chiffrement asymétrique de type Diffie Helman

    Cela revient plus ou moins à implémenter une version custom de SSL dans son application et implémenter son propre système cryptographique n'est jamais une bonne idée .

    trying to reinvent the wheel When you try to reinvent the wheel..

    Il est bien plus facile d’utiliser d’autres techniques de sécurisation tel que le SSL Pinning pour se prévenir des attaque MITM.

    Dernier point, pour un maximum de sécurité, il est conseillé de générer des tokens ayant une durée de validitée courte pour limiter les dégats causés par une éventuelle compromission.

    Stocker des données sensibles

    Lorsqu’il est nécessaire de stocker des données sensibles dans une application frontend, il est préférable d’utiliser les mécanismes mis à disposition par les créateurs de l’environnement de développement.

    Par exemple, dans une application mobile avec React Native, nous pouvons utiliser la Keychain d’Apple ou le Keystore d’Android . Ces mécanismes rendent plus difficile l’extraction de données sensibles depuis un device mais ils ne doivent pas non plus être considérés comme inviolables . (Eg: Apple Keychain exploit )

    Dans tous les cas, il est inutile de rajouter une couche de chiffrement supplémentaire réalisée avec une clé prédictible car un attaquant peut faire du reverse engineering sur l’application pour retrouver la clé.Ou encore plus facilement, simplement accéder à la clé stockée en mémoire .

    Cela est contre-productif va consommer inutilement des ressources CPU pour le chiffrement/déchiffrement et donc drainer la batterie .

    Obfusquer le code source

    Bien que je puisse comprendre que les développeurs puisse vouloir compliquer la tâche de reverse engineering d'une application, l’obfuscation de doit jamais être considérée comme une pratique de sécurisation .

    Elle peut au maximum décourager certains attaquants mais quelqu’un de motivé pourra toujours analyser et comprendre le fonctionnement de l'application.

    Surtout si un obfuscateur open-source est utilisé car celui-ci est donc connu et des dé-obfuscateurs doivent certainement déjà exister.

    De plus, l'obfuscation va rendre le code très difficile à interpréter et optimiser par les différents moteurs Javascript et il en résultera une baisse significative des performances de l’application .

    obfuscation benchmark Benchmark réalisé avec l' obfuscateur de react-native-obfuscating-transformer

    Sécurisez votre backend

    Comme nous l’avons vu, une application frontend ne peut pas être sécurisée . Comme il est impossible d’avoir le contrôle sur le terminal du client, il est impossible de s’assurer que celui-ci n’est pas compromis.

    C’est sur le backend que la majeure partie des éléments de sécurité doivent être mis en place.Il n’y a pas de recette magique pour sécuriser un backend, c’est un ensemble de bonnes pratiques de programmation qui permettra d’arriver à un résultat optimal.

    Ne jamais faire confiance à la saisie des utilisateurs

    Depuis le corps d’une requête HTTP , en passant par les headers ou encore les cookies , toutes ces informations qui peuvent être manipulées par l’utilisateur doivent être considérées comme potentiellement malicieuses.

    L’utilisation naïve des saisies des utilisateurs peut amener à toutes sortes d’attaques:

    Il est toujours nécessaire de vérifier et sanitiser ces données avant de les utiliser dans une application.

    Limiter les dénis de service (DoS)

    Les attaques par déni de service tentent de rendre une application indisponible.

    Il est possible, par exemple, d'envoyer de très grandes requêtes en JSON , ce qui peut ralentir ou même rendre indisponible votre application.

    Atténuer l'attaque : limiter la taille des requêtes dans les couches basses de votre application, de préférence directement dans les couches réseau.

    Si votre backend est écrit en Node.js, il est également nécessaire d'être vigilant lors de la création de nouvelles promesses .
    En effet, une Promesse est automatiquement envoyée à l'Event Loop et il est alors impossible de la retirer avant sa résolution ou son rejet.

    Un attaquant peut alors envoyer beaucoup de requêtes sur une route générant des promesses et donc saturer l'Event Loop .

    Atténuer l'attaque : implémenter un système de limite de requêtes simultanées utilisant uniquement des callbacks. Développer avec des callbacks c'est plus compliqué, mais les callbacks ne sont que des pointeurs vers des fonctions n'utilisant aucune ressource jusqu'à ce qu'ils soient invoqués. N'utilisez les promesses qu'après le système de limite de requêtes .

    Éviter le bruteforce

    Afin d’éviter un bruteforce de l’authentification d’un utilisateur, il est nécessaire d’introduire une limite au nombre de tentative de connexion .

    Cette limite peut prendre la forme d’un blocage après X tentatives rajouté à la route d’authentification.

    Stocker les mots de passe de façon sécurisée

    En 2019, il y avait encore des entreprises qui stockent les mots de passes de leurs utilisateurs en clair .

    Cette pratique doit être évitée à tout prix afin de protéger vos utilisateurs en cas de fuite de données de votre application. Pas seulement pour votre propre application : la majorité des utilisateurs réutilisent le même mot de passe pour plusieurs comptes . L'impact d'une fuite de mot de passe peut être catastrophique, tant pour vos utilisateurs que pour l'image de votre entreprise.

    Les mots de passe doivent être stockés à l'aide d'une fonction cryptographique unidirectionnelle (ou fonction de hachage).

    Le choix de la fonction de hachage doit être basé sur un algorithme robuste tel que bcrypt par exemple. Si vous le pouvez, renforcez les mots de passe faibles en utilisant du key stretching , et pour une couche de sécurité supplémentaire, vous pouvez également utiliser un salt et un pepper sur les mots de passes.

    Et tellement d'autres!

    Il y a énormément d’attaques possibles sur un backend, et la plupart sont peu ou pas connues. Certaines sont particulièrement difficiles à détecter et à prévenir :

    Une simple comparaison de deux chaînes de caractères peut se donner lieu à une attaque temporelle et permettre à un attaquant de deviner un mot de passe ou un token.

    Mais encore l’utilisation d’une librairie d’expression régulières vulnérable à une attaque ReDoS .

    C’est pourquoi la sécurisation d’un backend n’est pas une tâche à prendre à la légère et doit être confiée à des experts en sécurité pour former les développeurs mais aussi auditer le code afin de s’assurer d’avoir le minimum de failles possibles car la sécurité parfaite n’existe pas .

    Le mot de la fin

    Lors du développement d'une application, la sécurité doit être prise en compte du début à la fin et la réflexion doit couvrir l'ensemble du périmètre de l'application , du backend au frontend, y compris les canaux de communication et l'hébergement.

    La sécurité coûte cher et est donc souvent négligée. C'est pourquoi il est préférable d'utiliser des frameworks et des backends conçus par des ingénieurs possédant les compétences et connaissances nécessaires pour assurer une sécurité suffisante aux utilisateurs finaux.

    J'aimerais remercier toute l'équipe de Kuzzle qui m'a aidé à rédiger cet article et en particulier Sébastien Cottinet et Yannick Combes pour leur expertise en sécurité et cryptographie.


    English version available on Kuzzle blog

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /articles/peut-on-securiser-app-frontend

    • chevron_right

      Du responsive sans media queries

      news.movim.eu / PutainDeCode · Wednesday, 30 October, 2019 - 00:00 · 9 minutes

    Aujourd'hui, il est assez difficile d'imaginer faire des designs web responsivesans avoir recours aux media queries. Cette idée vieille de 1994, devenuerecommendation du W3C en 2012 (une fois supportée par tous les navigateurs) apris son temps et a su s'imposer comme l'outil de référence pour faire du designadaptatif.

    À tel point qu'il parait impossible de faire du responsive sans media querydans l'imaginaire collectif.

    Pourquoi voudrais-tu faire du responsive sans media query ?

    Il faut pas se le cacher : travailler avec les media queries n'est pas toujoursévident. Cela implique pour chaque "morceau" de votre site ou appli qui vadevoir s'adapter de prévoir un ou plusieurs breakpoints lié à la tailledisponible de votre viewport. Écrire du code lié au viewport pour un "composant"bas niveau peut paraître clairement étrange.

    Ce côté contre intuitif des media queries m'a toujours dérangé : on se retrouve à ciblerune taille d’écran, et non pas de cibler la taille disponible pour un élémentdonné.

    Lorsque l'on creuse un peu, on tombe souvent sur le concept de "elementquery". Le rêve de tout intégrateur web : ce serait la solution à tous lesproblèmes posés par les media queries.

    Franchement, écrire du code qui permet à un même composant de se retrouver surune même page a 2 endroits mais avec des dimensions différentes ça serait pascool ? Pas qu'un peu.

    Alors il y a bien quelques techniques à ce jour notamment "les fab four" ou encore des tricks à base de floats ou d'autres trucs plus exotiques, maismalheureusement ce n'est pas toujours maintenable ou intuitif.

    Dans notre monde "moderne" (j'en vois déjà certain cracher sur leur écran),pourquoi ne pas utiliser JavaScript? (voilà vous pouvez essuyer votre écran).Sérieusement, on pourrait se dire que dans notre contexte, il pourrait êtrepertinent de simuler des elements queries à coup de getBoundingClientRect accompagné d'un observeur.

    Certain dirons que encore une fois tout est question de compromis .

    Mais si on veut faire du rendu côté serveur… Le JavaScript ne sera pas unebonne solution (oui ça m'arrive de penser à ce concept).

    Comment faire du responsive sans media query

    Rentrons dans le vif du sujet pour celles et ceux qui seraient intéressé·e·s parcette opportunité. Voici donc quelques astuces et pratiques que je vais vouslivrer.

    Note: afin de mieux profiter du rendu des exemples prévus pour écran large,pensez à consulter les exemples en paysage si vous êtes sur mobile(ou directement sur CodePen qui offre une option de dézoom).Bah oui, on fait un article sur le responsive, donc regarder sur mobile desexemples prévus pour grand écran ça va pas le faire.

    Première chose à bien visualiser : nous allons partir du principe que nousvoulons nous contenter de Flexbox. Aujourd'hui supporté par tous lesnavigateurs, Flexbox est le candidat idéal à ce jour pour faire du code propreet maintenable.

    Avec Flexbox on peut "juste" faire donc des lignes et des colonnes.

    Pour les colonnes, c’est très souvent peu problématique. Tout simplement par ceque l’on scroll le plus souvent verticalement. Je ne vais donc pas spécialementaborder cette axe là et me concentrer sur l’axe horizontal. Mais en changeantd’axe, ces pratiques seront toutes aussi pertinentes selon votre besoin.

    Alors que faire ? On commence par quoi ?

    On va prendre un exemple très simple où je me retrouve avec une ligne et troisblocs intérieurs. Dès que c’est possible je veux que ces trois blocs soient surune ligne. Par exemple sur un ordinateur de bureau. Ou un iPad. Ou un smartphonesacrément gros. Ou un smartphone en paysage.

    <sectionstyle="display: flex;"><articlestyle="flex: 1; height: 50px; background: #fbb;">1</div><articlestyle="flex: 1; height: 50px; background: #bfb;">2</div><articlestyle="flex: 1; height: 50px; background: #bbf;">3</div></section>

    Si on réduit notre exemple on va donc se retrouver avec ceci. Pas ouf.

    Partant de ceci ça va être assez simple de faire la première étape. On varajouter flex-wrap.

    <sectionstyle="display: flex; flex-wrap: wrap;"><articlestyle="flex: 1; height: 50px; background: #fbb;">1</div><articlestyle="flex: 1; height: 50px; background: #bfb;">2</div><articlestyle="flex: 1; height: 50px; background: #bbf;">3</div></section>

    C’est plutôt moche et pas très réaliste. Ajoutons donc un petit peu de contenu,et un peu d’espace.

    Avant qu'on me crache dessus car j'ai mis des div en guise de gouttière, jesouligne que c'est pour le cas d'école.

    <sectionstyle="display: flex; flex-wrap: wrap;"><divstyle="width: 10px;"></div><articlestyle="flex: 1; background: #fbb;"><h2style="margin: 0; padding: 20px; font: 900 64px monospace;">Red</h2></article><divstyle="width: 20px;"></div><articlestyle="flex: 1; background: #bfb;"><h2style="margin: 0; padding: 20px; font: 900 64px monospace;">Green</h2></article><divstyle="width: 20px;"></div><articlestyle="flex: 1; background: #bbf;"><h2style="margin: 0; padding: 20px; font: 900 64px monospace;">Blue</h2></article><divstyle="width: 10px;"></div></section>

    Si on rétrécit l’espace disponible, on aura un rendu qui va tenter de s’adaptercomme il peut.

    Imaginons que ce rendu ne soit pas forcément souhaitable dans notre contexte.Formulé autrement: ces marges sont sacrément dégueulasses .

    Pour être précis, elles ne sont pas adaptées à nos contraintes et au rendu quel’on souhaite avoir : on se retrouve avec un bout de marge perdu à un endroit oùl’on a pas vraiment envie qu’il se trouve.

    Du coup comment qu’on fait ?

    Petite technique facile à mettre en place et efficace : on va placer desdemi-marges sur le bloc plutôt qu’utiliser le concept de gouttière. Comme ceci :

    <sectionstyle="display: flex; flex-wrap: wrap;"><divstyle="flex: 1; padding: 0 10px;"><articlestyle="background: #fbb;"><h2style="margin: 0; padding: 20px; font: 900 64px monospace;">Red</h2></article></div><divstyle="flex: 1; padding: 0 10px;"><articlestyle="background: #bfb;"><h2style="margin: 0; padding: 20px; font: 900 64px monospace;">Green</h2></article></div><divstyle="flex: 1; padding: 0 10px;"><articlestyle="background: #bbf;"><h2style="margin: 0; padding: 20px; font: 900 64px monospace;">Blue</h2></article></div></section>

    Ce changement n’a aucun impact sur le rendu grand format, mais cela va nouspermettre en petit format d’obtenir le rendu suivant :

    En fonction du contenu intérieur des blocs que vous allez avoir, vous allezpouvoir utiliser plutôt min-width ou flex-basis . Je vous laisse jouer un peuavec histoire de vous faire la main.

    Codepen un peu plus lisible

    En fait je n’ai que cette astuce.

    Je plaisante à peine. Car si on ajoute à cela le côté malin de overflow: hidden pour cacher de l'information optionnelle, on peut faire destrucs assez puissant.

    Regardons ça avec un exemple plus complexe : on va imaginer le header d'un site.

    En appliquant cette technique à l’extrême, (ce qui n’est pas une quantité detravail astronomique, et reste quelque chose de propre et tout à faitmaintenable, surtout avec une approche composant et non document) on peut seretrouver avec un code très simple, sans media query qui donnerait les rendus suivants :

    Mettez l'exemple ci-dessus avec un zoom à 0.5× pour mieux visualiser

    <style>Header, Header * {  box-sizing: border-box;  position: relative;  display: flex;  flex-direction: column;}.Header {  flex-grow: 0;  flex-shrink: 0;  flex-direction: row;  background: #333;  align-items: center;}  .Logo {    flex-grow: 0;    flex-shrink: 1;    min-width: 80px;    height: 80px;    flex-direction: row;    flex-wrap: wrap;    overflow: hidden;  }    .LogoIcon {      flex-grow: 1;      flex-shrink: 0;      font-size: 64px;      text-align: center;      padding: 010px;    }    .LogoText {      flex-grow: 0;      flex-shrink: 1;      margin: 0;      padding: 20px;      font: 90032px sans-serif;      color: #fff;    }  .Center {    flex-grow: 1;    flex-shrink: 1;    flex-basis: 800px;    flex-direction: row;    flex-wrap: wrap;    justify-content: center;    align-items: center;    overflow-x: hidden;  }    .Links {      flex-direction: row;      flew-wrap: wrap;      align-items: center;      flex-grow: 4;      flex-shrink: 1;      min-width: 50%;       overflow-x: auto;    }        .Link {        margin: 0;        padding: 20px;        font: 60020px sans-serif;        color: #fff;        text-decoration: none;      }      .Search {        flex: 1;        max-width: 200px;        min-width: 100px;        border: 0;        border-radius: 6px;        padding: 10px20px;        margin: 10px20px;        font-size: 20px;        background: #444;      }        .LinkGradient {        content: "";        position: absolute;        top: 0;        bottom: 0;        width: 40px;      }      .LinkGradient-left {        left: 0;        background: linear-gradient(to left, rgba(51, 51, 51, 0), rgba(51, 51, 51, 1));      }      .LinkGradient-right {        right: 0;        background: linear-gradient(to right, rgba(51, 51, 51, 0), rgba(51, 51, 51, 1));      }    .Networks {      flex-grow: 1.5;      flex-shrink: 0;      min-width: calc(64px * 2 + 40px);      flex-direction: row;      flex-wrap: wrap;      max-height: 64px;    }      .Network {        flex-grow: 1;        justify-content: center;        align-items: center;        padding: 10px6px;       }</style><headerclass="Header"><divclass="Logo"><divclass="LogoIcon">♥️</div><divclass="LogoText">Logo</div></div><divclass="Center"><divclass="Links"><ahref="#"class="Link">Lien</a><ahref="#"class="Link">Lideux</a><ahref="#"class="Link">Limoche</a><ahref="#"class="Link">Libeau</a><inputplaceholder="Search"class="Search" /></div><divclass="Networks"><aclass="Network">👴</a><aclass="Network">🐦</a><aclass="Network">📸</a><aclass="Network">📌</a></div><divclass="LinkGradient LinkGradient-left"></div><divclass="LinkGradient LinkGradient-right"></div></div></header>

    Code de cette example sur Codepen

    Vous allez me dire "mais ton exemple il est pas fou là", et vous avez pas tort.Je pense que cela dit, vous avez l'idée en étudiant le code.

    Des exemples de ce type-là, surtout en exploitant bien flex-basis , peuvent serévéler extrêmement puissants. On peut très bien se contenter de ça. Après commesouvent lorsqu’on a des besoins plus complexes, il sera à vous de juger sicontinuer à utiliser cette technique est pertinent, ou s’il est plus judicieuxd’utiliser des media queries afin d’éviter de vous défoncer le cerveau. Ou alors de fairedu rendu conditionnel avec JavaScript si votre platforme vous le permet.

    Cette technique est aussi intéressante dans un contexte où les media queries nesont pas accessibles. Ce peut être le cas si vous utilisez un framework ou unelib qui ne propose qu’un sous-ensemble de CSS, comme React Native, qui vouslimitera dans l'ensemble à Flexbox et position absolute pour gérer votrelayout.

    On peut aussi se retrouver à utiliser le même moteur que React Native surplusieurs plateformes directement avec Yoga ou Stretch .

    On pourrait aussi avoir la même envie si on se retrouve dans un contexte Web oùCSS serait utilisable, mais où l’on se retrouve avec une abstraction qui nepermet pas de les intégrer simplement. Vous allez peut-être répondre : « mais ilest fou ? Il se fait du mal ».

    Peut-être un peu. À moins qu’une des contraintes choisies soit de partager ducode entre différentes plates-formes (coucou react-native-web , react-native-windows , react-native-macos …) afind'éviter de faire une grosse app qui te bouffe bien la RAM car basé sur Electron(coucou Slack).

    Dans tous les cas, media query disponible ou pas, cette astuce est pour moibien plus que ça puisque c'est devenu ma principale méthode pour faire duresponsive, faisant beaucoup d'appli React Native et/ou React Native Web.

    Rien que pouvoir avoir le même composant produisant différents rendus sur unmême écran (en fonction de la taille disponible par son parent), ça devrait vousdonner envie !

    [|"Bisous", "À la prochaine"|]|>Js.Array.joinWith(" et ")|>Js.log;
    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /articles/responsive-sans-media-queries

    • chevron_right

      Les paramètres optionnels avec ReasonML

      news.movim.eu / PutainDeCode · Tuesday, 25 June, 2019 - 00:00 · 5 minutes

    Le type option est vraiment super à utiliser en Reason, je vous conseille la lecture de l'introduction à ReasonML et l'article sur le type option . Mais j’ai un peu buté sur un petit point, c'est le lien entre les types option et les paramètres optionnels d’une fonction ou d’un composant React malgré la doc à ce sujet , alors je résume cela dans cet article.

    On va prendre comme exemple un composant User qui prend en paramètre un name et une imageSrc qui peut être facultative.

    Une solution peut être d’utiliser le type option explicitement comme type de paramètre.

    /* User.re */[@react.component]let make = (~name: string, ~imageSrc: option(string)) => {  <div>    <div>      {imageSrc       ->Belt.Option.map(src => <img src />)       ->Belt.Option.getWithDefault(React.null)}    </div>    <div> name->React.string </div>  </div>;};

    À l’intérieur du composant le type option est parfait pour gérer l’optionalité du paramètre. En l'état, nous sommes obligé de l’utiliser de cette manière :

    <User name="Ariel" imageSrc={Some("https://example.com/user.jpg")} /><User name="Manon" imageSrc={None} />

    Il est impossible d’omettre le paramètre imageSrc . Il faudra obligatoirement lui renseigner un option. Pour rendre le renseignement du paramètre vraiment optionnel, on peut déclarer le paramètre comme étant optionnel avec la syntaxe du point d'interrogation.

    /* User.re */[@react.component]let make = (~name: string, ~imageSrc: option(string)=?) => { … };

    Le point d'interrogation va permettre de renseigner directement le type inclus dans l’option (ici un string) ou d’omettre complètement le paramètre, ce qui donne ceci à l'usage :

    <User name="Ariel" imageSrc="https://example.com/user.jpg" /><User name="Manon" />

    La valeur sera automatiquement encapsulée dans un option , ce qui fait que l’implémentation ne change pas et on profite toujours des avantages du type option dans le composant.

    Bon alors c'est tout ?

    Là on pourrait se dire que c’est bon c’est fini, mais en fait pas vraiment.

    Si on résume, notre component accepte en paramètre que imageSrc soit absent, ou qu’il soit un string. Mais à première vue, impossible de lui donner un option, et cela peut être embêtant à l’usage.

    Par exemple, User est utilisé dans un autre composant Follower qui lui prend en paramètre un avatarUrl , et qui sera un type option. Pour transmettre la valeur de avatarUrl à imageSrc nous seront obligé de faire quelque chose comme ça :

    /* Follower.re */[@react.component]let make = (~name: string, ~avatarUrl: option(string)=?) =>  <div>    {avatarUrl    ->Belt.Option.map(url => <User name imageSrc={url} />)    ->Belt.Option.getWithDefault(<User name />)}  </div>;

    Vu que imageSrc ne prend pas de type option, on est obligé d'appeler <User> différement selon que avatarUrl soit Some() ou None .

    Heureusement il existe une notation qui va nous permettre de renseigner un option, et on retrouve là encore le point d'interrogation :

    <User name imageSrc=?{avatarUrl} />

    Le point d'interrogation va permettre de renseigner le paramètre avec un type option, de transmettre la valeur contenue dans le Some() de avatarUrl ou d’omettre complètement le paramètre imageSrc si avatarUrl est None .

    Deux syntaxes utilisant le point d'interrogation

    Si on résume, la première syntaxe qui s'utilise dans la déclaration d'une fonction permet de déclarer un paramètre comme optionnel, la fonction acceptera de recevoir une valeur, comme il acceptera qu'on n'en passe aucune .

    Ensuite, la seconde syntaxe qui s'utilise à l'appel d'une fonction permet de transformer une valeur de type option en paramètre optionnel .

    Dis comme ça, cela peut sembler inutilement complexe, sauf que la complexité est gérée par le langage , et qu’à l’utilisation on y gagne en souplesse :

    • J’ai un composant avec un paramètre optionnel que je peux gérer, en interne, grâce aux avantages du type option
    • Je peux renseigner ce paramètre avec une valeur string, option ou l’omettre.

    Notations raccourcis et typage

    Pour les besoins de l’exemple j’ai volontairement utilisé du code très explicite, mais il faut savoir qu’on a quelques raccourcis dans la notation.

    Punning

    L’avantage du JSX de ReasonReact par rapport à React.js, c’est le punning. Le raccourci lorsqu'on a une prop ayant le même nom que la variable qu'on y passe. On a déjà l’habitude en JS avec les objets, au lieu de return {name: name} , on peut faire return {name} .

    On peut aussi l’utiliser avec le point d'interrogation, par exemple :

    [@react.component]let make = (~name: string, ~avatarUrl as imageSrc:option(string)=?) =>  <div>    <User name imageSrc=?{imageSrc} />  </div>;

    devient

    [@react.component]let make = (~name: string, ~avatarUrl as imageSrc:option(string)=?) =>  <div>    <User name ?imageSrc />  </div>;

    (d’ailleurs, si vous écrivez le code sans punning, le Reformat vous réécrit automatiquement votre code avec).

    C’est aussi l’occasion d’aborder le fait de pouvoir renommer un argument avec as . Il ne faut vraiment pas s’en priver. En fait il faut savoir que (~name) est lui même un raccourci pour (~name as name) .

    Inférence de type

    L’inférence de type est vraiment très bonne en Reason, si j’ai explicité le type option et leur contenu string, je peux très bien m’en passer. Ce qui nous donne un exemple final moins verbeux :

    /* User.re */[@react.component]let make = (~name, ~imageSrc=?) =>  <div>    <div>      {imageSrc      ->Belt.Option.map(src => <img src />)      ->Belt.Option.getWithDefault(React.null)}    </div>    <div> name->React.string </div>  </div>;/* Follower.re */[@react.component]let make = (~name, ~avatarUrl as imageSrc=?) =>  <div> <User name ?imageSrc /> </div>;

    Le type option se révèle très pratique lors de l'écriture d'un fonction ou d'un component. Les paramètres optionnels, avec les deux syntaxes utilisant le point d'interrogation, permettent de ne pas complexifier l'usage de la fonction ou du component.

    Dans l'exemple final, l'implémentation de User n'a pas changé depuis le début car nous voulons gérer l'aspect facultatif du paramètre. En revanche Follower n'avait absoluement pas besoin de le gérer dans son implémentation, le code s'en serait trouvé inutilement alourdi.Nous gardons ainsi une écriture fluide tout en permettant de gérer l'optionnalité d'une valeur lorsque cela est nécessaire.

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /articles/les-parametres-optionnels-avec-reasonml

    • chevron_right

      Tradeoffs

      news.movim.eu / PutainDeCode · Monday, 20 May, 2019 - 00:00 · 10 minutes

    Notre métier implique d'arbitrer ce qu'on appelle des tradeoffs. Il s'agit de définir les points positifs et négatifs d'une solution et d'en estimer la balance. On choisit ainsi la solution correspondant le mieux (du moins à nos yeux) à nos besoins de cette façon.

    Il existe cependant dans notre industrie un système de dogme. On ne cherche alors plus à mettre en perspective des tradeoffs et à les comparer, mais à faire se battre des "écoles de pensées", chacune ayant développé des axiomes (principe non démontré mais utilisé comme base d'un raisonnement).

    Suite à un énième débat sur les technologies modernes utilisées en front-end, réfutées par les défenseurs de certains de ces axiomes, je vais tenter de rationaliser notre approche et d'expliquer ses tradeoffs.

    L'idée est ici de faire comprendre pourquoi on utilise ces approches et technologies, dans quel contexte , et non de les imposer à qui que ce soit. Avec un peu de chance, cet article fera passer certains discours de "nimportawak (sic)" à "ce n'est pas pour ma typologie de projet".

    Au départ, le Web est conçu comme un ensemble de documents : chaque page en est un. À chaque navigation, on déclenche un nouveau cycle de vie de page : on termine la page courante, on initialise la suivante.

    Ce modèle est très simple et permet une expérience très correcte pour des pages majoritairement statiques.

    On a une page servie en HTML, une feuille de style servie en CSS. Et that's it . On apprend qu'il faut bien les séparer (au nom du principe de separation of concerns , au cas où il y'en ait un qui foire, une expérience dégradée doit être proposée.

    Avec les années, les exigences des utilisateurs sont devenues plus hautes : il a fallu y répondre par des pages plus interactives et des techniques de rechargements partiel de page (le cycle de vie d'une page étant plutôt coûteux). On a donc commencé à ajouter une intelligence limitée avec un peu de JS, notamment quelques briques interactives, charger des bouts de page avec AJAX.

    Ces techniques ont permis de drastiquement améliorer l'expérience de navigation des utilisateurs : on a moins de données à charger, on affiche ce que les gens veulent voir plus vite (ce serait quand même un poil relou de charger une nouvelle page dès que vous zoomez sur Google Maps). Et c'est ici le premier tradeoff :

    • on charge plus de données au chargement initial de page (le JS),
    • mais cela nous permet de charger moins de données aux chargements successifs.

    Puis arrive la multiplication des plateformes mobiles. Pour toucher les utilisateurs, le Web n'est plus LA plateforme, mais UNE plateforme parmi d'autres. Il devient alors stratégiquement intéressant pour les entreprises de commencer à développer des socles communs sous la forme d' APIs auxquelles de multiples clients sous différentes plateformes souscriront .

    À cette époque, le front-end connait une mutation sans précédent : on commence à créer de véritables applications , non plus des documents auxquels on greffe hasardeusement quelques fonctionnalités. On se dote alors d'outillages plus avancés, issus de patterns déjà éprouvés dans d'autres domaines du software (comme le MVC). L'ère du fichier JS fourre tout qui initialise 3 plugins jQuery pour faire des carousels est révolue.

    On commence alors à réfléchir en termes de vues . On gagne également une certaine indépendance vis à vis du back-end, on peut générer notre interface directement depuis le JS.

    On n'a plus à nécessairement apprendre le fonctionnement de stack back-end, son organisation, son langage de templating : on devient maîtres de nos stacks.

    On s'approprie de nouvelles problématiques comme le routage, le data-fetching et la création de caches clients intelligents. Des frameworks proposant des solutions à celles-ci émergent alors (Angular, Ember et Backbone pour ne citer qu'eux).

    Puis débarque React avec une approche unique face à ses concurrents: les composants. On en crée un pour chaque bloc réutilisable de l'application.

    Un composant, c'est une boite noire qui prend des paramètres ( props ), peut avoir un état local ( state ) et qui va décrire l'interface à n'importe quel point dans le temps.

    React arrive également avec JSX, une extension de JS, qui permet de décrire son interface sous une forme ressemblant à HTML (du XML), mais s'affranchissant de ses limitations (comme la nécessité de serialiser les attributs). Tout en conservant la familiarité d'HTML (et la pertinence d'une telle syntaxe pour représenter un arbre d'éléments), JSX répond à une frustration grandissante face aux templates "logic-less" qui forçaient la création de helpers et la transformation de donnée en amont.

    En nous abstrayant complètement du DOM et en nous offrant un modèle conceptuel simple ( (props, state) => UI ), React permet de créer des interfaces plus riches, plus simplement et surtout d'une manière maintenable : le comportement d'un composant étant couplé à son markup, on n'a plus à naviguer entre un fichier HTML et un JS pour les synchroniser. L'isolation des composants permet d'éviter les effets de bords indésirables.

    HTML et JS sont donc colocalisés, leur édition est mise en commun. Surprise : on s'est rendu compte que c'était une façon de faire plus productive et qu'on avait moins tendance à laisser pourrir du vieux code dans son coin.

    Ce problème subsiste avec CSS : il est toujours possible d'écrire du code CSS ayant un impact non désiré sur un composant autre que celui que l'on visait . On constate des guerres de spécificités, des régressions visuelles et un manque de visibilité sur l'impact d'un changement. Si vous héritez de code avec lequel vous n'êtes pas ou plus familier, le risque de casser quelque chose est grand.

    Les techniques d' isolation "manuelles" telles que BEM prennent de la popularité. On évite alors les sélecteurs ésotériques, et on fait au plus simple, avec une méthodologie de découpage faite en parallèle de nos composants (les classNames de mon composant Button vont être préfixées par Button ), plus maintenable. Étant à la discrétion des devs, cette méthodologie reste sujette à l'erreur, il faut vérifier que l'on n'utilise et ne casse pas un namespace existant.

    Puis arrivent les solutions automatisant cette isolation, délégant la tâche à la machine plutôt qu'à l'humain : CSS Modules et CSS-in-JS. Avec ces techniques, une erreur ne peut plus dépasser le scope de son composant . Le CSS non utilisé sur une route donnée n'est jamais injecté : le CSS mort est éliminé par défaut (un problème virtuellement impossible, et pour le moins non automatisable, avec une feuille de style traditionnelle).

    CSS-in-JS ramène le style au sein du composant , dans son scope. Notre composant contient désormais son markup, son style et son comportement.

    Il a été dit que cette approche rompt la separation of concerns , mais cette vision part du postulat que l'on doit impérativement coder des documents et oublier l'approche composant. Un postulat qu'on a oublié de réévaluer avec la perspective du développement tel qu'il est fait. Dans un contexte applicatif, séparer markup, style et comportement revient à s'imposer une séparation technologique non nécessaire et pouvant au nom d'une "bonne pratique" impacter négativement l'expérience des devs et des users.

    Il n'existe plus de raison autre que la "nostalgie du bon vieux temps" de le faire, il s'agit de reflexes acquis à l'époque mais jamais remis en perspective. Demandez à quelqu'un pourquoi c'est mal, il vous répondra "SEPARATION OF CONCERNS!". Demandez-lui pourquoi, il y a peu de chances qu'il vous sorte quoique ce soit de tangible.

    L'approche CSS-in-JS ne pose pas de problème lorsque l'application est entièrement gérée côté client. Mais elle peut-être embêtante pour des applications rendues côté serveur : le CSS sera absent de la page HTML chargée initialement et vous aurez un FOUC (Flash Of Unstyled Content). Heureusement, la grande majorité des solutions de CSS-in-JS proposent l'extraction des styles lors du rendu serveur. Il extrait les styles critiques de la page et les accole au rendu de l'application générée. Vous chargez moins de CSS et l'application côté client prendra le relai pour charger et injecter les règles au besoin.

    Chaque solution possède ses tradeoffs. Prenons pour exemples les temps de chargement des différentes approches et notons les avec des lettres de A à F (A étant le plus rapide, F le moins):

    • approche traditionnelle
      • premier chargement : C (on doit charger le fichier CSS pour la première fois avant rendu)
      • navigation directe : B (on ne peut pas contrôler la performance perçue de la transition, mais le fichier de style est en cache)
      • chargement ultérieur : B (le fichier CSS est en cache)
    • approche React sans SSR :
      • premier chargement : D (le HTML est "vide" mais on doit charger le JS, on doit charger le fichier CSS)
      • navigation directe : A (la transition est contrôlée et nécessite peu d'effort)
      • chargement ultérieur : C (les fichiers CSS et JS sont en cache)
    • approche React avec SSR :
      • premier chargement : C (on doit charger le fichier CSS pour la première fois avant rendu)
      • navigation directe : A (la transition est contrôlée et nécessite peu d'effort)
      • chargement ultérieur : C (les fichiers CSS et JS sont en cache)
    • approche React avec SSR et CSS-in-JS :
      • premier chargement : B (la page est 100 % disponible et visible dès la fin d'une seule requête pour le HTML)
      • navigation directe : A (la transition est contrôlée et nécessite peu d'effort)
      • chargement ultérieur : B (la page est 100 % disponible, les fichiers CSS et JS sont en cache)
    • approche React avec SSR et CSS-in-JS et service worker :
      • premier chargement : B (la page est 100 % disponible et visible dès la fin d'une seule requête pour le HTML)
      • navigation directe : A (la transition est contrôlée et nécessite peu d'effort)
      • chargement ultérieur : A (la page peut proposer un contenu immédiatement sans network et peut proposer une expérience de chargement le cas échéant)

    Chacune de ces solutions peut correspondre à vos besoins. Un document privilégiera le chargement initial et une application les navigations en son sein. Rien n'est parfait, il s'agit (et s'agira encore probablement pour longtemps) de décider du tradeoff que vous êtes prêt à faire.

    On écrit aujourd'hui des applications avec une technologie en pleine évolution, initialement prévue uniquement pour faire des documents.

    On se heurte au conservatisme de certains que refusent de voir le modèle "détourné" d'une utilisation telle qu'elle a été prévue il y a 20 ans. Mais il faut leur rappeler qu'on veut faire des applications utiles à nos utilisateurs, plus légères, plus rapides, qui dépassent le cadre prévu initialement par une approche document, et qu'on veut pouvoir les faire maintenant , parce que nos users n'ont pas grand chose à carrer du fait qu'une fonctionnalité doive exister dans les standards pour que les devs puissent l'utiliser. Si Dulux Valentine ne vendait que des couleurs primaires, ça vous viendrait à l'idée d'aller gueuler sur les gens qui font leur mur en vert en mélangeant du jaune et du bleu ?

    Alors on expérimente, on détourne des usages, on crée des choses, on tire profit d'APIs pas prévues pour ça à la base, on délivre des applications capables de choses qu'on n'imaginait pas possible sur le Web il y a quelques années. Grâce à toutes ces approches, et en s'enlevant le poids de règles obsolètes, on le fait plus vite, on le fait mieux, on le fait plus proprement.

    Et vous savez quoi ? Ça ne casse pas le Web. Ça ne casse pas vos pages. Ça n'est pas moins accessible. Ça ne fait qu'utiliser les outils standards Web qu'on a disposition pour faire plus. Et ça permet en plus de guider le W3C en leur montrant que certaines solutions sont utilisées pour résoudre certaines problématiques qui ne sont pas directement adressées par les standards.

    Vous n'avez personnellement pas besoin de ces approches ? Le jour où ces besoins émergent, vous saurez qu'elles existent, il suffit de comprendre pourquoi elles sont mises en place, quelles problématiques elles adressent, quels sont leur tradeoffs . Vous pourrez toujours ne pas les apprécier, mais au moins vous les aurez comprises, et elles constitueront une alternative supplémentaire pour le jour fatidique. Quand on a suffisamment de cordes à son arc, on peut jouer de la harpe.

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /articles/tradeoffs

    • chevron_right

      S02E04 CSS layout

      news.movim.eu / PutainDeCode · Monday, 8 April, 2019 - 00:00

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /podcasts/s02e04-css-layout

    • chevron_right

      S02E03 Server-Side-Rendering

      news.movim.eu / PutainDeCode · Monday, 8 April, 2019 - 00:00

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /podcasts/s02e03-server-side-rendering

    • chevron_right

      Le type option, c'est quoi et ça règle quel problème ?

      news.movim.eu / PutainDeCode · Monday, 25 March, 2019 - 00:00 · 6 minutes

    La plupart des langages populaires aujourd'hui ont une valeur particulière appelée null . Elle représente l'absence délibérée de valeur. JavaScript possède aussi undefined , qui fonctionne à peu près de la même façon mais pour d'autres significations.

    Un des problèmes souvent rencontré dans ces langages est que null est implicitement accepté comme valeur possible de n'importe quelle variable. Il est donc assez facile de se trouver avec un null is not an object ou une célèbre NullPointerException avec une stacktrace qui ne vous dira pas d'où est sorti ce null .

    null est une valeur importante pour la conception de programme : on n'a pas toujours de valeur, et il faut être en mesure de l'exprimer dans notre code. Pourtant, la plupart des langage fonctionnels statiquement typés n'ont pas de concept de null .

    Comment gèrent-ils ça ? Avec un type option (aussi appelé type maybe dans certains langages), qui est un petit conteneur qui englobe la valeur (ou la non-valeur). Puisqu'il nous faut un langage statiquement typé pour nos exemples, nous allons utiliser ReasonML dont je vous ai déjà parlé et avec lequel on a construit ce site .

    Le type

    Le type option est un variant , qui peut de loin s'apparenter à un type d'union. Par exemple:

    type status =  | Inactive  | Active;

    Une valeur de type status pourra être soit Inactive , soit Active et elle ne pourra être qu'une seule de ces valeurs à la fois.

    Maintenant, voyons la définition du type option :

    type option('value) =  /* on définit les différentes valeurs possibles,    une valeur du type option sera forcément d'une des deux listées ci-dessous */  | None/* pas de valeur */  | Some('value); /* une valeur du type `'value`*/

    'value est ici ce qu'on appelle un paramètre de type , ça permet au type d'être «génerique»: il se fout du type de la valeur contenue, et vous laisse le spécifier à l'usage ou laisse l'inférence de type le deviner.

    let isMyself = fun  | Some("Matthias") => true  | Some(_) | None => false;

    Ici, la fonction aura la signature suivante :

    let isMyself: option(string) => bool;                  /* ^ le compiler a compris qu'il s'agissait d'une chaîne de caractères! */

    Cette généricité fait de l'option une abstraction générale pour représenter la présence ou l'absence de n'importe quel type de valeur. Cela nous permet par exemple de créer une fonction map :

    let map = (opt, f) =>  switch (opt) {  | Some(x) => Some(f(x))  | None => None  };

    Et cette fonction pourra être utilisée pour n'importe quelle option . Jetons un œil à sa signature :

    let map: (option('a), 'a => 'b) => option('b);

    On peut lire cette signature de cette façon :

    • on a une fonction map
    • elle prend une option contenant une valeur de type a
    • elle prend une fonction prenant une valeur de type a et retourne une valeur de type b
    • elle retourne une option contenant une valeur de type b
    Some(2)->map(x => x *3) //Some(6)None->map(x => x *3) //None

    Un autre exemple de fonction utile est flatMap :

    let flatMap = (opt, f) =>  switch (opt) {  | Some(x) => f(x)  | None => None  };/* let flatMap: (option('a), 'a => option('b)) => option('b); *//* `get` retourne une option */let zipCode =  get("profile")  ->flatMap(profile => profile->get("address"))  ->flatMap(address => address->get("zipCode"));/* zipCode est un `option(string)` */

    Le problème résolu par le type option

    Prenons pour exemple la fonction Array.prototype.find de JavaScript :

    let result = array.find(item => item === undefined || item.active);

    result sera:

    • un objet s'il a un champ active ayant une valeur évaluée comme vraie
    • undefined si un item de array est undefined
    • undefined si rien n'est trouvé

    Avec cette implémentation naïve, on est incapable de savoir dans quel cas on se trouve : soit on a trouvé un item undefined , soit on a rien trouvé.

    Notez que le problème se pose ici avec undefined mais qu'il en serait de même un tableau contenant des null et une fonction find d'une bibliothèque retournant null dans le cas où elle ne trouve rien

    Si l'on veut être capable de faire la différence entre les deux derniers cas, on doit utiliser une autre fonction: findIndex :

    let index = array.findIndex(item => item === undefined || item.active);if (index == -1) {  // not found} else {  // foundlet result = array[index];}

    Le code est plus lourd, moins lisible, et manque d'expressivité. find ne nous donne pas assez d'information au travers de la valeur retournée: undefined est "aplati", et requiert une logique supplémentaire (ici index , si un item est trouvé, il sera supérieur à -1 , sinon il sera égal à -1 )

    Le problème ne vient pas de la fonction find elle même mais de la façon dont null et undefined sont traités. null est la valeur, il la remplace . option l'englobe : c'est un conteneur.

    openBelt; /* la stdlib *//* `getBy` est l'equivalent de `find` */let result = array->Array.getBy(  fun    | None => true    | Some({active}) => active);

    D'abord, array a le type suivant:

    let array: array(option(value));

    Et getBy celui-ci:

    let getBy: (array('a), 'a => bool) => option('a);

    Si on remplace les paramètres de type par le type vraiment utilisé dans notre cas précis, on se retrouve avec ça :

    let getBy:  (    array(option(value)),    option(value) => bool  ) => option(option(value));

    result aura donc le type suivant :

    let result: option(option(value));

    C'est une option d' option de value . Et ça signifie qu' on peut extraire l'information qui nous intéresse de la valeur de retour:

    • si le résultat est Some(Some(value)) : on a trouvé une valeur true pour le champ active
    • si le résultat est Some(None) : on a trouvé une valeur None
    • si le résultat est None : on n'a rien trouvé dans le tableau

    Le type option a éliminé par design certains problèmes inhérents à null et undefined en se comportant comme un conteneur plutôt qu'un substitut.

    La nature des option dans les langages statiquement typés permet d'éviter de nombreuses erreurs de conception. Les fonctions ne sont plus juste autorisées à retourner null et à vous laisser la responsabilité implicite de le gérer à grand coup de if(value == null) { a } else { b } , elles retournent un type option qui vous force à prendre en compte l'optionalité de la valeur .

    Avant de pratiquer un langage fonctionnel typé, je n'arrivais pas à piger comment ces langages pouvaient de débrouiller sans valeur null . J'espère que si vous êtes dans le même cas, ce petit post vous aidera à mettre ces deux approches en perspective.

    Sir Tony Hoare, l'inventeur de la référence null l'appelle aujourd'hui sa billion dollar mistake . Le gars l'a inventé en 1965, on va pas lui en vouloir, mais plus de 50 ans après il serait peut-être temps de reconsidérer le bien fondé du truc et se pencher sur les alternatives qui éliminent le problème plutôt que de continuer à mettre des if partout et de continuer à détricoter des stacktraces ne contenant même pas la source du bug.

    Bisous.

    • wifi_tethering open_in_new

      This post is public

      putaindecode.io /articles/le-type-option-c-est-quoi-et-ca-regle-quel-probleme