← Retourner à la page principale


Conseils et recommandations générales en WLangage® *

* Les marques "PC SOFT®" et "WINDEV®" sont déposées par la société PC SOFT®.

Charte de programmation
Eléments de projet
Procédures
Variables
Instructions
Fonctions WLangage®
Réduire le code source à maintenir
Respecter l'ordre des instructions
Etre rigoureux
Autres informations
La charge mentale

Charte de programmation

Privilégier la charte de programmation livrée en standard par PCSOFT®. Elle est acceptée et utilisée par la majorité des développeurs en WLangage®.
La convention de nommage et le préfixage permet de connaître rapidement le type d'éléments, ainsi que le type de variable et sa portée.

// Quelques exemples : 
sNom // Le préfixe s correspond à une variable de type chaîne 
nAge // Le préfixe n correspond à une variable de type entier 
rTVA // Le préfixe r correspond à une variable de type réel 
dNaissance // Le préfixe d correspond à une variable de type date 
FEN_TABLE_SOCIETE // Le préfixe FEN_ correspond à un élément de type fenêtre 
gtaPersonnel // Le préfixe gta correspond à une variable de type tableau associatif avec une portée globale 
gm_sCheminFicherDll // Le préfixe gm_s correspond à un membre d'une classe de type chaîne avec une portée globale 

Pour consulter la charte de programmation :

  1. aller dans la description du projet
  2. cliquer sur l'onglet "Options"
  3. dans le chapitre "Charte de programmation"
  4. cliquer sur le bouton "Editer la charte"

Eléments de projet

Nommer les éléments de projet de manière simple et explicite dans l'"esprit" de la nomenclature PCSOFT®.
Si la langue du code est en français alors utiliser des noms en français pour tous les éléments du projet (fenêtres, états, champs, variables, procédures, classes, ...).


Procédures

Typer les valeurs de retour de toutes les procédures.

Privilégier les procédures internes si leur exécution n'est faite que depuis un seul code appelant.

Préfixer les appels aux procédures globales par le nom de la collection de procédures.

Rédiger le nom des fonctions avec un verbe à l'infinitif. L'objectif est que le nom de la fonction puisse donner suffisamment d'informations pour éviter d'aller lire le code. Une fonction doit faire une seule chose. Son code idéal doit être séquentiel, lisible de haut en bas. Le nombre de ses paramètres doit être réduit (moins de 4).

Paramètres de procédures

Utiliser les types de variables énumérations et combinaisons dans les paramètres de procédures. Ceci afin d'éviter des erreurs et de clarifier le code, de fiabiliser l'appel aux procédures et de faciliter la maintenance.

Nom de fenêtre dans l'appel d'une procédure :

UneProcédure("NomFenêtre")

Doit s’écrire :

UneProcédure(NomFenêtre..Nom)

S'il y a un renommage du nom de la fenêtre, l'éditeur changera automatiquement le nom dans l’appel de la procédure alors que dans le premier code, le développeur doit penser à modifier le nom de la fenêtre entre guillemets.

Ne pas oublier l'attributs d'extension dans la déclaration de la procédure. Ceci afin de faciliter l'appel à la procédure :

PROCÉDURE UneProcédure(LOCALE sNomFenêtre est une chaîne <nom de fenêtre>)
PROCÉDURE UneProcédure(LOCALE sNomEtat est une chaîne <nom d'état>)

Variables

Typer toutes les variables selon leur contenu. Ne pas oublier également les paramètres des procédures.

Eviter la négation dans le nom d'une variable de type booléen.

Limiter le nombre de variables globales.

Nommer les constantes en majuscules.

Préfixer les utilisations des variables globales par le nom de la collection de procédures.


Instructions

Instruction SORTIR

Éviter d'utiliser l'instruction SORTIR dans les boucles. Ceci afin d'améliorer la lisibilité du code et visualiser rapidement toutes les conditions de sortie de la boucle.

Remplacer un couple (POUR , SORTIR) par une boucle TANTQUE.
A titre d'exemple, le premier code ci-dessous peut être remplacé par le deuxième code :

tabTP est un tableau de TâcheParallèle
bSortir est un booléen = Faux
TANTQUE PAS bSortir
    bSortir = Vrai
    POUR i = tabTP..Occurrence _À_ 1 _PAS_ -1
        SI PAS tabTP[i]..Terminée ALORS
            bSortir = Faux
            Temporisation(25, tempoSourisEtClavier)
            SORTIR
        FIN
    FIN
FIN
tabTP est un tableau de TâcheParallèle
nIndice est un entier = 1
TANTQUE (nIndice = 1 À tabTP..Occurrence)
    SI tabTP[nIndice]..Terminée ALORS
        nIndice++
    SINON
        Temporisation(25, tempoSourisEtClavier)
    FIN
FIN

Instruction SELON CAS

Dans une instruction SELON, toujours ajouter le mot-clé AUTRE CAS à la fin.
Et si vous êtes sûr qu'il n'y aura aucun passage dans ce traitement alors y ajouter un point d'arrêt par programmation avec le mot-réservé STOP .
Cela permet de s'arrêter lors du débogage du projet; et pourquoi pas d'ajuster l'instruction SELON en conséquence.

SELON nAge
 	CAS 0 À 18 : Trace("Mineur") 
 	CAS 19 À 150 : Trace("Majeur") 
 	AUTRE CAS : 
 		STOP // Ne devrait jamais s'arrêter ici
FIN

Fonctions WLangage®

En WLangage®, il existe plusieurs fonctions qui renvoient un résultat identique. N'utiliser qu'une seule et uniquement qu'une seule de ces fonctions.

Le résultat de ces fonctions sont identiques mais une recherche dans le code source obligerait et pénaliserait les développeurs à effectuer plusieurs recherches plutôt qu'une seule.
Voici une liste d'exemples :

Utiliser ces fonctions Ne pas utiliser
DateSys() DateDuJour()
HeureSys() Maintenant()
DateVersJourDeLaSemaine() DateVersJour()
API() Appel32()
FenPrécédente() ParentObjet(MaFenêtre)
FenRepeint() MultitâcheRepeint()
Ferme(<NomFenêtre>) FenEtat(<NomFenêtre>, Inexistant)
DonneFocusEtRetourUtilisateur() RepriseSaisie()
gValeurMémorisée() gLien()
ChaîneVersNumérique() Val()
fRepExiste() fRépertoireExiste()
fRepTemp() fRépertoireTemp()
PROCÉDURE FONCTION
... ...

Réduire le code source à maintenir

S'il y a moins de code alors il y aura moins de bugs. Le code source sera moins long à comprendre et plus facile à lire et à maintenir. C'est un fait reconnu que le code est lu beaucoup plus souvent qu'il n'est écrit. C'est pourquoi sa lisibilité est essentielle.
Cependant réduire et optimiser la taille du code ne signifie pas nécessairement que le code sera plus rapide.
A titre d'exemple, le premier code ci-dessous peut être réduit par le deuxième :

bEstFichierTexte est un booléen
SI (fFichierExiste(sUnFichier) = Vrai) ALORS
    SI (fExtraitChemin(sUnFichier, fExtension) = ".txt") ALORS
        bEstFichierTexte = Vrai
    SINON
        bEstFichierTexte = Faux
    FIN
SINON
    bEstFichierTexte = Faux
FIN

ST_Rectangle est une Structure
    nX est un entier
    nY est un entier
    nLargeur est un entier
    nHauteur est un entier
FIN
stUnRectangle est un ST_Rectangle
stUnRectangle.nX = 0
stUnRectangle.nY = 0
stUnRectangle.nLargeur = 0
stUnRectangle.nHauteur = 0

ST_Point est une Structure
    nX est un entier
    nY est un entier
FIN
stUnPoint est un ST_Point
stUnPoint.nX = 0
stUnPoint.nY = 0

tabEntier est un tableau de entier = [1, 2, 3, 4, 5]
nSomme est un entier
POUR i = 1 _À_ tabEntier..Occurrence
    nSomme += tabEntier[i]
FIN
bEstFichierTexte est un booléen
bEstFichierTexte = (fFichierExiste(sUnFichier) _ET_ (fExtraitChemin(sUnFichier, fExtension) = ".txt"))

UnRectangle est un Rectangle
VariableRAZ(UnRectangle)

UnPoint est un Point
VariableRAZ(UnPoint)

tabEntier est un tableau de entier = [1, 2, 3, 4, 5]
nSomme est un entier = Somme(tabEntier)

Respecter l'ordre des instructions

Dans le code ci-dessous la trace affichera "30/04/2019" au lieu de "31/04/2019" souhaité.

dTemp est une Date = "20120923"
dTemp..Jour = 31 // Cette ligne est à déplacer avant la fonction Trace()
dTemp..Mois = 4
Trace(DateVersChaîne(dTemp, "JJ/MM/AAAA"))

Etre rigoureux

L'exécutation du code ci-dessous est correcte, c'est-à-dire il donne le bon résultat quelque soit la réponse de l'utilisateur :

SI (OuiNon("Etes-vous un humain ?") = Vrai) ALORS
    Info("L'utilisateur a dit Oui.")
SINON
    Info("L'utilisateur a dit Non.")
FIN

Cependant la documentation précise que le résultat de la fonction OuiNon() est une des deux constantes Oui ou Non.
Alors avec ce code rien de trop problématique mais avec le code ci-dessous, le résultat n'est pas celui attendu. Le traitement est bloqué dans une boucle infinie. Mais pourquoi ?

nChiffreDeux est un entier

PROCÉDURE INTERNE piInit()
    ThreadPause(100)
    nChiffreDeux = 2
FIN

ThreadExécute("piInit", threadNormal, piInit)
TANTQUE (ThreadEtat("piInit") <> Inexistant)
    Temporisation(25, tempoSourisEtClavier)
FIN
Trace( nChiffreDeux )

Pour revenir dans un état cohérent, remplacer Inexistant par threadInexistant.


Algorithmie et base de la programmation en WLangage®

Nous allons créer un algorithme simple qui calcule la somme de deux nombres.

Procédure de base

Voici une déclaration rapide de la procédure :
PROCÉDURE AdditionnerDeuxNombres(nombre1, nombre2)
RENVOYER nombre1 + nombre2
Cette procédure attend deux paramètres (nombre1 et nombre2) et renvoie la somme des 2 paramètres grâce à l'opérateur +.
Nous pouvons la tester avec le code suivant :
soit laSomme = AdditionnerDeuxNombres(1, 2)
Info("La somme de 1+2 vaut : "+laSomme)
// La somme de 1+2 vaut : 3
Le résultat '3' est bien celui attendu. C'est-à-dire que la variable nommée 'laSomme' vaut 3.
Maintenant que se passe-t-il si au lieu d'envoyer les nombres 1 et 2, nous envoyons les lettres "a" et "b" à cette procédure ?
soit laSomme = AdditionnerDeuxNombres("a", "b")
Info("La somme de 'a'+'b' vaut : "+laSomme)
// La somme de 'a'+'b' vaut : ab
Le résultat est "ab" car l'opérateur + permet aussi la concaténation de 2 chaînes.
Or à la base, nous souhaitons créer une procédure qui calcule la somme de 2 nombres. Donc notre procédure doit attendre uniquement 2 nombres.

Première modification : le type des paramètres

Modifions la procédure en conséquence pour qu'elle respecte l'énoncé :
PROCÉDURE AdditionnerDeuxNombres(nombre1 est un numérique, nombre2 est un numérique)
RENVOYER nombre1 + nombre2
Nous venons d'ajouter un type aux paramètres de la procédure. Grâce à cela, la procédure nommée "AdditionnerDeuxNombres" attend réellement 2 numériques.
soit laSomme = AdditionnerDeuxNombres("a", "b")
Info("La somme de 'a'+'b' vaut : "+laSomme)
// La somme de 'a'+'b' vaut : 0
Le programme essaie de convertir les caractères "a" et "b" en numérique. Il n'y arrive pas donc dans la procédure, les variables nombre1 et nombre2 vaut 0 (zéro), d'où le résultat final 0 (zéro).
En sachant cela, si nous envoyons les caratères "1" et "2" alors le programme arrive à les convertir en numérique :
soit laSomme = AdditionnerDeuxNombres("1", "2")
Info("La somme de '1'+'2' vaut : "+laSomme)
// La somme de '1'+'2' vaut : 3
Nous pouvons aller plus loin en précisant le type de retour de la procédure.

Deuxième modification : le type de retour de la procédure

Nous savons que la somme de 2 nombres de type numérique est aussi un nombre de type numérique :
PROCÉDURE AdditionnerDeuxNombres(nombre1 est un numérique, nombre2 est un numérique) : numérique
RENVOYER nombre1 + nombre2
Rajouter le type de retour d'une procédure est une bonne pratique. Si le code source de la procédure est longue de plusieurs lignes alors un développeur pourra facilement savoir le type de retour de la procédure en observant simplement sa déclaration et sa signature :
PROCÉDURE AdditionnerDeuxNombres(nombre1 est un numérique, nombre2 est un numérique) : numérique
D'un seul coup d'oeil, le développeur sait que la procédure additionne 2 nombres grâce au nom de la procédure. De plus, elle attend 2 numériques en paramètre (nombre1 et nombre2) et elle retourne un numérique.

Troisième modification : la qualité du code

Pour améliorer la lisibilité, nous pouvons utiliser la chartre de programmation du WLangage® et ajouter un commentaire à la procédure :
// Résumé : Calcule la somme de 2 nombres.
// Paramètres :
//	xNombre1 (numérique) : le 1er nombre
//	xNombre2 (numérique) : le 2ème nombre
// Valeur de retour :
// 	numérique : somme des 2 paramètres (1er nombre + 2ème nombre)
//
PROCÉDURE AdditionnerDeuxNombres(xNombre1 est un numérique, xNombre2 est un numérique) : numérique
RENVOYER (xNombre1 + xNombre2)
Ainsi nous pouvons également mieux utiliser cette procédure en ajoutant le type de la variable (numérique) retourné :
xLaSomme est un numérique = AdditionnerDeuxNombres(1, 2)
Info("La somme de '1'+'2' vaut : "+xLaSomme)
// La somme de '1'+'2' vaut : 3
De plus comme nous avons utilisé le type de variable "Numérique", les nombres décimaux avec virgule sont gérés :
xLaSomme est un numérique = AdditionnerDeuxNombres(1.2, 2.3)
Info("La somme de '1.2'+'2.3' vaut : "+xLaSomme)
// La somme de '1.2'+'2.3' vaut : 3.5
Si nous avions utilisé le type de variable "Entier" au niveau des paramètres de cette fonction alors les nombres décimaux avec virgule n'auraient pas été gérés. Et la fonction auraient renvoyée une valeur incohérente.
PROCÉDURE AdditionnerDeuxEntiers(nNombre1 est un entier, nNombre2 est un entier) : entier
RENVOYER (nNombre1 + nNombre2)
nLaSomme est un numérique = AdditionnerDeuxEntiers(1.4, 2.3)
Info("La somme de '1.4'+'2.3' vaut : "+nLaSomme)
// La somme de '1.4'+'2.3' vaut : 3 - la valeur est incohérente : 1.4+2.3 <> 3
Ajouter le mot-clé LOCALE car les paramètres xNombre1 et xNombre2 ne sont pas modifiés dans la procédure et ne doivent pas l’être.
Ainsi ces deux paramètres sont passés par valeur. C’est-à-dire que si les paramètres sont modifiés dans la procédure, le traitement appelant la procédure récupérera le paramètre avec sa valeur NON modifiée.
PROCÉDURE AdditionnerDeuxNombres(LOCALE xNombre1 est un numérique, LOCALE xNombre2 est un numérique) : numérique
RENVOYER (xNombre1 + xNombre2)

Tests unitaires

Nous venons de coder une procédure qui additionne 2 nombres.
Maintenant il nous faut tester cette procédure.
Nous utiliserons la fonction dbgVérifieEgalité() qui vérifie si 2 expressions sont égales :
// Tester avec des nombres : 
// Test avec résultat positif et l'élément neutre (zéro)
dbgVerifieEgalite( AdditionnerDeuxNombres(1, 2), 3)
dbgVerifieEgalite( AdditionnerDeuxNombres(2, 1), 3) // Commutativité de l'addition
dbgVerifieEgalite( AdditionnerDeuxNombres(4, 0), 4)
dbgVerifieEgalite( AdditionnerDeuxNombres(0, 4), 4) // Commutativité de l'addition
// Test avec résultat négatif
dbgVerifieEgalite( AdditionnerDeuxNombres(-1, -2), -3)
dbgVerifieEgalite( AdditionnerDeuxNombres(-2, -1), -3) // Commutativité de l'addition
// Test avec résultat décimal
dbgVerifieEgalite( AdditionnerDeuxNombres(1.2, 2.3), 3.5)
dbgVerifieEgalite( AdditionnerDeuxNombres(2.3, 1.2), 3.5)  // Commutativité de l'addition
dbgVerifieEgalite( AdditionnerDeuxNombres(-1.2, -2.3), -3.5)
dbgVerifieEgalite( AdditionnerDeuxNombres(-2.3, -1.2), -3.5)  // Commutativité de l'addition
// Test avec résultat à 0 (zéro)
dbgVerifieEgalite( AdditionnerDeuxNombres(1, -1), 0)
dbgVerifieEgalite( AdditionnerDeuxNombres(-1, 1), 0)  // Commutativité de l'addition
dbgVerifieEgalite( AdditionnerDeuxNombres(0, 0), 0)

// Tester avec des chaînes : 
// Test avec résultat positif et l'élément neutre (zéro)
dbgVerifieEgalite( AdditionnerDeuxNombres("1", "2"), 3)
dbgVerifieEgalite( AdditionnerDeuxNombres("2", "1"), 3) // Commutativité de l'addition
dbgVerifieEgalite( AdditionnerDeuxNombres("4", "0"), 4)
dbgVerifieEgalite( AdditionnerDeuxNombres("0", "4"), 4) // Commutativité de l'addition
// Test avec résultat négatif
dbgVerifieEgalite( AdditionnerDeuxNombres("-1", "-2"), -3)
dbgVerifieEgalite( AdditionnerDeuxNombres("-2", "-1"), -3) // Commutativité de l'addition
// Test avec résultat décimal
dbgVerifieEgalite( AdditionnerDeuxNombres("1.2", "2.3"), 3.5)
dbgVerifieEgalite( AdditionnerDeuxNombres("2.3", "1.2"), 3.5)  // Commutativité de l'addition
dbgVerifieEgalite( AdditionnerDeuxNombres("-1.2", "-2.3"), -3.5)
dbgVerifieEgalite( AdditionnerDeuxNombres("-2.3", "-1.2"), -3.5)  // Commutativité de l'addition
// Test avec résultat à 0 (zéro)
dbgVerifieEgalite( AdditionnerDeuxNombres("1", "-1"), 0)
dbgVerifieEgalite( AdditionnerDeuxNombres("-1", "1"), 0)  // Commutativité de l'addition
dbgVerifieEgalite( AdditionnerDeuxNombres("0", "0"), 0)
// Test avec caractère et chaîne
dbgVerifieEgalite( AdditionnerDeuxNombres("a", "b"), 0)
dbgVerifieEgalite( AdditionnerDeuxNombres("nombre", "chiffre"), 0)

// A FAIRE : Tests aux extrémités (positif et négatif) : ajouter "est un numérique (*)"

Autres informations

Il faut garder à l'esprit que coder en langage WL5® ne signifie pas que le développeur ne doit pas prendre en compte les problématiques mémoire lorsqu'il programme. En effet, une vraie prise en compte est nécessaire même si le raisonnement est différent de ce qu'il peut-être dans d'autres langages.
Les outils de PCSOFT® vous permettent donc de pratiquement tout faire. Pour certains, il est tentant de considérer qu'ils sont suffisants et qu'il ne faille pas aller voir ailleurs de temps à autre. C'est une grave erreur ! En effet, bien que ces outils permettent de tout faire, cela ne signifie pas qu'ils soient la meilleure solution dans tous les cas. Ainsi chaque langage de programmation présente des avantages et des inconvénients en fonction du type d'application à implémenter et du domaine cible.
Avec moins de lignes de code, les développeurs ont plus de temps à consacrer au besoin même du métier. Le développeur peut se concentrer sur les besoins utilisateurs à forte valeur ajoutée.
L'une des forces du WLangage® est sa simplicité. Cependant comme n'importe quel langage de programmation, WLangage® réserve aussi sa part de subtilités que l'on saisit avec l'expérience.


La charge mentale

Un développeur a 2 choses en main : le problème qu'on lui demande de résoudre et la technique qui lui permet de le résoudre.
On cherche à avoir la technique la plus efficace afin de se concentrer sur le problème.
L'objectif : c'est le problème. La technique n'est qu'un moyen.
Donc il existe des outils pour simplifier la vie des développeurs, pour rendre la partie technique plus efficace. C'est tout naturel.
Seulement ces outils censés simplifier la vie ajoutent en réalité une "charge mentale" aux développeurs.
Cette charge mentale est la connaissance/maitrise de ces outils tels que GIT, Docker, les différents frameworks, ...