gwift-book/source/part-1-workspace/environment/_index.adoc

25 KiB
Raw Blame History

Boite à outils

Python

Le langage Python est un langage de programmation interprété, interactif, orienté objet (souvent), fonctionnel (parfois), open source, multi-plateformes, flexible, facile à apprendre et difficile à maîtriser.

A première vue, certains concepts restent difficiles à aborder: lindentation définit létendue dun bloc (classe, fonction, méthode, boucle, condition, …​), il ny a pas de typage fort des variables et le compilateur nest pas là pour assurer le filet de sécurité avant la mise en production (puisquil ny a pas de compilateur 😛). Et malgré ces quelques points, Python reste un langage généraliste accessible et "bon partout", et de pouvoir se reposer sur un écosystème stable et fonctionnel.

Il fonctionne avec un système daméliorations basées sur des propositions: les PEP, ou "Python Enhancement Proposal". Chacune dentre elles doit être approuvée par le Benevolent Dictator For Life.

Si vous avez besoin dun aide-mémoire ou dune liste exhaustive des types et structures de données du langage, référez-vous au lien suivant: Python Cheat Sheet.

Note
Le langage Python utilise un typage dynamique appelé duck typing: "When I see a bird that quacks like a duck, walks like a duck, has feathers and webbed feet and associates with ducks — Im certainly going to assume that he is a duck" (Source: Wikipedia (as usual)).

PEP8 - Style Guide for Python Code

La première PEP qui va nous intéresser est la PEP 8Style Guide for Python Code. Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour lindentation, le nommage des variables et des classes, …​ En bref, elle décrit comment écrire du code proprement, afin que dautres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité.

Dans cet objectif, un outil existe et listera lensemble des conventions qui ne sont pas correctement suivies dans votre projet: pep8. Pour linstaller, passez par pip. Lancez ensuite la commande pep8 suivie du chemin à analyser (., le nom dun répertoire, le nom dun fichier .py, …​). Si vous souhaitez uniquement avoir le nombre derreur de chaque type, saisissez les options --statistics -qq.

$ pep8 . --statistics -qq

7       E101 indentation contains mixed spaces and tabs
6       E122 continuation line missing indentation or outdented
8       E127 continuation line over-indented for visual indent
23      E128 continuation line under-indented for visual indent
3       E131 continuation line unaligned for hanging indent
12      E201 whitespace after '{'
13      E202 whitespace before '}'
86      E203 whitespace before ':'

Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous voulez juste avoir un retour sur une analyse de votre code, essayez pyflakes: cette librairie analysera vos sources à la recherche de sources derreurs possibles (imports inutilisés, méthodes inconnues, etc.).

PEP257 - Docstring Conventions

Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code. Cela impose une certaine rigueur, mais améliore énormément la qualité (et la reprise) du code par une tierce personne. Cela implique aussi de tout documenter: les modules, les paquets, les classes, les fonctions, méthodes, …​ Tout doit avoir un docstring associé :-).

Warning
Documentation: be obsessed!

Il existe plusieurs types de conventions de documentation:

  1. PEP 257

  2. Numpy

  3. Google Style (parfois connue sous lintitulé Napoleon)

  4. …​

Les conventions proposées par Google nous semblent plus faciles à lire que du RestructuredText, mais sont parfois moins bien intégrées que les docstrings officiellement supportées (typiquement, par exemple par clize qui ne reconnait que du RestructuredText). Lexemple donné dans les styleguide est celui-ci:

def fetch_smalltable_rows(table_handle: smalltable.Table,
                          keys: Sequence[Union[bytes, str]],
                          require_all_keys: bool = False,
) -> Mapping[bytes, Tuple[str]]:
    """Fetches rows from a Smalltable.

    Retrieves rows pertaining to the given keys from the Table instance
    represented by table_handle.  String keys will be UTF-8 encoded.

    Args:
        table_handle: An open smalltable.Table instance.
        keys: A sequence of strings representing the key of each table
          row to fetch.  String keys will be UTF-8 encoded.
        require_all_keys: Optional; If require_all_keys is True only
          rows with values set for all keys will be returned.

    Returns:
        A dict mapping keys to the corresponding table row data
        fetched. Each row is represented as a tuple of strings. For
        example:

        {b'Serak': ('Rigel VII', 'Preparer'),
         b'Zim': ('Irk', 'Invader'),
         b'Lrrr': ('Omicron Persei 8', 'Emperor')}

        Returned keys are always bytes.  If a key from the keys argument is
        missing from the dictionary, then that row was not found in the
        table (and require_all_keys must have been False).

    Raises:
        IOError: An error occurred accessing the smalltable.
    """

Cest-à-dire:

  1. Une courte ligne dintroduction, descriptive, indiquant ce que la fonction ou la méthode réalise. Attention, la documentation ne doit pas indiquer comment la fonction/méthode est implémentée, mais ce quelle fait concrètement (et succintement).

  2. Une ligne vide

  3. Une description plus complète et plus verbeuse

  4. Une ligne vide

  5. La description des arguments et paramètres, des valeurs de retour (+ exemples) et les exceptions qui peuvent être levées.

Un exemple (encore) plus complet peut être trouvé dans le dépôt sphinxcontrib-napoleon.

Pour ceux que cela pourrait intéresser, il existe une extension pour Codium, comme nous le verrons juste après, qui permet de générer automatiquement le squelette de documentation dun bloc de code:

python docstring vscode
Figure 2. autodocstring
Note
Nous le verrons plus loin, Django permet de rendre la documentation immédiatement accessible depuis son interface dadministration.

Linters

Il existe plusieurs niveaux de linters:

  1. Le premier niveau concerne pycodestyle (anciennement, pep8 justement…), qui analyse votre code à la recherche derreurs de convention.

  2. Le deuxième niveau concerne pyflakes. Pyflakes est un simple [1] programme qui recherchera des erreurs parmi vos fichiers Python.

  3. Le troisième niveau est Flake8, qui regroupe les deux premiers niveaux, en plus dy ajouter flexibilité, extensions et une analyse de complexité de McCabe.

  4. Le quatrième niveau [2] est PyLint.

PyLint est le meilleur ami de votre moi futur, un peu comme quand vous prenez le temps de faire la vaisselle pour ne pas avoir à la faire le lendemain: il rendra votre code soyeux et brillant, en posant des affirmations spécifiques. A vous de les traiter en corrigeant le code ou en apposant un tag indiquant que vous avez pris connaissance de la remarque, que vous en avez tenu compte, et que vous choisissez malgré tout de faire autrement.

Pour vous donner une idée, voici ce que cela pourrait donner avec un code pas très propre et qui ne sert à rien:

from datetime import datetime

"""On stocke la date du jour dans la variable ToD4y"""

ToD4y = datetime.today()

def print_today(ToD4y):
    today = ToD4y
    print(ToD4y)

def GetToday():
    return ToD4y


if __name__ == "__main__":
    t =   Get_Today()
    print(t)

Avec Flake8, nous obtiendrons ceci:

test.py:7:1: E302 expected 2 blank lines, found 1
test.py:8:5: F841 local variable 'today' is assigned to but never used
test.py:11:1: E302 expected 2 blank lines, found 1
test.py:16:8: E222 multiple spaces after operator
test.py:16:11: F821 undefined name 'Get_Today'
test.py:18:1: W391 blank line at end of file

Nous trouvons des erreurs:

  • de conventions: le nombre de lignes qui séparent deux fonctions, le nombre despace après un opérateur, une ligne vide à la fin du fichier, …​ Ces erreurs nen sont pas vraiment, elles indiquent juste de potentiels problèmes de communication si le code devait être lu ou compris par une autre personne.

  • de définition: une variable assignée mais pas utilisée ou une lexème non trouvé. Cette dernière information indique clairement un bug potentiel. Ne pas en tenir compte nuira sans doute à la santé de votre code (et risque de vous réveiller à cinq heures du mat', quand votre application se prendra méchamment les pieds dans le tapis).

Létape daprès consiste à invoquer pylint. Lui, il est directement moins conciliant:

$ pylint test.py
************* Module test
test.py:16:6: C0326: Exactly one space required after assignment
    t =   Get_Today()
      ^ (bad-whitespace)
test.py:18:0: C0305: Trailing newlines (trailing-newlines)
test.py:1:0: C0114: Missing module docstring (missing-module-docstring)
test.py:3:0: W0105: String statement has no effect (pointless-string-statement)
test.py:5:0: C0103: Constant name "ToD4y" doesn't conform to UPPER_CASE naming style (invalid-name)
test.py:7:16: W0621: Redefining name 'ToD4y' from outer scope (line 5) (redefined-outer-name)
test.py:7:0: C0103: Argument name "ToD4y" doesn't conform to snake_case naming style (invalid-name)
test.py:7:0: C0116: Missing function or method docstring (missing-function-docstring)
test.py:8:4: W0612: Unused variable 'today' (unused-variable)
test.py:11:0: C0103: Function name "GetToday" doesn't conform to snake_case naming style (invalid-name)
test.py:11:0: C0116: Missing function or method docstring (missing-function-docstring)
test.py:16:4: C0103: Constant name "t" doesn't conform to UPPER_CASE naming style (invalid-name)
test.py:16:10: E0602: Undefined variable 'Get_Today' (undefined-variable)

--------------------------------------------------------------------
Your code has been rated at -5.45/10

En gros, jai programmé comme une grosse bouse anémique (et oui, le score dévaluation du code permet bien daller en négatif). En vrac, on trouve des problèmes liés:

  • au nommage (C0103) et à la mise en forme (C0305, C0326, W0105)

  • à des variables non définies (E0602)

  • de la documentation manquante (C0114, C0116)

  • de la redéfinition de variables (W0621).

Pour reprendre la documentation, chaque code possède sa signification (ouf!):

  • C convention related checks

  • R refactoring related checks

  • W various warnings

  • E errors, for probable bugs in the code

  • F fatal, if an error occurred which prevented pylint from doing further* processing.

TODO: Expliquer comment faire pour tagger une explication.

Formatage de code

Nous avons parlé ci-dessous de style de codage pour Python (PEP8), de style de rédaction pour la documentation (PEP257), dun linter pour nous indiquer quels morceaux de code doivent absolument être revus, …​ Reste que ces tâches sont parfois (très) souvent fastidieuses: écrire un code propre et systématiquement cohérent est une tâche ardue. Heureusement, il existe des outils pour nous aider (un peu).

A nouveau, il existe plusieurs possibilités de formatage automatique du code. Même si elle nest pas parfaite, Black arrive à un compromis entre la clarté du code, la facilité dinstallation et dintégration et un résultat.

Est-ce que ce formatage est idéal et accepté par tout le monde ? Non. Même Pylint arrivera parfois à râler. Mais ce formatage conviendra dans 97,83% des cas (au moins).

By using Black, you agree to cede control over minutiae of hand-formatting. In return, Black gives you speed, determinism, and freedom from pycodestyle nagging about formatting. You will save time and mental energy for more important matters.

Black makes code review faster by producing the smallest diffs possible. Blackened code looks the same regardless of the project youre reading. Formatting becomes transparent after a while and you can focus on the content instead.

Traduit rapidement à partir de la langue de Batman: "En utilisant Black, vous cédez le contrôle sur le formatage de votre code. En retour, Black vous fera gagner un max de temps, diminuera votre charge mentale et fera revenir lêtre aimé". Mais la partie réellement intéressante concerne le fait que "Tout code qui sera passé par Black aura la même forme, indépendamment du project sur lequel vous serez en train de travailler. Létape de formatage deviendra transparente, et vous pourrez vous concentrer sur le contenu".

Complexité cyclomatique

A nouveau, un greffon pour flake8 existe et donnera une estimation de la complexité de McCabe pour les fonctions trop complexes. Installez-le avec pip install mccabe, et activez-le avec le paramètre --max-complexity. Toute fonction dans la complexité est supérieure à cette valeur sera considérée comme trop complexe.

Typage statique

→ Mypy

Tests unitaires

→ PyTest

Comme tout bon framework qui se respecte, Django embarque tout un environnement facilitant le lancement de tests; chaque application est créée par défaut avec un fichier tests.py, qui inclut la classe TestCase depuis le package django.test:

from django.test import TestCase

class TestModel(TestCase):
    def test_str(self):
        raise NotImplementedError('Not implemented yet')

Idéalement, chaque fonction ou méthode doit être testée afin de bien en valider le fonctionnement, indépendamment du reste des composants. Cela permet disoler chaque bloc de manière unitaire, et permet de ne pas rencontrer de régression lors de lajout dune nouvelle fonctionnalité ou de la modification dune existante. Il existe plusieurs types de tests (intégration, comportement, …​); on ne parlera ici que des tests unitaires.

Avoir des tests, cest bien. Sassurer que tout est testé, cest mieux. Cest là quil est utile davoir le pourcentage de code couvert par les différents tests, pour savoir ce qui peut être amélioré.

TODO: Vérifier comment les applications sont construites. Type DRF, Django Social Auth, tout ça.

Couverture de code

La couverture de code est une analyse qui donne un pourcentage lié à la quantité de code couvert par les tests. Attention quil ne sagit pas de vérifier que le code est bien testé, mais juste de vérifier quelle partie du code est testée. En Python, il existe le paquet coverage, qui se charge dévaluer le pourcentage de code couvert par les tests. Ajoutez-le dans le fichier requirements/base.txt, et lancez une couverture de code grâce à la commande coverage. La configuration peut se faire dans un fichier .coveragerc que vous placerez à la racine de votre projet, et qui sera lu lors de lexécution.

# requirements/base.text
[...]
coverage
django_coverage_plugin
# .coveragerc to control coverage.py
[run]
branch = True
omit = ../*migrations*
plugins =
    django_coverage_plugin

[report]
ignore_errors = True

[html]
directory = coverage_html_report
$ coverage run --source "." manage.py test
$ coverage report

    Name                      Stmts   Miss  Cover
    ---------------------------------------------
    gwift\gwift\__init__.py       0      0   100%
    gwift\gwift\settings.py      17      0   100%
    gwift\gwift\urls.py           5      5     0%
    gwift\gwift\wsgi.py           4      4     0%
    gwift\manage.py               6      0   100%
    gwift\wish\__init__.py        0      0   100%
    gwift\wish\admin.py           1      0   100%
    gwift\wish\models.py         49     16    67%
    gwift\wish\tests.py           1      1     0%
    gwift\wish\views.py           6      6     0%
    ---------------------------------------------
    TOTAL                        89     32    64%
    ----

$ coverage html

Ceci vous affichera non seulement la couverture de code estimée, et générera également vos fichiers sources avec les branches non couvertes.

Matrice de compatibilité

Décrire un fichier tox.ini

$ touch tox.ini

Configuration globale

Décrire le fichier setup.cfg

$ touch setup.cfg

Makefile

Pour gagner un peu de temps, nhésitez pas à créer un fichier Makefile que vous placerez à la racine du projet. Lexemple ci-dessous permettra, grâce à la commande make coverage, darriver au même résultat que ci-dessus:

# Makefile for gwift
#

# User-friendly check for coverage
ifeq ($(shell which coverage >/dev/null 2>&1; echo $$?), 1)
    $(error The 'coverage' command was not found. Make sure you have coverage installed)
endif

.PHONY: help coverage

help:
    @echo "  coverage to run coverage check of the source files."

coverage:
    coverage run --source='.' manage.py test; coverage report; coverage html;
    @echo "Testing of coverage in the sources finished."

The Zen of Python

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Environnement de développement

Concrètement, nous pourrions tout à fait nous limiter à Notepad ou Notepad++. Mais à moins daimer se fouetter avec un câble USB, nous apprécions la complétion du code, la coloration syntaxique, lintégration des tests unitaires et dun debugger, ainsi que deux-trois sucreries qui feront plaisir à nimporte quel développeur.

Si vous manquez didées ou si vous ne savez pas par où commencer:

Si vous hésitez, et même si Codium nest pas le plus léger (la faute à Electron…​), il fera correctement son travail (à savoir: faciliter le vôtre), en intégrant suffisament de fonctionnalités qui gâteront les papilles émoustillées du développeur impatient.

codium
Figure 3. Codium en action

Un terminal

A priori, les IDE [3] proposés ci-dessus fournissent par défaut ou via des greffons un terminal intégré. Ceci dit, disposer dun terminal séparé facilite parfois certaines tâches.

A nouveau, si vous manquez didées:

  1. Si vous êtes sous Windows, téléchargez une copie de Cmder. Il nest pas le plus rapide, mais propose une intégration des outils Unix communs (ls, pwd, grep, ssh, git, …​) sans trop se fouler.

  2. Pour tout autre système, vous devriez disposer en natif de ce quil faut.

terminal
Figure 4. Mise en abîme

Un gestionnaire de base de données

Django gère plusieurs moteurs de base de données. Certains sont gérés nativement par Django (PostgreSQL, MariaDB, SQLite); a priori, ces trois-là sont disponibles pour tous les systèmes dexploitation. Dautres moteurs nécessitent des librairies tierces (Oracle, Microsoft SQL Server).

Il nest pas obligatoire de disposer dune application de gestion pour ces moteurs: pour les cas dutilisation simples, le shell Django pourra largement suffire (nous y reviendrons). Mais pour faciliter la gestion des bases de données elles-même, et si vous nêtes pas à laise avec la ligne de commande, choisissez lune des applications dadministration ci-dessous en fonction du moteur de base de données que vous souhaitez utiliser.

Un gestionnaire de mots de passe

Nous en auront besoin pour gé(né)rer des phrases secrètes pour nos applications. Si vous nen utilisez pas déjà un, partez sur KeepassXC: il est multi-plateformes, suivi et sintègre correctement aux différents environnements, tout en restant accessible.

keepass

Un système de gestion de versions

Il existe plusieurs systèmes de gestion de versions. Le plus connu à lheure actuelle est Git, notamment pour sa (très) grande flexibilité et sa rapidité dexécution. Il est une aide précieuse pour développer rapidement des preuves de concept, switcher vers une nouvelle fonctionnalité, un bogue à réparer ou une nouvelle release à proposer au téléchargement. Ses deux plus gros défauts concerneraient peut-être sa courbe dapprentissage pour les nouveaux venus et la complexité des actions quil permet de réaliser.

Même pour un développeur solitaire, un système de gestion de versions (quel quil soit) reste indispensable.

Chaque "branche" correspond à une tâche à réaliser: un bogue à corriger (Hotfix A), une nouvelle fonctionnalité à ajouter ou un "truc à essayer" [4] (Feature A et Feature B). Chaque "commit" correspond à une sauvegarde atomique dun état ou dun ensemble de modifications cohérentes entre elles.[5] De cette manière, il est beaucoup plus facile pour le développeur de se concenter sur un sujet en particulier, dans la mesure où celui-ci ne doit pas obligatoirement être clôturé pour appliquer un changement de contexte.

git workflow
Figure 6. Git en action

Cas pratique: vous développez cette nouvelle fonctionnalité qui va révolutionner le monde de demain et daprès-demain, quand, tout à coup (!), vous vous rendez compte que vous avez perdu votre conformité aux normes PCI parce les données des titulaires de cartes ne sont pas isolées correctement. Il suffit alors de:

  1. sauver le travail en cours (git add . && git commit -m [WIP])

  2. revenir sur la branche principale (git checkout main)

  3. créer un "hotfix" (git checkout -b hotfix/pci-compliance)

  4. solutionner le problème (sans doute un ; en trop ?)

  5. sauver le correctif sur cette branche (git add . && git commit -m "Did it!")

  6. récupérer ce correctif sur la branche principal (git checkout main && git merge hotfix/pci-compliance)

  7. et revenir tranquillou sur votre branche de développement pour fignoler ce générateur de noms de dinosaures rigolos que lunivers vous réclame à cor et à a cri (git checkout features/dinolol)

Finalement, sachez quil existe plusieurs manières de gérer ces flux dinformations. Les plus connus sont Gitflow et Threeflow.

Décrire ses changements

→ parler de la bonne structure dun commit.


1. Ce nest pas moi qui le dit, cest la doc du projet
2. Oui, en Python, il ny a que quatre cercles à lEnfer
3. Integrated Development Environment
4. Oui, comme dans "Attends, jessaie vite un truc, si ça marche, cest beau."
5. Il convient donc de sabstenir de modifier le CSS dune application et la couche daccès à la base de données, sous peine de se faire huer par ses relecteurs au prochain stand-up.