Blog // Exirel.me

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.

Les rencontres conviviales

Par Florian Strzelecki - 00:24 - 18.02.2016

Tags : Django, Python, Web, Société, Code de conduite, Communauté

Cette année, j’ai accepté l’honneur (et le challenge) d’organiser une rencontre Django à Rennes, et plus exactement “la” rencontre Django FR 2016. Ce sera le 21 Mai, et pour l’instant nous avons ouvert l’appel à orateurs, et publié notre code de conduite présenté sous la forme de judicieux conseils, ce qui est loin d’être un choix anodin.

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.

Python logging et log level

Par Florian Strzelecki - 23:59 - 03.11.2014

Tags : Python, Documentation, Programmation, logging

Dans le précédent article, je parlais de l'héritage des loggers, technique très pratique pour mutualiser des comportements ou au contraire de les séparer. Vous avez pu voir comment faire en sorte que deux loggers n'utilisent pas le même niveau de log ou pas les mêmes handlers.

Un point que je n'ai pas abordé (parmi tant d'autres) est la gestion du niveau de log : il existe deux configurations de niveau, puis à l'usage il suffit de choisir la bonne méthode pour le bon niveau de log désiré. Certes, j'ai utilisé le niveau de log des loggers, mais je n'ai pas vraiment utilisé la notion de log level du handler.

Commençons donc par un exemple simple, cette fois-ci avec un seul logger ayant deux handlers :

from logging import getLogger
from logging.config import dictConfig

if __name__ == '__main__':

    dictConfig({
        'version': 1,
        'handlers': {
            'console': {
                'level': 'INFO',
                'class': 'logging.StreamHandler',
            },
            'file': {
                'level': 'INFO',
                'class': 'logging.FileHandler',
                'filename': 'log.txt'
            }
        },
        'loggers': {
            'main': {
                'level': 'INFO',
                'handlers': ['console', 'file'],
            },
        }
    })

    main = getLogger('main')

    main.info('My message.')
    main.error('My error message.')

Ce qui donne comme résultat :

$ python log.py
My message.
My error message.

Et dans le fichier log.txt vous retrouverez exactement les même messages.

Python logging et héritage de loggers

Par Florian Strzelecki - 22:20 - 02.11.2014

Tags : Python, Documentation, Programmation, logging

Prenons un exemple simple, un fichier log.py qui va définir deux loggers : main et main.part. Ces loggers pourraient très bien être définis dans un package main et son sous-module part. Pour l'exemple, nous allons faire beaucoup plus simple :

from logging import getLogger
from logging.config import dictConfig

if __name__ == '__main__':

    dictConfig({
        'version': 1,
        'handlers': {
            'console': {
                'level': 'INFO',
                'class': 'logging.StreamHandler'
            }
        },
        'loggers': {
            'main': {
                'level': 'INFO',
                'handlers': ['console', ],
            }
        }
    })

    main = getLogger('main')
    part = getLogger('main.part')

    main.info('My message on the main logger.')
    part.info('And my second message on the main.part logger.')

Ici, il y a donc :

Si vous faites python log.py c'est ceci qui devrait s'afficher dans votre console :

$ python log.py
My message on the main logger.
And my second message on the main.part logger.

Sauf que si vous retirez la partie dictConfig de ce bout de code, plus rien ne s'affiche. Il y a donc bien quelque chose à regarder de ce côté là.