← Retourner à la page principale



Questions fréquentes et aides techniques en WLangage® *

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

 - L'AGL est récalcitrant, que faire ?
 - La gestion des erreurs et des exceptions
 - Précision des réels et des numériques
 - Comment se connecter à une base de données (HFSQL, MSSQL Server, Classic) ?
 - Comment consulter et afficher un document externe ?
 - Comment faire choisir un document externe ?
 - Comment optimiser l'affichage d'une table basée sur une requête ?
 - Comment extraire un mémo binaire avec la fonction HExtraitMémo() ?
 - Comment parcourir tous les éléments d'un tableau ?
 - Analyse du mot-clé MesParamètres
 - Fonction LigneCommande()
 - Exemple d'algorithmie et base de la programmation en WLangage®


Étapes à effectuer lorsque l'AGL WINDEV® est récalcitrant

Voici les étapes non-exhaustives lien externe à effectuer lorsque l'AGL WINDEV® est récalcitrant :
1) Réparer le projet (mise à niveau)
2) Recompiler le projet
3) Fermer le projet
4) Supprimer les dossiers "<NomDuProjet.cpl>", "Historique" et "Sauvegarde" du projet
5) (optionnel) Supprimer le fichier <NomDuProjet.env>
6) Ouvrir à nouveau le projet



Gestion des erreurs et des exceptions

En complément des pages d'aide PCSOFT® sur la Gestion des erreurs en exécution lien externe.
Il existe 2 types d'erreurs en exécution :
- les erreurs non fatales et non bloquantes (exemple : la lecture du contenu d'un fichier externe qui n'existe pas)
- les erreurs fatales et bloquantes (exemple : la division d'un nombre par zéro)


Gérer les erreurs non fatales

La gestion des erreurs non fatales peut être effectuée dans le traitement avec les solutions suivantes :

SI PAS <condition> ALORS
    // Code de traitement de l'erreur non fatale...
    // Pour obtenir toutes les informations sur la dernière erreur non fatale : 
    Trace(ErreurInfo(errComplet))
SINON
    // Suite du code...
FIN
SI ErreurDétectée() ALORS 
    // Code de traitement de l'erreur non fatale...
    // Pour obtenir toutes les informations sur la dernière erreur non fatale : 
    Trace(ErreurInfo(errComplet))
SINON
    // Suite du code...
FIN
ErreurChangeParamètre(...)
CAS ERREUR: 
    // Code de traitement de l'erreur non fatale...
    // Pour obtenir toutes les informations sur la dernière erreur non fatale : 
    Trace(ErreurInfo(errComplet))

- En cliquant sur le lien "Si Erreur : par programme" dans l'entête d'un code


Gérer les erreurs fatales / les exceptions

La gestion des erreurs fatales peut être effectuée dans le traitement avec les solutions suivantes :

QUAND EXCEPTION DANS
    // Code pouvant provoquer une erreur fatale...
FAIRE
    // Code de traitement de l'erreur fatale...
    // Pour obtenir toutes les informations sur la dernière erreur fatale : 
    Trace(ExceptionInfo(errComplet))
FIN
// Suite du code
CAS EXCEPTION:
    // Code de traitement de l'erreur fatale...
    // Pour obtenir toutes les informations sur la dernière erreur fatale : 
    Trace(ExceptionInfo(errComplet))

- En cliquant sur le lien "Quand exception : par programme" dans l'entête d'un code


Traiter une erreur fatale propagée au niveau du projet

Pour traiter une erreur fatale propagée au niveau du code d'initialisation du projet :

QUAND EXCEPTION
    sFichierException est une chaîne = ComplèteRep(fRepExe()) + DateVersChaîne(DateSys(),"AAMMJJ") +"_"+ "Exceptions.log"
    
    // Écriture du dump de débogage
    dbgSauveDumpDébogage(fExtraitChemin(sFichierException, fDisque+fFichier+fRépertoire) + ".wdump")
    
    // Écriture de l'erreur fatale dans un fichier log
    fSauveTexte(sFichierException, ...
        "------------------------------------------------------------------" +RC+ ...
        "------------------------------------------------------------------" +RC+ ...
        DateVersChaîne(DateSys(),"JJ/MM/AAAA")+"-"+HeureVersChaîne(HeureSys(),"HH:MM:SS")+" : " +RC+ ...
        "--------------------------------------------------------EXCEPTION-" +RC+ ...
        ExceptionInfo(errComplet) +RC+ ... // Dernière erreur fatale
        "-----------------------------------------------------------ERREUR-" +RC+ ...
        ErreurInfo(errComplet) +RC+ ... // Dernière erreur non fatale
        fChargeTexte(sFichierException) ...
    )
FIN


Précision des variables de type réel et numérique

En complément de la page d'aide PCSOFT® sur les variables de type réel lien externe.

rTemp1 est un réel
rTemp2 est un réel

// Utiliser la syntaxe 0n devant la valeur pour obtenir une précision maximale
rTemp1 = 0n1.234567
rTemp2 = 0n1.234568
// L'opérateur "=" est précis à 10e-6 près sur les réels
SI (rTemp1 <> rTemp2) ALORS
    // La conversion des réels en chaîne utilise un algorithme complexe donc la trace est correcte
    Trace( "NOK", rTemp1, rTemp2 )
FIN
rTemp1 = 0n1.2345678
rTemp2 = 0n1.2345679
SI (rTemp1 = rTemp2) ALORS
    // La conversion des réels en chaîne utilise un algorithme complexe donc la trace est correcte
    Trace( "OK", rTemp1, rTemp2 )
FIN


// (*) signifie que le format de la variable s'adapte automatiquement à la valeur contenue
xTemp1 est un numérique (*)
xTemp2 est un numérique (*)

// Utiliser 0n devant la valeur pour forcer l'affectation d'un type numérique
xTemp1 = 0n1.23456789012345678901234567890123456789
xTemp2 = 0n1.23456789012345778901234567890123456788
SI (xTemp1 <> xTemp2) ALORS
    Trace( "NOK", xTemp1, xTemp2 )
FIN

Pour les calculs avancés, le type réel est plus rapide que le type monétaire. De même que le type monétaire est plus rapide que le type numérique.


Limites des différents types d'entiers en WL

En complément de l'aide sur les différents types d'entiers lien externe :

Type d'entiers Min Max
Entier sur 1 octet - TINYINT -128 127
Entier sur 2 octets - SMALLINT -32 768 32 767
Entier sur 4 octets
(ou Entier) - INT
-2 147 483 648 2 147 483 647
Entier sur 8 octets - BIGINT -9 223 372 036 854 775 808 9 223 372 036 854 775 807
Entier sans signe sur 1 octet
- TINYINT UNSIGNED
0 255
Entier sans signe sur 2 octets
- SMALLINT UNSIGNED
0 65 535
Entier sans signe sur 4 octets
(ou Entier sans signe) - INT UNSIGNED
0 4 294 967 295
Entier sans signe sur 8 octets
- BIGINT UNSIGNED
0 18 446 744 073 709 551 615

Pour le type "Entier système" : si compilé en 32 bits voir "Entier sur 4 octets", si compilé en 64 bits voir "Entier sur 8 octets".

Les valeurs exactes de ces différents types d'entiers se trouvent dans le fichier "Limites.wl" présent dans le répertoire "...\Personnel\Externe\"


Variable numérique

Si une variable numérique est manipulée comme un opérateur logique (booléen) alors le zéro est équivalent à Faux et toutes les autres valeurs sont équivalentes à Vrai.
Donc les deux lignes de code sont équivalentes :

SI nIndice ALORS ... 
SI (nIndice <> 0) ALORS ... 

PCSOFT® recommande d'utiliser la première syntaxe.
Pour une optimisation du temps d'exécution du code alors utiliser la première syntaxe sinon pour une meilleure compréhension du code, utiliser la seconde syntaxe.



Procédures de connexion à une base de données

HFSQL client / serveur :

PROCÉDURE PUBLIQUE GLOBALE HConnexion_HFSQL(cnxConnexion est une Connexion, LOCALE sHPasse est une chaîne, LOCALE nNumPort est un entier sans signe, LOCALE sServeur est une chaîne, LOCALE sBaseDeDonnees est une chaîne, LOCALE sUtilisateur est une chaîne, LOCALE sMotDePasse est une chaîne) <métier> : booléen

// En cas d'erreur, on sort en renvoyant Faux et on propage l'erreur
ErreurChangeParamètre(epRenvoyerErreur, Faux)

// Permet de modifier le délai de connexion
HCS.DélaiRéponse = INTRANET // Temps d'attente de réponse faible 

// Permet de modifier la taille de la trame de données envoyée
// Dans le cas général, il n'est pas nécessaire d'intervenir sur la taille des trames
HCS.TailleTrame = (HCS.DélaiRéponse = INTERNET) ? hTrameInternet SINON hTrameDéfaut

//Niveau de sécurité/protection souhaité :
HSecurite(0)
// - 0 : mécanisme de sécurité désactivé. La vitesse d'écriture dans les fichiers de données est maximale.
// - 1 : mécanisme de sécurité activé. La vitesse d'écriture est plus lente qu'avec l'option 0, mais la sécurité est assurée lors de l'écriture dans les fichiers de données.
// - 2 : mécanisme de sécurité maximal. La vitesse d'écriture dans les fichiers de données est plus lente qu'avec l'option 1. 

// Description de la connexion
cnxConnexion..Serveur 		= sServeur + ((nNumPort > 0) ? ":"+NumériqueVersChaîne(nNumPort) SINON "")
cnxConnexion..BaseDeDonnées 	= sBaseDeDonnees
cnxConnexion..Utilisateur 	= sUtilisateur
cnxConnexion..MotDePasse 	= sMotDePasse
cnxConnexion..Provider 		= hAccèsHFClientServeur
cnxConnexion..Accès 			= hOLectureEcriture
cnxConnexion..OptionsCurseur = hCurseurClient
// https://doc.pcsoft.fr/fr-FR/?9000124&name=informations_optionnelles_connexion
cnxConnexion..InfosEtendues 	= ""
cnxConnexion..Cryptage 		= hCryptageRC5_16
cnxConnexion..Compression 	= (HCS.DélaiRéponse = INTERNET)

// Optimiser les temps de connexion aux bases HFSQL Client/Serveur : https://doc.pcsoft.fr/fr-FR/?9000176

// Affectation de la connexion à tous les fichiers de données
HChangeConnexion("*", cnxConnexion) 

// Fixe le mot de passe utilisé pour créer ou pour ouvrir les fichiers de données
SI PAS (sHPasse ~= "") ALORS
    HPasse("*", sHPasse)
FIN

// Ouverture de la connexion
HOuvreConnexion(cnxConnexion)

// Désactive la gestion des triggers applicatifs
HGèreTrigger(Faux)

// Gestion des caches dans le moteur HFSQL Client/Serveur (1 à 5000)
// La valeur par défaut sur un PC est 1000.
HGèreCache(hTaillePage, 2000)

// Permet de modifier la priorité de l'application appelante. 
// Cette valeur est comprise entre -10 (basse priorité) et 10 (haute priorité). 
// Par défaut, la priorité est de 0 (priorité normale). 
HPriorite(cnxConnexion, 5)

SI PAS (sBaseDeDonnees ~= "") ALORS
    RENVOYER (Position(TAB+HListeBaseDeDonnées(cnxConnexion, hLstNormal)+TAB, TAB+sBaseDeDonnees+TAB) > 0)
SINON
    RENVOYER Vrai
FIN

MS SQL Server (avec le connecteur natif SQL Server lien externe - Accès Natif) :

// https://doc.pcsoft.fr/fr-FR/?5515003
// https://doc.pcsoft.fr/fr-FR/?5515005&name=connecteur_natif_sql_server_specificites_remarques
PROCÉDURE PUBLIQUE GLOBALE HConnexion_MSSQL(cnxConnexion est une Connexion, LOCALE sServeur est une chaîne, LOCALE nNumPort est un entier sans signe, LOCALE sBaseDeDonnees est une chaîne, LOCALE sUtilisateur est une chaîne, LOCALE sMotDePasse est une chaîne) <métier> : booléen

// En cas d'erreur, on sort en renvoyant Faux et on propage l'erreur
ErreurChangeParamètre(epRenvoyerErreur, Faux)

// H.ModeSQLServer (Variable) : https://doc.pcsoft.fr/fr-FR/?3044276&name=hmodesqlserver_variable
// 0 pour forcer l'utilisation du Connecteur Natif SQL Server via OLE DB (wd280sqlserver.dll ou wd280sqlserver64.dll).
// 1 pour utiliser le Connecteur Natif installé. Si les deux versions du Connecteur Natif sont installées, le Connecteur Natif via OLE DB est utilisé en priorité.
// 3 pour forcer l'utilisation du Connecteur Natif SQL Server via ODBC (wd280sqlserver2.dll ou wd280sqlserver2_64.dll).
H.ModeSQLServer = 1 

// Description de la connexion
cnxConnexion..Serveur 		= sServeur + ((nNumPort > 0) ? ","+NumériqueVersChaîne(nNumPort) SINON "")
cnxConnexion..BaseDeDonnées 	= sBaseDeDonnees
cnxConnexion..Utilisateur 	= sUtilisateur
cnxConnexion..MotDePasse 	= sMotDePasse
cnxConnexion..Provider 		= hAccèsNatifSQLServer
cnxConnexion..Accès 			= hOLectureEcriture
cnxConnexion..OptionsCurseur = hCurseurClient
// https://doc.pcsoft.fr/fr-FR/?9000124&name=informations_optionnelles_connexion
// WD Command Timeout : http://vincent-lecomte.blogspot.fr/2013/03/wd17-le-delai-dattente-de-la-requete.html
cnxConnexion..InfosEtendues = "Initial Catalog="+sServeur+"; WD Connection Timeout=10; WD Command Timeout=120" 

// Ouverture de la connexion
HOuvreConnexion(cnxConnexion)

// Affectation de la connexion à tous les fichiers de données
HChangeConnexion("*", cnxConnexion)

// Désactive la gestion des triggers applicatifs
HGèreTrigger(Faux)

RENVOYER Vrai

HFSQL Classic :

PROCÉDURE PUBLIQUE GLOBALE HConnexion_Classic(cnxConnexion est une Connexion, LOCALE sHPasse est une chaîne, LOCALE sCheminBDD est une chaîne) <métier> : booléen

// En cas d'erreur, on sort en renvoyant Faux et on propage l'erreur
ErreurChangeParamètre(epRenvoyerErreur, Faux)

// Optimisation de l'accès aux données en HFSQL Classic.
HChangeLocalisation("*", hDisque)

// Description de la connexion
//VariableRAZ(cnxConnexion)
cnxConnexion..Serveur 		= ""
cnxConnexion..BaseDeDonnées 	= ""
cnxConnexion..Utilisateur 	= ""
cnxConnexion..MotDePasse 	= ""
cnxConnexion..Provider 		= hAccèsHF7
cnxConnexion..Accès 			= hOLectureEcriture
cnxConnexion..OptionsCurseur = hCurseurClient
cnxConnexion..InfosEtendues 	= ""
cnxConnexion..Cryptage 		= hCryptageRC5_16

// Affectation de la connexion à tous les fichiers de données
HChangeConnexion("*", cnxConnexion) 

// Fixe le mot de passe utilisé pour créer ou pour ouvrir les fichiers de données
SI PAS (sHPasse ~= "") ALORS
     HPasse("*", sHPasse)
FIN

// Ouverture de la connexion
HOuvreConnexion(cnxConnexion)

// Désactive la gestion des triggers applicatifs
HGèreTrigger(Faux)

// Modifie le chemin d'accès à la BDD
RENVOYER HChangeRep("*", ComplèteRep(sCheminBDD))


Consulter et afficher un document externe

PROCÉDURE fLance(LOCALE sCheminEtNomDuFichier est une chaîne)
// Ouvre le document dans une nouvelle fenêtre non modale (traitement de texte, tableur, lecteur PDF ou éditeur d'images)
SI (OuvreDocument(sCheminEtNomDuFichier, "", odOptionFenêtreMaxi) = odEchec) ALORS 
    // Ouvre directement le document dans son application associée
    SI PAS LanceAppliAssociée(sCheminEtNomDuFichier, "open") ALORS
        // Ouvre la fenêtre "Ouvrir avec..."
        LanceAppli("rundll32.exe shell32.dll, OpenAs_RunDLL """+ sCheminEtNomDuFichier +"""")
    FIN
FIN

Un exemple d'utilisation :

fLance( "C:\logo.jpg" )
fLance(fRepExe() +[fSep()]+ "logo.jpg")


Faire sélectionner un document externe à l'utilisateur

PROCÉDURE fSélectionne(LOCALE sExtensionParDefaut est une chaîne = "jpg") : booléen
sCheminEtNomDuFichier est une chaîne

SI PAS (sExtensionParDefaut ~= "jpg") ALORS
     // Ouvre le sélecteur de fichiers avec les extensions de fichier
     sCheminEtNomDuFichier = fSélecteur("", "", "Sélectionnez un fichier...", "Tous les fichiers (*.*)" +TAB+ "*.*" +RC+ "Traitement de texte" +TAB+ "*.doc;*.docx;*.odt;*.rtf" +RC+ "Tableur" +TAB+ "*.xls;*.xlsx;*.ots;*.csv" +RC+ "Document PDF" +TAB+ "*.pdf" +RC+ "Image" +TAB+ "*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.svg", sExtensionParDefaut)
SINON
     // Ouvre le sélecteur d'image uniquement avec les extensions de fichier "Image"
     sCheminEtNomDuFichier = fSélecteurImage("", "", "Sélectionnez une image...")
     SI PAS fEstUneImage(sCheminEtNomDuFichier) ALORS
          RENVOYER faux
     FIN
FIN

// Teste l'existence du fichier externe
RENVOYER fFichierExiste(sCheminEtNomDuFichier) 

Un exemple d'utilisation :

fSélectionne("jpg")
fSélectionne("docx")
fSélectionne("")


Optimiser l'affichage d'un champ table basé sur une requête SQL

1) Tester la requête SQL directement dans le SGBD pour mesurer son temps d'exécution :
   - copier / coller le code de la requête SQL dans le SGBD
   - remplacer à la main les paramètres nécessaire pour la requête SQL
   - lancer l'exécution de la requête SQL
   - si le temps d'exécution est trop important alors modifier la requête SQL

2) Tester la requête SQL dans l'éditeur de requête pour mesurer son temps d'exécution :
   - créer une nouvelle requête puis copier / coller le code SQL dans l'éditeur de requête
   - faire un Go uniquement de la requête (tester l'élément) puis saisir les paramètres demandés et nécessaire
   - si le temps d'exécution est trop important alors vérifier la connexion avec la base de données (matériel, réseau, port, anti-virus, etc...) et vérifier également l'utilisation de fonctions qui peuvent ralentir l'exécution comme par exemple HSimuleRéseau(), HTTPSimuleRéseau() ou HSécurité()

3) Dans la fenêtre du champ table :
   - utiliser les propriétés ..AffichageActif et ..TotauxActif :

<NomChampTable>..AffichageActif = Faux
<NomChampTable>..TotauxActif = Faux
// Exécution de la requête SQL
// Traitements  : TableAffiche(), etc...
<NomChampTable>..TotauxActif = Vrai
<NomChampTable>..AffichageActif = Vrai

   - vérifier le mode d'initialisation de la requête : hRequeteSansCorrection / hRequeteSansCorrectionHF / etc...
   - vérifier le temps d'exécution cumulé de tous les événements du champ table, principalement : "Affichage d'une ligne" et "Fin d'initialisation"
   - utiliser l'analyseur de performance lien externe dans le traitement lent (fonctions AnalyseurDébut() et AnalyseurFin()) afin de cibler précisement le traitement qui provoque la lenteur



Extraire un mémo binaire avec la fonction HExtraitMémo()

Il y a 2 pré-requis avant d'utiliser le code ci-dessous, il faut :
- se positionner sur l'enregistrement en utilisant une fonction H* (exemple : HLitRecherche, HLitRecherchePremier, ...)
- utiliser le SGBD HFSQL

// sNomTable : table de la base de données 
// sNomRubrique : nom de la rubrique de type mémo binaire 
// sCheminDossier : chemin / répertoire où sera extrait le fichier externe 
PROCÉDURE HExtraitMémo_HFSQL(LOCALE sNomTable est une Source de Données, LOCALE sNomRubrique est une chaîne, LOCALE sCheminDossier est une chaîne) : chaîne
// Se positionner sur l'enregistrement en utilisant une fonction H* (exemple : HLitRecherche, HLitRecherchePremier, ...)
// Ne fonctionne que sous HFSQL

// Récupère les informations du mémo
sHInfoMémo est une chaîne = HInfoMémo(sNomTable, sNomRubrique)
// S'il existe un mémo binaire avec des informations disponibles
SI (sHInfoMémo <> "") _ET_ (ExtraitChaîne(sHInfoMémo, 2, TAB) <> "") ALORS
    // S'il existe un mémo binaire avec des informations disponibles
    sCheminFichier est une chaîne = ComplèteRep(sCheminDossier) + fExtraitChemin(ExtraitChaîne(sHInfoMémo, 2, TAB), fFichier+fExtension)
    QUAND EXCEPTION DANS 
        HExtraitMémo(sNomTable, sNomRubrique, sCheminFichier)
    FAIRE
        ExceptionPropage() // Exceptions possibles : "Répertoire inexistant", "Fichier déjà ouvert par une application", ...
    SINON
        SI fFichierExiste(sCheminFichier) ALORS
            RENVOYER sCheminFichier
        FIN
    FIN
FIN
RENVOYER ""

Un exemple d'utilisation :

SI HLitRecherchePremier(SOCIETE, IDSOCIETE, 3) ALORS
    sCheminFichier est une chaîne = HExtraitMémo_HFSQL(SOCIETE, SOCIETE.Logo..Nom, fRepExe())
    LanceAppliAssociée(sCheminFichier)
FIN


Parcours d'un tableau

Parcours d'un tableau à une dimension

En complément de l'aide pour l'Instruction POUR TOUT (parcours de tableaux) lien externe.

tabChaîne est un tableau de chaîne
TableauAjoute(tabChaîne, "UneValeur1")
TableauAjoute(tabChaîne, "UneValeur2")
TableauAjoute(tabChaîne, "UneValeur3")
// tabChaîne = ["UneValeur1", "UneValeur2", "UneValeur3"]

// Parcourir un tableau à une dimension
POUR TOUT sValeur, nIndice, nCompteur DE tabChaîne 
    Trace(nCompteur, nIndice, sValeur) 
FIN 

// Le résultat de la trace vaut : 
// 1 1 UneValeur1
// 2 2 UneValeur2
// 3 3 UneValeur3

Parcours d'un tableau associatif

En complément de l'aide pour l'Instruction POUR TOUT (parcours de tableaux associatifs) lien externe.

taTableau est un tableau associatif (ccSansCasse+ccSansAccent+ccSansEspace, "", wlChaîne) de chaîne
//                            avec (Combinaison d'options, Valeur par défaut, Type de clé) 
taTableau["UneCle1"] = "UneValeur1"
taTableau["UneCle2"] = "UneValeur2"
SI taTableau["UneCle3"]..Vide ALORS // Remplir si la clé n'est pas déjà présente 
    taTableau["UneCle3"] = "UneValeur3"
FIN
// taTableau = [["UneCle1", "UneValeur1"], ["UneCle2", "UneValeur2"], ["UneCle3", "UneValeur3"]]

// Parcourir un tableau associatif (clé, valeur)
POUR TOUT sValeur, sCle, nCompteur DE taTableau
    Trace(nCompteur, sCle, sValeur)
FIN

// Le résultat de la trace vaut : 
// 1 UneCle1 UneValeur1
// 2 UneCle2 UneValeur2
// 3 UneCle3 UneValeur3


Analyse du mot-clé MesParamètres

Le mot-clé MesParamètres lien externe permet de manipuler tous les paramètres d'une procédure.

Par exemple, la procédure ci-dessous calcule la somme de ses 3 paramètres en entrée :

PROCÉDURE CalculerSomme(nX est un entier, nY est un entier, nZ est un entier) : entier
    nSomme est un entier
    POUR i = 1 _À_ MesParamètres..Occurrence
        nSomme += MesParamètres[i] // nSomme = nSomme + MesParamètres[i] 
    FIN
    RENVOYER nSomme
FIN

// Le résultat de la trace vaut 7 
Trace( CalculerSomme(1, 2, 4) )

L'exemple ci-dessus est une procédure avec seulement 3 paramètres.
Et donc la procédure peut être écrite simplement sans utiliser le mot-clé MesParamètres :

PROCÉDURE CalculerSomme(nX est un entier, nY est un entier, nZ est un entier) : entier
    RENVOYER (nX + nY + nZ)
FIN

Mais pourrait-on faire une procédure générique qui calcule la somme de tous ses paramètres sans en connaitre le nombre exact ? Oui, grâce à l'utilisation du signe "*".

// Le nombre de paramètres passé à la procédure est variable
PROCÉDURE CalculerSomme(*) : entier
    nSomme est un entier
    POUR i = 1 _À_ MesParamètres..Occurrence
        nSomme += MesParamètres[i] 
    FIN
    RENVOYER nSomme
FIN

// Le résultat de la trace vaut 15 
Trace( CalculerSomme(1, 2, 4, 8) )

L'exemple ci-dessus est une procédure avec des paramètres ayant le même type (entier).
Et donc la procédure peut être écrite simplement sans utiliser le mot-clé MesParamètres :

PROCÉDURE CalculerSomme(tabEntier est un tableau d'entier) : entier
    RENVOYER Somme(tabEntier)
FIN

// Le résultat de la trace vaut 15 
Trace( CalculerSomme([1, 2, 4, 8]) )

Le mot-clé MesParamètres est puissant car il accepte aussi les paramètres de type différent. Et ils peuvent directement être passés à une autre procédure / fonction.
Ces paramètres variables apportent un grand nombre de possibilités afin de rendre les traitements génériques.

PROCÉDURE OuvrirFenetre(sNomFenêtre est une chaîne <nom de fenêtre>, *)
    Trace(sNomFenêtre)
    Ouvre(sNomFenêtre, MesParamètres[2 A])
FIN


La fonction LigneCommande()

La fonction LigneCommande() correspond à l'argument "char *argv[]" en langage C++.
A compléter avec les fonctions ProjetInfo(piNomProjet) et exeInfo(exeNom).



Algorithmie et base de la programmation en WLangage®

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


La 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 (*)"