Blog // Exirel.me

Risque et qualité

Par Florian Strzelecki - 01:33 - 25.08.2016

Tags : Programmation, Bonne pratique, Qualité

Récemment, mon père me disait sa satisfaction à utiliser son nouveau smartphone, un Nexus 5 que j'ai utilisé 2 ans et qu'il possède désormais. Avant, il utilisait un téléphone bas de gamme, qui remplissait parfaitement ses fonctions et qui n'avait rien à se reprocher.

Le Nexus 5 est un appareil de bonne facture et très agréable à utiliser : c'est un produit de qualité, et cela, mon père, pour qui la puissance du processeur et autres chiffres ne sont que du charabia, l'a très bien ressenti dans son usage quotidien.

Qualité

J'ai mis le doigt récemment sur un comportement que j'adopte lorsque je suis face à un problème de qualité sur un projet : je cherche par tous les moyens à minimiser les risques encourus par le projet pour surmonter ledit problème de qualité.

Jusqu'à présent, je n'avais pas réalisé pourquoi j'adoptais parfois une attitude si défensive dans un projet informatique, alors que sur d'autres je peux prendre beaucoup plus de risques, faisant même parfois preuve d'un optimisme qui frôle la témérité !

Essayons de mettre certaines choses au clair dans mon esprit, en mettant bout à bout ce à quoi je pense.

Rapport proportionnel

Mon intime conviction est que le risque d'un projet est inversement proportionnel à la qualité du projet, qu'elle soit interne ou externe à ce dernier.

Par exemple, lorsque je travaille sur un bout de code :

Je sais que je dois redoubler de vigilance. Pas seulement parce que c'est "compliqué", mais aussi parce que je risque simplement d'ajouter plus de bugs que je n'en corrige. Parce que la qualité intrinsèque (ou plutôt son absence) d'un bout de code augmente drastiquement le risque de rencontrer un problème en travaillant dessus.

Si je veux m'en sortir, je dois alors faire en sorte de réduire les risques : je peux augmenter la qualité (en documentant au fur et à mesure, ou en écrivant des tests), et je peux augmenter le niveau de défense autour du code - en limitant les dépendances, en limitant les modifications, en relisant plusieurs fois mon propre code, et de manière générale en ajoutant plusieurs contrôles autour du code.

Dans tous les cas, j'adopte une posture défensive, où j'avance pas à pas, et où je réduis au maximum la prise de risque. Cela demande souvent plus de temps et génère bien plus de fatigue mentale qu'une tâche complexe pourrait provoquer à elle seule.

Du code au projet

La qualité d'un bout de code est du domaine de la micro-gestion, et si j'adopte une posture défensive à ce niveau là, je reproduis le même schéma au niveau macro-gestion : si la quantité de bugs est importante, si les fournisseurs ne sont pas fiables, si les documents de travail ou les réunions ne sont pas claires ou satisfaisantes, alors je vais chercher à réduire les risques au niveau du projet lui-même.

C'est dans ce genre de cas où je vais me mettre à poser plus de questions, à écrire plus de documents, et à prendre plus de temps pour analyser les causes internes et externes aux problèmes rencontrés. Je ne vais pas seulement chercher à comprendre pourquoi je travaille, je vais aussi chercher à comprendre comment le projet en est arrivé là. Je suis plutôt convaincu que l'histoire d'un projet permet de mieux comprendre pourquoi il faut faire certains choix aujourd'hui.

Quant à réduire les risques, je vais proposer moins de fonctionnalités, ou définir plus de limitations. De manière générale, je vais augmenter le temps passé et, surtout, je vais faire en sorte de diminuer les attentes des clients : s'ils s'attendent à moins, ils seront moins déçus si le projet se passe mal.

Du projet aux humains

Je suis rarement d'accord avec mes collègues à partir du moment où je me mets dans une posture défensive pour réduire les risques. Dans les cas les plus extrêmes j'ai à peu près tout entendu : je serais paranoïaque, je ferais dans la sur-qualité, mon code serait trop défensif et serait une pure perte de temps et de moyens, et le pire de tous, je n'aurais aucune vision business et mon avis serait donc à ignorer complètement.

Autant vous dire que j'ai surtout eu des problèmes en ESN, où la qualité n'est clairement pas un objectif de l'entreprise - le client quant à lui se faisant surtout tondre comme un mouton en payant des rallonges de jours-homme sur son projet.

Le plus souvent, je suis simplement en désaccord avec la façon de procéder, sur le rythme à suivre, sur la démarche initiale et sur la vision à moyen terme. Bref, je vise à limiter les risques, tout en partageant le même objectif final - simplement, pas de la même façon.

Réciproque

Lorsque la qualité est absente, le risque augmente. Lorsque le risque augmente, il arrive toujours un problème grave auquel il faut répondre rapidement. Je n'aime pas ça, parce qu'en général les petites mains qui doivent réparer en catastrophe, ce sont les miennes.

Mais il n'y a pas que ça. Si augmenter la qualité permet de faire diminuer les risques, je pense que la réciproque est vraie aussi : en diminuant les risques, la qualité augmente - ne serait-ce qu'un peu. Parce qu'en diminuant les risques, on diminue le nombre des catastrophes. On diminue le nombre d'interventions faites dans l'urgence. On diminue les problèmes que peuvent subir les clients.

Parce que la qualité (ou son absence), et bien au bout du compte, ce sont surtout les clients qui en paient le prix.

A New High Score

Par Florian Strzelecki - 23:52 - 10.08.2016

Tags : Jeux vidéo, Steam, Récompense, Culpabilité

Il y a certains soirs où je n'ai plus envie de rien. Se lever de sa chaise, préparer un repas ou aller au restaurant, l'idée même de devoir choisir entre ces deux options aussi triviales soient-elles me fatigue au plus haut point. C'est le genre de soirée perdue à décider quoi faire et quand le faire, avec le sentiment confus d'une certaine culpabilité, mêlée d'ennuis et de frustrations.

Je décide d'ouvrir ma bibliothèque de jeux Steam. Je devrais peut-être les trier, non ? Les ranger dans des petites catégories, les uns après les autres. Ceux là dans les "Action RPG", ceux ci dans "Arcade", et celui là dans "Tower Defense". Je pourrais sans doute jouer à l'un d'eux, histoire de dire "un de moins" sur la longue liste des jeux auxquels je n'ai jamais joué, la longue liste des jeux que je ne fais que "posséder" (si tant est que cela puisse encore vouloir dire quelque chose à l'ère du produit dématérialisé).

Alors je parcours les 402 jeux que j'ai à ce jour. Celui là non. Celui ci bof. En voilà un que j'ai déjà terminé. Ce jeu là, j'ai essayé, mais quelque chose m'a déplu au point de le désinstaller presque immédiatement. Je pourrais sans doute installer les 20 Go de ce JRPG, mais ai-je vraiment envie de me lancer dans cette aventure ? De devoir apprendre de nouvelles mécaniques, de nouveaux systèmes, de remettre en cause mes compétences en tant que joueur et de devoir m'investir à nouveau des dizaines d'heures dans un nouveau monde...

C'est le genre de soirée où je me donne bonne conscience en me disant que j'attends simplement le bon jeu, celui qui sort bientôt et qui est plein de promesses.

Ou alors...

Bejeweled 3

Oui, je suis sérieux. Non, ce n'est pas une blague. Tout de même... c'est... Et puis pourquoi ai-je ce jeu ? Comment cela se fait-il qu'au milieu du reste...

Oh et puis tant pis ! Je n'ai que ça à faire, et j'ai déjà cliqué sur "installer". Avec la fibre, je n'ai plus le temps d'y penser qu'il est déjà là, prêt à jouer, prêt à se lancer.

...

Je ne sais pas si je dois prendre le jeu au sérieux ou si c'est une blague très élaborée. L'écran d'accueil est là, rutilant de sphères de diamant, chacune m'invitant à jouer à l'une des variantes possibles. Oh bon sang il y a un mode quête ! Dans Bejeweled ! Je le lance, pour voir. Chaque niveau est un mini-jeu, qui permet de débloquer une perle, qui permet de débloquer un "artefact" de Bejeweled. La première partie se lance.

C'est coloré, tout scintille, et surtout, c'est fluide.

D'autant plus qu'il y a un paramètre graphique "ultra". Rien que ça.

Unbelievable

La première chose que je remarque, après la fluidité des mouvements, ce sont les bruits : chacun donne un feedback sur ce qui se passe, que le mouvement soit accepté ou refusé.

Mais il y a mieux : l'annonceur.

Good

Euh... merci ? Je veux dire, je n'ai fait qu'intervertir deux gemmes, ce n'était rien, vraiment. La suite, ce n'est pas moi, c'est seulement le hasard.

Excellent!

Ah bon d'accord c'était si bien que ça alors ?

Level Complete

... Déjà ?

Bien entendu, c'est le premier niveau, c'est donc vraiment très facile. Il n'en reste pas moins que chaque mouvement un peu "spécial" permet de recevoir les félicitations appuyées de l'annonceur, d'une voix grave et profonde, comme si chaque petite action était l'accomplissement d'une tâche héroïque.

Déplacer une gemme devient l'équivalent d'un déplacement de montagne.

Trop

Ma soirée n'avait pas grand intérêt, et Bejeweled n'est qu'un passe temps. Et le jeu doit certainement le savoir, car il en fait des tonnes. Il surjoue absolument tout, de l'écran d'accueil à l'annonceur, en passant par le bruits des combos, le scintillement des gemmes, et les effets visuels des gemmes spéciales.

Je suis à la fois fasciné et terrifié à l'idée que des gens se sont sérieusement posés dans une pièce pour se dire :

Et là, lorsque la gemme explose, il faut des arcs électriques qui partent d'elle et rejoignent jusqu'à 3 autres gemmes dans un effet de foudre, et le bruit doit être conséquent mais pas trop bruyant et surtout ne pas crépiter. Ah et au fait pour les mini-jeu, il en faudrait un avec des colonnes de glaces qui remontent petit à petit pour bloquer la progression.

Je comprends très bien que chaque détail est important. C'est même tout à fait normal en réalité : le plaisir de ce jeu repose en grande partie sur le puissant sentiment d'accomplissement dans la destruction d'un plateau de gemmes scintillantes. Et plus le nombre de gemmes explosées est grand, plus l'effet visuel et sonore doit être retentissant.

Jouer à Bejeweled, c'est faire l'expérience du vide qui en fait trop. C'est l'expérience du plaisir immédiat et de la récompense de héros pour des gestes banals. C'est la couche de reconnaissance du moindre des petits accomplissements du joueur. C'est le menu big-mac du petit jeu sur smartphone porté sur grand écran.

Comme si la culpabilité de lancer un passe temps alors qu'il existe tellement de bons jeux là dehors, prêt à être jouer pendant des heures, devait être lavée par un maelstrom de félicitations pour avoir bouger le curseur de sa souris.

...

Je ferais bien d'aller me coucher.

La lettre de Vannes

Par Florian Strzelecki - 00:00 - 04.08.2016

Tags : Ma vie, 4 Août, Bretagne

Il se trouve que nous sommes le 4 Août, et c'est au moins le 3ème article que je publie un 4 Août. À croire qu'il y a une raison derrière tout ça.

Quoi qu'il en soit, j'habite en Bretagne - à Rennes - depuis maintenant 6 ans. Et l'Histoire de la Bretagne est une très longue histoire, avec des traités, des batailles, et tout un tas de gens importants.

Je regardais, tout à fait au hasard, l'article Wikipédia concernant l'Union de la Bretagne à la France, lorsque j'ai découvert que :

Le 4 août 1532, les États de Bretagne, convoqués par François Ier à Vannes, adressent au monarque une supplique pour « unir et joindre par union perpétuelle iceluy pays et duché de Bretagne au royaume, le suppliant de garder et entretenir les droits, libertés et privilèges dudit pays et duché »

C'est plutôt rare de trouver une occurrence du mot "iceluy" (ou plutôt icelui pour le CNRTL). Il faut bien que la langue évolue, alors des mots disparaissent, et d'autres se forment.

Tant mieux, tant pis.

Je n'écris plus

Par Florian Strzelecki - 13:07 - 04.05.2016

Tags : Ma vie, Écrire

J'aime écrire, et j'écris depuis que j'ai appris à le faire. Cependant, je n'écris plus du tout comme il y a 10 ans, et surtout, je n'écris presque plus "pour moi".

Lorsque je regarde un peu sur ces deux dernières années, j'ai beaucoup écrit de documentation, très souvent technique et parfois fonctionnelle. Mais des textes personnels ? Presque jamais. Que ce soit ici ou ailleurs, je n'écris plus vraiment "pour moi", ou "de moi".

J'aime toujours écrire, même si c'est "juste" de la documentation (j'ai fait une conférence là dessus au BreizhCamp en Mars dernier), et je suis un peu triste de ce constat.

J'aimerais bien changer cela, et surmonter mes difficultés : je butte sur chaque mot, j'hésite sur chaque phrase, je réorganise trois fois chaque paragraphe. Cela me rend parfois l'exercice déplaisant, et j'ai toujours l'impression de ne pas arriver à exprimer pleinement ce que je pense.

Ce qui m'effraie un peu, disons le tout net.

J'aimerais écrire à nouveau sur Blog2Rolistes, et continuer le récit d'une partie de Fallout 4. J'aimerais parler de Tsutomu Nihei dont j'adore les mangas, ou des musiques que j'écoute en ce moment, et qui m'inspirent. Mais je n'y arrive pas.

J'imagine que je devrais me trouver une routine, me forcer à l'exercice de l'écriture personnelle. Bref, faire quelque chose, n'importe quoi, pour reprendre goût, pour retrouver la motivation.

Sans doute.

Le vrai prix de Fallout 4 ?

Par Florian Strzelecki - 23:42 - 28.11.2015

Tags : Jeux vidéo, Ma vie, Fallout, Steam, FNAC

Un ciel bleu, un chien pour compagnon, et des centaines de capacités spéciales : Fallout 4 a de quoi séduire le joueur que je suis. Mieux encore, il semble que cet épisode corrige quelques erreurs et efface certains inconvénients du précédent opus - que je n'avais pas tout aimé.

Je laisse à la critique spécialisée le soin de peser le pour et le contre de Fallout 4, car pour ma part j'attends d'avoir quelques heures de plus avant de me prononcer (comme je l'avais fait pour Skyrim).

Ce qui m'intrigue, c'est la politique tarifaire de Fallout 4.

Sur console, pas de surprise, le jeu est à 70€. C'est cher, c'est le prix des jeux neufs sur PS4 et xBox One. Comme je suis surtout un joueur PC, ce prix très élevé ne me concerne pas.

Mais sur PC, c'est une autre histoire : le jeu nécessite Steam pour se lancer, et il peut y être acheté directement pour 60€ - plutôt classique pour un jeu neuf. C'est le même prix qu'en magasin, que ce soit à la FNAC ou dans une autre enseigne du même genre - en version boîte évidement.

Donc, si je résume bien, les consoles se font avoir, et la version numérique est au même prix que la version boîte en magasin.

C'était sans compter sur Amazon.fr et... FNAC.com : en version boîte, en ligne, c'est 40€ au lieu de 60€. Je n'arrive pas à expliquer cette différence, qui me semble complètement absurde : Amazon comme la FNAC peuvent offrir la livraison, et au final je me retrouve avec une clé CD à utiliser avec Steam, comme si je l'avais acheter en version numérique.

Mention spéciale à FNAC.com qui doit prendre un exemplaire de Fallout 4 depuis ses entrepôts en région parisienne pour me livrer le matin à la FNAC de Rennes, pour un produit que je trouve plus cher dans les rayons...

... ce monde ne tourne pas rond.

Assert

Par Florian Strzelecki - 00:02 - 13.10.2015

Tags : Python, Bonne pratique, Unit Testing, ProTips, assert

Le mot clé assert, tout le monde connaît à peu près :

def test_your_function()
    assert my_function() == 'good result', (
        'We expect `good result` here.')

Il permet, dans un test unitaire, de vérifier que tout se passe comme prévu. Il est à noter qu'un assert qui se passe bien est un assert qui ne "fait" rien. Il ne fait quelque chose (lever une exception) que lorsque quelque chose ne va pas.

Généralement, il est utilisé dans les tests unitaires, avec pour but de vérifier un aspect particulier du code - d'où souvent le côté "unitaire" de ces tests.

AssertionError

Il est tout à fait possible de recréer l'effet d'un assert avec le bon raise :

>>> assert False, 'We expect True here, not False.'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: We expect True here, not False.

Ceci revient à utiliser raise :

>>> raise AssertionError('We expect True here, not False.')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: We expect True here, not False.

C'est d'ailleurs grâce à cela que le framework unittest peut proposer des méthodes spécifiques pour écrire des tests unitaires. Il se trouve qu'aujourd'hui, d'autres framework de tests en Python reviennent à assert, et préfèrent faire une introspection du code pour proposer une meilleure lecture du résultat du test.

Cela veut dire aussi qu'il est tout à fait possible d'attraper l'exception levée par un assert :

>>> try:
...     assert False, 'Oh no!'
... except AssertionError:
...     print('Error detected.')
... 
Error detected.

Là encore, c'est ce qui permet aux frameworks de tests d'exécuter plusieurs tests à la suite, peu importe qu'ils passent ou qu'ils échouent. assert n'est donc qu'un peu de sucre syntaxique au-dessus d'une fonctionnement somme toute très classique d'exception.

Pas seulement dans les tests.

Partant de là, il est aussi possible d'utiliser assert dans du code en dehors des tests unitaires. Oui, mais comment bien faire ?

Pas comme ça...

Tout d'abords, ne validez jamais les valeurs d'une entrée utilisateur ou d'un système externe avec assert : ce n'est pas son but. Cela veut dire :

Par exemple, si vous devez vérifier que la valeur fournie est bien supérieur à 0, ne faites pas ceci :

assert user_input > 0

Mais cela :

if user_input <= 0:
    raise ValueError(user_input)

assert ne doit jamais servir le fonctionnel.

Il y a une raison simple à cela : l'option -O de python. Voici ce qui se passe lorsque je lance un petit programme sans cette option :

$ python app.py 
Traceback (most recent call last):
  File "app.py", line 7, in <module>
    validate(0)
  File "app.py", line 4, in validate
    assert user_input > 0
AssertionError
$ echo $?
1

Et maintenant avec :

$ python -O app.py
$ echo $?
0

Le assert a été purement et simplement ignoré - ce qui n'est pas du tout le résultat attendu ! Si vous voulez vraiment que votre programme utilise une AssertionError, il vaut mieux la lever vous même plutôt qu'utiliser un assert.

Alors comment ?

Un assert permet de vérifier une condition et de "laisser passer" si elle est vraie : nous pouvons utiliser son pouvoir de ne rien faire si tout se passe bien à notre avantage.

La plupart du temps, une application dispose de deux ou plus niveau de code :

assert est une forme de communication.

Si dans le code de haut niveau nous ne pouvons pas utiliser assert, le code de bas niveau est un excellent candidat pour ça. Par exemple, imaginons une fonction qui divise un nombre par un paramètre donné, mais seulement différent de 0 :

def divide_by(number):
    assert not number == 0, (
        'You can not `divide_by` 0.')
    # process number

Ainsi, un développeur pour s'assurer qu'aucun de ses collègues ne tente d'appeler cette fonction en dehors de son module avec un paramètre incorrect - les développeurs ne lançant que très rarement leur application avec l'option -O.

Personnellement, je l'utilise beaucoup pour valider des keywords argument obligatoire dans du code interne :

def do_something(self, bar=None, **kwargs):
    assert 'foo' in kwargs, 'We expect foo!'
    assert bar is not None, 'We expect a value for bar''
    super().do_something(bar=bar, **kwargs)

De cette façon, je peux faire en sorte de conserver la même signature que la fonction parent, tout en ajoutant des pré-conditions. Cela reste un cas spécifique, et j'invite chacun à faire des choix judicieux et réfléchis.

Digression : couverture de code

Dans un précédent article, je parlais de la couverture de code : assert implique une différence importante pour ladite couverture.

Si vous prenez ces deux bouts de code qui ont l'air identique :

def divide_by(number):
    assert not number == 0, (
        'You can not `divide_by` 0.')
    return CONSTANT / number

def divide_user_input(user_input):
    if user_input == 0:
        raise AssertionError('You can not `divide_by` 0.')
    return CONSTANT / number

Ils peuvent être testés de la même façon :

>>> assert divide_by(1) == CONSTANT
>>> assert divide_user_input(1) == CONSTANT

Cependant, le test de divide_by donnera une couverture de 100%, alors que l'autre fonction n'aura une couverture de seulement 66%.

Pourquoi ? Parce qu'un if génère une branche du code, qu'il faut couvrir aussi, tandis qu'un assert ne génère pas de branche, et n'a pas à être "vérifier" par un test.

C'est une distinction importante, car elle permet de faire la distinction entre :

C'est à vous de juger si une fonction peut être appelée avec des paramètres externes à l'application, ou si cela reste purement en interne. Dans le doute, évitez les assert, mais sinon, c'est un outil très puissant, à la fois pour la simplicité du code, pour éviter des tests "en trop", et, enfin, pour la communication entre développeurs.

Couverture menteuse

Par Florian Strzelecki - 18:49 - 03.10.2015

Tags : Python, Programmation, Bonne pratique, Unit Testing, Code Coverage

Le domaine des tests unitaires est vaste, les approches foisonnent, et les polémiques sont nombreuses. Il en est une toute particulière que j'affectionne qui concerne la couverture de code.

Bien sûr, je ne jure que par une bonne couverture de code. Écrire des tests et les exécuter sans regarder la couverture de code (ou "code coverage" dans le jargon), ne représente pas un grand intérêt pour moi. Si je n'ai pas l'assurance que je teste l'ensemble du code, je me sens comme en hiver sans un bon pull : à découvert devant le froid et la colère des éléments.

La couverture de code est un mensonge.

Cette expression, je l'entends très souvent. Trop souvent même, que ce soit par des gens qui sont contre ou des gens qui sont pour - oui, en 2015, il y a encore des gens qui sont contre la couverture de code, et même contre les tests. Ce qui est un peu triste, d'ailleurs - mais passons.

Cependant, rare sont les fois où j'entends des explications claires ou des exemples d'une "bonne" mesure. Comment y parvenir ? Comment détecter les trous ? Comment mieux prévenir les problèmes ?

Je ne vais pas couvrir l'ensemble des cas possibles, mais je suis tombé sur un cas d'école cette semaine à mon travail.

Une note ou deux avec Markdown

Par Florian Strzelecki - 22:37 - 15.08.2015

Tags : Django, Python, Markdown

J'aime bien mettre des mots ou des phrases en exergue :

L'exergue donne du poids à mon message.

Sur mon blog, j'utilise du Markdown pour la mise en forme de mes articles : c'est une syntaxe assez simple et pas prise de tête. Je lui préfère généralement ReStructuredText, mais pour quelques articles de blog, Markdown est plus léger et largement suffisant.

À propos de RST

ReStructuredText est une syntaxe relativement légère et puissante, notamment utilisée par l'outil de documentation Sphinx.

Mais l'est-il vraiment ? Oui, jusqu'à présent, je n'ai pas eu à me plaindre. Le seul hic, c'est qu'il est peut-être un peu trop léger, et que je me retrouve très souvent à vouloir faire une "admonition", ou un encart.

Et pour cela j'ai besoin d'utiliser une extension ou deux de Markdown...

Utiliser une extension

Pour utiliser une extension avec la bibliothèque python Markdown c'est plutôt simple :

import markdown

html = markdown.markdown('some text', extensions=[ext1, ext2, ... ])

Il suffit de fournir une liste d'extensions, et "ça marche". Cependant, ce n'est pas vraiment pratique lorsque l'on sait que plusieurs éléments vont devoir être formatés avec les mêmes extensions. Dans ce cas là, il vaut mieux passer par un objet markdown.Markdown bien configuré, et d'utiliser sa méthode convert:

import markdown

md = markdown.Markdown(extensions=[ ... ])

html1 = md.convert('first text')
md.reset()
html2 = md.convert('another text')
md.reset()

Et pour Django ?

Personnellement, je crée une instance de Markdown dans mon models.py, là où je vais l'utiliser dans les méthodes save de mes modèles.

Et c'est à peu près tout. Rien de bien sorcier, mais y penser permet de gagner très légèrement en performance lors de la sauvegarde de mes objets.

L'autre idée, c'est d'utiliser deux champs dans un modèle : l'un va contenir les données au format markdown, et l'autre la conversion en HTML :

import markdown

from django.db import models

md = markdown.Markdown(extensions=[
    'markdown.extensions.admonition',
    'markdown.extensions.abbr',
    'markdown.extensions.footnotes'
])


class Entry(models.Model):
    title = models.CharField(max_length=250)
    text = models.TextField()
    _text = models.TextField(editable=False)

    def save(self):
        self._text = md.reset().convert(self.text)
        super(Entry, self).save()

    def get_text_html(self):
        return self._text

De cette façon, je n'ai plus qu'à utiliser ce bout de template là où j'en ai besoin : {{ entry.get_text_html }} et le tour est joué.

O et 0

Par Florian Strzelecki - 21:53 - 07.08.2015

Tags : Python, Programmation, Ma vie, loldev, Erreur

Je crois que je travaille trop, je viens de perdre 5 bonnes minutes sur ce problème là :

>>> [][0:None]
[]
>>> [][O:None]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'O' is not defined

C'est sans doute l'erreur la plus stupide de ma journée (voire de ma semaine). J'ai confondu la lettre o écrite en majuscule, avec le chiffre 0 (zéro)...

Fatigue ? Inattention ? Moment de faiblesse ?

Certainement. Mais j'aime trop mon métier pour m'arrêter à ça.

Identité, authentification, et autorisation

Par Florian Strzelecki - 00:31 - 05.08.2015

Tags : Web, Technique, Vulgarisation, Sécurité, Identité, Authentification, Autorisation

La gestion de l'identité et des comptes utilisateurs est un problème très courant dans le développement web : nous construisons des interfaces de gestion, des "back offices" pour les utilisateurs, et des "dashboards" pour les opérateurs ; bref, nous donnons un accès privilégié à certains utilisateurs.

Pour autant, je suis toujours un peu surpris du manque de culture autour de ces trois notions, qui ne sont que rarement vues comme trois domaines distincts - ce qu'elles sont pourtant. Alors, une fois n'est pas coutume, j'ai envie d'aborder ce sujet là, en essayant de rester simple.

Identité

L'identité d'une personne, c'est qui elle est. Par exemple, je suis Florian Strzelecki, et je suis aussi @Exirel. Ce sont des identités : quelque part, ce sont les données qui existent sur moi. Ces données peuvent être plus ou moins publiques, et peuvent être partagées avec plus ou moins de degrés de confidentialité.

Lorsqu'Amazon affiche mon nom en haut à gauche ou dans mon panier d'articles, c'est mon identité que j'ai chez Amazon qu'il utilise.

Par nature, l'identité n'est pas une clé ni une autorisation : je peux toujours prétendre être quelqu'un d'autre, il me faut plus qu'une identité pour avoir de la crédibilité.

Authentification

C'est là qu'entre en jeu l'authentification : c'est le fait de garantir l'authenticité des informations fournies. De dire : je suis bien le "moi" que je dis être.

Par exemple, toujours sur Amazon, lorsque je souhaite passer au paiement d'une commande, je dois prouver que je suis bien moi, en fournissant mon identifiant de compte, et le mot de passe associé. J'apporte la garantie de l'authenticité de mes propos lorsque je dis que je suis bien moi, en utilisant une information qui n'est connue que de moi seul (à noter qu'Amazon ne possède pas mon mot de passe, mais le moyen de vérifier que celui que je donne correspond bien à l'information qu'il possède).

Cependant, rien ne dit que l'authentification a besoin de l'identité : imaginez que vous vous rendez à un club privé où il faut fournir un mot de passe. Dans ce cas, rien ne dit qui vous êtes, votre mot de passe ne fait que dire que vous êtes un utilisateur authentifié - ou une utilisatrice authentifiée, il n'y a pas de raison d'exclure quelqu'un s'il ou elle a le mot de passe.

C'est d'autant plus important de comprendre la différence entre l'identité et l'authentification, car l'un est public (ou partageable), et l'autre doit rester secret. Prenez la biométrie : bien que théoriquement unique et personnel, les informations restent publiques. Il suffit de laisser une trace de doigt sur une porte, et il y a au moins votre ophtalmologiste pour connaître les motifs de vos yeux. Et pour un peu, votre ADN est déjà dans un fichier de police...

Ainsi, la biométrie n'est pas un bon moyen de s'authentifier : c'est, au mieux, un excellent moyen de définir une identité, aussi bien qu'un identifiant de compte.

Il y a, bien entendu, d'autres applications à cette différence : vous pouvez très bien obtenir un jeton d'authentification (ou "token" en Anglais) pour accéder à un service, tout en restant anonyme.

Autorisation

Le dernier point est le plus facile à distinguer des deux autres : il s'agit de définir ce à quoi une personne (identifiée ou non) a droit ou non, ce à quoi elle a accès ou non.

Amazon me laisse choisir des produits et payer ma commande, mais il ne me permet pas d'avoir accès aux commandes des autres utilisateurs.

L'autorisation cependant a besoin soit de l'identité, soit de l'authentification :

Pour reprendre l'exemple du club privé, chaque membre qui a un mot de passe a exactement les mêmes droits (et devoirs) que les autres. Il n'y a pas de différence entre l'identité de l'un et de l'autre.

Sécurité et données

Lorsque l'on parle de sécurité, ces trois notions se retrouvent très vite sur la table : que ce soit pour accéder à des données ou à des services. Il est important de comprendre chaque concept, et de comprendre où et comment il s'applique à chaque situation - et à chaque besoin.

Par exemple, certaines informations sont liées à l'identité d'une personne (son nom, son identifiant, etc.), alors que d'autres ne sont liées qu'à son ordinateur ou à son téléphone (l'IP, le navigateur, l'emplacement géographique). Le traitement sera alors différent, et les techniques pour assurer la sécurité du système le seront tout autant.

De même, bien séparer l'identité de son authentification permet de faire évoluer l'un et l'autre séparément : peu importe si vous utilisez HTTP ou HTTPS pour connaître votre prénom, alors qu'il vaut mieux chiffrer les mots de passe dans une base de données (pour que cela reste un secret).

Bref, ce ne sont que quelques explications (un peu imprécises, je m'en excuse) sur ces trois notions, et j'espère que vous serez moins perdu la prochaine fois que quelqu'un vous parlera de l'authentification et des autorisations associées à votre identité.

Nuit du 4 août 1789

Par Florian Strzelecki - 16:16 - 04.08.2015

Tags : Société, Ma vie, Sexisme, 4 Août, Racisme

En bon élève que je suis, j'ai appris sur les bancs de l'école que la nuit du 4 Août 1789, l'Assemblée constituante mit fin au système féodal - voici l'introduction tirée de Wikipédia :

La nuit du 4 août 1789 est un événement fondamental de la Révolution française, puisque, au cours de la séance qui se tenait alors, l'Assemblée constituante met fin au système féodal. C'est l'abolition de tous les droits et privilèges féodaux ainsi que de tous les privilèges des classes, des provinces, des villes et des corporations, à l'initiative du Club breton, futur "Club des Jacobins".

Rien que ça : l'abolition de tous les droits et privilèges de ceux que l'on pourrait appeler "les dominants". Non, je ne vais pas me lancer dans un exercice complexe de définition et de description de ce que sont des "dominants" et des "dominés", pas plus que je ne vais sortir d'analyse quelconque de ce qui s'est passé lors de la Révolution française.

Mais tout de même, je suis songeur.

Je lis, je m'interroge, et j'apprends beaucoup, sur la sociologie entre autre, et sur des maux qui rongent notre société : sexisme, racisme, homophobie, l'injustice sociale, la "crise", la corruption. La haine des uns pour les autres, le conservatisme, les positions extrêmes des uns et des autres.

J'ai une longue route à faire pour comprendre tous les tenants et aboutissants des problèmes que j'aimerais résoudre, et je ne doute pas un seul instant que je n'aurai jamais une vision assez objective de la situation, ni la prétention de détenir une quelconque vérité sur ce qu'il faut faire, sur ce qu'il ne faut pas faire, et encore moins croire ou ne pas croire.

J'essaie, humblement, et avec beaucoup d'erreurs, d'accepter ce que je ne sais pas, d'écouter au-delà de ma conception forcément limitée du monde, en cherchant à dépasser mes préjugés. Parfois je soutien une initiative personnellement, parfois je ne fais que partager un lien sur Twitter, ou une photo sur Tumblr.

Alors, cette nuit du 4 Août, qu'est-ce qu'il nous en restera dans 100, 200, ou 300 ans ?

Remote

Par Florian Strzelecki - 22:02 - 16.07.2015

Tags : Ma vie, Travail, Remote, Rythme de vie

C'est la fin d'après-midi, et je suis dans le train qui me ramène à Rennes, après deux jours à Paris. Ces aller-retours sont devenus fréquents - toutes les deux semaines exactement - et ce depuis bientôt un an : je suis en télé-travail pour la société Oscaro.com, et je travaille (principalement), sur le site oscaro.be.

Cet article me trotte dans la tête depuis quelques temps - depuis le premier jour en réalité - et j'ai attendu d'avoir suffisamment d'expériences à partager pour l'écrire.

Plutôt que parler de mon travail, je souhaite surtout parler de ce qui a changé dans ma vie.

Mise à jour

Par Florian Strzelecki - 20:47 - 14.07.2015

Tags : Bonjour, Django, Web, Ma vie, Divers

Heh, ça fait longtemps, non ? Depuis Novembre dernier, je n'ai rien publié ici. Je n'en avais pas l'envie la plupart du temps, et pas la motivation le reste du temps. J'avais envie de mettre à jour le site, le blog, le serveur, l'application, etc. comme une excuse avant de publier à nouveau (et, quelque part, avant d'écrire à nouveau).

Un peu de style

Pour commencer, j'ai donc regardé comment "améliorer" le style du blog. Rien de très folichon : j'ai opté pour un design encore plus sobre, encore plus épuré, avec une grosse police de caractères. D'ailleurs, je n'ai indiqué aucune police en particulier : c'est du sans-serif pour les titres, et du serif pour le texte. C'est un choix délibéré de ma part.

Je vais sans doute retravailler un peu cela prochainement, si l'envie m'en prend. Mais pour le moment, cela me suffit ainsi.

Un peu de script

Il y avait pas mal de JavaScript inutile sur ce blog (des reliquats d'un temps ancien). J'en ai donc supprimé une bonne partie pour ne garder que l'essentiel (ie. un plugin pour les images, qu'il faut que je change aussi).

Au passage, je suis passé à Grunt pour gérer tous mes fichiers "frontend" (ie. le CSS et le JavaScript). Ce n'était pas très compliqué, et une fois les bases posées, c'est allé assez vite.

J'ai presque envie de dire que j'ai apprécié cette expérience.

Un nouveau serveur

Parce qu'il n'était pas question de garder un vieux Ubuntu 10.04 en production, j'ai opté pour un nouveau serveur, avec Ubuntu 14.04 - je reste donc sur des LTS.

Je suis aussi passé de Apache à nginx, et de Gunicorn à uwsgi. Enfin, j'ai laissé de côté runit pour utiliser upstart.

Le plus difficile a été de configurer l'ensemble correctement, en utilisant des fichiers de configuration valides, avec des variables d'environnements qui contiennent les données sensibles avec un accès en lecture seule pour l'utilisateur root seulement.

Il me faut encore configurer proprement le système de log, et ajouter une supervision à mes nouveaux serveurs (j'avais déjà migré d'autres applications sur un autre serveur auparavant).

Django avec Postgres

Auparavant, j'utilisais exclusivement MySQL. J'ai, depuis, décidé de braver les épreuves pour apprendre à installer et utiliser correctement PostgreSQL. Je suis loin d'en maîtriser les arcanes, mais je sais au moins créer les utilisateurs, les bases de données, et configurer proprement les droits d'accès.

Enfin, il n'était pas question de rester sur une version non-supportée de Django, et j'ai donc tout naturellement migré vers Django 1.8.

J'ai donc pu :

Lâchez vos com'

C'est la grande nouveauté en terme de fonctionnalité. C'est une petite révolution pour moi : j'ai accepté d'utiliser Disqus. J'avais choisi à l'origine de ne pas mettre de commentaires sur mon blog, car la gestion du spam est particulièrement pénible, et que, la plupart du temps, la qualité des commentaires n'étaient pas suffisantes pour justifier le travail supplémentaire.

Avec Disqus, j'ai ouvert les commentaires en déléguant cette partie à un prestataire. Je verrai bien à l'usage ce qu'il en est. Alors n'hésitez pas.

J'autorise les anonymes à écrire des commentaires, mais tous emails non encore validés devra attendre une modération avant de voir ses commentaires publiés. L'avenir nous dira s'il faut apporter des modifications à ce système.

Bref, allez-y, vous pouvez commenter. Mais n'espérez pas trop de mises à jour, je publie souvent par paquet de billets d'un coup, sans aucune fréquence particulière.

Framework overflow

Par Florian Strzelecki - 00:02 - 10.11.2014

Tags : Framework, Web, Bonne pratique, twitter bootstrap, Angular.js

Je mets toujours beaucoup de temps avant d'adhérer à un framework. Contrairement à ce que l'on pourrait croire, je ne suis pas du genre à adhérer à une nouvelle technologie qui vient à peine de sortir, pas plus que je ne me jette à corps perdu dans un nouveau framework ou une nouvelle méthode. Il faudrait sans doute que j'écrive un long article sur le sujet à l'occasion, car je suis presque sûr que mes amis me voient comme le plus hipster d'entre eux en la matière.

Si vous ne l'avez pas encore fait, je vous invite à lire cet excellent article "Stop Breaking the Web", qui aborde un problème récurrent dans le monde du développement web : l'usage abusif de frameworks et de solutions qui ne résolvent pas les bons problèmes.

Si sur la forme je retrouve la rengaine habituelle de ce que nous devrions faire et que nous ne faisons pas (faire du progressif au lieu de chercher à supporter tous les navigateurs de la même façon), sur le fond, il y a plusieurs points intéressants qui sortent de l'ordinaire ; et avec lesquels je suis tout à fait d'accord.

Dans les passages qui ont attiré mon attention il y a celui-ci :

You should be delivering the content in human-viewable form first, [...] then you can add your fancy JavaScript magic on top of that.

Je ne peux qu'insister lourdement sur cette approche qui n'apporte pas seulement quelques bénéfices : cette approche est la base même de tout ce que nous devrions faire.

Nous devrions faire un premier rendu, sans JavaScript et avec un CSS si possible minimal, de sorte à pouvoir donner l'accès au contenu. C'est le contenu que les utilisateurs, que les êtres humains, viennent chercher en premier. Et il n'est jamais trop tard pour ajouter une couche de JavaScript par dessus.

De plus, cela permet d'avoir un socle solide, unique, et stable, sur lequel s'appuyer pour faire tout le reste : la vue mobile, la vue tablette, la vue desktop, avec ou sans JavaScript, avec ou sans la dernière mise à jour du navigateur.

C'est la base même du web, et cet article nous rappelle à quel point nous l'avons oublié.

Robustesse et anti-fragile

Il y a quelques temps déjà un de mes amis, Sylvain, me disait que nous ne devrions plus penser avec des applications monolithiques, que nous devrions repenser notre façon d'aborder les tests, mais surtout, que nous ne devrions plus chercher à faire des applications robustes, mais penser nos applications avec une approche anti-fragiles.

L'idée, c'est que quoi qu'il arrive, un ensemble minimal doit toujours rester fonctionnel. Que ce qui ne marche pas ne devrait pas avoir d'effet sur le reste, autre que retirer une partie des fonctionnalités.

J'ai un exemple très récent en tête, une expérience désastreuse avec Angular.js : suite à un bug dans la gestion de l'état de l'application, une erreur impromptue bloquait toute l'application, la rendant complètement inutilisable à moins de recharger la page. Certes, il y avait des contrôles pour que l'application soit robuste, mais à la première erreur imprévue, plus rien du tout ne fonctionnait.

Ce genre de problème ne devrait pas arriver : les bugs sont toujours possibles, mais nous devrions être capable de faire en sorte qu'ils aient le moins d'effets de bords possible, et de toujours garder une base fonctionnelle quoi qu'il arrive.

Les choses à l'envers

Le second point soulevé par cet article est que nous faisons les choses à l'envers. Nous ne devrions pas chercher à supporter toutes les nouvelles fonctionnalités dans les vieux navigateurs : à la place, nous ne devrions activer les fonctionnalités que si le navigateur les supporte :

We are doing things backwards. We are treating modern browsers as "the status quo", and logically if someone doesn't conform to "the status quo", we'll be super helpful and add our awesome behavioral polyfills.

La mise en exergue est de moi.

Là encore, c'est pourtant quelque chose qui devrait nous sembler évident. Si quelqu'un utilise un vieux navigateur, il est aussi tout à fait possible qu'il utilise un vieux PC. Ou pour une raison ou une autre, il a des limitations sur son environnement de navigation (peut-être qu'il utilise un bloqueur de pub un peu trop agressif, certes, mais il faut bien dire que les pubs sont souvent très agressives aussi).

Qu'est-ce qui intéresse vraiment l'utilisateur ? Un super système de routage d'URL côté client ? Ou le contenu ? Les textes ? Les images ? Ou appuyer le bouton "ajouter au panier" ? Remplir le formulaire de contact ?

Tous ces problèmes de développement pourraient se résumer à "faire les choses biens". J'aimerais ajouter cependant un petit conseil, donné par ma compagne : nous devrions créer nos applications en se basant sur les conditions d'accès de la zone Afrique - Asie du Sud ; des connexions lentes et du matériel dépassés.

Car quand on y pense, entre les connexions en Edge (ou même la 3G ce n'est pas toujours parfait), et la flotte de vieux terminaux Android et les iPhone 4 devenus trop lent avec les dernières mises à jours, ce sont, peu ou prou, les conditions de nos utilisateurs au quotidien. Et je ne parle même pas de tous ces PCs dans les entreprises qui n'utilisent que les versions spécifiques de Firefox (parfois bloquées dans une version ancienne) ou d'IE (avec un Vista ou un Seven jamais mis à jour et IE8 ou 9).

Un peu de bon sens ! Voici ce dont nous aurions bien besoin.

Subtile compréhension de listes

Par Florian Strzelecki - 23:59 - 04.11.2014

Tags : Python, Documentation, Programmation, list-comprehension, generator expression

Si vous faites du python, un jour ou l'autre, vous tomberez sur ce genre de structure :

new_list = [int(x) for x in list_of_string_values]

Il s'agit de l'expression très connue appelée list-comprehension. Puissante et pratique, elle permet d'améliorer la lisibilité (dans certains cas), et offre quelques outils intéressants pour réduire la complexité des algorithmes.

Mais connaissez-vous les generator-expression ? Et les set et dict comprehension ?

Bon, tout n'est pas toujours disponible dans toutes les versions de python. Alors je vous préviens, moi, je parle de Python 3.4, version que j'utilise désormais au quotidien. La plupart des exemples et concepts peuvent être transposés, d'une façon ou d'une autre, en Python 2.7.