Blog // Exirel.me

Fantastic Tests

Par Florian Strzelecki - 18:25 - 13.04.2018

Tags : Programmation, Bonne pratique, Développement, Unit Testing, Tests

And How to Write Them

J'ai donné le mois dernier un talk mystère à Software Crafts·wo·manship, car j'avais envie de parler des tests (en programmation). Le format ne permettant pas de s'étendre beaucoup sur le sujet, je prends le temps aujourd'hui de l'explorer un peu plus par écrit.

J'aime les tests. Tous les types de tests : unitaire, intégration, QA, utilisateur, performance, chaos, etc. il en existe plus que je ne peux en lister. C'est, avec la documentation, un outil indispensable pour moi. Si je ne suis pas un extrémiste du TDD, je tente de le pratiquer aussi souvent que possible, et je ne cesse d'en défendre l'utilité et les usages.

Pourtant, je tombe encore et toujours sur les mêmes débats sans fin, les mêmes remarques subjectives, les mêmes traits d'esprit qui se veulent toujours plus malins, et les mêmes conflits stériles qui cristallisent des positions bien campées là où il faudrait plutôt résoudre les problèmes - et de répondre aux vrais besoins.

2 unit tests. 0 integration tests

Je prends souvent en exemple le cas des blagues "2 unit tests. 0 integration tests". Il en existe beaucoup de variantes, toute plus ou moins drôles.

Je n'aime pas cette blague. Ce n'est pas tant qu'elle soit employée à tort et à travers, que le fait qu'elle se base sur un paradigme que je trouve fondamentalement inadapté : il y aurait d'un côté les tests unitaires, et de l'autre les tests d'intégration. Certains verront dans cette blague une défense des tests d'intégrations, d'autre une attaque des tests unitaires, alors que le problème de fond, ce n'est pas l'un ou l'autre, ou l'un sans l'autre, mais l'inadéquation des tests avec les besoins réels. Passer son temps à dire qu'il faut les deux ne permet toujours pas de dire comment écrire de bons tests.

Le problème de fond qui m'intéresse, moi, c'est d'écrire des tests qui ont un sens ; des tests qui m'apportent quelque chose, non pas en statistique ou par respect de certains principes, mais en sécurité, en maintenabilité, et surtout, en utilisabilité.

À la fin de la journée, lorsque je reçois un rapport de test positif, ce qui compte, c'est que je sois rassuré : l'utilisateur va pouvoir utiliser mon application.

Youtube & Cinéma

Par Florian Strzelecki - 13:52 - 10.03.2018

Tags : Cinéma, Youtube

J'aime le cinéma, et je passe beaucoup de temps sur Youtube : c'est l'occasion de partager une liste de chaînes Youtube auxquelles je suis abonné.

Il n'y a aucun ordre, pas même alphabétique. Certaines sont plus intéressantes que d'autres, certaines ne vous plairont pas, et d'autres encore sont absentes : vous êtes seul juge, et vous pouvez toujours partager vos trouvailles.

Critiques de films

Une opinion ? Une critique ? Un avis ?

Divertissements

Parce que le cinéma est vaste et qu'il y a toujours moyen d'en rire ou de s'en amuser - et pas seulement en regardant des films.

Analyses de films & séries

Pour aller un peu plus loin dans les thématiques d'un film, d'une série, d'un genre.

Analyses des techniques

Derrière chaque film il y a des équipes qui réalisent, produisent, montent, assemblent. Les métiers du cinéma sont nombreux, les techniques pour donner vie à un films encore plus.

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