Blog // Exirel.me

Thierry Roland

Par Florian Strzelecki - 10:05 - 04.08.2017

Tags : Ma vie, 4 Août, football

Thierry Roland, né le 4 août 1937 à Boulogne-Billancourt et mort le 16 juin 20121 à Paris, est un journaliste sportif français et un célèbre commentateur de matchs de football.

Wikipédia

Je n'ai jamais trop été intéressé par le football, déjà parce que c'est un sport et que j'ai une attention très limitée quand il s'agit de sport ; ensuite parce que concrètement il ne se passe jamais grand chose dans les 90min que prend un match.

Mais Thierry Roland, ça, je m'en souviens. Aussi loin que je me souvienne, c'est sa voix que j'entendais à la TV lorsque ça parlait de foot, sa voix reconnaissable entre mille.

Mais bon, moi, le foot...

DRY, de la fonction au décorateur

Par Florian Strzelecki - 23:38 - 12.07.2017

Tags : Python, Programmation, Développement, decorator

Prenons un exemple de code python simpliste :

def more(number, i):
    """Add ``number`` and ``i``"""
    return number + i

print(more(5, 2))

Sans grande surprise, ceci affiche dans le terminal le nombre "7". Maintenant, mettons que j'ai envie d'afficher du texte avant de faire l'addition... mais seulement de temps en temps ?

La version simpliste, c'est d'écrire une autre fonction, comme ceci :

def more_verbose(number, i):
    """Print a sentence and return ``more(number, i)``"""
    print('Calling function `more`')
    return more(number, i)

print(more_verbose(5, 2))

Ce qui affiche :

Calling function `more`
7

Et maintenant, qu'est-ce qui se passerait si je voulais faire la même chose avec une autre fonction ?

Limites de sphinx-autodoc

Par Florian Strzelecki - 17:31 - 09.07.2017

Tags : Python, Documentation, sphinx, autodoc

J'utilise Sphinx pour écrire des documentations depuis quelques années maintenant, que ce soit pour des projets Python ou non, et avec le recul j'en viens à en considérer les limites. Elles sont nombreuses, et de plus en plus frustrantes.

Mais pour le moment, je vais me concentrer sur son extension autodoc, qui inspecte le code et permet d'en extraire les docstring. Son but est simple : générer une documentation exhaustive et à jour (ce que j'appelle "la référence technique"), puisqu'elle concorde avec le code source documenté (pour l'exercice, on supposera toujours vrai que les docstrings soient toujours à jour avec le code source).

Ma première expérience de documentation fut avec Doxygen, à l'IUT (entre 2004 et 2006). Ce fut une bonne expérience, et une ouverture sur la documentation et sur son importance pour la qualité.

Sans expérience en la matière, il m'apparut très vite comme une pratique essentielle du métier, bien que ce ne soit pas l'avis de tout le monde. Autant dire qu'il y a de quoi écrire des livres entiers sur la place de la documentation, que ce soit dans notre culture ou dans nos passages à la pratique.

Plus tard, j'ai été amené à utiliser des outils similaires, pour d'autres langages, et pour un lectorat de plus en plus varié. Depuis, j'ai même eu l'occasion de donner plusieurs ateliers et conférences sur le sujet, qui me passionne peut-être même plus que le code lui même.

Obsolescence de la documentation

Par Florian Strzelecki - 18:21 - 28.01.2017

Tags : Documentation, Lecture, Obsolète

Les déménagements ont ceci de formidable qu'ils permettent de regarder ses plus vieilles affaires oubliées, et de faire un choix : garder, ou jeter. N'ayant plus la place de tout garder, et n'étant pas spécialement attaché aux affaires du passé, j'ai tendance à jeter assez vite - sauf les livres, c'est beaucoup trop difficile avec les livres.

Il se trouve que j'ai eu ce choix difficile devant un carton de livres techniques. Garder ou jeter ? Et puis, j'ai lu les titres, j'ai relu quelques passages, et surtout, j'ai vérifié les dates de publication : de 2006 à 2011. Le choix a finalement été beaucoup plus facile que prévu.

La documentation n'est ni éternelle ni immuable, elle doit changer, évoluer, et s'adapter aux évolutions de son sujet (langage, framework, outil, etc.). Les livres papiers, malheureusement, ne peuvent pas faire évoluer leur contenu - pas sans une intervention physique, généralement plus destructrice que bénéfique si j'en crois mon expérience. Je rajoute d'ailleurs que les magazines techs ont exactement le même problème : à partir du moment où c'est imprimé sur du papier, il y a une date de péremption au dos.

Je ne nie pas l'intérêt historique ou d'archivage de tels documents, qui restent très intéressant pour voir l'évolution de notre univers informatique - c'est toujours un peu drôle de voir 300 pages sur jQuery ou PHP 4 - mais d'intérêt pratique au quotidien, pour apprendre et transmettre des savoirs.

Au final, concernant les livres, je n'en achète plus, ayant réalisé déjà il y a quelques temps qu'ils ne peuvent pas rester une source d'informations fiables suffisamment longtemps pour en justifier l'achat.

Et en ligne, me direz-vous ? La situation n'est pas toujours mieux : dans l'univers JavaScript, il m'est souvent difficile de trouver la bonne documentation. Parfois l'URL de la doc n'est plus la même entre deux versions (par exemple pour Webpack), ou bien la documentation ne précise pas quelle version du logiciel elle documente (pour Godot), ou pire encore la documentation d'une version précédente, toujours supportée, a été retirée et n'est plus consultable en ligne.

Maintenir une documentation n'est pas une tâche aisée, j'en conviens, mais je suis toujours aussi surpris de voir que nous avons si peu d'outils à notre disposition pour améliorer la situation...

Python, Makefile, et tests

Par Florian Strzelecki - 19:15 - 24.10.2016

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

Depuis quelques temps, j'essaie plusieurs fichiers Makefile pour mes projets en pythons : pour lancer les tests, les validateurs divers, et générer les différents rapports (tests, couverture, et qualité).

Voici ce à quoi j'arrive pour le moment :

# Project tasks
# ==========

test:
    coverage run --source=<package> setup.py test

report: pylint
    coverage html

pylint:
    pylint <package> > pylint.html || exit 0

isort:
    isort -rc <package> test.py

flake8:
    flake8

quality: isort flake8

all: quality test report

En général, je lance simplement avec make all, pour tout faire d'un coup sans me poser de questions. Cela va donc lancer :

Premier constat : inutile pour moi de lancer des tests sur du code qui n'est pas correctement formaté. Le coût d'un code propre est minime voire quasi inexistant - j'ai l'habitude, ça aide beaucoup - alors autant se fixer des règles strictes et s'y tenir.

Second constat : je n'utilise plus (ou presque plus) de lanceur de tests spécifiques (py.test, nosetests, etc.). Pourquoi ? Parce qu'au final, ces outils ne font qu'ajouter de la déco sur les tests, mais ils ne m'aident pas à écrire de meilleurs tests. Je suis habitué à unittest, et la plupart du temps, je n'ai pas besoin d'une explication particulière pour un test.

D'ailleurs, que l'on utilise simplement assert ou les méthodes du framework unittest, un message manuel est souvent préférable à une interprétation du lanceur de tests :

import unittest

class TestIsPositive(unittest.TestCase):
    def test_behavior(self):
        assert is_positive(0) is True, (
            'is_positive must return True with 0')
        assert is_positive(2) is True, (
            'is_positive must return True with > 0')
        self.assertFalse(
            is_positive(-1),
            'is_positive must return False with < 0')

Bien entendu, c'est un cas où le message est un peu accessoire : il faut imaginer des cas plus complexes, où le résultat d'une fonction est d'un type complexe, et donc la raison d'être demande un peu plus qu'une simple "ceci doit être égal à cela".

Quoi qu'il en soit, make all est maintenant ma routine quotidienne, simple, rapide, et efficace.

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é.