Catégorie : développement

Tout ce qui concerne le développement en général, que ce soit des choses générales, ou des choses bien précises. Cela va de la gestion de projet à la recherche du fonctionnement de pointeurs en C.

Go langage

Mémo go

Uniquement des notes sur ce qui est « original » dans le langage « go », par rapport aux autres langages.

Variables

Déclaration

Par rapport à tous les autres langages, il faut le faire à l’envers :
var x int
Et pour un tableau : var [nom] [[longueur]][type], exemple :
var s [3]int

Initialisation avec des valeurs

Pas d’explication, un exemple direct suffit : arr := [2]int{2, 4}

Inférence de type

Une déclaration complète d’une variable serait var i int = 12
Une déclaration ultra simplifiée acceptable est i := 12 et dans ce cas, Go va tenter de deviner le type (« type inference »).

Comparaison de tableaux

La déclaration de variables se fait toujours en précisant la longueur, et au moment de comparer, il faut que les tableaux aient exactement la même longueur, sinon on aura une erreur. C’est pour cela que les tableaux sont moins utilisés que le type slice.

Comparaison = avoir des types identiques

Go n’essaie jamais de transformer les variables en booléens pour pouvoir les comparer, c’est à nous transformer une variable ou l’autre de manière à ce qu’elles aient toujours le même type.

Création d’une variable dans un « if »

Exemple direct : if b:= a / 2; b > 5 {...} else {...}
La variable b n’existera que dans le bloc if / else.

Boucle for / range

s := "Bonjour"
for k, v := range s {
    fmt.Println(k, v, string(v))
}

k correspond à l’index, et n’est pas toujours incrémenté que de 1, selon l’encodage. C’est la méthode à utiliser pour parcourir une chaîne en utf8. Car via un classique for, Go parcourra la chaîne « à la » C, c’est à dire que un caractère = un octet (ce qui n’est pas le cas en utf8).

Switch / case

Par défaut, les « break » sont implicites, pas besoin de les mettre. Si on veut exécuter l’instruction du « case » suivant (= comme en C ou JavaScript), il faut le dire explicitement via « fallthrough« .

Il est possible de créer une variable qui ne vivra que le temps du switch / case, exemple :

switch l := len(word); word {
    ...blabla...
}

Contrairement aux autres langages, il ne faut pas mettre plusieurs case d’affilée pour gérer plusieurs cas au même endroit, il suffit de les séparer par une virgule, exemple pour continuer le précédent :

switch l := len(word); word {
case 1 < l && l < 10, word == "ok":
    fmt.Println("Soit mot : 1 < longueur < 10, soit il vaut 'ok'")
}

Fonctions

Appels

Tous les appels de fonction sont par valeur. Jamais par référence. Nombre, booléen, ainsi que les tableaux.

Retours

Le type résultat de retour est après les parenthèses des paramètres et avant l’accolade ouvrante :
func mult(a, b) int {
    return a + b
}

Vous pouvez renvoyer plusieurs valeurs ! Les résultats de retour sont entre parenthèse après les parenthèses des paramètres et avant l’accolade ouvrante :
func divAndRemainder(a, b) (int, int) {
    return a / b, a % b
}
d, r := divAndRemainder(2, 3)
d, _ := divAndRemainder(2, 3)
_, d := divAndRemainder(2, 3)

Vous pouvez nommer les paramètres de retour et les « remplir » dans la fonction. Il faut simplement ajouter return sans rien pour qu’il renvoie le retour sans y toucher :
func divAndRemainder(a, b) (div, remainder int) {
    div = a / b
    remainder = a % b
    return
}
d, r := divAndRemainder(2, 3)
d, _ := divAndRemainder(2, 3)
_, d := divAndRemainder(2, 3)

Fonctions en tant que variable

Exemple direct :
func myFunc() {
    f := func(a int) int {
        return a + 1
    }
    fmt.Println(f(1))
}

Fonctions en tant que paramètres

Exemple direct d’un paramètre qui doit être « une fonction qui prend un entier en tant que paramètre et renvoie un entier » :
func myFunc(a int, f func(int) int) {
    fmt.Println(f(a))
}

Tableaux

Base : var a [2]int

fmt.Println("Array a", a, len(a))

Mieux : slice var a []int

La différence entre slice et array ? On ne précise pas le nombre d’éléments.

Pointeurs

Très proche du C. Un exemple direct suffit :
var p *int
i := 4 // int declaration
p = &i // assign the address of the variable to the pointer
*p = 45 // change the value of the variable i
fmt.Println(p, *p) // prints address then value

Switch / case

C’est l’inverse du C : les break sont implicites, et si on veut vraiment continuer sur le case suivant, il faut utiliser le mot-clé fallthrough :

switch i := myfunc(); {
case i < 0:
    fmt.Println("negative")
    fallthrough
case i == 0:
    fmt.Println("zero")
default:
    fmt.Println("positive")
}

Conversions

Bizzareries

Pointeurs et structures

Vous pouvez déclarer une structure, et la créer dynamiquement, puis créer un pointeur vers elle. En C, quand on utilise les pointeurs, l’indirection pour accéder aux membres est "->", donc on ne peut pas se tromper. Ici, on utilise la même syntaxe, que ce soit avec une structure, directement, ou avec un pointeur, donc indirectement. Exemple concret :

type People struct {
    name string
    height int
}
var value People
value.name = "Olivier"
value.height = 190
ptr := &value
ptr.name = "Thibault"
ptr.height = 185

Donc on écrit value.name et ptr.name alors que l’un est une struct et l’autre un pointeur. Je n’aime pas du tout cela, cela prête clairement à confusion.

Astuces classiques

Site officiel

Packages

Serveur asynchrone TCP Python. Et le client C# Unity !

Deux exemples très courts pour vous mettre sur les rails, qui envoient et reçoivent du binaire « pur » = très peu de bande passante, avec une connexion persistante.

Je vous donne deux envois-réception qui devraient vous permettre de faire tous vos envois binaires :

  1. C# : le client envoie un octet, qui correspond à un booléen, pour dire s’il est en big ou little endian ;
  2. C# : le client envoie un message encodé en UTF-8 (oui j’ai trouve la solution qui fonctionne !) ;
  3. Python : le serveur lit ce booléen ;
  4. Python : le serveur lit le message et le dit à voix haute (sous Windows) ;
  5. Python : le serveur envoie un entier non signé, puis deux float ;
  6. C# : le client lit l’entier non signé puis deux floats.

Avec ça, vous avez de quoi comprendre et faire tous les échanges que vous voulez !

Serveur asynchrone TCP Python

import asyncio
import struct
from asyncio import StreamWriter, StreamReader
import pythoncom
import win32com.client as win32_client
HOST = '192.168.1.31'
PORT = 9696
async def handle(reader: StreamReader, writer: StreamWriter):
    is_little_endian = False
    buffer = bytearray(100)
    addr = writer.get_extra_info('peername')
    print(f"Connected with {addr!r}")
    is_little_endian, = struct.unpack_from(
        '?', await reader.read(struct.calcsize('c'))
    )
    print(f'{is_little_endian=}')
    data = await reader.read(4096)
    message = data.decode('utf8')
    pythoncom.CoInitialize()
    speak = win32_client.Dispatch('SAPI.SpVoice')
    speak.Speak(message)
    print(f"Received {message!r} from {addr!r}")
    print(f"Send: {message!r}")
    float1 = 1.1
    float2 = 2.2
    struct.pack_into(
        # =: native order, std. size & alignment
        # H: unsigned short
        # f: float
        "=Hff",
        buffer, 0, 1, float1, float2)
    writer.write(buffer)
    await writer.drain()
    print("Close the connection")
    writer.close()
async def main():
    server = await asyncio.start_server(handle, HOST, PORT)
    print(f'Serving on {server.sockets[0].getsockname()}')
    async with server:
        await server.serve_forever()
asyncio.run(main())

Client C# Unity

using System;
using System.IO;
using System.Net.Sockets;
using UnityEngine;
public class Connexion : MonoBehaviour
{
    public string server;
    public string message;
    public ushort port;
    private void Start()
    {
        // working sample to send text:
        byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
        byte isLittleEndian = BitConverter.IsLittleEndian ? (byte)1 : (byte)0;
        TcpClient client = new TcpClient(server, port);
        NetworkStream stream = client.GetStream();
        // Send the message to the connected TcpServer.
        stream.WriteByte(isLittleEndian);
        stream.Write(data, 0, data.Length);
        Debug.Log($"Sent: {message}");
        // read sample
        BinaryReader reader = new BinaryReader(stream);
        uint len = reader.ReadUInt16();
        var x = reader.ReadSingle();
        var y = reader.ReadSingle();
        Debug.Log("len=" + len);
        Debug.Log($"x={x}, y={y}");
    }
}

Pour la note, ces deux exemples paraissent simples, mais ils m’ont pris un temps fou, et je n’ai eu aucune réponse au bout de 3 semaines sur stackoverflow…

Python : EAFP vs LBYL

Très souvent vous pouvez avoir deux styles de codes différents qui font la même chose en Python :


import os

if os.path.exists("fichier.txt"):
    os.unlink("fichier.txt")

import os
try:
    os.unlink("fichier.txt")
except OSError:  # levé si le fichier n'existe pas
    pass

Alors, lequel choisir ?

Personnellement, j’ai toujours préféré le premier choix, et pourtant… dans la documentation officielle, ils le déconseillent !

Pourquoi cela ? Explication : l’opposé de EAFP, c’est LBYL.

EAFP : Easier to ask for forgiveness than permission

Plus facile de demander pardon que la permission. Ce style de codage très utilisé en Python suppose l’existence de clés ou d’attributs valides et intercepte les exceptions si l’hypothèse s’avère fausse. Ce style propre et rapide se caractérise par la présence de nombreuses déclarations try and except. La technique contraste avec le style LBYL commun à de nombreux autres langages tels que C.

LBYL : Look before you leap

Réfléchir avant d’agir.

Ce style de codage teste explicitement les conditions préalables avant d’effectuer des appels ou des recherches. Ce style contraste avec l’approche EAFP et se caractérise par la présence de nombreuses déclarations if.

Dans un environnement multi-thread, l’approche LBYL peut risquer d’introduire une condition de concurrence entre « la vérification » et « la validation ». Par exemple, le code : if key in mapping: return mapping [key] peut échouer si un autre thread supprime la clé du mappage après le test, mais avant la recherche. Ce problème peut être résolu avec des verrous ou en utilisant l’approche EAFP.

Django et git : bonnes pratiques / idées pour faire du CI

Conseil d’un ami :

  • gitlab n’est pas 100% opensource, ils proposent une édition communautaire limité et la totalité des fonctionnalités est dispo avec la version entreprise, leur modèle économique c’est de brider la version CEE (community) dans pas mal de coin pour te pousser à prendre une licence, perso je trouve que c’est plus un freeware qu’autre chose
  • gitlab est pas mal mais honnêtement pour l’avoir beaucoup (vraiment beaucoup) utilisé dans le passé ce n’est pas la meilleur alternative.

Si tu cherche à mettre en place un truc je te conseille fortement de jeter un œil à :

Cette stack est un peu plus compliquée à mettre qu’un gitlab, mais c’est très puissant, surtout la partie gerrit qui transcende la manière de faire des revues de code.

Si le code Django / Python n’est pas correct, il y a des méthodes pour améliorer les choses notamment :

  • https://nvie.com/posts/a-successful-git-branching-model/
  • https://semver.org/

Il est également important de mettre en place un système de « core developer » même au sein d’un projet privé en entreprise, afin de garantir la cohérence des données et l’intégrité de l’historique. En effet, si tout le monde à les droits d’écriture sur tout, ça finira toujours à un moment donné à partir dans tous les sens… L’idée, c’est de donner les droits d’écriture à seulement 2-3 personnes de confiance qui ont un certain niveau et à tous les autres imposer de faire des forks dans leur namespace gitlab et de proposer des merge request (l’équivalent des pull requests avec github). Ainsi, la revue de code est obligatoire et il n’est plus possible de merger du mauvais code… et les développeurs « standard » (sans droits donc) ont juste 2-3 commandes de base à connaître pour bosser avec les autres. Sans ce genre de système cela ne peut pas réellement fonctionner car git à été developpé pour ce mode de fonctionnement (au même titre que svn et d’autres gestionnaire de version) et donc son workflow est idéal dans ce cadre.

Fonctionner comme ça m’a permis de :

  1. coder un petit robot qui réagissait au merge requests proposé et donc qui checkait tout un tas de choses automatiquement et qui proposait des commandes si les choses n’allaient pas
  2. responsabiliser les gens de l’équipe en les rendant responsables de leur propre forks
  3. faire monter en compétence l’équipe qui était ultra frileuse à faire du versionning puis au final à pris goût au truc…

J’ai fais des petites formations au début pour leur expliquer l’intérêt et expliquer la sémantique des changement et qu’il est important de penser ses commits de manière propre. Cela permet :

  • dans certains cas de retirer (revert) des bugs complet d’un seul coup,
  • de faire du debug de manière automatique sans rien toucher et de trouver les changements responsable d’un bug (git bisect),
  • de faire un gain énorme de qualité au final
  • d’avoir des historiques clairs avec des messages de commit ayant une réelle plus value (exemple)
  • d’automatiser tout un tas d’actions qui vont augmenter la qualité globale du projet et vont réduire les taches inutiles et donc laisser plus de temps pour les trucs fun à faire.

C’est gagnant gagnant, mais au début les gens râlent… il faut juste s’y préparer et avec le temps ils changent d’avis !

Utiliser Chrome pour imprimer / générer un PDF

Google Chrome a plein d’options cachées en ligne de commande, et parmi celles-ci, une option qui permet d’imprimer en PDF.
De plus, si, comme moi, vous ne voulez pas les numéros de pages / nom de fichier en haut et en bas, il vous faut ajouter l’option --print-to-pdf-no-header

google-chrome --headless --disable-gpu --run-all-compositor-stages-before-draw --print-to-pdf-no-header --print-to-pdf=Bureau/test.pdf file://Bureau/git_book.html

Et vous aurez votre fichier PDF qui sera généré ;

[0730/112224.164336:INFO:headless_shell.cc(615)] Written to file Bureau/test.pdf

JavaScript : comment surcharger console.log()

J’avais besoin de décaler mes log pour voir rapidement si j’étais, ou pas, à l’intérieur d’une fonction.

Eh oui, après plusieurs années, même si on peut faire des points d’arrêt, JavaScript est là pour répondre à des événements utilisateurs et autres… donc il ne faut souvent pas arrêter pendant l’exécution ou la visualisation d’une page Web… on n’a toujours pas d’autre choix que de faire le bon console.log() des familles qui fait que le développement est extrêmement ralenti…

Je me suis donc crée les fonctions log_in(), log() et log_out().
Ainsi, quand j’entre dans une fonction, j’appelle log_in(), et tous mes log() dans cette fonction seront décalés, ce qui fait que je vois immédiatement où sont appelés mes logs ! Ensuite, ne pas oublier de faire un log_out().

let logLevel = 0;
let log = function () {
    let args = Array.from(arguments);
    if (logLevel > 0) {
        args = [Array(logLevel).fill(' ').join('')].concat(args);
    }
    console.log.apply(null, args);
};
let log_in = function () {
    log.apply(null, arguments);
    logLevel++;
}, log_out = function (s) {
    if (logLevel>0) {
        logLevel--;
    }
    log.apply(null, arguments);
};

Django : comment vérifier si on est en mode debug dans le template

Vous trouverez l’information dans beaucoup de sites, et cela semble très simple : dans votre template, il suffit de faire :

{% if not debug %}Je suis en debug !{% endif %}

En réalité cela ne suffit pas.

Il faut dans votre fichier settings.py, configurer correctement les adresses IP’s qui précisent qu’on est / ou pas / en mode « développement » :

INTERNAL_IPS = ['127.0.0.1', ]

(Notez que ce code peut être largement optimisé et dépendant de l’environnement, par exemple j’ai fait un settings.py qui prend cela en compte)

curl

Curl hints / aide

curl -v -H "Host:www.djangoproject.com" https://www.olivierpons.fr/

Django : faire des pages d’erreur sur mesure (404, 500, etc.)

Lorsque vous voulez faire des pages d’erreur sur mesure, c’est très simple… une fois que vous savez !
En effet, la documentation n’est pas très claire…

Voici un résumé de mon expérience, pour faire des pages d’erreur sur mesure (404, 500, etc.).
Il faut d’abord savoir que les erreurs sont des fonctions appelées (= vous ne pouvez pas le faire via les vues génériques).
Ensuite, la documentation donne un exemple, mais ce n’est pas assez.
Grâce aux raccourcis de Django, vous avez la fonction render() à laquelle vous pouvez passer un template et un contexte (= donc des variables).
Je me suis servi de cela pour créer un dictionnaire qui contient les erreurs, et les passer dans dans le contexte avec la clé title et content.


Voici le code des vues qui affiche une erreur « proprement » :

from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
VIEW_ERRORS = {
    404: {'title': _("404 - Page not found"),
          'content': _("A 404 Not found error indicates..."), },
    500: {'title': _("Internal error"),
          'content': _("A 500 Internal error means..."), },
    403: {'title': _("Permission denied"),
          'content': _("A 403 Forbidden error means ..."), },
    400: {'title': _("Bad request"),
          'content': _("A 400 Bad request error means ..."), }, }
def error_view_handler(request, exception, status):
    return render(request, template_name='errors.html', status=status,
                  context={'error': exception, 'status': status,
                           'title': VIEW_ERRORS[status]['title'],
                           'content': VIEW_ERRORS[status]['content']})
def error_404_view_handler(request, exception=None):
    return error_view_handler(request, exception, 404)
def error_500_view_handler(request, exception=None):
    return error_view_handler(request, exception, 500)
def error_403_view_handler(request, exception=None):
    return error_view_handler(request, exception, 403)
def error_400_view_handler(request, exception=None):
    return error_view_handler(request, exception, 400)


Une fois les vues faites, il faut aller dans la déclaration principale de vos vues. Donc le fichier urls.py qui est la racine de votre projet. Si vous mettez le code ailleurs, il sera ignoré.

Dedans, déclarez vos fonctions qui gèrent les erreurs :

handler404 = 'app.views.errors.error_404_view_handler'
handler500 = 'app.views.errors.error_500_view_handler'
handler403 = 'app.views.errors.error_403_view_handler'
handler400 = 'app.views.errors.error_400_view_handler'

Et enfin, dans vos templates, créez un fichier errors.html dans lequel vous pouvez construire votre page HTML qui gère l’erreur « proprement », avec, en plus dans le contexte, les variables {{ title }} et {{ content }} qui affichent respectivement le titre et le détail de l’erreur.

Maintenant, comment, en mode « développement », tester ces pages ? Dans la pratique vous ne pouvez pas, car en mode développement, une erreur affiche les informations de déboguage, et pas vos pages !
La solution : faire une URL qui « simule » l’erreur. Exemple avec 404 : vous ajouter dans vos urls : path('404/', error_404_view_handler), et puis d’afficher votre URL adéquate, soit http://localhost:8000/404/ et vous verrez l’erreur !