Cours VB.NET

Date de mise à jour : 06/06/2009


XVI. Un peu de théorie pour en déduire de bonnes règles
XVI-A. Diverses sortes de programmation
XVI-A-1. Programmation impérative
XVI-A-2. Programmation structurée
XVI-A-3. Language fonctionnelle
XVI-A-4. Programmation procédurale
XVI-A-5. Programmation défensive
XVI-A-6. Programmation sécurisée
XVI-A-6-a. Conception
XVI-A-6-b. Réalisation
XVI-A-6-c. Exécution
XVI-A-7. Programmation évènementielle
XVI-A-8. Programmation Objet
XVI-B. Programmation 'procédurale' ou 'objet' ?
XVI-B-1. L'approche procédurale
XVI-B-2. Approche Objet
XVI-B-3. Conclusion
XVI-C. Programmation 'procédurale' : faire de bonnes procédures
XVI-C-1. Approche procédurale, analyse 'descendante' ou 'ascendante'
XVI-C-2. Pourquoi utiliser des procédures ?
XVI-C-3. La 'cohésion' de la procédure doit être importante
XVI-C-4. Le 'couplage' entre procédures doit être modéré
XVI-C-5. Squelette d'un programme
XVI-C-6. Les paramètres
XVI-C-7. Utiliser une 'Sub' ou une 'Function' ?
XVI-C-8. Programmation défensive
XVI-D. Programmation 'objet' : faire de bonnes Classes
XVI-D-1. Rappel
XVI-D-2. Pourquoi utiliser 'Classe' et 'Objet' ?
XVI-D-3. Identifier les objets
XVI-D-4. Faire un 'couplage' modéré
XVI-D-5. Conserver une bonne abstraction et une bonne cohérence
XVI-D-6. Créer des méthodes par paires
XVI-D-7. L'encapsulation doit être bonne
XVI-D-8. Initialisez les données dans le constructeur d'une Classe
XVI-D-9. Problèmes liés à l'héritage
XVI-E. Faire du bon 'code'
XVI-E-1. Bonnes variables
XVI-E-1-a. Utilisez Option Strict=On et Option Explicit=On
XVI-E-1-b. Donnez à chaque variable un seul rôle
XVI-E-1-c. Évitez les variables avec des significations non évidentes
XVI-E-1-d. Initialisez les variables dès leur déclaration
XVI-E-1-e. Utilisez le principe de proximité
XVI-E-1-f. Travaillez sur des variables qui restent actives le moins de temps possible
XVI-E-1-g. Si vous codez la valeur d'une variable en 'dur', utilisez plutôt des constantes
XVI-E-1-h. Groupez les instructions liées
XVI-E-1-i. Réduisez la portée des variables (problème des variables globales)
XVI-E-1-j. Les Booléens sont des Booléens
XVI-E-1-k. Utiliser les variables Date pour stocker les dates
XVI-E-2. Règles de bonne programmation
XVI-E-2-a. Séparer l'interface utilisateur et l'applicatif
XVI-E-2-b. Utiliser le typage fort
XVI-E-2-c. Forcer la déclaration des variables et les conversions explicites
XVI-E-2-d. Utiliser des constantes ou des énumérations
XVI-E-2-e. Vérifier la validité des données que reçoit une Sub une Function ou une Classe
XVI-E-2-f. Se méfier du passage de paramètres 'par valeur' ou par 'référence'
XVI-E-2-g. Structurez le code, évitez les Goto
XVI-E-2-h. Ne faire aucune confiance à l'utilisateur du logiciel
XVI-E-2-i. Rendre le code lisible par tous
XVI-E-3. Rendre le code lisible : commentaires, noms de variables
XVI-E-3-a. Ajoutez des commentaires
XVI-E-3-b. Choisissez les noms de procédures et de variables avec soin
XVI-E-3-c. Éclaircir, aérer le code


XVI. Un peu de théorie pour en déduire de bonnes règles


XVI-A. Diverses sortes de programmation

Programmation impérative.

Programmation structurée.

Programmation fonctionnelle.

Programmation procédurale.

Programmation évènementielle.

Programmation défensive.

Programmation Objet.


XVI-A-1. Programmation impérative

On en fait sans le savoir:

Le programmeur spécifie explicitement l'enchaînement des instructions devant être exécutées :

Fais ceci, puis cela.

Fais ceci, si cela est vrai.

Fais ceci, tant de fois .


Un programme impératif est composé de différentes instructions indiquant de manière explicite comment résoudre un problème.


On l'a déjà vu, les instructions sont effectuées de manière séquentielle:

Instruction1
Instruction2
Instruction3
...

Il y a des choix:

If.. Then

Il y a des boucles:

For .. next

L'action de base dans la programmation impérative est l'affectation, c'est-à-dire la modification de la valeur associée à une variable du programme.

A=3
L'affectation va être soit effectuée en séquences successives, soit avec des choix, soit itérée selon les compositions définies par les algorithmes.

En programmation impérative, on travaille sur le modèle de la machine de Turing, avec une mémoire centrale et des instructions qui modifient son état grâce à des assignations successives.

Le langage machine et l'assembleur sont des langages impératifs. Le code en Visual Basic l'est aussi.

Exemple: réinitialiser un tableau en donnant la valeur 0 à tous ses éléments.

10 Dim tableau (10) as Integer
20 Dim compteur As Integer
30 compteur= 0
40 boucle:
50 tableau(compteur)=0
60 compteur= compteur + 1
70 If Compteur <11 Then Goto boucle
Ici on utilise un Basic très ancien avec un goto pour faire une boucle et des numéros de ligne; c'est pas très 'lisible'.


Et c'est quoi la programmation non impérative?

C'est par exemple la programmation déclarative, elle consiste à énoncer les propriétés d'un système de résolution (à les déclarer) plutôt qu'à décrire les opérations à effectuer comme dans le cas de la programmation impérative. VB ne permet pas la programmation déclarative.

Un programme déclaratif définit (ou "déclare") différentes entités et leurs relations, à charge ensuite pour le programme d'utiliser ces relations pour résoudre le problème.


XVI-A-2. Programmation structurée

Pour éviter les programmes 'spaghetti', on a structuré et utilisé les procédés suivant:


Découpage en fonction:

L'approche structurée découpe le problème en fonctions:

L'analyse se fait de manière descendante: on découpe un problème complexe en problèmes plus simples qui sont eux mêmes découpés en problèmes plus simples. On découpe jusqu'à ne plus avoir que des problèmes simples.

Il existe aussi l'analyse ascendante: ayant à sa disposition des procédures simples, on les assemble en les faisant appeler par des procédures plus complexes pour atteindre la solution.

Si le projet est entièrement nouveau, on fait plutôt une analyse descendante; si on travaille sur un projet possédant déjà toutes les procédures simples, on raisonnera en analyse ascendante.

Rien d'empêche d'avoir une analyse mixte.

Les programmeurs doivent donc décomposer leur code en petites fonctions, assez petites pour être facilement comprises et claires.


Utilisation de variables locales:

En général les programmes doivent éviter d'utiliser des variables globales.

Au lieu de cela, les sous-programmes doivent utiliser des variables locales et agir sur des arguments ou paramètres qui leurs sont envoyés.


Organisation hiérarchique simple du code:

La programmation structurée recommande une organisation hiérarchique simple du code. Pour cela on utilise des structures de contrôles while, Do Loop, for, if .. then .. else.

Il est également recommandé de n'avoir qu'un point d'entrée pour chaque boucle (et un point de sortie unique dans la programmation structurée originelle).


Eviter les 'Goto'

L'instruction Goto, directement héritée des instructions de saut des langages machines (Jmp), était nécessaire dans les langages primitifs (Fortran, Assembleur) comme instruction de base permettant de réaliser des boucles et autres structures de contrôles (voir exemple sur la programmation impérative).

En programmation structurée (depuis les années 1970), l'instruction Goto n'est guère appréciée des programmeurs, car elle casse la structure séquentielle du programme et rend souvent les programmes plus difficiles à comprendre et à maintenir (on parle dans ce cas de programmation spaghetti). On utilise plus généralement des structures comme les sauts conditionnels (si .. alors .. sinon .. ) ou les boucles (pour, tant que, etc.)

En VB, des instructions effectuent des sauts conditionnels ou inconditionnels (If.. Then) et remplacent usage de l'instruction Goto, d'autres ( For..Next, Do.. Loop) permettent d'élégantes boucles.


Rupture de séquence:

Il y a même des instructions qui 'cassent' élégamment les sorties de boucles; c 'est le cas des instructions comme Continue ou Exit For. L'erreur majeure de Goto, se situe dans le fait que cette instruction renvoie vers une position précédente du code (aboutissant à ce que l'on appelle le "code spaghetti"), tandis que les deux autres renvoient (le plus souvent) vers un point du code situé logiquement après la boucle qu'elles interrompent.

While condition
    ..Continue While
End While

Exemple: réinitialiser un tableau en donnant la valeur 0 à tous ses éléments.

initialisetableau:
Dim tableau (10) as Integer
Dim compteur As Integer
For compteur= 0 To 10
  tableau(compteur)=0
Next compteur
Return

On crée un sous-programme nommé initialisetableau qui a la fonction d'initialiser le tableau. Il n'utilise plus de GoTo mais une boucle For Next.

Ce sous-programme était appelé par un Gosub (Gosub n'existe plus en VB.Net)


XVI-A-3. Language fonctionnelle

C'est un language généralisant l'usage des fonctions mathématiques. En "programmation fonctionnelle", les entités sont des fonctions au sens mathématique du terme. Une fonction en appelle d'autres qui en appelle d'autres.

Le programme principal est lui-même considéré comme une fonction qui fait appel à d'autres fonctions qui elles-mêmes...

Les langages de programmation fonctionnelle dits "purs" ne proposent ni affectation de variable, ni allocation de mémoire, ni boucles. Ces deux derniers procédés sont respectivement remplacés par les allocations automatiques et l'usage intensif de la récursivité.

Exemple de language fonctionnel: LISP. (Pas VB)


XVI-A-4. Programmation procédurale

VB en fait.

La programmation procédurale utilise des fonctions nommées 'procédure'. Une procédure, aussi appelée routine, sous-routine, méthode ou fonction (Sub et Function en VB) contient simplement une portion de code qui effectue une fonction précise. N'importe quelle procédure peut être appelée à n'importe quelle étape de l'exécution du programme, incluant d'autres procédures ou même la procédure elle-même (récursivité).

On peut , en appelant la procédure , envoyer des paramètres.

Avantages:
  • La possibilité de réutiliser le même code à différent emplacement dans le programme sans avoir à le retaper.
  • Une façon plus simple de suivre l'évolution du programme.
  • La création d'un code plus modulaire et structuré.
Exemple: réinitialiser un tableau en donnant la valeur 0 à tous ses éléments.

Sub InitialiseTableau ( tableau() As Integer)
Dim compteur As Integer
For compteur= 0 To Ubount (tableau,1)
  tableau(compteur)=0
Next compteur
End Sub
Ici on utilise une procédure, une Sub qui a pour seule fonction la réinitialisation du tableau.


XVI-A-5. Programmation défensive

Se dit d'une programmation où l'on considère que le programme peut contenir des erreurs et que l'utilisateur est parfaitement malveillant et fera tout pour faire planter le programme. Ainsi donc, il faut s'en défendre.

Elle consiste à ajouter du code vérifiant systématiquement l'état du système ainsi que la valeur des paramètres des fonctions et s'assurant que le changement d'état est consistant. Si une erreur est détectée, elle sera traitée.

En premier lieu donc on vérifiera que toutes les entrées ( saisie au clavier, lecture de fichier..) sont valides pour le programme et ne contiennent pas, par exemple, une valeur pouvant provoquer une exception non gérée ultérieurement.

Pour se défendre des entrées invalides, on utilise la 'tolérance de faute':

Pour toutes entrées:
  • On teste les valeurs, on accepte uniquement les valeurs permises.
  • On gère les exceptions avec Try.. Catch.
  • On utilise les assertions.

Une entrée invalide entraîne grâce à la programmation défensive:
  • soit l'arrêt du programme (programmation défensive forte).
  • soit l'utilisation de valeur par défaut ou d'une ancienne valeur.
  • soit l'envoie à l'appelant l'indication qu'il y a une mauvaise entrée:
    • retour d'une valeur de diagnostic
    • déclenchement d'une exception chez l'appelant ( utilisation de Throw en VB dans une classe).

L'appelant, le client doit donc tester si la valeur de retour est valide ou bien gérer les exceptions qui sont retournées (solution qui semble préférable). Il devra donc traiter l'erreur.

Exemple

Try 
SaveFile (maFile)
Catch E As Exception
    MsgBox (Exception.Message)
End Try

XVI-A-6. Programmation sécurisée

La programmation sécurisée va au delà de la programmation défensive; Elle consiste à prendre en compte la sécurité informatique à tous les moments de la conception, de la réalisation et de l'utilisation d'un programme. Cela permet d'éviter au maximum les trous de sécurité et autres bugs.


XVI-A-6-a. Conception
Lors de la conception, il s'agit de concevoir le programme de façon modulaire et nécessitant le moins de droits utilisateurs possible. Il est préférable d'avoir plusieurs programmes de taille réduite qui collaborent entre eux, qu'un gros programme.


XVI-A-6-b. Réalisation
Ensuite, lors de la réalisation, il faut penser à bien valider les données entrées par l'utilisateur. L'idée générale et la plus importante est de ne jamais faire confiance à l'utilisateur. Ne jamais faire des hypothèses sur les entrées sans les vérifier soi-même (par exemple taille de l'entrée, signe du nombre,...)C'est la programmation défensive.

Mais on peut généraliser ce processus:
  • En testant les entrées de toutes les procedures (Préconditions)
  • En testant les sorties de toutes les procédures afin qu'elles soient conformes à ce qu'attend l'appelant (Postconditions)
On peut aussi faire de la programmation par contrat: l'appelant vérifie que les préconditions sont remplis et envoie à la procédure des 'paramètres valides'. La procédure effectue sont code et vérifie que ce qu'elle retourne est valide. Il y a contrat entre l'appelant et la procédure appelée: l'appelant vérifie les pré conditions seulement et sait qu'on lui retournera des informations valides. La procédure sait que les paramètres qu'elle recevra sont valides ; elle vérifiera la validité de ses résultats avant de les retourner.

Invariants : en plus des pré et post conditions, tous les objets inclus dans l'échange doivent être laissés dans le même état entre le début et la fin de celui-ci. Il faut s'assurer que le système entier conserve une certaine uniformité (et que l'on évite donc les effets de bords disgracieux).


XVI-A-6-c. Exécution
Enfin lors de l'exécution, il faut penser par exemple à appliquer les différentes mise à jour de sécurité lorsqu'elles sortent. Pour ce faire, il peut être pratique de la part du concepteur de l'application de proposer un système de mise à jour simplifié de l'application.

Effet de bord:
  • Il peut arriver qu'une variable dispose d'une portée qui dépasse la procédure qui la contient, ceci afin d'être accessible à partir d'autres procédures. Certaines procédures peuvent ainsi modifier une variable dans le seul but de les maintenir dans un état donné, fixé par le développeur. Cette capacité d'une fonction de modifier l'état d'une valeur (variable globale ou statique, argument d'une autre fonction, affichage ou écriture des données) autre que celle qu'elle renvoie définit l'effet de bord.
  • Ce mécanisme crée une sorte d'interdépendance entre les fonctions, rendant souvent plus complexe la compréhension d'un programme... D'autant que la modification d'une fonction peut dès lors avoir des conséquences inattendues sur le résultat d'autres fonctions "liées".

XVI-A-7. Programmation évènementielle

Avant VisualBasic 1 :

Programme Basic SANS programmation évènementielle :


10 PRINT "Donne ton nom";
20 INPUT N$
30 PRINT "Donne ton prénom";
40 INPUT P$
50 PRINT "Bonjour "; P$; " "; N$
60 END

L'exécution de ce programme conduit au résultat suivant :


C: > RUN
Donne ton nom ? LASSERRE
Donne ton prénom ? PHILIPPE
Bonjour PHILIPPE LASSERRE
C: >


Le programme affiche des informations à l'écran avec PRINT et utilise la commande INPUT lorsqu'il a besoin que l'utilisateur lui communique une information, au moyen du clavier.

Le programmeur indique la succession des lignes , leur ordre est imposé. Le programme s'arrête quand il attend une frappe au clavier puis redémarre quand l'utilisateur a validé sa réponse (en appuyant sur la touche 'Return'). On constate que l'ordre de saisie des informations est totalement déterminé par le programme. Il n'est pas question ici que l'utilisateur indique son prénom avant son nom.

Parfois pour assouplir les choses, on créait une boucle qui lisait à la volée, le clavier et qui en fonction de la touche appuyée permettait un choix.


Depuis Visual Basic 1:

En environnement graphique, l'interaction avec l'utilisateur est beaucoup plus élaborée :
  • Il y a un environnement graphique.
  • la saisie d'informations peut s'effectuer au moyen du clavier , mais aussi de champs de saisie, boutons, listes, sélecteurs, cases à cocher, menus.
  • et surtout, l'ordre d'exécution des différentes opérations n'est pas strictement déterminé à l'avance.
  • Toute action de l'utilisateur sur l'interface graphique déclenche des évènements. Si l'utilisateur clique sur un bouton l'évènement bouton.Click est déclenché.
  • Pour chaque évènement on exécute une procédure (une Sub).
Du point de vue du développeur, cela change complètement la donne : ce n'est plus lui qui décide ce que va faire l'utilisateur ! Au contraire, il doit s'attendre a priori à n'importe quelle action de la part de ce dernier.

C'est ici qu'intervient la notion de programmation événementielle : le programme est structuré non plus pour exécuter une séquence d'instructions dans un ordre prédéfini, mais pour réagir à des événements qu'il consomme l'un après l'autre.

Exemple pratique: l'utilisateur saisie son nom et son prénom, il clique sur le bouton 'Ok' ; une boite s'ouvre indiquant "Bonjour..'

Pour faire ce programme, il faut dessiner l'interface utilisateur, Vb fourni les évènements et leur procédure. Il faut ensuite écrire dans la procédure correspondant à l'évènement 'Bouton1-Click' le code qui affiche 'Bonjour..'.

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
 Handles Button1.Click
    MsgBox("Bonjour " &amp; TextBox1.Text &amp; TextBox2.Text)
End Sub 
Il existe d'autres évènements (Form.Load; Button1.MouseDown...) qui peuvent ne pas être utilisés.


Décortiquons le code; comment cela se passe en VB?

Le composant (le bouton) doit se lier à un écouteur d'événement à qui il va déléguer le traitement de l'événement

Le traitement de l'événement est délégué à l'écouteur et l'écouteur exécute une méthode spécifique qui prend en paramètre l'événement qui s'est produit

Dans la Région " Code généré par le Concepteur Windows Form " il y a:

Friend WithEvents Button1 As System.Windows.Forms.Button

On crée une variable Button1 pour le bouton de commande; WithEvents indique que l'objet Button1 a des évènements.

On voit dans le code la procédure évènement Button1_Click

Protected Sub Button1_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles Button1.Click

End Sub
Le terme Handles provoque l'association d'un événement (Button1.Click situé après Handles) à un gestionnaire d'événements ( la Sub Button1_Click) il crée une délégation. Ainsi, à l'évènement Button1.Click correspond la procédure Sub Button1_Click.

la Sub pourrait d'ailleurs se nommer différemment, cela n'a pas d'importance.

Plusieurs évènement peuvent déclencher une seule Sub.

L'association d'événements aux gestionnaires d'événements se fait au moment de la compilation et ne peut pas être modifiée.


En conclusion avant la programmation évènementielle on imposait:

Faire ceci.
Faire cela.  
Lire le clavier
Faire ceci.
..
Avec la programmation évènementielle, c'est plutôt:

Si l'utilisateur tape du texte faire ceci.
Si l'utilisateur clique sur le bouton faire cela.
...
 

XVI-A-8. Programmation Objet

Les objets sont les choses physiques ou abstraits qui nous entourent. Typiquement, dans un programme de fiches de paie, le bulletin de salaire, l'employé, etc. sont des objets.

Un objet est une entité cohérente rassemblant des données et du code travaillant sur ses données.

Un logiciel est alors vu comme des d'objets communiquant par des méthodes.

Une classe peut être considérée comme un moule à partir duquel on peut créer des objets.

" Ne commencez pas par demander ce que fait le système, demandez À QUOI il le fait ! "


XVI-B. Programmation 'procédurale' ou 'objet' ?

On a vu qu'on pouvait créer des programmes avec des Sub et des Functions mais aussi avec des objets. Détaillons.


XVI-B-1. L'approche procédurale

Elle découpe le problème en fonctions:

Chaque fonction effectue une tache précise. Avec l'aide de variables et de structures.

La base de la réflexion est effectuée autour des traitements. Le concepteur considère ainsi les tâches que doit accomplir le programme, en décomposant celui-ci en une série de tâches simples (approche descendante) ou en le construisant par composition de traitements (fonctions) disponibles (approche ascendante).


Il y a donc

Exemple:

Calcul du salaire d'un employé. (Nombre d'heure * Taux horaire)

Il faut écrire une Fonction CalculSalaire:

Public Function CalculSalaire(Taux As Single, Heure As Single) As Single
    Return Taux*Heure
End Function

Pour calculer un salaire il faut appeler la fonction avec les bons paramètres.

Dim TauxHoraire As Single
Dim HeuresTravaillées As Single
Dim Salaire As Single
TauxHoraire=30
HeureTravaillées=70
 Salaire=CalculSalaire(TauxHoraire,HeuresTravaillées)

Pour structurer le programme, on utilise des 'Modules Standards' contenant les divers fonctions Un module contient par exemple Function CalculSalaire.

Le point d'entrée du programme est une procédure Main():
 
Module module1
 
Sub main()
....Créer une instance du formulaire de démarrage et l'ouvrir
End Sub
 
 
Public Function CalculSalaire(Taux As Single, Heure As Single) As Single
    Return Taux*Heure
End Function
 
 
End Module

Si on utilise des variables globales visible dans la totalité du programme (c'est à éviter), il faut les mettre dans un module standard et les définir comme Public:

Exemple: Nom du programme et version du programme.


Exemple nom et version du programme:

Module module1
Public nomProgramme As String= "Calcul Salaire"
Public versionProgramme As String= "1.2"
....
 
End Module

Ainsi le nom du programme et sa version sont accessibles partout:

Dans un formulaire on peut afficher le nom du programme dans la barre supérieure.

Me.Text= nomProgramme
Cette accessibilité semble un avantage, en fait c'est dangereux: n'importe quelle procédure peu modifier les données.

Pour structurer les données, on peut utiliser les Structures.

Exemple: créons une structure 'Salarié'

Structure Salarié
Public Nom As String
Public TauxHoraire As Single
Public HeuresTravaillées As Single
End Structure
Ensuite pour avoir 100 salariés

Dim Salaries(100) As Salarié
On pourra utiliser Salaries(1).Nom ou Salaries(2).TauxHoraire

On verra plus loin qu'utiliser des variables dites 'globales'( visible partout) n'est pas une bonne chose, il vaut mieux créer des procédures d'accès aux données.

Plutôt que d'écrire Salaries(1).Nom="Lulu" il est préférable d'écrire une procédure SalariesAdd() qui ajoute un salarié avec son nom dans le tableau.


XVI-B-2. Approche Objet

Elle est centrée sur les Objets (et non sur les taches).

Elle nécessite de créer une Classe (le moule).

Avec l'aide de la classe on peut déclarer des objets.

Chaque Objet à des propriétés, des méthodes.

Exemple:

Calcul du salaire d'un employé. (Nombre d'heure * Taux horaire)

Il faut écrire dans un module de Class une Class Employé:


Public Class Employé
Private T As Single     'propriétés privées à la classe pour stocker les heures et taux horaires
Private H As Single
 
Public Property Taux As Single 'propriété Taux
Get
    Return T
End Get
Set(By Val Value)
    T=value
End Set
End Property
 
Public Property Heure As Single 'propriété heure
Get
    Return H
End Get
Set(By Val Value)
    H=value
End Set
End Property
 
Public Property Salaire As Single 'méthode Salaire
Get
    Return T*H
End Get
End Property
End Class

Pour calculer un salaire il faut créer un objet Employé,donner les bonnes valeurs aux propriétés et appeler la méthode salaire.

Dim UnEmployé As new Employé
UnEmployé.Taux=30
UnEmployé.Heure=70
 Dim Salaire As Single =UnEmployé.Salaire
On voit donc qu'il faut créer des 'Modules de Classe' pour y mettre les nouvelles classes.

On évitera les modules standards qui ne sont pas dans l'esprit 'Objet'.

Le point d'entrée du programme pourrait être une Classe statique (Classe ne nécessitant pas d'instancier un objet):

Public Class main2
Public Shared Sub main()
...
End Sub
End Class
Si on utilise des variables qui doivent être accessibles, il faut les mettre dans une Classe et les définir comme Shared: Ainsi une variable partagée d'une classe (Shared) a non seulement une valeur commune à toutes les instances de la classe, mais en plus on peut travailler directement sur la Classe (sans instancier):

Exemple nom et version du programme:

Class MonProgramme
Public Shared nomProgramme As String= "Calcul Salaire"
Public Shared versionProgramme As String= "1.2"
....
 
End Class

Ainsi le nom du programme et sa version sont accessibles partout:

Dans un formulaire on peut afficher le nom du programme dans la barre supérieure.

Me.Text= MonProgramme.nomProgramme
 
On peut créer une classe qui hérite des propriétés d'une autre classe:

Dans notre exemple en programmation Objet, on créera une Class 'Patron' qui hérite de la classe 'Employé', mais dont la méthode Salaire sera redéfinie ( Overrides ). (En programmation procédurale il faudra écrire une nouvelle fonction SalairePatron).

 
Public Class Patron
Inherit Employé

Public Property  Overrides  Salaire As Single 'méthode Salaire
Get
    Return T*H
End Get
End Property
End Class

Pour gérer un ensemble, un groupe de données, on utilise une classe encapsulant une collection privée d'objets voir chapitre 5-7.

Public Class LesEmployes
     Private maCol As ArrayList

     Public Sub New()
          maCol = New ArrayList()    'cela crée une ArrayList
     End Sub


     Public Function Add(ByVal LEmploy As Employe) As Employe     
      ...
     End Function

     Public Property Item(ByVal lIndex As Integer) As Employe 
     ...     
     End Property

     
End Class

On rappelle que les classes peuvent contenir des méthodes, des variables publiques ou privées, mais elles sont 'PAR REFERENCE'

Les partisans de la programmation Objet auront tendance à utiliser exclusivement les classes du Framework plutôt que les instructions de Microsoft.VisualBasic dans leur code.


XVI-B-3. Conclusion

La programmation fonctionnel se focalise sur les actions à effectuer, alors que la programmation Objet se focalise sur les données.


La méthode procédurale est plus intuitive, on a l'impression d'être plus proche de la réalité 'physique', le code est probablement plus rapide.

L'emploi d'objet permet une abstraction plus importante, une puissance inégalée grâce à l'héritable, aux surcharges.


On peut être puriste et ne programmer qu'en procédurale ou ne programmer qu'en objet.

Visual Basic permet de mélanger les 2 approches.


XVI-C. Programmation 'procédurale' : faire de bonnes procédures

Quand on fait de la programmation procédurale: on n'utilise pas de classe mais des modules standards, des Sub et des Functions.

Suivant quelles règles découper son programme?

Si on programme depuis longtemps, on le sait de manière 'intuitive' (après de nombreuses erreurs); il est tout de même intéressant de connaître les grandes règles à suivre. Très peu de sites ou d'ouvrages en parlent!!

Analyse ascendante, descendante?

Pourquoi découper en procédures?

La cohésion doit elle être importante?

Le couplage doit-il être modéré?

Comment utiliser les paramètres?

Sub ou Function?

Programmation défensive?


XVI-C-1. Approche procédurale, analyse 'descendante' ou 'ascendante'

L'approche procédurale découpe le problème en fonctions (ou procédures):

L'analyse se fait de manière descendante: on découpe un problème complexe en problèmes plus simple qui sont eux mêmes découpés en problèmes plus simples. On découpe jusqu'à ne plus avoir que des problèmes simples.

Il existe aussi l'analyse ascendante: ayant à sa disposition des procédures simples, on les assemble en les faisant appeler par des procédures plus complexes pour atteindre la solution.

Si le projet est entièrement nouveau, on fait plutôt une analyse descendante; si on travaille sur un projet possédant déjà toutes les procédures simples, on raisonnera en analyse ascendante.

Rien d'empêche d'avoir une analyse mixte.


On rappelle:

Chaque procédure effectue une tache précise.

Les procédures sont composées des ' Sub ' et des ' Function '.

Une procédure est un ensemble d'instructions, de lignes de code , un groupement d'instructions bien définies effectuant une tache précise.

Les procédures 'Sub':

Elles débutent par le mot Sub et se terminent par End Sub.

Exemple:

Sub MaProcédure()
   A=1
End Sub
Pour appeler la Sub:

MaProcedure()
Les procédures 'Function':

Les 'Function' retournent une valeur.

Elles débutent par Function et se terminent par End Function.

Exemple:

Function SurfaceCercle( Rayon as Single)
    Return 3.14*Rayon*Rayon        'Return indique ce que la fonction doit retourner 
End Function
Dans la procédure qui appelle, il faut une variable pour récupérer la valeur retourner par la Fonction:

S= SurfaceCercle()
infoOn rappelle que le nom de la procédure doit être en minuscule avec la première lettre de chaque mot en majuscule.
SurfaceCercle() est correct.

XVI-C-2. Pourquoi utiliser des procédures ?

D'abord , dans certains cas, c'est obligatoire!! Il y a les procédures évènements automatiquement crées par VB. Ensuite, dès que l'on veut écrire du code, il faut créer des procédures Sub ou Function dans les modules de formulaire ou dans les modules standard (voir 5-10)

La programmation procédurale avec découpage en procédures a des avantages:
  • On évite la répétition du même code:
    Si il faut 30 lignes de code pour faire un calcul et que le calcul est effectué 10 fois dans le programme, il est pratique de créer une Sub CalculTotal() contenant les 30 lignes de calcul et d'appeler 10 fois la Sub.

    Voici la Sub:
    
    Public Sub CalculTotal()
    A=B+C
    D=E+3
    ..
    End Sub 
    

    Voici les appels:
    
    CalculTotal()
    ..
    CalculTotal()
    

  • On simplifie les modifications:
    S'il y a une modification à faire dans le calcul, la modification est effectuée 1 fois (Avec 10 fois le code du calcul disséminé dans le programme, on risque d'oublier de modifier un des codes).


  • On introduit une abstraction qui clarifie le code:
    CalculTotal() est plus compréhensible que A=B+C....

    On oublie le code interne à la procédure, pour ne plus se souvenir que de la fonction de la procédure: La procédure fait le Calcul du total..

    On masque ainsi l'implémentation.


  • On réduit la complexité:
    Chaque tache complexe est décomposée en plusieurs procédures qui elles-mêmes sont décomposées en procédures plus simples.


  • On améliore la portabilité:
    On isole dans les procédures le code non portable et on le groupe.

    Si on doit effectuer un changement (changement de type de base de données par exemple en modifiant un programme pour qu'il fonctionne sous MySQL alors qu'il fonctionnait sous Acces), il suffit de modifier uniquement les procédures d'accès à la base de données.


  • On peut masquer certains fonctions:
    Certaines fonctions sont complexes et leur code est peu intéressante, une fois écrite il faut mieux ne plus les voir. (fonction booléens complexes)


XVI-C-3. La 'cohésion' de la procédure doit être importante

info La 'cohésion' fait référence aux liens établis entre les opérations de la procédure DANS la procédure.
Un exemple:

La Sub Carré() à une forte cohésion: elle est entièrement consacrée à une tache: le calcul du carré d'un nombre.

La Sub CarréEtCube() qui calcul le carré ou le cube d'un nombre a une cohérence faible!! (Il vaut mieux dans ce cas écrire une procédure Carré() et une procédure Cube() )

Compris?

Si la cohésion est faible , il a été prouvé que les procédures contenaient plus d'erreurs de programmation.

warningDonc une procédure ne doit faire qu'une seule chose.
  • On parle ici de cohésion fonctionnelle: la fonction de la procédure est unique.
    EffaceFichier(), CalculAge() ont une bonne cohésion fonctionnelle, SaisirDate&CalculAge() a une moins bonne cohésion.

Il existe d'autres sortes de cohésion:
  • La cohésion temporelle:
    Différentes opérations qui n'ont rien a voir entre elles doivent être effectuées au même moment:

    Exemple: La Sub Demarrage() appelée au début d'un programme va ouvrir les fichiers, initialiser les variables, lire le fichier de configuration.


  • La cohésion séquentielle:
    Lorsque les opérations qui n'ont rien à voir entre elles doivent impérativement être effectuées dans un ordre précis:

    Exemple: La Sub CalculAnnéeRetraite() qui a partir de la date de naissance va calculer l'age puis l'année de retraite. (On pourrait aussi dans ce cas écrire une routine CalculAge() qui serait appelée par CalculAnnéeRetraite() )


Certaines cohésion sont à éviter:
  • La cohésion de communication:
    Les opérations travaillent sur les mêmes données mais n'ont pas de relation.

    ImprimeAndEffaceTableau() me parait à éviter: écrire les procédures ImprimeTableau() et EffaceTableau()


D'autres sont à bannir:
  • La cohésion logique:
    La sélection des opérations (sans relation entre elles) à effectuer est pilotée par un paramètre.

    Seul le paramètre est commun!!

    La Sub ImprimerTableauAfficherResultat (Flag) ou le Flag indique s'il faut imprimer ou afficher est à bannir.


XVI-C-4. Le 'couplage' entre procédures doit être modéré

info Le couplage traite des connexions entre les différentes procédures, il doit être modéré, ce qui signifie qu'il doit y avoir peu de connexions.
  • Une procédure doit ne communiquer qu'avec un nombre " minimum " d'autres procédures du logiciel. L'objectif est de minimiser le d'interconnexions entre les procédures. L'intérêt de cette règle est de garantir un meilleur respect de la protection des procédures.

  • Lorsque deux procédures communiquent entre elles, l'échange d'information doit être minimal. Il s'agit de minimiser la taille des interconnexions entre modules.
    Eviter le tout 'variables globales'.
    Une Sub ayant 2 paramètres c'est bien, une Sub en ayant 15 c'est trop.


  • Lorsque deux procédures communiquent, l'échange d'information doit être explicite:
    Eviter qu'une variable soit visible dans une procédure, alors qu'elle est déclarée dans une autre.
    Si vous modifié des variables globales utilisées par un autre module c'est dangereux!!
    Si vous utilisez un paramètre pour passer une données, c'est explicite, c'est Ok.


  • Toute information dans une procédure doit être répartie en deux catégories : l'information privée et l'information publique. Ce principe permet de modifier la partie privée sans que les clients () soient confrontés à un quelconque problème à cause de modifications ou de changements.
    Plus la partie publique est petite, mieux c'est..
    On déclarera en début de procédure des variables privées, et on travaillera sur ses variables privées; seul le résultat sera accessible de l'extérieur.


Une procédure doit donc être autonome:

warningUne procédure est autonome et ne doit pas lire ni modifier directement les variables du programme qui l'appelle, sans passer par des paramètres.

XVI-C-5. Squelette d'un programme

Exemple simpliste de l'agencement des procédures:

De manière générale, le programme est composé de procédure, en appelant d'autres, qui en appelle d'autres..

'Squelette' de programme:
 
Sub Démarrage
    OuvrirFichier()
    LireConfig()
    InitialiserVariable()
    LireDonnées()
End Sub
 
Sub Quitter
        EnregistrerDonnée()
        FermerFichier()
End Sub

Voici la Sub Main(), procédure de démarrage du programme :

Sub Main()
    Démarrage()
    'puis affiche le formulaire principal
End Sub
 

Dans le formulaire principal, un bouton 'Quitter' déclenche la Sub suivante:

Sub BoutonQuitter_Click(..)
    Quitter()
End Sub
 

Un bouton 'Nouveau' (travail sur de nouvelle données) contient:

Sub Nouveau_Click(..)
       EnregistrerDonnée()
       InitialiserVariable()
End Sub
 

On remarque que EnregistrerDonnée() et InitialiserVariable() sont appelés de plusieurs endroits.


Ensuite il suffit d'écrire tous le code des Sub.


XVI-C-6. Les paramètres

Dans quel ordre les mettre?

Un Sub reçoit 3 paramètres: P1 qui est une donnée qu'utilise la Sub, P2 qui est modifié par la Sub , P3 qui contient un résultat retourné par la Sub.

Et bien il est conseillé de les mettre dans l'ordre P1, P2, P3 (Entrée, Modification, Sortie)

Exemple Sub CalculCarré(nombre, carré) 'quand on appelle cette Sub on envoie un nombre, on récupère le carré.


S'il y a une variable d'erreur ou d'état, la mettre en fin.

        Sub Divise(Nombre1, Nombre2, CalculImpossible) 
'cette routine calcule Nombre1/Nombre2 mais retourne CalculImpossible=True si Nombre2 est égal à zéro.


Nombre de paramètres?

7 maximum, au delà de 7 on s'y perd!!

Dans les 7, il peut y avoir des tableaux.


Paramètres pour plusieurs routines:

Si plusieurs routines utilisent les mêmes paramètres, les mettre dans le même ordre.


AfficheText(LeTexte, Couleur) et ImprimeText(LeTexte, couleur) sont cohérent.


Ne pas utiliser de paramètre comme variable de travail.

Sub calcul (A, B)
A=A+2                    Est à éviter: le paramètre A ne contient plus la valeur d'entrée!!
...                      car on a utiliser A comme variable de travail.
End Sub
 

Faire plutôt:

Sub calcul (A, B)
Dim V As Integer= A      'On passe la valeur du paramètre dans une variable locale.
V=V+2                    'C'est mieux! A contient toujours la valeur d'entrée.
...
End Sub
 

Il faut donc déclarer en début de Sub des variables locales propres à la Sub et travailler avec elles dans la Sub.


Mettre des commentaires en début de sub:

Sub Divise(Nombre1, Nombre2,Resultat, CalculImpossible)
'***Division de 2 Nombres
'Entrée: Nombre1, Nombre2 de type Single
'Sortie: Résultat de type Single
'Teste Nombre2 si =0 retourne CalculImpossible=True
If Nombre2= 0 then
    CalculImpossible=True: Exit Sub
End If
Resultat=Nombre1/Nombre2
End Sub
 

Tester la validité des paramètres en début de Sub:

Voir l'exemple précédent

If Nombre2= 0 then    'teste le paramètre Nombre2 et quitte la Sub s'il n'est pas valide. 
C'est mieux de le faire au début pour tous les paramètres plutôt que de le faire dans toute la procédure.


XVI-C-7. Utiliser une 'Sub' ou une 'Function' ?

Les puristes diront qu'il faut utiliser une fonction quand on a une valeur de sortie (une seule).

Plusieurs paramètres d'entrée sont possible par contre.

Exemple:

La fonction FileExist(), avec comme entrée le nom d'un fichier, retourne uniquement un Booléen (True ou False), c'est bien une fonction qu'il fallait utiliser.

On peut élargir la définition en employant une fonction quand l'objectif principal est de retourner la valeur indiquée dans le nom de la fonction:


MetAuFormat( In, Out) transforme le texte 'In' en 'Out'; si la fonction à échoué elle retourne False.


On l'utilisera comme cela:

If MetAuFormat(In, Out) = True Then  LeText.Text=Out

On aurait pu utiliser une Sub, avec comme troisième paramètre une variable indiquant le succès ou l'échec.


MetAuFormat( In, Out, Succes)

Certains utilisent des noms de variable commençant par Is afin d'indiquer clairement que c'est une fonction et ce qu'elle retourne:

IsOpen() pour voir si un fichier est ouvert (retourne True si le fichier est ouvert). ce qui permet d'utiliser:

If IsOpen("monfichier") Then...
 

XVI-C-8. Programmation défensive

C'est comme quand on conduit une voiture; il faut se méfier des autres: bien regarder aux croisements et ralentir, même si on a la priorité!!

Il faut donc:

Tester la validité des paramètres en début de Sub:

On l'a déjà vu:

If nombre2= 0 then 'teste le paramètre Nombre2 et quitte la Sub s'il n'est pas valide ce qui évite une division par zéro si l'expression nombre1/nombre2 est utilisée.

C'est mieux de le faire au début pour tous les paramètres plutôt que de le faire dans toute la procédure.

Il faut le faire surtout si les données viennent de l'"extérieur": fichier, réseau.

Il faut encore plus le faire si c'est l'utilisateur qui a saisi les données.

Vérifier si la données est dans la plage attendue:

Si on attend le numéro d'un jour du mois, vérifier que c'est un nombre, s'il est positif, compris entre 1 et 31, entier..

Vérifier si la données est une String valide:

Si on attend un nom de fichier, vérifier que c'est du texte, éliminer les espaces en début et fin, y a t-il une extension? des caractères invalides?

En VB il y a des fonctions qui font cela.


Tester la validité des paramètres de sortie:


Prendre en charge les erreurs:

Il y a des procédures 'tolérantes':

  • Si une valeur n'est pas valide, on peut donner une valeur par défaut, redonner la précédente valeur..
  • C'est le cas des prises de température à une fréquence importante. S'il manque une valeur; reprendre la précédente.
Il y a des procédures 'strictes', cela entraîne une des actions suivantes:

  • L'arrêt du programme!!
  • La procédure redemande la valeur.
  • Elle retourne une variable indiquant qu'elle a échoué

Pendant le développement utiliser les assertions

C'est une manière de se contrôler soi-même en cours de développement.

On place des assertions dans le code:

Si elles sont vraie c'est que cela se passe comme prévu.

Si elles sont fausses c'est qu'une erreur inattendue, inacceptable se produit.

Debug.Assert affiche une fenêtre Windows et stoppe le programme si une assertion(une condition) passe à False.

Debub.Assert(Assertion)
Debub.Assert(Assertion, Message1)
Debub.Assert(Assertion, Message1, Message2) 
L'exemple suivant vérifie si le paramètre 'type' est valide. Si le type passé est une référence null (Nothing dans Visual Basic), Assert ouvre une boite de dialogue nommé 'Echec Assertion' avec 3 boutons 'Abandonner, Recommencer' 'Ignorer'.. La liste des appels est affichée dans la fenêtre (procédure en cours en tête de liste, module et numéro de ligne en première ligne)

Public Shared Sub UneMethode (type As Type, Typedeux As Type)
Debug.Assert( Not (type Is Nothing), "Le paramètre Type est=Nothing ", _
 "Je ne peux pas utiliser un Nothing")
....
End Sub UneMethode
Il n'est pas souhaitable que l'utilisateur puisse voir les messages des assertions, elles disparaissent dans le code de production.

En résumé: Une procédure doit avoir:

  • de la modularité: un sous-programme réalise une tâche et une seule (par exemple, une fonction de calcul ne doit pas afficher de résultat) ;
  • de l'autonomie: un sous-programme est autonome et ne doit pas lire ni modifier directement les variables du programme qui l'appelle, sans passer par des paramètres ;
  • de la transparence: un sous-programme doit être conçu de façon à ce que le programmeur qui y fait appel (non nécessairement son concepteur) n'ait pas à tenir compte des choix du concepteur ;
  • de la convivialité: l'appel du sous-programme doit être la plus évidente possible.

XVI-D. Programmation 'objet' : faire de bonnes Classes

warning Ce chapitre tente de clarifier mes connaissances, ne pas hésiter à critiquer et me donner des conseils.
Quand on programme avec une approche Objet, on crée des classes, on n'utilise par de modules standard ni de Sub et de Fonction.

Quelles règles suivre pour créer des objets?

Si on programme depuis longtemps, on le sait de manière 'intuitive' (après de nombreuses erreurs); il est tout de même intéressant de connaître les grandes règles à suivre. Très peu de sites ou d'ouvrages en parlent!!

Comment faire de bonnes Classes?

Comment bien utiliser l'héritage?


XVI-D-1. Rappel

On rappelle: Une Classe sert à créer (instancier) des objets.

En programmation Objet, dans un module de Classe, on crée la Classe:

Classe MyClasse
..
End Class
 

puis dans le corps du programme, on crée un objet:

Dim MonObjet As New MyClasse.

XVI-D-2. Pourquoi utiliser 'Classe' et 'Objet' ?

Ils permettent:

Modélisation des objets réels.

Le programme travaille avec des objet réels: une facture, un client.

Créer une classe par objet puis:

  • Y mettre les données liées à l'objet (le nom, l'adresse du client)
  • Y ajouter les méthodes contenant le comportement de l'objet (Calcul de la facture)

Modélisation des objets abstraits.

Dans un programme de comptabilité, les classes 'technicien' , 'ouvrier', dirigeant' sont concrètes; une classe 'Salarié' est plus abstraite. Les 3 premières pourront hériter de cette dernière.

Autre exemple classique: Les classes Line, Triangle sont concrètes, la classe Shape (forme en français) est abstraite, ici aussi Line et Triangle hériterons de Shape

Il existe même des Classes abstraites: Un Classe abstraite est une Classe qu'on ne peut pas instancer, elle sert uniquement à définir des caractéristiques de base pour créer des classes qui seront dérivées de la Classe abstraite.


Facilité pour réutiliser le code.

Une fois crées, les Classes servent de briques pour construire un nouveau projet.

L'héritage permet la création de nouvelle Classe héritant d'une autre.

Réduction et isolement de la complexité.

Une tache complexe est découpée en plusieurs Classes.

Masquage de l'implémentation

Un fois la Classe créée, il faut oublier les détails et l'utiliser sans connaître son fonctionnement interne.


C'est l'encapsulation.

Masquage des données globales.

Si vous devez absolument utiliser des données globales vous les masquez derrière une interface de classe.


XVI-D-3. Identifier les objets

Identifier les Objets pour définir ensuite les classes:
  1. On identifiera les objets nécessaire et leurs attributs (méthodes, données)
    Exemple:

    Système de facturation:

    Objet:Client Objet:Facture Objet: FicheHoraire
    NomClient
    Adresse
    Date
    NomClient
    Montant
    Date
    NonClient
    NombreDheure
    TauxHoraire
      CalculerFacture()
    ImprimerFacture()
     

  2. Déterminer ce que l'on peut faire avec chaque objet:
    Exemple:

    Modifier l'adresse du Client.


  3. Déterminer ce que chaque objet peut faire aux autres objets
    Une facture peut contenir plusieurs fiches horaires.

    Un Objet peut en hériter d'un autre.


  4. Déterminer les parties visible (l'interface publique)

  5. En conclusion, pour chaque objet il faut définir:
  6. Définir l'interface utilisateur :
  7. Identifier les requêtes (information demandée à la classe); Ce sont les Property
  8. Identifier les commandes (procédures): Ce sont les méthodes. Sub ou Function
  9. Identifier les contraintes (pré-, post-condition).
  10. Définir les constructeurs. Sub New()
  11. Choisir une représentation de l'information (attributs).
  12. Implanter les fonctions, procédures et opérateurs.
  13. Définir (si nécessaire) le constructeur de copie, le destructeur

XVI-D-4. Faire un 'couplage' modéré

info Le couplage traite des connexions entre les différentes Classes, il doit être modéré, ce qui signifie qu'il doit y avoir peu de connexions.
  • Une Classe doit ne communiquer qu'avec un nombre " minimum " d'autres Classes du logiciel. L'objectif est de minimiser le nombre d'interconnexions entre les Classes. L'intérêt de cette règle est de garantir un meilleur respect de la protection des Classes.
  • Lorsque deux Classes communiquent entre elles, l'échange d'information doit être minimal. Il s'agit de minimiser la taille des interconnexions entre Classes.
    Un classe qui possède 4 méthodes Public c'est mieux qu'une Classe qui en possède 15.

    Il faut donc réduire l'accessibilité des classes et des membres.

  • Lorsque deux Classes communiquent, l'échange d'information doit être explicite:
  • Toute information dans une Classe doit être répartie en deux catégories : l'information privée et l'information publique. Ce principe permet de modifier la partie privée sans que les clients (fonctions utilisant cette classe) soient confrontés à un quelconque problème à cause de modifications ou de changements. Plus la partie publique est petite, mieux c'est...

XVI-D-5. Conserver une bonne abstraction et une bonne cohérence

Une classe qui:

  • met en forme les données.
  • qui initialise le programme.
  • qui imprime un rapport.
n'est pas cohérente.


Une classe qui:

  • Initialise une donnée
  • Charge une donnée
  • Sauve une donnée
est cohérente.


De plus l'abstraction est constante: elle se situe au même niveau (celui d'une donnée); si on rajoutait dans la classe une fonction effectuant une recherche dans une liste de données cela ne serait plus le cas.


XVI-D-6. Créer des méthodes par paires

Une méthode doit entraîner l'écriture de la méthode contraire.

Activation/désactivation.

Ajout/Suppression...

Si il existe la méthode 'AddLine' , on aura forcement besoin de 'RemoveLine'.


XVI-D-7. L'encapsulation doit être bonne

L'encapsulation est une barrière qui empêche l'utilisateur de voir l'implémentation..


Il ne faut pas exposer les données:

Eviter les variables Public, directement accessible par l'utilisateur de la Classe.

Class MaClasse
Public X As Single
..
End Class
C'est pas génial!!


Class MaClasse
Private mX As Single
Public Property X() as Single
Get
    Return mX
End Get
Set(By Val Value)
    mX=value
End Set
End Property
..
End Class
C'est mieux , l'utilisateur n'a pas accès directement à 'mX', l'utilisateur voit 'X', il peut modifier X ce qui entraîne une modification de mX mais sous contrôle, on peut même rajouter des conditions qui seront testées avant de modifier mX.


XVI-D-8. Initialisez les données dans le constructeur d'une Classe

Il est important de ne pas oublier d'initialiser les variables dans une classe, le moment idéal est quand on crée une instance de cette Classe.

Class MaClasse
Private mX As Integer
 
Sub New()
    mX=2
End sub
End Class 
 

XVI-D-9. Problèmes liés à l'héritage

Ne pas multiplier les classes dérivées:

Si on a une Classe 'Animal', on peut créer les classes 'Cheval' et 'Poisson' qui dérivent de la Classe 'Animal'.

Par contre il ne faut pas créer les classes 'PoissonRouge' ou 'ChevalNoir' car la couleur est plus un attribut d'une classe qu'un déterminant de Classe.


Eviter trop de classe intermédiaire:

Si 'Cheval' dérive de 'Mammifèreà4Pattes' qui dérive de 'Mammifère' qui dérive d'Animal', c'est lourd!! surtout si 'Mammifèreà4Pattes' et 'Mammifère' sont des classes abstraites qui ne seront pas instanciées.


Eviter l'héritage de construction:

Eviter de dériver une Classe en ajoutant des attributs qui modifie le concept de la classe mère.

Une classe 'Triangle' peut dériver de 'Polygone', la classe 'Carré' peut difficilement dériver de 'Triangle' (En y ajoutant les attributs d'un quatrième point!!)

Un Carré est un Polygone,oui. On ne peut pas dire qu'un Carré est un Triangle avec un sommet de plus!!


Bien comprendre la différence entre héritage et agrégation:

Comme on l'a vu dessus, quand une classe dérive d'une autre, on peut dire que la classe dérivée 'est une' classe mère.

'Carré' est un 'Polygone'

Par contre 'Carré' est composé de 4 'Point' (possède 4 points): un objet 'Carré' contient 4 objets 'Point'. On parle d'agrégation (ou de composition pour certains).


XVI-E. Faire du bon 'code'


XVI-E-1. Bonnes variables


XVI-E-1-a. Utilisez Option Strict=On et Option Explicit=On
Travailler avec

Option Strict=On
Vous serez obligé de faire du 'cast' à longueur de temps (conversion d'une variable d'un type vers un autre), mais au moins le code sera rigoureux.

Les conversions seront évidentes, explicites:

Dim I As Integer
Dim J As Long
J= Ctype(I, Long)

et avec

Option Explicit=On
Vous serez obligé de déclarer les variables avant de les utiliser. C'est indispensable.

Voir chapitre 1-7 pour le détail.


XVI-E-1-b. Donnez à chaque variable un seul rôle
A l'époque ou il y avait très peu de mémoire, un octet était un octet, aussi on utilisait parfois une même variable pour différentes choses:

Dim total As Integer
total= nbArticle*3
Affiche (total)
total= nbFacture*2
Affiche (total)
Ici total contient le total des articles puis le total des factures!!


Il vaut mieux créer 2 variables et écrire:

Dim totalArticle As Integer
totalArticle= nbArticle*3
Affiche (totalArticle)
Dim totalFacture As Integer
totalFacture= nbFacture*2
Affiche (totalFacture)

Dans le même ordre d'idée, éviter les variables nommées 'temp'(temporaire) qui serviront à plusieurs reprises pour des résultats temporaires successifs.


XVI-E-1-c. Évitez les variables avec des significations non évidentes
Eviter les significations cachées.

Exemple à ne pas faire:

  • nbFichier contient le nombre de fichier sauf s'il prend la valeur -1 , dans ce cas cela indique qu'il y a eu une erreur de lecture!!

  • clientId contient le numéro du client sauf s'il est supérieur à 100000 dans ce cas soustraire 10000 cela donne le numéro de la facture.

Dans le premier exemple, la solution est d'utiliser en plus de nbFichier une variable isReadOk indiquant si la lecture s'est bien déroulée.


XVI-E-1-d. Initialisez les variables dès leur déclaration
Cela évite de l'oublier. Les erreurs d'initialisation sont très fréquentes.

Dim Taux As Integer= 12

Un truc: si vous initialisez ou réinitialisez une variable avec NoThing, elle prend la valeur obtenu après déclaration pour ce type. Pour une structure, c'est valable pour chaque variable.

Exemple:

Soit une structure Adresse

Public Structure Adresse
Dim Numero As Integer
Dim Id As Integer
Dim Rue As String
Dim Ville As String
End Structure

Je déclare une variable Adresse

Dim MonAdresse As Adresse
J'initialise:

MonAdresse.Numero = 12
MonAdresse.Id= 15
MonAdresse.Ville = "lyon"

Maintenant si je fais:

MonAdresse = Nothing

Cela entraîne:

MonAdresse.Numero = 0
MonAdresse.Id= 0
MonAdresse.Ville = Nothing

XVI-E-1-e. Utilisez le principe de proximité
Déclarer, initialiser une variable le plus prêt possible de sa première utilisation, puis grouper les instructions dans lesquelles la variable est utilisée:


Mauvais exemple: (chaque variable est repérée par une couleur)

Dim Prixtotal As Integer
Dim Taux As Integer
Dim Prix As Integer
Dim Quantité As Integer
Dim  Ristourne As Integer
Taux=12
Ristourne=0
Prix=0
Prixtotal= Quantité*Prix
Ristourne=Taux*Quantité

Prixtotal n'est pas initialisé, on utilise les variables loin de leur déclaration...

Code dur à lire, source d'erreur.


Bonne manière de faire:

Dim Taux As Integer=12
Dim Prix As Integer=0
Dim Quantité As Integer=2
 
Dim Prixtotal As Integer=0
Prixtotal= Quantité*Prix
 
Dim  Ristourne As Integer=0
Ristourne=Taux*Quantité

XVI-E-1-f. Travaillez sur des variables qui restent actives le moins de temps possible
Les instructions utilisant une même variables doivent être si possible regroupées.

Exemple : la variable Acte est utilisée dans les lignes rouges:


Mauvaise méthode: il y a plusieurs lignes entre 2 lignes contenant la même variable, le cheminement est difficile à suivre!!


Dim Acte As Integer
Dim Bib As Integer
Dim C As Integer
C=2
Call Calcul()
Call Affiche()
Acte=Acte+1
Call CalculTaux()
Call AfficheResultat (Acte)
Bonne méthode: le cheminement est clair, les lignes utilisant la variable Acte sont regroupées,la lecture est facile est facile à suivre.

Dim Acte As Integer
Acte=Acte+1
Call AfficheResultat (Acte)
Dim Bib As Integer
Dim C As Integer
C=2
Call Calcul()
Call Affiche()
Call CalculTaux()

XVI-E-1-g. Si vous codez la valeur d'une variable en 'dur', utilisez plutôt des constantes
Une valeur codé en dur est une valeur imposée par le programmeur et écrite sous forme d'un littéral:

I=123  'c'est codé en dur!! Que représente 123?
Ecrire:
Const nbMaxLignes As Integer= 123
I=nbMaxLignes 'c'est plus significant

Autre exemple:

Plutôt que:


For i= 0 To 120
Next i

Ecrire:


Const  MAXELEMENT As Integer =120
 
For i= 0 To MAXELEMENT
Next i
Cela a tous les avantages: Quand on regarde le code, MAXELEMENT est plus explicite que 120.

De plus si on change la valeur de MAXELEMENT , la modification est prise en compte partout dans le code et les boucles fonctionnent toutes bien.


On peut utiliser des '0' (pour démarrer les boucle: For i= 0 to..) ou des '1' pour incrémenter des compteurs (i=i+1) mais on évitera les autres littéraux.


Usez aussi des énumérations.


XVI-E-1-h. Groupez les instructions liées
Regroupez les instructions faisant référence aux mêmes choses:


Mauvaise méthode: c'est un peu fouillis

CalculNewTotal()
CalculOldTotal()
AfficheNewTotal()
AfficheOldTotal()
ImprimeNewTotal()
ImprimeOldTotal()

Bonne méthode:


CalculNewTotal()
AfficheNewTotal()
ImprimeNewTotal()
 
CalculOldTotal()
AfficheOldTotal()
ImprimeOldTotal()

XVI-E-1-i. Réduisez la portée des variables (problème des variables globales)
Une variable 'globales' est une variable visible dans la totalité du programme.

Exemple: créons une variable globale nommé Index


Module MonModule
Public Index As Integer
..
End Module

A l'inverse une variable 'locale' est visible uniquement dans une procédure ou une Classe:

Class Form1
Private Dim Index As Integer
..
End Class

Avantages et inconvénients des 2 manières de faire:


Utilisation de variables globales:

Exemple:

Public maVariable As Integer
 
Sub Mescalculs()
.. maVariable=12
End Sub
Toutes les procédures ont accès à la variable globale.

Les variables globales sont commodes, d'accès facile, il n'y a peu de paramètres à passer quand on appelle une fonction. Mais on prend des risques: n'importe quelle procédure peut modifier la variable globale.


Utilisation de variables locales:

La variable a une portée limitée, pour qu'une procédure l'utilise, il faut lui fournir en paramètre:

Exemple:


Sub Main()
 Dim maVariable As Integer
 Call MesCalculs(maVariable)
End Sub
 
Sub Mescalculs(V As Integer)
.. V=12
End Sub
La variable locale aide à la maniabilité, vous restez maître de ce à quoi chaque procédure a accès.

Chaque procédure est autonome. Cela conserve donc une bonne modularité et un bon masquage des informations.


Autre danger des variables globales que l'on utilise comme paramètre:

Si on a des variables globales et en plus on utilise ces variables comme paramètres d'une procédure, la même variable peut être modifiée en utilisant 2 noms différents:


Public maVariable As Integer
Call Mescalculs (maVariable)
 
Sub Mescalculs(By Ref v As Integer)
.. maVariable=12            'maVariable et v sont la même variable c'est dangereux!!
.. v=15
End Sub

Utiliser le maximum de variables locales.

(Il sera toujours possible et plus facile d'augmenter la portée si nécessaire, plutôt que la diminuer)

Eviter donc les variables globales qui sont visibles dans la totalité du programme.


Procédures d'accès aux variables:

Si vous tenez quand même à utiliser des variables globales utilisez des 'procédures d'accès à ces variables'.

Le programme n'ira pas modifier directement la valeur de la variable globale mais passera par une Sub ou une Classe qui ira modifier la variable.


Exemple avec un Sub:

Public maVariable As Integer
Au lieu d'écrire maVariable=12 ce qui modifie directement l'accès à la variable globale, créer une Sub modifiant cette variable:

Sub SetVariable ( Valeur As Integer)
    maVariable=Valeur
End Sub
Cela a l'avantage de pouvoir ajouter un contrôle des données, on peut ajouter par exemple des conditions, interdire des valeurs..


Pour modifier la variable globale on écrira SetVariable(12)


De manière plus générale plutôt que de créer un tableau de variables globales du genre:

Public Variables(100) As Variable
et d'écrire Variables(50)=485


créer une procédure SetVariable() une procédure LetVariable() une procédure VariableCount()..


En programmation objet, on crée une classe contenant les données (tableau, collection) mais 'Privé', on crée aussi des membres de cette classe qui permettent de lire (les accesseurs)ou de modifier(les mutateurs) les données.


XVI-E-1-j. Les Booléens sont des Booléens
Utiliser une variable Integer pour stocker un Flag dont la valeur ne peut être que 'vrai' ou 'faux' et donner la valeur 0 ou -1 est à proscrire.

Faire:

Dim Flag As Boolean
Flag=True
(Utiliser uniquement True et False)

Eviter aussi d'abréger à la mode Booléens ce qui n'en est pas.

Dim x,y As Integer
If x And y then  (pour tester si x et y sont = 0)  est à éviter.
Faire plutôt:

If x<>0 And y <>0

XVI-E-1-k. Utiliser les variables Date pour stocker les dates
Ne pas utiliser de type Double.

Dim LaDate As Date
LaDate=Now

XVI-E-2. Règles de bonne programmation

Pour faire un code solide, éviter les bugs, avoir une maintenance facile, il faut suivre quelques règles.

Relire les chapitres :

sur les bonnes procédures.

sur les bonnes Classes

sur le bon code: bonnes variables

sur les bons commentaires le code lisible

Voici un résumé des points forts.


XVI-E-2-a. Séparer l'interface utilisateur et l'applicatif
Exemple: un formulaire affiche les enregistrements d'une base de données:

Créer:

  • Les fenêtres dont le code gère uniquement l'affichage. C'est l'interface utilisateur ou IHM (Interface Homme Machine)
  • une Classe gérant uniquement l'accès au base de données.
Cela facilite la maintenance: si on désire modifier l'interface, on touche au fenêtre et pas du tout à la Classe base de données.

Architecture à 3 niveaux.

Elle peut être nécessaire dans certains programmes, les 3 niveaux sont:

  • Application ,interface.
  • Logique.
  • Données.
Exemple: Un formulaire affiche certains enregistrements d'une base de données.

  • L'interface affiche les enregistrements.
  • Les classes ou modules 'logiques' déterminent les bons enregistrements.
  • Les classes ou modules données vont chercher les données dans la base de données.
Si au lieu de travailler sur une base Access, je travaille sur une base SQLServer, il suffit de réécrire la troisième couche.

Utiliser des design pattern: ce sont des 'patrons', des modèles en programmation objet.


XVI-E-2-b. Utiliser le typage fort
Cela consiste à spécifier le type de données pour toute variable, en plus il faut utiliser des variables le plus typées possible.


Éviter le type 'Object'.


Le code en sera plus rapide, le compilateur interceptera des erreurs (cela évitera des erreurs à la compilation).


XVI-E-2-c. Forcer la déclaration des variables et les conversions explicites
Option Explicit étant par défaut à On, toute variable utilisée doit être déclarée. Conserver cette option. Cela évite les erreurs liées aux variables mal orthographiées.


Si Option Strict est sur On, seules les conversions de type effectuées explicitement sur les variables seront autorisées. Le mettre sur On.


Voir Strict et Option Explicite.


XVI-E-2-d. Utiliser des constantes ou des énumérations
L'usage de constantes facilite les modifications.


Exemple : un programme gère des utilisateurs;:

Faire:


Créer une constante contenant le nombre maximum d'utilisateurs.

Const NombreUtilisateur= 20
Dim VariableUtilisateur (NombreUtilisateur)  'on utilise NombreUtilisateur et non 20
For i=  0 To NombreUtilisateur-1
Next i

Plutôt que:

Dim VariableUtilisateur (20)
For i=  0 To 19
Next i
Si ultérieurement on veut augmenter le nombre d'utilisateurs possibles à 50, il suffit de changer une seule ligne:

Const NombreUtilisateur= 50
Utiliser les constantes VB, c'est plus lisible:

Form1.BorderStyle=2 est à éviter

Form1.BorderStyle= vbSizable   c'est mieux

XVI-E-2-e. Vérifier la validité des données que reçoit une Sub une Function ou une Classe
Vous pouvez être optimiste et ne pas tester les paramètres reçus par votre Sub. Les paramètres envoyés seront toujours probablement bons!! Bof un jour vous ferez une erreur, ou un autre n'aura pas compris le type de paramètre à envoyer et cela plantera!!


Donc, il faut vérifier la validité des paramètres.


On peut le faire au fur et à mesure de leur utilisation dans le code, il est préférable de faire toutes les vérifications en début de Sub.


Vérifier aussi ce que retourne la procédure.


XVI-E-2-f. Se méfier du passage de paramètres 'par valeur' ou par 'référence'
Par défaut les paramètres sont envoyés 'par valeur' vers une procédure. Aussi, si la variable contenant le paramètre est modifiée, cela ne modifie pas la valeur de la variable de la procédure appelante.

Si on a peur de se tromper utilisons 'ByVal' et 'ByRef' dans l'en-tête de la Sub ou de la Fonction.


XVI-E-2-g. Structurez le code, évitez les Goto
Faire de bonnes Sub ou de bonnes classes.

Découper les problèmes en sous-ensemble plus simples et ne faisant chacun qu'une tâche.

Structurer le code en evitant les GoTo.



XVI-E-2-h. Ne faire aucune confiance à l'utilisateur du logiciel
Si vous demandez à l'utilisateur de saisir un entier entre 1 et 7.


Vérifier:

qu'il a tapé quelque chose!!

Qu'il a tapé une valeur numérique.

Que c'est un entier.

Que c'est supérieur à 0 et inférieur à 8.


Accorder les moindres privilèges:

Ne permettre de saisir que ce qui est valide.


XVI-E-2-i. Rendre le code lisible par tous
S'il est lisible par tous, il sera lisible et comprehensible par vous même dans un an.

S'il est lisible par tous, il sera déboguable plus facilement.


Pour améliorer la lisibilité:

-Bien choisir le nom des variables.

-Mettre des commentaires.

-Ecrire les algorithmes compréhensibles par tous.



XVI-E-3. Rendre le code lisible : commentaires, noms de variables

Pour faire un code lisible:

  • Mettre des commentaires.
  • Choisir de bons noms de variable.
  • Aérer le code.

XVI-E-3-a. Ajoutez des commentaires
Pour vous; pour les autres.

Au début de chaque routine, Sub, Function, Classe , noter en commentaire ce qu'elle fait et quelles sont les caractéristiques des paramètres:

  • Le résumé descriptif de la routine, la Sub ou Function.
  • Une description de chaque paramètre.
  • La valeur retournée s'il y en a une.
  • Une description de toutes les exceptions..
  • Un exemple d'utilisation
  • Une explication sur le fonctionnement de la routine.

Ne pas ajouter de commentaire en fin de ligne (une partie ne sera pas visible) mais plutôt avant la ligne de code. Seule exception ou on utilise la fin de ligne: les commentaires après les déclarations de variable.


Dim i As Integer    'Variable de boucle
'Parcours du tableau à la recherche de..
For i=0 To 100
...

Paradoxalement, trop de commentaires tue le code autant que le manque de commentaires. Pour éviter de tomber dans le tout ou rien, fixons nous quelques règles:

  • Commentez le début de chaque Sub, Fonction, Classe
  • Commentez toutes les déclarations de variables
  • Commentez toutes les branches conditionnelles
  • Commentez toutes les boucles
Commentaire en Xml

On peut ajouter des commentaires en xml dans VB2005.

Exemple:

Pour une Sub: Sur une ligne blanche au dessus de la Sub Calcul, tapez "'''" (3 "'").


ou


Pour une variable: Curseur sur la variable, bouton droit puis 'Insérer un commentaire' dans le menu.


Un bloc Xml "Summary" se crée automatiquement; pour l'exemple de la Sub, ajouter 'Fonction Calculant le total'


Quand ensuite on tape le nom de la Sub , le commentaire s'affiche.

De plus Visual Basic génère automatiquement un fichier de documentation XML lorsque vous créez le projet. Ce fichier apparaît dans le répertoire de sortie de l'application sous le nom AssemblyName.xml.


XVI-E-3-b. Choisissez les noms de procédures et de variables avec soin
On concatène plusieurs mots pour former un nom de fonction, de variable de Classe..

Il y a 3 manières d'utiliser les lettres capitales dans ces mots (on appelle cela la capitalisation!)

  • Pascal Case: La première lettre de chaque mot est en majuscule:
    
    CalculTotal()
    
  • Camel Case: La première lettre de chaque mot est en majuscule, sauf la première:
    
     iNombrePatient
    
  • UpperCase: Toutes les lettres sont en majuscule:
    
     MACONSTANTE
    

De plus le nom doit être explicite.

Microsoft propose quelque règles:

  • Sub , Fonctions
    Utilisez la 'case Pascal' pour les noms de routine (la première lettre de chaque mot est une majuscule).

    Exemple: CalculTotal()

    Évitez d'employer des noms difficiles pouvant être interprétés de manière subjective, notamment Analyse() pour une routine par exemple.

    Utilisez les verbe/nom pour une routine : CalculTotal().

  • Variables
    Pour les noms de variables, utilisez la 'case Camel' selon laquelle la première lettre des mots est une majuscule, sauf pour le premier mot.

    Exemple: iNombrePatient

    noter ici que la première lettre indique le type de la variable (Integer), elle peut aussi indiquer la portée(gTotal pour une variable globale).

    Évitez d'employer des noms difficiles pouvant être interprétés de manière subjective, 'YYB8' ou 'flag' pour une variable.

    Ajoutez des méthodes de calcul ( Min, Max, Total) à la fin d'un nom de variable, si nécessaire.

    Les noms de variable booléenne doivent contenir Is qui implique les valeurs True/False, par exemple fileIsFound.

    Évitez d'utiliser des termes tels que Flag lorsque vous nommez des variables d'état, qui différent des variables booléennes car elles acceptent plus de deux valeurs. Plutôt que documentFlag, utilisez un nom plus descriptif tel que documentFormatType.

    Même pour une variable à courte durée de vie utilisez un nom significatif. Utilisez des noms de variable d'une seule lettre, par exemple i ou j, pour les index de petite boucle uniquement.

  • Paramètre
    Pour les noms de paramètres, utilisez la 'case Camel' selon laquelle la première lettre des mots est une majuscule, sauf pour le premier mot.

    Exemple: typeName

  • Constantes
    Utiliser les majuscules pour les constantes: MYCONSTANTE

    N'utilisez pas des nombres ou des chaînes littérales telles que For i = 1 To 7. Utilisez plutôt des constantes par exemple For i = 1 To DAYSINWEEK, pour simplifier la maintenance et la compréhension.

  • Objet, Classe
    Pour les noms de Classe, utiliser le case Pascal:

    Exemple Class MaClasse

    Idem pour les évènement, espace de nom, méthodes:

    Exemple: System.Drawing, ValueChange..

    Dans les objets, il ne faut pas inclure des noms de classe dans les noms de propriétés Patient.PatientNom est inutile, utiliser plutôt Patient.Nom.

    Les interfaces commencent par un I

    Exemple: IDisposable

    Pour les variables privées ou protégées d'une classe utilisez le case Camel:

    Exemple: lastValue (variable protégée)

    En plus pour les variables privées d'une classe mettre un "_" avant:

    Exemple: _privateField

    Pour une variable Public d'une classe:

    Exemple TotalAge

  • Tables
    Pour les tables, utilisez le singulier. Par exemple, utilisez table 'Patient' plutôt que 'Patients'.

    N'incorporez pas le type de données dans le nom d'une colonne.

  • Divers
    Minimisez l'utilisation d'abréviations,

    Lorsque vous nommez des fonctions, insérez une description de la valeur retournée, notamment GetCurrentWindowDirectory().

    Évitez de réutiliser des noms identiques pour divers éléments.

    Évitez l'utilisation d'homonymes et des mots qui entraînent souvent des fautes d'orthographe.

    Évitez d'utiliser des signes typographiques pour identifier des types de données, notamment $ pour les chaînes ou % pour les entiers.

    Un nom doit indiquer la signification plutôt que la méthode.


XVI-E-3-c. Éclaircir, aérer le code
Eviter plusieurs instructions par ligne.

Ajouter quelques lignes blanches.

Décaler à droite le code contenu dans une boucle ou une section If.. End If:

Une mise en retrait simplifie la lecture du code, par exemple :


If ... Then
    If ... Then
        ...
    Else
        ...
    End If
Else
...
End If
 
  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.