Matérialisons pratiquement
certains concepts d’une interface de communication à l’aide d’un exemple
orienté interface.
Nous voulons faire saisir par l’utilisateur deux entiers et l’autoriser à effectuer leur somme et leur produit uniquement lorsque les deux entiers sont entrés. Aucune sécurité n’est apportée pour l’instant sur les données. |
Dès que les deux entiers
sont entrés, le bouton calcul est activé:
Lorsque l’on clique sur le
bouton calcul, le bouton effacer est activé et les résultats
apparaissent dans leurs zones :
Un clic sur le bouton effacer ramène l’interface à l’état initial.
1.2 Réalisation de l’implantation en Delphi
Un graphe événementiel complet de notre interface pourrait être :
Avec comme conventions X1 = exécuter les calculs sur les valeurs entrées. X2 = exécuter l’effacement des résultats. M1 = message à l’edit1 de tout effacer. M2 = message à l’edit2 de tout effacer. |
Changer
Edit1 |
Calcul activable
(si changer Edit2 a eu lieu) |
Changer
EDIT2 |
Calcul activable
(si changer Edit1 a eu lieu) |
Clic
CALCUL |
Exécuter X1
EFFACER activable Afficher les résultats |
Clic
EFFACER |
EFFACER désactivé
CALCUL désactivé Message M1 Message M2 |
Edit1 | activé |
Edit2 | activé |
Bcalcul | désactivé |
Beffacer | désactivé |
Implantation :
ButtonCalcul: TButton;
Buttoneffacer: TButton;
Edit1: TEdit;
Edit2: TEdit;
LabelSomme: TLabel;
LabelProduit: TLabel;
Implantation : deux variables
booléennes
var Som_ok,Prod_ok:boolean; |
procedure
TForm1.Edit1Change(Sender: TObject); begin Som_ok:=true; // drapeau de Edit1 levé end; |
procedure
TForm1.Edit2Change(Sender: TObject); begin Prod_ok:=true; // drapeau de Edit2 levé end; |
Implantation du test des drapeaux
:
procedure
TestEntrees; {les drapeaux sont-ils levés tous les deux ?} begin if Prod_ok and Som_ok then Form1.ButtonCalcul.Enabled:=true // si et seulement si oui: le bouton calcul est activé end; |
Implantation n°2 du gestionnaire de OnChange :
procedure TForm1.Edit1Change(Sender:
TObject); begin Som_ok:=true; // drapeau de Edit1 levé TestEntrees; end; |
procedure TForm1.Edit2Change(Sender:
TObject); begin Prod_ok:=true; // drapeau de Edit2 levé TestEntrees; end; |
Implantation du gestionnaire
de OnClick du ButtonCalcul :
procedure TForm1.ButtonCalculClick(Sender:
TObject); var S,P:integer; begin S:=strtoint(Edit1.text); // transtypage : string à integer P:=strtoint(Edit2.text); // transtypage : string à integer LabelSomme.caption:=inttostr(P+S); LabelProduit.caption:=inttostr(P*S); Buttoneffacer.Enabled:=true // le bouton effacer est activé end; |
Edit1 | activé |
Edit2 | activé |
Bcalcul | désactivé |
Beffacer | désactivé |
procedure RAZTout;
begin with Form1 do begin Buttoneffacer.Enabled:=false; //le bouton effacer se désactive ButtonCalcul.Enabled:=false; //le bouton calcul se désactive LabelSomme.caption:='0'; // RAZ valeur somme affichée LabelProduit.caption:='0'; // RAZ valeur produit affichée Edit1.clear; // message M1 Edit2.clear; // message M2 Prod_ok:=false; // RAZ drapeau Edit2 Som_ok:=false; // RAZ drapeau Edit1 end end; |
Implantation du gestionnaire
de OnClick du Buttoneffacer:
procedure TForm1.ButtoneffacerClick(Sender:
TObject); begin RAZTout; end; |
Implantation du gestionnaire
de OnCreate de la fiche Form1:
procedure TForm1.FormCreate(Sender:
TObject); begin RAZTout; end; |
Lorsque nous essayons notre interface nous constatons que nous avons un problème de sécurité à deux niveaux dans notre saisie.
Le premier niveau est celui des corrections apportées par l’utilisateur (effacement de chiffres déjà entrés) et la synchronisation avec l’éventuelle vacuité d’au moins un des champs text après une telle modification : en effet l’utilisateur peut effacer tout le contenu d’un " Edit " et lancer alors le calcul, provoquant ainsi des erreurs.
Le deuxième niveau classique
est celui du transtypage incorrect, dans le cas où l’utilisateur commet
des fautes de frappe et rentre des données autres que des chiffres
(des lettres ou d’autres caractères du clavier).
1.3 Améliorations de sécurité du premier niveau par plan d’action
Delphi.Interfaces \Calcul.V.2.dlfi
Nous protégeons les calculs dans le logiciel, par un test sur la vacuité du champ text de l’Edit ; dans le cas favorable où le champ n’est pas vide, on autorise la saisie ; dans l’autre cas on désactive systématiquement le ButtonCalcul et l’on abaisse le drapeau qui avait été levé lors de l’entrée du premier chiffre, ce qui empêchera toute erreur ultérieure. L’utilisateur comprendra de lui-même que tant qu’il n’y a pas de valeur dans les entrées, le logiciel ne fera rien et on ne passera donc pas au plan d’action suivant (calcul et affichage). Cette amélioration s’effectue dans les gestionnaires d’événement OnChange des deux TEdit.
Implantation n°2 du gestionnaire de OnChange :
procedure TForm1.Edit1Change(Sender:
TObject); begin if Edit1.text<>'' then// champs text non vide ok ! begin Som_ok:=true; TestEntrees; end else begin ButtonCalcul.enabled:=false; // sinon bouton désactivé Som_ok:=false; // drapeau de Edit1 baissé end end; |
procedure TForm1.Edit1Change(Sender:
TObject); begin if Edit1.text<>'' then// champs text non vide ok ! begin Som_ok:=true; TestEntrees; end else begin ButtonCalcul.enabled:=false; // sinon bouton désactivé Som_ok:=false; // drapeau de Edit1 baissé end end; |
procedure TForm1.Edit2Change(Sender:
TObject); begin if Edit2.text<>'' then // champs text non vide ok ! begin Prod_ok:=true; TestEntrees; end else begin ButtonCalcul.enabled:=false; // sinon bouton désactivé Prod_ok:=false; // drapeau de Edit2 baissé end end; |
1.4 Améliorations de sécurité du second niveau par filtrage
Nous pouvons améliorer cet état de la saisie des caractères chiffres en construisant un analyseur de filtrage qui ne conserve que les caractères valides tapés dans chaque Tedit. La syntaxe de l’entrée est fournie par le diagramme suivant :
var i:integer; saisie :string; CarValides:set of char; begin CarValides:=['0'..'9']; // les chiffres seulement saisie:=entree; if length(saisie)<>0 then begin i:=1; while saisie[i] in CarValides do i:=i+1; // le premier caractère non juste if not(saisie[i] in CarValides)then delete(saisie,i,1); end; resultat:=saisie end; |
var sortie :string ; begin Filtrage(Edit1.text,sortie); Edit1.text:=sortie; Som_ok:=true; // drapeau de Edit1 levé TestEntrees; end; |
var sortie :string ; begin Filtrage(Edit2.text,sortie); Edit2.text:=sortie; Prod_ok:=true; // drapeau de Edit2 levé TestEntrees; end; |
Le filtrage est effectué lors de la saisie des entrées dans les gestionnaires d’événement OnChange des deux Tedit. Chaque changement du contenu du champ text d’un Edit (comme l’entrée d’un nouveau caractère) provoque le déclenchement de l’événement OnChange qui rejette alors les caractères non valides.
Implantation n°4 du gestionnaire de OnChange :
var sortie :string ; begin Filtrage(Edit1.text,sortie); Edit1.text:=sortie; if Edit1.text<>'' then// champs text non vide ok ! begin Som_ok:=true; TestEntrees; end else begin ButtonCalcul.enabled:=false; // sinon bouton désactivé Som_ok:=false; // drapeau de Edit1 baissé end end; |
var sortie :string ; begin Filtrage(Edit2.text,sortie); Edit2.text:=sortie; if Edit2.text<>'' then // champs text non vide ok ! begin Prod_ok:=true; TestEntrees; end else begin ButtonCalcul.enabled:=false; // sinon bouton désactivé Prod_ok:=false; // drapeau de Edit2 baissé end end; |
En combinant la sécurité du premier et du second niveau dans le gestionnaire d’événement OnChange nous obtenons un niveau acceptable de sécurisation de notre logiciel grâce à son interface. Il nous manque encore une protection sur les débordements de calcul (dépassement de l’intervalle sur les integer qui ne provoquent pas d’incidents mais induisent des résultats erronés) pour assurer une sécurité optimale de fiabilité.
Pour terminer la réalisation
de cet exemple, nous pouvions aussi procéder à un autre genre
de protection sans avoir à écrire un analyseur de filtrage (qui
était dans ce cas un AEFD), en utilisant directement les facilités
de protection fournies par les exceptions.
1.5 Améliorations de sécurité du second niveau par exceptions
On déporte le problème de la protection non plus sur le séquencement des plans d’actions mais au cœur même de l’action en protégeant directement le code où l’erreur se produit par un gestionnaire d’exception. Afin de présenter un traitement complet de l’exemple nous anticipons légèrement sur le chapitre sur la programmation défensive ; le lecteur pourra donc en première lecture sauter ce paragraphe s’il le désire et y revenir plus tard.
On fait exécuter le
programme en provoquant volontairement l’erreur (on entre des lettres au
lieu de chiffres) ; le logiciel signale la levée de l’exception EconvertError
et nous programmons le gestionnaire associé à cette levée
d’exception. Elle apparaît lorsque la fonction StrtoInt essaye de transtyper
le champ text de l’Edit et échoue ; c’est donc cette ligne de code
qui doit être protégée :
S:= StrtoInt(edit1.text); except on EconvertError do begin Edit1.text:='0'; S:=0 end end; |
Nous avons décidé
ici de mettre 0 dans le champ text de l’Edit1 et de forcer la valeur de la
variable de calcul S à 0. Ce traitement est identique pour la variable
P à partir du champ text de l’Edit2. Ces deux traitements de protection
sont effectués lors du click sur ButtonCalcul (traitement de l’erreur
après saisie).
var S,P:integer; begin try S:= StrtoInt(edit1.text); except on EconvertError do begin Edit1.text:='0'; S:=0 end end; try P:= StrtoInt(edit2.text); except on EconvertError do begin Edit2.text:='0'; P:=0 end end; LabelSomme.caption:=inttostr(P+S); LabelProduit.caption:=inttostr(P*S); Buttoneffacer.enabled:=true // le bouton effacer est activé end;{ButtonCalculClick} |
Ce logiciel d’interface simplifiée
met en œuvre tous les concepts de base d’une interface à l’exception
des temps d’attente qui ne sont pas pertinents dans cet exemple :
Un chapitre spécial est
consacré à la programmation défensive afin de nous aider
à améliorer la résistance aux erreurs de nos logiciels
de communication.