Autoblog de sametmax.com

Ce site n'est pas le site officiel de sametmax.com
C'est un blog automatisé qui réplique les articles de sametmax.com

La course du bus de l’innovation sur le chemin de la croissance 5

Sat, 22 Jul 2017 20:10:46 +0000 - (source)

Ceci est un post invité de 01ivier posté sous licence creative common 3.0 unported.

Bonsjours à toutes et à tous,

Dans un sursaut de patriotisme, je me suis dit qu’il me fallait, moi aussi, participer à l’effort national et promouvoir le savoir-faire technologique français. Partant du constat que, de nos jours, proposer quelque chose de nouveau semble suffisant pour être innovant, j’ai fait comme tout bon startupeur, je ne me suis pas embarrassé à concevoir quelque chose d’utile. À vrai dire, ce n’est même pas nouveau. Mais je suis français et j’ai un compte Twitter, je crois que c’est amplement suffisant pour prétendre au label #CitizenOfTheFrenchTechNation.

 
frenchtech

 

Où le bus l’innovation va tout droit.

Parce que je suis bien conscient de l’influence que cet article va avoir dans le monde du jeu vidéo, je vais détailler les différentes étapes de la réalisation de ce projet bouleversant.

Tout d’abord, j’ai fait en sorte que le bus de l’innovation aille tout droit sur un chemin de la croissance qui est tout droit.

#-*- coding: utf-8 -*-
 
# On importe de quoi dormir
from time import sleep
 
# On définit le graphisme en accord avec l'équipe de designers
chemin = " " 
arbre = "A"
bus = "B"
 
# On définit la qualité de l'image après avoir consulté les experts en GPU du Digital-Agile-Open-Tech-Lab-Responsive.IO
largeur_ecran = 70
 
# On définit l'état initial conjointement avec la team level design.
# (elle-même ayant sollicité au préalable le narrative designer, bien entendu)
largeur_chemin = 15
position_chemin = 28
position_bus = 35
 
while 1:
 
    # On place la première portion d'arbres dans le paysage
    paysage = arbre * position_chemin
    # On place le chemin
    paysage += chemin * largeur_chemin
    # On remplit le reste du paysage par la deuxième portion d'arbres
    paysage += arbre * (largeur_ecran - len(paysage))
    # On place le bus sur notre paysage
    paysage = paysage[:position_bus] + bus + paysage[position_bus:]
 
    # On affiche le tout
    print(paysage)
 
    # On dort un peu
    sleep(0.2)

bus_innovation_droit

Oui. Il est effectivement possible d’arriver plus simplement au même résultat, mais, mes qualifications ne m’offrent pas le loisir d’être incompétent. J’anticipe donc les futurs mouvements du bus de l’innovation ainsi que les inéluctables virages du chemin de la croissance.

Ce doit être un peu déroutant pour vous, j’en conviens, mais c’est l’expérience qui parle.

Faites-moi confiance.

 

Où le bus l’innovation va tout droit mais en bien plus beau.

Parce que l’on n’est jamais assez exigent sur l’apparence d’un produit de luxe, j’ai voulu changer le rendu des arbres en remplaçant le “A” par un “█” bien plus raffiné. Voici le résultat que j’ai obtenu :

bus_innovation_droit_arbre

Diable comme le succès est semé d’embûches !

En effet, un petit…

>>> len("█")
3

… m’a appris que le “█” comptait pour trois caractères, faussant mon ingénieuse gestion des positions.

Mon talent m’avait permis d’anticiper les mouvements et autres virages mais pas ça. Damned. Qu’allais-je faire ?

Un petit replace() bien placé et le tour était joué. Sans compter que je pouvais désormais me permettre de mettre de la couleur.

La classe internationale.

#-*- coding: utf-8 -*-
 
from time import sleep
 
chemin = " " 
arbre = "A"
#On définit un arbre classe et vert en utilisant les codes ANSI
arbre_classe_et_vert = "\33[32m█\033[0m"
bus = "B"
 
largeur_ecran = 70
largeur_chemin = 15
position_chemin = 28
position_bus = 35
 
while 1:
 
    paysage = arbre * position_chemin
    paysage += chemin* largeur_chemin
    paysage += arbre * (largeur_ecran - len(paysage))
    paysage = paysage[:position_bus] + bus + paysage[position_bus:]
    # PAF ! On remplace l'arbre ridicule par un arbre classe et vert après avoir géré la position des différents éléments.
    paysage = paysage.replace(arbre, arbre_classe_et_vert)
 
    print(paysage)
 
    sleep(0.5)

BIM !

bus_innovation_droit_arbre_vert

 

Où le bus de l’innovation va tout droit mais sur un chemin de la croissance qui tourne.

Pour faire tourner le chemin de la croissance je fais un petit +2/-2 sur sa position avec un randint(), et zou.

Par contre, pour forcer le chemin de la croissance à rester dans l’écran, j’ai été surpris de ne pas trouver de fonction prête à l’emploi pour contraindre un nombre dans un intervalle donné. Je suis donc passé par une “astuce” pécho sur Stack Overflow que j’ai trouvée élégante. On classe par ordre croissant le nombre donné avec les bornes de l’interval et on récupère le deuxième élément.

position = sorted(position_min, position, position_max)[1]

Si vous avez mieux, ça m’intéresse.

#-*- coding: utf-8 -*-
 
# On importe de quoi choisir des nombres au hasard
from random import randint
 
from time import sleep
 
chemin = " " 
arbre = "A"
arbre_classe_et_vert = "\33[32m█\033[0m"
bus = "B"
 
largeur_ecran = 70
largeur_chemin = 15
position_chemin = 28
position_bus = 35
 
# On définit une position maximale pour le chemin de la croissance
position_max_chemin = largeur_ecran - largeur_chemin
 
while 1:
 
    # On calcule la nouvelle position du chemin de la croissance
    # Un peu plus à droite, un peu plus à gauche ou un peu plus tout droit...
    position_chemin += randint(-2, 2)
 
    # En s'assurant qu'il ne déborde pas de la largeur de l'écran
    position_chemin = sorted([1, position_chemin, position_max_chemin])[1]
 
    paysage = arbre * position_chemin
    paysage += chemin * largeur_chemin
    paysage += arbre * (largeur_ecran - len(paysage))
    paysage = paysage[:position_bus] + bus + paysage[position_bus:]
    paysage = paysage.replace(arbre, arbre_classe_et_vert)
 
    print(paysage)
 
    sleep(0.5)

bus_innovation_chemin_virage

 

Où le bus de l’innovation crache des flammes.

Avant de me pencher sur les mouvements du bus de l’innovation, j’ai pris le temps de le tuner un peu.

Déjà, direct, je l’ai peint en rouge, rapport au fait que le bus de l’innovation, c’est un peu la Ferrari de l’entrepreneuriat.

Et puis, je me suis dit qu’il fallait qu’il n’y ai vraiment absolument aucun doute sur le fait que c’était bel et bien le bus de l’innovation. Je lui ai donc fait écrire “LE BUS DE L’INNOVATION” sur la route. Je m’en remets à vous pour me dire s’il reste une ambiguïté.

Accessoirement, le “A” utilisé pour placer les arbres est devenu un “a“, pour ne pas être confondus avec le “A” présent dans “INNOVATION”. C’est un détail, mais les générations d’ingénieurs qui liront ça dans deux cent ans seront bien contents de trouver cette explication.

#-*- coding: utf-8 -*-
 
from random import randint
from time import sleep
 
chemin = " " 
arbre = "a"
arbre_classe_et_vert = "\33[32m█\033[0m"
 
largeur_ecran = 70
largeur_chemin = 15
position_chemin = 28
position_bus = 35
 
# On définit le texte écrit par le bus de l'innovation
texte_du_bus = "LE BUS DE L'INNOVATION "
# On récupère le nombre de caractères dans le texte écrit par le bus de l'innovation
nb_caractere = len(texte_du_bus)
# On initialise un compteur pour gérer la succession des caractères
compteur = 0
 
position_max_chemin = largeur_ecran - largeur_chemin
 
while 1:
 
    position_chemin += randint(-2, 2)
    position_chemin = sorted([1, position_chemin, position_max_chemin])[1]
 
    paysage = arbre * position_chemin
    paysage += chemin * largeur_chemin
    paysage += arbre * (largeur_ecran - len(paysage))
 
    # Dans le texte écrit par le bus de l'innovation, on prend le caractère 
    # indiqué par le compteur modulo le nombre de caractères possibles
    caractere = texte_du_bus[compteur%nb_caractere]
    # On peint le caractère en rouge Ferrari
    bus = "\33[31m{0}\033[0m".format(caractere)
    # On incrémente le compteur pour avoir le caractère suivant au prochain tour
    compteur += 1
 
    paysage = paysage[:position_bus] + bus + paysage[position_bus:]
    paysage = paysage.replace(arbre, arbre_classe_et_vert)
 
    print(paysage)
 
    sleep(0.5)

Magnifique.

bus_innovation_flamme

 

Où l’on cherche à faire tourner le bus de l’innovation.

Je ne vais pas vous mentir, la course du bus de l’innovation sur le chemin de la croissance n’est pas vraiment une nouveauté vidéoludique. Nous, c’est à dire La Labomedia, l’avons déjà présentée à Toulouse lors du THSF 2014 ainsi qu’au PSESHSF 2016 de Choisy-le-Roi dont il existe même une vidéo de la Master Class.

Sur cette photo spectaculaire, vous pouvez découvrir la pertinence de notre ingénieuse interface biologique imputrescible nourrie aux anti-biotiques NF.

busdelinnovationsurlechemindelacroissance

Mais, non seulement j’avais perdu totalement le code initial mais en plus l’installation s’appuyait sur l’excessivement chère MakeyMakey qui, par ailleurs, telle qu’elle est vendue, impose à l’utilisateur d’être relié à une masse.

Pour sa résurrection, je lui ai donc préférée la Capacitive Touch HAT pour RaspberryPi conçue par Adafruit et qui fonctionne direct au simple touché.

(Il va de soit que les natural chicken flavor interfaces n’ont, elles, pas été remises en question.)

Voici le code minimal pour utiliser le Capacitive Touch HAT une fois la librairie d’Adafruit installée :

import Adafruit_MPR121.MPR121
from time import sleep
 
interface = Adafruit_MPR121.MPR121.MPR121()
interface.begin()
 
while 1:
 
  # Si la patine numéro 0 est touchée
  if interface.is_touched(0):
      print("GAUCHE !")
 
  # Si la patine numéro 10 est touchée
  if interface.is_touched(10):
      print("DROITE !")
 
  sleep(0.1)

Qui devient ceci quand on instaure un seuil de détection pour tenir compte de la conductivité des pattes de poulet.

import Adafruit_MPR121.MPR121
from time import sleep
 
interface = Adafruit_MPR121.MPR121.MPR121()
interface.begin()
 
seuil = 100
 
while 1:
 
  # Si la patte de poulet reliée à la patine numéro 0 est touchée
  if interface.filtered_data(0) < seuil:
      print("GAUCHE !")
 
  # Si la patte de poulet reliée à la patine numéro 0 est touchée
  if interface.filtered_data(10) < seuil:
      print("DROITE !")
 
  sleep(0.1)

On ne peut pas dire que ce soit très compliqué. À noter tout de même la nécessité d’activer l’I2C et de lancer le script en root. Une formalité pour celles et ceux qui ont de l’ambition dans la Vie.

Une fois intégré dans notre formidable simulation de sport mécanique extrême, le script ressemble alors à ça :

#-*- coding: utf-8 -*-
 
# On importe la librairie d'Adafruit
import Adafruit_MPR121.MPR121 as MPR121
 
from random import randint
from time import sleep
 
# On instancie l'interface...
interface = MPR121.MPR121()
# ... et on la démarre.
interface.begin()
 
chemin = " " 
arbre = "a"
arbre_classe_et_vert = "\33[32m█\033[0m"
 
largeur_ecran = 70
largeur_chemin = 15
position_chemin = 28
position_bus = 35
 
texte_du_bus = "LE BUS DE L'INNOVATION "
nb_caractere = len(texte_du_bus)
compteur = 0
 
position_max_chemin = largeur_ecran - largeur_chemin
 
seuil = 100
 
while 1:
 
    # En fonction des patines touchées,
    # on déplace le bus de l'innovation vers la droite...
    if interface.filtered_data(0) < seuil:
        position_bus += 1
 
    # ... ou vers la gauche.
    if interface.filtered_data(10) < seuil:
        position_bus -= 1
 
    position_chemin += randint(-2, 2)
    position_chemin = sorted([1, position_chemin, position_max_chemin])[1]
 
    paysage = arbre * position_chemin
    paysage += chemin * largeur_chemin
    paysage += arbre * (largeur_ecran - len(paysage))
 
    caractere = texte_du_bus[compteur%nb_caractere]
    bus = "\33[31m{0}\033[0m".format(caractere)
    compteur += 1
 
    paysage = paysage[:position_bus] + bus + paysage[position_bus:]
    paysage = paysage.replace(arbre, arbre_classe_et_vert)
 
    print(paysage)
 
    sleep(0.5)

bus_innovation_tourne

 

Où le bus de l’innovation est tenu de rester sur le chemin de la croissance.

Écoutez, que les choses soient bien claires, moi aussi je trouve cet article beaucoup trop long, mais c’est du rayonnement de la France dont il est question. Dois-je vous le rappeler ?

Voici donc comment j’ai tenu compte des sorties du chemin de la croissance par notre bus de l’innovation :

#-*- coding: utf-8 -*-
 
import Adafruit_MPR121.MPR121 as MPR121
 
from random import randint
from time import sleep
 
interface = MPR121.MPR121()
interface.begin()
 
chemin = " " 
arbre = "a"
arbre_classe_et_vert = "\33[32m█\033[0m"
 
largeur_ecran = 70
largeur_chemin = 15
position_chemin = 28
position_bus = 35
 
texte_du_bus = "LE BUS DE L'INNOVATION "
nb_caractere = len(texte_du_bus)
compteur = 0
 
position_max_chemin = largeur_ecran - largeur_chemin
 
# On définit un booléen qui rendra compte de l'état du bus de l'innovation
le_bus_roule = True
 
seuil = 100
 
while 1:
 
    if interface.filtered_data(0) < seuil:
        position_bus += 1
 
    if interface.filtered_data(10) < seuil:
        position_bus -= 1
 
    # Si le bus roule...
    if le_bus_roule:
 
        position_chemin += randint(-2, 2)
        position_chemin = sorted([1, position_chemin, position_max_chemin])[1]
 
        paysage = arbre * position_chemin
        paysage += chemin * largeur_chemin
        paysage += arbre * (largeur_ecran - len(paysage))     
 
        # Si le bus sort de la route, à gauche ou à droite...
        if position_bus <= position_chemin or position_bus >= position_chemin + largeur_chemin:
 
            # On change l'apparence du bus (qui devient une croix verte)
            bus = "\33[32mX\033[0m"
            # On change l'état du bus de l'innovation
            le_bus_roule = False
 
        # Sinon, on affiche le bus comme précédement défini
        else:
            caractere = texte_du_bus[compteur%nb_caractere]
            bus = "\33[31m{0}\033[0m".format(caractere)
            compteur += 1
 
        paysage = paysage[:position_bus] + bus + paysage[position_bus:]
        paysage = paysage.replace(arbre, arbre_classe_et_vert)
 
        print(paysage)
 
        # Si, entre temps, le bus de l'innovation s'est arrêté de rouler
        if not le_bus_roule:
 
            # On affiche un message sympathique après avoir sauté une ligne
            print("\nIl n'est pas exclu que le bus de l'innovation se soit pris un arbre...")
 
        sleep(0.5)

bus_innovation_crash

 

Où la course du bus de l’innovation sur le chemin de la croissance va pouvoir enfin commencer.

Afin que le public intègre bien le défi collectif que représente la relance de l’économie par le financement et le développement d’innovants nouveaux projets novateurs avec de la technologie à la pointe de la technologie, j’ai fait en sorte d’afficher la croissance totale que l’ensemble des joueurs auront fait parcourir au bus de l’innovation.

Ainsi, n’y a-t-il jamais un individu qui perd, mais toujours un collectif qui gagne.

Quelque chose entre la victoire éternelle et le succès permanent.

Une véritable leçon de Vie.

#-*- coding: utf-8 -*-
 
import Adafruit_MPR121.MPR121 as MPR121
 
from random import randint
from time import sleep
 
interface = MPR121.MPR121()
interface.begin()
 
chemin = " " 
arbre = "a"
arbre_classe_et_vert = "\33[32m█\033[0m"
 
largeur_ecran = 70
largeur_chemin = 15
 
# On met en place un système d'initialisation des positions
# du chemin de l'innovation et du bus de la croissance
init_position_chemin = 28
init_position_bus = 35
position_chemin = init_position_chemin
position_bus = init_position_bus
 
texte_du_bus = "LE BUS DE L'INNOVATION "
nb_caractere = len(texte_du_bus)
compteur = 0
 
position_max_chemin = largeur_ecran - largeur_chemin
 
seuil = 100
 
le_bus_roule = True
 
# On déclare des variables qui vont nous permettre de rendre compte
# des quantités de croissance parcourue par le bus de l'innovation
croissance_parcourue_totale = 0
croissance_parcourue = 0
 
# On définit un petit texte à formater pour présenter les quantités en question
texte_crash ='''
Bravo !
Tu t'es pris un arbre mais tu as parcouru {0} mètres de croissance !
 
Pour ton information, le Bus de l'Innovation a parcouru {1} mètres
de croissance depuis son départ.
 
Pour le faire redémarrer, appuie sur la cuisse de poulet du milieu.
La France compte sur toi !
'''
 
while 1:
 
    if interface.filtered_data(0) < seuil:
        position_bus += 1
 
    if interface.filtered_data(10) < seuil:
        position_bus -= 1
 
    # On met en place un moyen de reprendre le chemin de la croissance
    # si le bus de l'innovation s'est pris un arbre
    if if interface.filtered_data(5) < seuil and not le_bus_roule:
        le_bus_roule = True
        croissance_parcourue = 0
        position_chemin = init_position_chemin
        position_bus = init_position_bus
        compteur = 0
 
    if le_bus_roule:
 
        position_chemin += randint(-2, 2)
        position_chemin = sorted([1, position_chemin, position_max_chemin])[1]
 
        paysage = arbre * position_chemin
        paysage += chemin * largeur_chemin
        paysage += arbre * (largeur_ecran - len(paysage))     
 
        if position_bus <= position_chemin or position_bus >= position_chemin + largeur_chemin:
 
            bus = "\33[32mX\033[0m"
            le_bus_roule = False
 
        else:
            caractere = texte_du_bus[compteur%nb_caractere]
            bus = "\33[31m{0}\033[0m".format(caractere)
            compteur += 1
            # On incrémente la croissance parcourue à chaque tour
            croissance_parcourue += 5
 
        paysage = paysage[:position_bus] + bus + paysage[position_bus:]
        paysage = paysage.replace(arbre, arbre_classe_et_vert)
 
        print(paysage)
 
        if not le_bus_roule:
 
            # On calcule la croissance totale parcourue
            croissance_parcourue_totale += croissance_parcourue
            # On formate notre petit texte pour informer le joueur de sa 
            # performance individuelle et collective.
            print(texte_crash.format(croissance_parcourue, croissance_parcourue_totale))
 
        sleep(0.5)

bus_innovation_arcade


Le pattern strategy version gastronomique 9

Fri, 14 Jul 2017 19:11:06 +0000 - (source)

Allez, un petit article de POO un peu avancée pour faire marcher ses neurones ce WE.

Le design pattern strategy, qui consiste à déléguer une partie du comportement d’un objet à un autre objet, est probablement l’un des motifs de conception les plus utiles en programmation. Trop souvent les gens utilisent l’héritage là où la composition serait plus adaptée, et une injection de dépendance bien faite permet de gagner beaucoup en qualité de code.

Si vous ne vous souvenez plus de ce qu’est le pattern strategy, vous pouvez faire un saut sur le chapitre qui en parle dans le guide de la POO :)

Mais comme un petit rappel ne fait pas de mal, en très court, strategy ressemble à ça :

class MonObjet:
    def __init__(self):
        self.strategie = MaStrategie()
 
    def foo(self):
        return self.strategie.foo()

Ce qui permet à une sous-classe de changer la strategy ou non :

class MonSousObjet(MonObjet):
   def __init__(self):
        self.strategie = MonAutreStrategie()

Ou de changer la strat dynamiquement :

hop = MonObjet()
 
hop.stategie = SuperNewStrat()

Mais si vous vous en tenez à ce design, les utilisateurs de la classe vont très vite rencontrer des limitations.

D’abord, une bonne stratégie peut avoir besoin de contexte. Dans ce cas, donnez lui le choix d’avoir une référence à l’objet parent:

class MonObjet:
    def __init__(self):
        # Passer self permet à la stratégie de connaître son contexte. 
        # Le désavantage est l'introduction potentiel d'un couplage entre 
        # les deux objets, et potentiellement des effets de bords supplémentaires. Cela reste 
        # néanmoins souvent une bonne idée.
        self.strategie = MaStrategie(self)   
    ...

Ensuite, une stratégie devrait pouvoir être passée à la création de l’objet :

class MonObjet:
    def __init__(self, strategie=MaStrategie):
        # On donne la priorité à l'objet passé en paramètre. Si il n'y en a 
        # pas on crée la stratégie par défaut.
        self.strategie = strategie(self)  
    ...

Cela permet d’overrider la stratégie pour les usages plus avancés, tout en permettant aux débutants de ne pas se soucier de cela car il existe quand même une valeur par défaut.

truc = MonObjet(UneStrategieDifferente)

Comme le travail dans un init est souvent assez redondant, avoir un endroit pour permettre aux sous-classes de facilement overrider la stratégie est une bonne pratique. En Python il est courant d’utiliser les variables de classes pour cela :

class MonObjet:
 
    # On appelle souvent cet attribut "strategy_class" ou "strategy_factory"
    strategie_par_default = MaStrategie
 
    def __init__(self, strategie=None):
        self.strategie = strategie(self) if strategie else self.strategie_par_default(self) 
    ...
 
class MonSousObjet(MonObjet):
    # Et boom, overriding de la stratégie par la classe enfant en une ligne.
    # Django fait ça par exemple avec les classes based views et l'attribut 
    # model
    strategie_par_default = MonAutreStrategie

Une fois que vous avez fait tout ça, vous avez déjà fait mieux que 90% des programmeurs. Néanmoins si vous voulez vraiment mettre la petit touche pro à votre API, vous pouvez aussi permettre la création dynamique de la stratégie:

class MonObjet:
 
    strategie_par_default = MaStrategie
 
    def __init__(self, strategie=None):
        self.strategie = strategie(self) if strategie else self.build_strategy() 
 
    def build_strategy(self):
        return self.strategie_par_default(self)
 
    ...

Wow, ça en fait des self et des factories :) En fait, ça fait la même chose qu’avant, c’est à dire que le tout premier exemple de code tout simple qu’on a vu en début d’article marche toujours ! C’est la beauté de la chose.

La différence, c’est que maintenant une classe enfant peut overrider build_strategy() et créer des stratégies à la volée, en fonction du contexte d’exécution. Par exemple créer une stratégie différente en fonction d’une valeur de base de données. C’est rare que ça arrive, et c’est vraiment de l’usage avancé. Mais quand vous avez ça, vous êtes certains que votre code est prêt à être utilisé par autrui. Car si cet autrui n’est pas content, il peut faire une profonde coloscopie à votre code et y insérer ce qu’il veut, quand il veut.

Être dev après tout, c’est être un peu poète.


Le don du mois: Framasoft (bis) 7

Tue, 11 Jul 2017 07:15:38 +0000 - (source)

J’ai déjà donné à Framasoft il y a 2 ans alors que je n’utilisais plus vraiment le site. Plutôt en remerciement de tout ce qu’ils ont fait pendant que j’étais encore sous Windows XP à bricoler avec Phoenix, CloneCD et WinAmp.

Depuis 2001 cet excellent portail a fleuri pour devenir un hébergeur de nombreux services, particulièrement:

Pour l’instant je suis très content de l’agenda qui est une instance nextcloud avec une jolie UI, offrant plusieurs calendriers en parallèle, des partages et exports et tout le tintouin. Donc j’ai thunderbird linké dessus sous Ubuntu, et Solcalendar sous Android (avec pulse sms pour pouvoir taper les sms confortablement sur l’ordi, rien à avoir mais j’en suis content alors je plug).

Bref, cet outil arrive à point pour être bien productif. Reste plus qu’à trouver une alternative à GTG qui est lent comme une grand-mère asthmatique. Si vous avez des suggestions…

Néanmoins, héberger des services comme ça, ça coûte cher. Et framasoft les propose gratuitement.

Donc, don de 30 euros à l’asso. Merci messieurs-dames, vous déchirez.

Pour vous aussi faire un don, c’est par ici.


Caldigit et USB-C 13

Mon, 10 Jul 2017 11:41:39 +0000 - (source)

Mon rêve d’avoir un setup tri-écran, ethernet, casque-micro et chargement avec un seul câble est enfin devenu une réalité.

Et ça a pas été de la tarte.

J’ai un Dell XPS 15 qui a un port USB-C avec thunderbolt. J’ai acheté un dock USB-C caldigit qui propose une entrée micro, une sortie casque, un port HDMI, un display port et un port ethernet, le tout avec un seul branchement via USB-C.

Inutile de dire que ça n’a pas marché. L’USB-C est une vraie jungle, vous connaissez aussi mon avis sur les finitions du XPS 15 et puis la vidéo, ça fait partie de ces trucs qui sont toujours galère en 2017.

Mais…

Après avoir:

Ca marche.

Pas plug and play pour deux ronds. Mais j’ai enfin un seul câble qui charge l’ordi, m’envoie un débit fibré et étend mon affichage, automatiquement au boot, sous Linux et Windows.

Joie.

Si j’écris l’article, c’est pour dire que:

Mais surtout pour signaler que le support de caldigit a été fantastique, ce qui est assez rare pour le souligner. Ma première interlocutrice via chat a répondu vite et posé les bonnes questions. Identifiant qu’elle ne pouvait pas résoudre le problème, elle m’a redirigé vers le niveau supérieur du support en faisant suivre correctement tout le dossier. Niveau supérieur très technique qui a permis, mail après mail, détail après détail, à faire marche le bouzin. Pas une question à la noix. Pas besoin de me répéter 15 fois. Pas de remise en cause de ma bonne foi ou de prise pour un gogole. Quand les gens font leur boulot ça étonne toujours.


Alternative au do…while en Python 21

Mon, 03 Jul 2017 15:25:10 +0000 - (source)

De nombreuses instructions ont été volontairement écartées de Python. Le goto bien entendu, mais aussi le switch, unless et le do...while.

Le but est de limiter le nombre de mots clés à connaitre afin de comprendre le langage. Les créateurs ont choisi donc de mettre de côté des mots clés trop souvent mal utilisés, pas assez utilisés, ou qui possèdent des alternatives suffisantes.

La boucle while est rarement utilisée en Python, en tout cas beaucoup, beaucoup moins que sa petite soeur la boucle for. Avoir besoin d’un do...while est encore plus rare, et donc ne peut faire partie du club très fermé des mots clés réservés.

Si l’on souhaite obtenir l’effet du do..while en python, on fait donc généralement une boucle infinie suivie d’un break sur une condition. Exemple:

import random
 
choix = random.randint(0, 100)
 
while True:
    reponse = int(input('Devinez le nombre: '))
    if reponse < choix:
        print('Plus grand')
    elif reponse > choix:
        print('Plus petit')
    else:
        break
print('Bravo')

Le défaut de cette technique est qu’elle ne rend pas clair dès le début la condition de sortie de la boucle. Aujourd’hui en parcourant la mailling list python-idea, je suis tombé sur une idée pas conne:

import random
 
choix = random.randint(0, 100)
 
while "L'utilisateur n'a pas encore deviné le nombre":
    reponse = int(input('Devinez le nombre: '))
    if reponse < choix:
        print('Plus grand')
    elif reponse > choix:
        print('Plus petit')
    else:
        break
print('Bravo')

Ca marche car les chaînes non vides sont toujours vraies en Python, et ça documente le code :)


Les plus grosses roues du monde 13

Fri, 30 Jun 2017 12:58:08 +0000 - (source)

L’avantage d’avoir quelques années de programmations dans les pattes et un certain nombres de projets à son actif, c’est qu’on arrive à identifier des motifs communs qui se dégagent encore et encore.

Par exemple, quand j’étais en tout début de carrière, j’ai ouvert l’excellent bouquin “Head first design patterns” et je n’en ai pas retiré grand chose car je n’avais pas la matière pour pouvoir identifier l’utilité des solutions proposées. Bien plus tard, en le relisant, je me suis aperçu que j’avais en fait rencontré moult fois chaque chapitre IRL, base de code après base de code.

La vie apprend les design patterns bien plus efficacement que les écrits. Mais ces derniers ont l’avantage de mettre de l’ordre dans ses idées. Ils mettent des mots sur des pensées floues, et tracent des contours qui délimitent pragmatiquement les concepts.

Aujourd’hui néanmoins, nous n’allons pas parler de design pattern, bien que faire un dossier dessus serait une bonne idée. Mais ça fait des mois que je dois finir le dossier tests unitaires, alors je vais pas commencer un nouveau dossier.

Non, aujourd’hui, nous allons parler d’outils dont on a besoin dans quasiment tous les projets importants et qu’on réinvente, ou rapièce, presque à chaque fois.

Dispatching

Que ce soit parce que vous avez un observer, du pub/sub, un système de routing ou des events internes, votre projet finira par avoir besoin d’un système de dispatching. Le dispatching c’est la propagation/distribution de l’information et de son traitement.

Ils ont toujours la même chose en commun:

Par exemple:

Ce sont tous des implémentations spécialisées d’un système de dispatching. Le hello world du dispatching est le design pattern observer, qui minimalement ressemble à ça:

>>> class Dispatcher:
...     def __init__(self):
...         self.registry = {}
...     def on(self, event, callback):
...         self.registry.setdefault(event, []).append(callback)
...     def trigger(self, event):
...         for callback in self.registry[event]:
...                 callback()
... 
... hub = Dispatcher()
... 
... hub.on("j'arrive", lambda: print('coucou'))
... hub.on("j'arrive", lambda: print('salut'))
... 
... hub.trigger("j'arrive")
... 
coucou
salut

En fait, à moins de faire uniquement des scripts, vous avez utilisé plein de systèmes de dispatching sans le savoir.

Bien que pour qu’ils soient utiles il faut des versions spécialisées pour chaque usage, c’est un problème générique et il est ridicule que nous devions réimplementer à chaque fois ce truc. Un bon système de dispatching est utile dans tout gros projet. Vous voulez permettre à quelqu’un de lancer du code quand votre système s’initialise ? Créer une logique de plugin ? Bam, il faut du dispatching.

Il faudrait donc un framework en Python qui permette de fabriquer son propre système de dispatching. Il devra bien entendu inclure des implémentations spécialisées au moins pour les cas les plus courants sinon ça fera comme une lib zope et ça prendra la poussière.

Le but étant qu’au bout de quelques années, tout le monde base son implémentation sur cette brique, robuste et documentée, plutôt que de créer son propre système.

En effet, un bon système de dispatching doit pouvoir gérer les cas suivants :

Le tout bien entendu avec des backends pour chaque partie qu’on puisse swapper.

Configuration

La conf, c’est l’exemple exacte de la fragmentation dans notre métier. C’est l’usine à roues (carrées) réinventées.

Sérieusement, entre le parsing des arguments de la ligne de commande, les fichiers de config, les services de config (etcd anyone ?), les configs sauvegardées en BDD, les API de conf, les variables d’environnement, etc. c’est un bordel sans nom.

Tout le monde a fait son petit fichier params.(ini|yml|xml|json) ou sa table SQL settings dans un coin. Et la validation. Et la génération. Et les valeurs par défaut. Et l’overriding des envs. Ca change à chaque projet, à chaque framework, à chaque foutue lib.

C’est que le but est simple, mais le problème est complexe. Mais on en a tous besoin, et il n’y a rien, mais alors rien qui existe de générique.

Une bonne lib de conf doit:

Pas évident non ? Ça change de “ah bah je vais dumper tout ça dans un settings.py et on verra bien” :)

Les bénéfices d’avoir un bon système de settings sont énormes. D’abord, si il est largement adopté, plus besoin de fouiller dans la doc de chaque projet pour savoir comment l’utiliser. Les problèmes difficiles comme les “live settings” sont réglés une fois pour toute. Plus besoin d’écrire pour la millième fois le code de glue entre son schéma marshmallow + ses params clicks + son parser yml qui sera forcément bricolé. Et une expérience utilisateur bien meilleure avec de la doc, des messages standardisés, des checks de sources de données que d’habitude personne ne fait, etc.

Logging

Oui, je sais, je sais, Python a un excellent module de logging, très riche et polyvalent. Et puis ce ne sont pas les projets de logging qui manquent. Il y a celui de twisted, il y a logbook, logpy… En fait j’ai même pondu devpy.

Malgré ça, force est de constater que tous ces projets sont loin d’être une bonne solution pour convenir à tous.

Le module logging Python manque de configuration par défaut, n’a pas de gestion multiprocessing, aucune facilité pour générer des logs structurés ou binaires, etc. logbook et logpy sont des surcouches qui améliorent, mais sans aller assez loin, l’expérience. Twisted comme d’hab fait le café mais est indigeste. Logpy n’est bien que pour les cas simples.

Un bon module de logging devrait:

Au fait, aviez-vous noté que le cœur d’un système de log est un dispatcheur ? :)

Lifecyle

Dès que vous créez un projet, il a un cycle de vie. Il s’initialise, charge les paramètres, load les plugins si il y en a, lance son processus principal, puis finit par s’arrêter, ou foirer, ou les deux.

Si c’est un petit script, ce n’est pas très important, on ne s’en rend même pas compte.

Si c’est un gros projet, vous allez vouloir que le code du reste du monde puisse interagir avec ça. D’ailleurs, tous les gros frameworks vous permettent de réagir au cycle de vie. Django a le fichier appconfig.py par exemple pour lancer du code au démarrage du framework, et des middlewares pour intercepter les requêtes et les réponses. Twisted permet de dire “lance ce code dès que le reactor est en route”. Pour comprendre Angular ou une app Android, la moitié du boulot c’est de piger les différentes phases du cycle de chaque composant.

Le cycle de vie est en fait un système de dispatching (surprise !) couplé à une machine à état fini, et concrétisé dans un processus métier. La bonne nouvelle, c’est que des libs de state machines en Python on en a un max, et des bien fournies. La mauvaise, c’est qu’avec la popularité grandissante d’asyncio, on a de plus en plus besoin de gérer explicitement le cycle de vie de ses projets et qu’on a rien de générique pour ça alors la cyclogénèse envahie la communauté.

En effet, dès qu’on a une boucle d’événement comme avec asyncio/twisted/tornado, on a un cycle de vie complexe mais implicite qui se met en place puisque la loop démarre, s’arrête, est supprimée, est remplacée, est en train d’exécuter une tâche, une tâche qui peut générer des erreurs… Et très vite le cycle dégouline de partout, et on commence à coder ici et là pour gérer tout ça sans se rendre compte qu’on crée petit à petit un énième framework de lifecycle. Pas vrai Gordon ?

C’est l’histoire de la viiiiiiiiiiiiiiiie. C’est le cycle éterneleuuuuuuuuuh. De la roue infinieeeeeeee. Codée à la truelleuuuuuuh.

Structure de projet

Bon, imaginons que vous ayez une lib de life cycle, qui charge vos settings avec votre super lib de conf, logge tout grâce à votre géniale lib de logging, le tout powered par une lib de dispatching que le monde vous envie. Le perfide.

Je dis “imaginez” parce que dans votre projet vous avez plutôt un tas de crottes retenues par un cornet de glace que vous avez codé pour la énième fois à la va vite en utilisant 30% de libs tierce partie, 30% d’outils de votre framework du jour et 40% de roux (du NIH sans âme quoi…).

Donc imaginez ça. Et maintenant vous voulez mettre en place un moyen de diviser votre projet en sous parties. Peut être des apps, ou pourquoi pas des plugins. Mais vous voudriez que tout ça soit gérable par un point d’entrée principal, ou individuellement. Que ça se plug dynamiquement. Que ça joue bien avec votre système de conf et de lifecyle. Diantre, vous voulez qu’un code externe puisse être découvert et pluggé au système. Choisir si ça tourne dans des threads ou des processus séparés. Mais communiquer entre les parties. Et que tout ça soit découplé bien entendu ! Sauf qu’il y a une gestion de dépendances des plugins…

Pas de problème, vous prenez un bus de communication, un système de plugin, un graph de résolution de dépendances, vos super libs ci-dessus et vous gluez tout ça avec de la logique de chez mémé et de la sueur. Une mémé si ronde qu’elle a un pneu autour de la taille. Et un essieu.

Django a ses apps. jQuery a ses plugins. L’app d’un de mes clients avec un hack à base d’importlib et ctype qui loadait une dll pour charger les drivers de leur matos. Ca roule Maurice, ça roule à mort.

Il nous faut une lib de référence qui permette:

Et dans les ténèbres les lier

Une fois qu’on a tout ça, il faut bien entendu un gros framework qui permette de faire le lien entre tout ça et coder un projet automatiquement intégré.

Imaginez… Imaginez pouvoir faire un truc comme ça:

from super_framework import projets, config
 
# Tout est configurable et réassemblable, mais le framework offre des réglages 
# auto pour les usages simples
project, app, conf = projets.SimpleProject('name')
 
# Fichier de conf automatiquement créé, parsé et vérifié. Valeurs exposées en 
# CLI et overridables.
@config.source(file="foo.tml")
class Schema(config.Schema):
    foo = config.CharField(max_len=30, live=True)
    bar = config.DateField(optional=True, local=True)
    baz = config.TextField(
        verbose_name="Basile", 
        default="Je sers la science et c'est ma joie"
    )
 
# Lancé automatiquement à la phase d'init du projet
# Des events comme ça sont lancés pour chaque app, et chaque phase de vie de 
# chacune d'elles.
@project.on('init')
async def main(context):
    # un log sain automatiquement fourni
    app.log('Début du projet. Verbosité:', conf.log.level)
 
# Démarre l'event loop. Parse la ligne de commande et les 
# variables d'env, puis le fichier de conf. Mais seulement si le module n'est 
# pas importé (comme __name__ == "__main__")
# Print le fichier de log automatique dès le démarrage du programme
project.cmd()

Et imaginez que de ce petit script, ça scale sur 20 plugins qui peuvent communiquer, un système de settings live, de gestion d’erreurs et de logs aux petits oignons.

Imaginez que l’api bas niveau soit suffisamment flexible pour que les plus grands frameworks puissent réécrire exactement la même API qu’ils ont déjà en utilisant cette fondation. Imaginez que tous vos projets futurs soient du coup compatibles entre eux.

Vous pouvez imaginez longtemps, car ça n’arrivera jamais. Mais j’avais du temps à l’aéroport alors j’ai écrit cet article.


Accepter un ID mais retourner un objet pour les liens de Django Rest Framework 13

Thu, 08 Jun 2017 07:50:00 +0000 - (source)

DRF est une des perles de Django. De Python même. Comme marshmallow, requests, jupyter, pandas, SQLAlchemy ou l’admin Django. Python a tellement d’outils extraordinaires.

Mais aucune n’est parfaite, et une chose qui m’a toujours emmerdé avec celle-ci, c’est que si j’ai un modèle du genre:

class Foo(models.Model):
    name = models.CharField(max_length=64)
    bar = models.ForeignKey(Bar)

Et le serializer:

class FooSerialize(serilizers.ModelSerializer):
 
    class Meta:
        model = Foo

J’ai le choix entre soit avoir que des ID…

En lecture (chiant) :

GET /api/foos/1/

{
    name: "toto",
    bar: 2
}

Et en écriture (pratique) :

POST /api/foos/
{
    name: "tata",
    bar: 2
}

Soit avoir que des objets.

En lecture (pratique):

GET /api/foos/1/

{
    name: "toto",
    bar: {
       // tout l'objet bar disponible en lecture
    }
}
Et en écriture (chiant) :

POST /api/foos/
{
    name: "tata",
    bar: {
       // tout l'objet bar à se taper à écrire
    }
}

Il y a aussi la version hypermedia où l’id est remplacé par une URL. Mais vous voyez le genre : mon API REST est soit pratique en lecture mais relou à écrire, soit pratique en écriture (je fournis juste une référence), mais relou en lecture, puisque je dois ensuite fetcher chaque référence.

GraphQL répond particulièrement bien à ce problème, mais bon, la techno est encore jeune, et il y a encore plein d’API REST à coder pour les années à venir.

Comment donc résoudre ce casse-tête, Oh Sam! – sauveur de la pythonitude ?

Solution 1, utiliser un serializer à la place du field

class FooSerializer(serilizers.ModelSerializer):
 
    bar = BarSerializer()
 
    class Meta:
        model = Foo

Et là j’ai bien l’objet complet qui m’est retourné. Mais je suis en lecture seule, et il faut que je fasse l’écriture à la main. Youpi.

Pas la bonne solution donc.

Solution 2, écrire deux serializers

Ben ça marche mais il faut 2 routings, ça duplique l’API, la doc, les tests. Moche. Next.

Solution 3, un petit hack

En lisant le code source de DRF (ouais j’ai conscience que tout le monde à pas la foi de faire ça), j’ai noté que ModelSerializer génère automatiquement pour les relations un PrimaryKeyRelatedField, qui lui même fait le lien via l’ID. On a des classes similaires pour la version full de l’objet et celle avec l’hyperlien.

En héritant de cette classe, on peut créer une variante qui fait ce qu’on veut:

from collections import OrderedDict
 
from rest_framework import serializers
 
 
class AsymetricRelatedField(serializers.PrimaryKeyRelatedField):
 
    # en lecture, je veux l'objet complet, pas juste l'id
    def to_representation(self, value):
        # le self.serializer_class.serializer_class est redondant
        # mais obligatoire
        return self.serializer_class.serializer_class(value).data
 
    # petite astuce perso et pas obligatoire pour permettre de taper moins 
    # de code: lui faire prendre le queryset du model du serializer 
    # automatiquement. Je suis lazy
    def get_queryset(self):
        if self.queryset:
            return self.queryset
        return self.serializer_class.serializer_class.Meta.model.objects.all()
 
    # Get choices est utilisé par l'autodoc DRF et s'attend à ce que 
    # to_representation() retourne un ID ce qui fait tout planter. On 
    # réécrit le truc pour utiliser item.pk au lieu de to_representation()
    def get_choices(self, cutoff=None):
        queryset = self.get_queryset()
        if queryset is None:
            return {}
 
        if cutoff is not None:
            queryset = queryset[:cutoff]
 
        return OrderedDict([
            (
                item.pk,
                self.display_value(item)
            )
            for item in queryset
        ])
 
    # DRF saute certaines validations quand il n'y a que l'id, et comme ce 
    # n'est pas le cas ici, tout plante. On désactive ça.
    def use_pk_only_optimization(self):
        return False
 
    # Un petit constructeur pour générer le field depuis un serializer. lazy,
    # lazy, lazy...
    @classmethod
    def from_serializer(cls, serializer, name=None, args=(), kwargs={}):
        if name is None:
            name = f"{serializer.__class__.__name__}AsymetricAutoField"
 
        return type(name, (cls,), {"serializer_class": serializer})(*args, **kwargs)

Et du coup:

class FooSerializer(serializers.ModelSerializer):
 
    bar = AsymetricRelatedField.from_serializer(BarSerializer)
 
    class Meta:
        model = Foo

Et voilà, on peut maintenant faire:

GET /api/foos/1/

{
    name: "toto",
    bar: {
       // tout l'objet bar disponible en lecture
    }
}

POST /api/foos/
{
    name: "tata",
    bar: 2
}

Elle est pas belle la vie ?

Ca serait bien cool que ce soit rajouté officiellement dans DRF tout ça. Je crois que je vais ouvrir un ticket


Dell XPS 15 : une succession d’échecs 51

Thu, 01 Jun 2017 09:07:04 +0000 - (source)

J’aimais beaucoup mon Samsung 9 series, mais quand on utilise à outrance sa machine il faut la remplacer régulièrement. J’ai opté pour un Dell XPS 15, une version 15 pouces du modèle phare de la marque, puisque le XPS 13 a tant d’excellents retours. Ça faisait des années que je traînais sur un 13 pouces, et je voulais voir si coder sur un 15 allait vraiment rajouter au confort.

Le XPS 15 a plusieurs atouts. D’abord un écran superbe, quasiment sans bord, donc plus large que les 15 pouces traditionnels. Ensuite une carte nvidia intégrée. Et surtout, un port USB C thunderbolt avec lequel on peut théoriquement charger le PC, brancher ses écrans, faire passer de l’Ethernet, bref la promesse du monocable sur mon bureau malgré mon setup encombrant de 3 écrans et la fibre.

Marchant très bien avec un dual boot W10/Ubuntu, j’en ai profité pour lui plugger 32Go de Ram et 1To de SSD. La bestiole est donc supposée être un monstre de guerre qui devrait pouvoir avaler du travail de dev dans des conditions féeriques.

Après quelques mois d’utilisation, je peux vous affirmer que le produit n’est pas du tout à la hauteur du prix, à savoir plus de 2000 boules.

Bien que l’écran soit effectivement très confortable, et que les applications de tous les jours tournent sans soucis, l’intérêt du XPS 15 s’arrête là.

D’abord, il est lourd, le chargeur est énorme, et la batterie tient en moyenne 3h, pitoyable de nos jours. Ce n’est donc pas le meilleur ami du voyageur.

Mais surtout, il ne marche pas correctement. Et c’est bien ça le problème.

Pendant un mois les baffles ont tout simplement arrêté de produire un son cohérent, vomissant tel un métalleux bourré sans son micro la moindre note. Puis c’est revenu, sans explication, à la normale.

Un autre mois des écrans bleus incessants m’empêchaient tout simplement d’utiliser Windows jusqu’à ce que je tombe sur un thread de forum de support Dell que la marque a ignoré pendant longtemps. Un des utilisateurs recommande de downgrader le firmware du BIOS pour remédier au problème. Effectivement, ça marche.

Mais la réaction de Dell ? Désolé d’avoir niqué votre machine à un SMIC et demi ? Naaaa. Un mec poste laconiquement une vidéo YouTube d’un utilisateur sur internet d’une solution qui n’a pas marché pour moi. Et c’est tout. Le logiciel d’update DELL continue de suggérer des mises à jour qui refont planter tout le système.

Le port USB-C ne tient aucune de ses promesses. Parfois la charge s’arrête en cours de route sans prévenir. L’Ethernet n’a pas marché pendant plusieurs mois pour soudainement s’activer, mais uniquement sous Windows. Le triple écran qui marchait parfaitement sous Ubuntu et Windows a cessé de fonctionner sans prévenir. Maintenant au mieux je fais du dual screen sous Windows uniquement.

Si vous lancez un jeu, le laptop décide de temps en temps de faire tourner soudainement le CPU en mode éco, tuant les FPS et vous faisant perdre. Il se trouve que la machine chauffe trop, ce que vos doigts auraient pu vous signaler de toute façon. A croire que les ingés qui l’ont conçu n’ont jamais testé l’engin.

La solution ? Beaucoup de recherches (parce que j’ai que ça à faire, pallier aux erreurs de Dell sur une machine que j’ai payé cher pour gagner du temps dans mon travail) pour atterrir sur un autre thread du support Dell. Quelqu’un suggère de désactiver dans le BIOS Intel step. Ça marche, mais bouffe plus encore la maigre autonomie de la batterie.

Ce n’est pas la qualité de produit qu’on peut attendre d’une marque comme Dell, ni d’un modèle à ce prix. Mais surtout, l’absence totale de communication de Dell sur la question, le manque de réaction de leur part laisse à penser qu’ils ont fait une grosse erreur sur ce produit et espèrent que ça passe en faisant le mort.

Sinon j’ai aussi acheté un téléphone One Plus à la même époque, qui lui est toujours formidable pour un prix avantageux. Histoire de finir sur une note positive…


Vue, j’l’avais pas vu 50

Sun, 14 May 2017 13:10:42 +0000 - (source)

(Pour Max: je t’ai mis un exemple de code tout fait en bas de page car je sais que ça va te saouler de tout lire)

Elle était facile, mais ça fait des années que le blog est up, je peux pas avoir de l’inspiration tout le temps.

Dans le dernier article, je vous offrais votre dose maintenant obligatoire de bashing de l’écosystème JS. En l’occurrence en suggérant que React était un bouquet de roses avec plus d’épines que de pétales.

Mais bon, si les gens utilisent React, ça n’est pas QUE parce que c’est la mode. C’est parce que cette techno répond à un besoin de plus en plus impérieux en dev front end : avoir un outil valable pour créer des GUI avancées en JS.

Parce que le combo jQuery + moteur de template, ça nous a mené loin, mais on arrive au stade où ça scale plus.

Pas mal de tentatives ont été faites pour répondre au problème : knockout, angular 1, polymer, etc.

La nouvelle génération (react, riot, vue, angular 2+, etc) est beaucoup plus mature et performante, et offre des fonctionnalités très sympas:

En gros, partout où on faisait avant $(selecteur).trucmuche(element), on peut maintenant faire ça plus facilement, plus rapidement et plus proprement. (Par contre Vue/React/etc font pas l’AJAX, tournez-vous vers axios ou gardez jQuery sous le coude).

Bon, si ils offrent TOUS ces éléments là, pourquoi diable est-ce que j’ai une préférence pour Vue.JS plutôt que Riot, Angular ou React ?

Souvenez-vous, je reprochais essentiellement ceci à React:

Mais alors, qu’est-ce que Vue.js fait de mieux ?

Ben tout, mes amis. Tout.

Légèreté

A l’heure où les codeurs hypes de la vibe du flex ont tous un fichier de conf webpack de 3 km, mais déjà obsolète et qui pétera à la prochaine upgade, Vue offre un point de vue rafraîchissant sur la question.

TOUTE, j’ai bien dit TOUTE, la puissance de vue.js est accessible avec une simple inclusion de balise script.

Vous en avez marre des pages de 3Mo ? Minifié et gzippé, Vue.js pèse moins de 30ko.

Dans un monde où il faut soit faire un site Web, soit faire une SPA (aux chiottes l’amélioration progressive !), Vue permet de ne remplacer que quelques bouts de vos pages si vous n’avez pas envie de tout transformer en un monstre de JS.

La doc, très bien faite, vous permettra de prendre l’outil en main en une après-midi. En buvant du thé.

Le tout, sans besoin d’aucun écosystème particulier. Aucun. Rien. Ca marche out of the box.

En gros, là où tout le monde vous vend du scale up, Vue vous permet de scale down.

Puissance

Malgré cela, Vue reste parfaitement adaptée aux gros projets. En fait, elle a été créée pour et par l’équipe du site du géant chinois, alibaba.com. Alors oui, c’est sûr, c’est pas Facebook et son milliard d’utilisateurs, mais comme mes projets ne servent pas 10 millions de pages par jour, contrairement à Alibaba, je pense que je suis ok.

Dans la pratique, qu’est-ce que ça veut dire ?

Et bien d’abord, Vue est plus rapide que React. Ouais ça fait mal au cul de lire ça quand on vient de finir d’optimiser son pipeline de rendu JSX, surtout que la techno a été créée pour la vitesse. Mais voilà, c’est le cas.

Vue supporte aussi parfaitement l’inclusion dans un écosystème plus gros si le besoin s’en fait sentir :

En gros, Vue peut faire absolument tout ce que les autres font. Mais ne vous oblige pas à commencer en sortant le bazooka. Vue est ce que React aurait dû être depuis le début.

Notez qu’on peut remercier les devs de React pour avoir popularisé de nombreux concepts que Vue utilise. Standing on the giants’ shoulders et tout, et tout. Vue n’aurait jamais existé sans React. Mais maintenant que Vue est là, on peut laisser React en paix.

Elégance

De par son design, Vue vous permet donc de commencer doucement, en ajoutant 30ko à votre projet et en l’utilisant un peu ici et là. Puis quand vous en avez besoin, vous pouvez commencer à utiliser des fonctionnalités avancées.

Par exemple, vous pouvez comme avec Angular 1 balancer toutes vos variables et binding dans le HTML de votre page en vrac. Et plus tard tout refactoriser en composant comme avec React. Tranquille.

L’API de Vue est petite, et surtout, facile à comprendre car tout est bien nommé, et bien rangé. Voyez plutôt:

Vue({
  el: "#app", // l'élément sur lequel attacher la vue
  created: function(){
    // code lancé au démarrage
  },
  mounted: function(){
    // code lancé quand la vue est attachée à la page
  },
  updated: function(){
    // code lancé quand la vue est mise à jour
  },
  destroyed: function(){
    // code lancé quand la vue est détruite
  }
  data: {
    // ben ici y a vous données statiques
  },
  methods: {
    // ici les fonctions à rendre disponibles dans le HTML
  },
  computed: {
    // ici on met les données à recalculer à la volée
    // à chaque refresh
  }
})

Voilà vous avez là 50% de l’API de Vue. Very hard indeed.

On sent partout que les devs ont apporté des soins à des petits détails comme:

<!-- Lance truc() juste au premier click.
Appelle preventDefault automatiquement. -->
<a v-on:click.once.prevent="truc()">Foo</a>

D’une manière générale, tout template Vue est du HTML valide, ce qui fait qu’il est très facile d’insérer du Vue sur un site existant.

Un petit bout de vue

Mais alors, pour tous ceux qui en sont restés à jQuery et handlebar.js, à quoi ça ressemble tout ça ?

Ben un todo ressemble à ça en Vue:

<!DOCTYPE html>
<html>
<head>
  <title>Todo</title>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
<!-- Cette partie va être contrôlée par la vue -->
<div id="app">
 
<!-- Quand on soumet le formulaire, on ajoute la tâche -->
<form v-on:submit.prevent="addTask(newTask)">
  <!-- L'input est maintenant lié à la variable newTask -->
  <p><input v-model="newTask"> <button>Ajouter</button></p>
</form>
 
<ul>
  <!-- Créer un li pour chaque tache dans mon array 'tasks' -->
  <li v-for="task in tasks">{{task}}</li>
</ul>
 
</div>
 
<script type="text/javascript">
app = new Vue({
  el: "#app", // j'attache cette vue à l'élément avec l'id App
  data: {
    tasks: [], // mes tâches seront stockées dans l'array 'tasks'
    newTask: "", // ce que contient l'input du formulaire
  },
  methods: {
    addTask: function(task){
      this.tasks.push(task); // on ajoute la tâche dans l'array
      this.newTask = ""; // on vide l'input
    }
  }
})
</script>
 
</body>
</html>

(Vous pouvez littéralement copier-coller ce code dans un fichier todo.html et ça marche. Essayez de faire ça avec React…)

Comme avec Angular/React/Riot on a séparé complètement la logique de manipulation des données de celle de l’affichage qui est mis à jour automatiquement quand les données changent.

Contrairement à React, on n’est pas obligé de créer un arbre complexe de composants qui transforme toute sa page en soupe de JS(X). Mais contrairement à Angular, on n’est pas obligé de rester sur cet exemple simple, et on peut créer une hiérarchie de composants.

Un composant peut complètement isoler le JS, le HTML et le CSS (qui n’affecte alors que le composant):

Et pour que ça marche, pas besoin de préprocesseur, pas besoin de ES6, ES7, typescript, babel, webpack, npm… Vous pouvez les utiliser. Sur un gros projet je me fais chier à setup ce qui vaut le coup. Mais vous n’êtes pas obligé.

La communauté

La communauté est importante pour un projet, et celle de vue est très agréable. Ce sont des gens polis, humbles, et compétents. Ils pensent aux petits détails. Regardez par exemple cette page pleine de jolis schémas.

En comparaison, la communauté React est criarde. Les confs des devs de Facebook sont faites par des présentateurs tout droit sortis de South Park. Quand on critique react, on se fait insulter personnellement sur twitter par les fan boys plutôt que de tenter de discuter, preuve systématique de l’absence d’arguments.

Enfin les seuls éléments donnés pour combattre les critiques sont les raisons des problèmes, un rappel de la taille des utilisateurs et des “moi j’aime” laconiques.

Mais faites un test. Prenez 3 dev, un react, un jquery, un vue. Faites leur faire un agenda CRUD avec recherche et autocompletion qui tape en Ajax dans un backend Django/Rails/Whatever. Donnez leurs des machines vierges. Et sortez le pop corn.

Pour le moment, React possède encore deux avantages: react native, et la masse (fb, les utilisateurs, le nombre de plugins, etc).

Mais honnêtement ? Il y a très peu de bénéfices à utiliser React plutôt que Vue sur la plupart des projets pur Web. Et un coût garanti d’être élevé.

Angular 1 est en train de mourir de l’abandon de Google. Après la sucette de l’incompatibilité d’Angular 2, je n’ai aucune confiance en la techno et ne compte donc pas réinvestir de billes dedans. De plus Angular 2+ souffre du même problème d’obligation d’installer la terre entière avant de commencer à coder.

Riot est une meilleure alternative, mais l’obligation de passer par des composants est un no-go IMO. Créer des composants pour moi est l’exception, pas la règle. Je le fais en dernier, à la phase de refactoring.

Donc ne vous emmerdez pas, prenez quelques heures pour jouer avec Vue, ça coûte pas cher, et c’est chouette.

————-

On me signale dans l’oreillette de linker dans la doc française.


Réaction à ReactJS 33

Wed, 10 May 2017 18:39:23 +0000 - (source)

Grand utilisateur de jQuery, formateur Angular et maintenant adorateur de VueJS, vous vous demandez sûrement ce que je pense de React. Si, je le sais, mon avis est pour vous comme un phare dans la nuit sombre du frontend engluée dans le brouillard de JavaScript.

Après tout, Facebook est codé avec cette techno pixel par pixel, et ils servent des milliards de pages chaque jour. Ça ne peut pas être mauvais. Ce ne sont pas de cons quand même.

Et puis tout le monde en parle, les asticots gigotent autour des restes du gigot d’ordre que les devs des GAFAS tentent de mettre au menu dans leur régime sans sel et sans typage.

Alors merde, quoi, react, keskeçavo ?

Je vais passer peut être pour un réact (hashtag lol), mais franchement le gigot au brouillard, c’est pas mon plat préféré.

Taillons un peu JS, ça m’avait manqué.

Mon royaume pour un hello world

React est tellement chiant à setuper que la doc officielle vous propose le hello world dans un code pen histoire de pas vous faire fuir tout de suite.

En fait, si pour vous installer un module avec npm/yarn/bower (plutôt que de copier le truc directement ou d’utiliser un CDN) est déjà un truc qui vous fait grogner, vous êtes loin derrière.

Il va vous falloir un outil de build (gulp, grunt ou l’usine à Vespene Webpack) et la série de recettes obsolètes customisées trouvées sur un coin de github pour connecter tous les bouts de votre pipeline. React bien sûr, mais aussi Babel pour transformer le JSX en JS.

Babel suit la left-pad philosophie, à savoir que tout est configurable et éclaté en centaines de petites dépendances et settings. C’est tellement chiant que des packages existent, appelés “presets”, dont l’unique but est de configurer Babel pour une tâche particulière.

Une fois que vous avez tout ça up, vous vous allez à la pêche au tuto, seulement pour remarquer que tout le monde code en ES6, voir ES7, et vous rajoutez donc un peu plus de plugins à tout ce bordel. Allah vous garde si vous tentez d’utiliser TypeScript.

Et vient alors le moment tant attendu pour faire coucou de la main programmatiquement. Ça foire, bien entendu. Le debugger vous pointe sur un bundle.js de plusieurs Mo, et vous apprenez que le support des sources maps est inégal d’un navigateur à l’autre.

Bienvenue.

Le X c’est pas toujours excitant

Une fois passée la douloureuse expérience de mettre en place votre environnement de travail, vous trouvez un certains confort. L’autoreload de la page est franchement pratique, et pouvoir utiliser l’unpacking (pardon le spread), les valeurs de paramètres par défaut et les arrow functions sans vous soucier de la compat du navigateur c’est quand même cool. Avec un webpack tout bien huilé avec amour (et douleur), vous pouvez faire des imports en JS et on a presque l’impression d’utiliser un langage de programmation.

Et puis le JSX, de loin ça à l’air pas mal ! Du HTML directement dans le JS ça parait un peu bizarre mais on vous le vend comme un avantage : finis les langages de templates limités, vous pouvez utiliser la pleine puissance (hum…) du langage JavaScript pour créer vos balises.

Sauf que non, le JSX, ça pue du cul.

D’abord, il y a le fait que toutes les structures sont à faire en JS. Les conditions, les concaténations et bien entendu, les boucles. Donc vous avez une liste de noms et numéros de téléphone, en VueJS, vous faites:

<ul>
    <li v-for="pers in persons" v-if="pers">
        <strong>{{ pers.nom }} </strong>: {{ pers.tel }} 
    </li>
    <li v-else>
        <em>Nobody's here!</em>
    </li>
</ul>

Mais en React, vous allez faire péter une expression ternaire et une fonction anonyme en plein milieu rien que pour l’occasion:

<ul>
    {
      (this.state.persons)
        ? this.state.persons.map((pers) => (
            <li><strong>{ pers.nom } </strong>: { pers.tel } </li>
        ))
        : (
            <li>
                <em>Nobody's here!</em>
            </li>
        )
    }
</ul>

Vous la sentez bien la puissance de JavaScript là ?

Et attention à ces boucles, car dedans vous guette cette erreur qui va vous poursuivre jusque dans vos cauchemars:

SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag

Babel vous signale gentiment que ne pouvez pas produire un snippet de JSX qui contienne plus d’un élément à la racine. Donc il faut TOUT wrapper dans un container. TOUT. Vous allez avoir vite des DIVs et des SPANs inutiles partout, juste pour faire plaisir à React. C’est absurde. C’est à vomir. Ca nique votre CSS et remplit vos sessions “examinez un élément” de tendinites dues aux clics pour déplier tout l’arbre des emballages cadeaux de vos balises. Et aussi, nique la sémantique.

Pour éviter ça vous allez tenter d’inliner un maximum de trucs dans une seule expression JSX ou tout foutre dans des arrays. Car je ne l’ai pas précisé ? Les arrays de JSX sont automatiquement et magiquement convertis en sous-éléments. Et du coup vous pourrez profiter au maximum des qualités de lisibilité de JS.

JSX est bourré de petits trucs comme ça, pour faire plaisir. Par exemple, j’ai un bouton, quand je le clique il delete la personne de la ligne de mon agenda. Je mets aussi une classe pour tous et une selon que la personne est importante ou non. Un prevent default pour éviter que le browser recharge la page si je suis dans un form.

En Vue, je passe un objet qui dit quelle classe afficher ou non. Le click handler contient une instruction prevent qui me permet d’appeler preventDefault automatiquement. Et j’appelle deletePerson tout naturellement :

<button class="{'delete-person': true, 'important-person': pers.isImportant}" 
        v-on:click.prevent="deletePerson(pers)">
    Delete
</button>

En react, on ne peut pas passer de paramètre à son handler. Il faut le passer dans une closure. Mais alors la closure va recevoir l’objet event, qu’on doit donc faire suivre à notre handler, afin qu’il puisse appeler preventDefault à la main. A noter aussi que class s’appelle className. Et n’accepte que des strings donc la concaténation se fait à la mano.

// plus haut on appelle preventDefault dans handleDelete
 
<button className={'delete-person ' + (pers.isImportant ? 'important-person': '')}
        onClick={(e) => this.deletePerson(pers, e)}>
    Delete
</button>

Ce n’est pas juste chiant à lire, c’est surtout hyper chiant à trouver, à taper et à debugger.

Un peu de nutella sur votre confiture ?

React, c’est verbeux, il va falloir vous y faire. Il suffit de comparer le code nécessaire pour faire une TODO en react avec les autres frameworks.

Mais ce n’est pas juste ça qui est lourd.

Non, le vrai truc qui est super pesant, c’est que react est un cancer. Quand il est utilisé, il contamine tout.

Par principe, une fois que vous utilisez react, il faut mettre TOUTE VOTRE PAGE en react. Pas juste un petit bout. Par là j’entends que 90% de votre HTML va devoir migrer dans le code JSX. Votre beau code HTML bien propre, converti en cette monstruosité de mélange entre le langage le plus dégueulasse du monde et un monstre mimic qui essaye de se faire passer pour du HTML pour vous bouffer.

Or, toute l’idée c’est de faire des composants, de diviser votre pages en plus petits bouts. Mais voilà, React n’a rien prévu de simple pour la communication. Pour passer des données des composants enfants, il faut les passer via les attributs de balise HTML (appelés les props, pour faciliter la rechercher Google). Si vous avez 5 niveaux d’imbrications, vous vous tapez ça 5 fois.

Plus amusant, il n’y a aucun mécanisme pour passer des infos de l’enfant aux parents. La méthode standard est d’écrire puis passer manuellement un callback du parent à l’enfant, via props, et appeler ce callback dans l’enfant avec la valeur que le parent utilise ensuite. POUR. CHAQUE. PUTAIN. DE. VALEUR.

Passer des callbacks, ça va 5 minutes. Et ça ne résout pas un problème de communication entre composants parallèles, c’est à dire ni parent, ni enfant. Du coup tout le monde finit par utiliser une lib supplémentaire type EventEmitter pour servir de bus de communication.

Quand vous entendez les gens se plaindre de la difficulté d’utiliser l’écosystème de react, les fans répondent souvent que l’écosystème n’est pas obligatoire. On peut très bien s’en passer sur un petit projet.

Sauf que react est absolument inutilisable sans. Sans un bus d’event, un transpiler ES6 + JSX, un builder et un hot reloader au mininum, le projet ne dépasse jamais le stade du prototype.

Mais bon soyons franc, beaucoup de projets react tout court ne dépassent jamais le stade du prototype. On parle de codeurs JS là.

Etat

Les props ne doivent jamais changer. Seul l’état d’un composant react peut changer, ce qui trigger un nouveau rendu du composant.

Mais l’état lui-même est en read-only. On est supposé uniquement créer un nouvel état à chaque fois et remplacer l’ancien.

Au début on le fait à la main, mais JS n’est pas vraiment fait pour faire de l’immutabilité, et ça devient uber chiant très vite. Changer la propriété d’un objet qui est lui même dans un array demande de recréer tout l’array et l’objet. A chaque fois.

On se tourne alors vers des libs (encore une) type immutable.js qui fournit des listes et maps qui sont immutables et permettent de faire ces opérations sans y laisser ses jours de congé.

Une fois de plus, les connards qui vous disent qu’on peut faire du react sans la tonne de boiler plate qu’on voit dans les tutos ne le font jamais eux-mêmes. Parce que oui, on peut faire Paris-Amiens à pied en théorie, mais bon…

Des décisions à la con

Clairement, react a été fait pour créer des UI. Mais au bout d’un moment, les utilisateurs se sont réveillés et ont voulu une encapsulation pour des composants non UI. Mais react ne sait pas faire. Quand vous avez donc un composant non UI, par exemple la déclaration de votre routing, vous le déclarez… en JSX:

  render((
      <Router history={browserHistory}>
        <Route path="/foo" component={FooView}/>
        <Route path="/bar" component={BarViw}/>
      </Router>
  ), document.getElementById('app'))

Votre app va devenir très vite une pyramide de composants, certains qui s’affichent, d’autres non, tous se passant en cascade des données les uns aux autres.

Et au passage, quel est l’abruti qui a décidé des noms des méthodes des cycles de vie comme getInitialState, componentWillMount et componentDidMount ?

Surtout que vos méthodes et les méthodes héritées de la classe du component sont dans le même espace de nom.

Le bonheur des stores

Un truc dont on peut vraiment se passer par contre, ce sont les stores. Flux, redux, vuex, etc. On peut utiliser n’importe quelle solution pour stocker l’état de son projet côté client.

Mais si vous voulez profiter des promesses de react, comme le time travel et la concurrence parfaite, il vous faudra un store.

“store” c’est le nom huppé que react donne à ses modèles. Je ne vais pas rentrer dans les détails, et vous laissez décider par vous même du truc. Voici, comment la doc vous recommande d’ajouter une personne dans une liste d’un agenda avec redux, la store la plus populaire:

// Les imports
import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
 
// creation du store
const initialState = {'agenda': []};
 
function reducer(state=initialState, action){
  switch (action.type) {
    case 'ADD_PERSON':
      return {
        'agenda': [..., action.object]
      }
    default:
      return state
  }
}
 
const store = createStore(reducer);
 
 
// creation de l'action d'ajouter un objet dans le store
 
function addPersonAction(obj){
    return {
        'type': 'ADD_Person',
        'object': obj
    };
}
 
 
export function addPerson(Person){
    store.dispatch(addPersonAction(Person));
}
 
 
// composant qui affiche le form de l'agenda et la liste des personnes
var Agenda = React.createClass({
 
  handleAdd: function(){
      addPerson({
        "name": this.refs.name.value,
        "tel": this.refs.tel.value
      })
  },
 
  render: function(){
 
    return <form onClick={this.handleAdd}>
        <p><input ref="name" /><input ref="tel" /><button>Add</button></p>
    </form>
 
    <ul>
        {
            this.store.agenda.map((pers) => (
                <li><strong>{ pers.nom } </strong>: { pers.tel } </li>
            ))
        }
    </ul>
  }
 
 
});
 
 
// adapter qui permet de passer le store à l'agenda
const AgendaContainer = connect(function(state){
    return {'agenda': state.get("agenda")};
})(Agenda);
 
 
// affichage du bouzin
render((
<Provider store={store}>
  <AgendaContainer>
</Provider>
), document.getElementById('app'))

Vous imaginez bien que trouver ça tout seul a été un bonheur. Parce que oui, la doc est absolument à chier, dans la longue tradition des stacks JS modernes.


Powered by VroumVroumBlog 0.1.32 - RSS Feed
Download config articles