Developpez.com - Rubrique Pascal

Le Club des Développeurs et IT Pro

Bonnes pratiques de programmation en Pascal

Un débat pour rassembler les meilleurs conseils de programmation

Le 2011-03-28 06:47:27, par Alcatîz, Responsable Pascal, Lazarus et Assembleur
Bonjour à toutes et à tous,

Le forum Pascal regorge de conseils et de bonnes pratiques de programmation. Force est de constater qu'il faut beaucoup de recherches pour les retrouver et que certains conseils doivent sans cesse être répétés aux développeurs qui débutent en Pascal.
D'où l'idée de les regrouper dans un unique fil de discussion.

Nous en profitons pour vous rappeler l'article de Philippe Gormand sur l'écriture de code Pascal, qui contient plein de conseils d'indentation et de mise en forme, de choix d'identificateurs, etc.

Nous vous invitons à partager avec tous les membres du forum vos meilleures pratiques. Lorsqu'il y aura suffisamment de matière, nous pourrons rassembler tous vos conseils dans un véritable guide.

  Discussion forum
25 commentaires
  • Alcatîz
    Responsable Pascal, Lazarus et Assembleur
    Quelques pratiques pour initier le débat :
    • Pensez au couple papier+crayon !

    Droggo n'a de cesse de le répéter (depuis le temps, il y a un copyright ) : avant de commencer à coder, couchez votre programme sur papier. Que ce soit en pseudo-code ou en langage courant, écrivez votre programme ou votre algorithme de manière claire et exécutez-le sur papier. Ce n'est qu'après cette étape de conception et de tests que vous pourrez traduire votre programme en Pascal.
    • Indentez convenablement votre code

    Une bonne indentation facilite la lecture et permet de détecter beaucoup d'erreurs, comme, par exemple, l'absence de fermeture d'un bloc d'instructions.

    Qui peut facilement voir où manque un end dans ce code ?
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Program Chiffres_Lettres;
    Uses WinCrt;
    Var Caractere : Char;
    Begin
    InitWinCrt;
    repeat
    Write('Entrez un caractère ("X" pour quitter) : ');
    Caractere := ReadKey;
    WriteLn;
    if Caractere in ['0'..'9'] then
    WriteLn('C''est un chiffre.')
    else if Caractere in ['A'..'Z','a'..'z'] then begin
    Write('C''est une lettre ');
    if Caractere in ['A'..'Z'] then
    WriteLn('majuscule.') else WriteLn('minuscule.');
    else WriteLn('C''est une lettre accentuée ou un caractère spécial.');
    WriteLn;
    until Caractere = 'X';
    DoneWinCrt;
    End.
    ...alors qu'on le voit tout de suite quand c'est indenté :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    Program Chiffres_Lettres;
    Uses WinCrt;
    Var Caractere : Char;
    Begin
      InitWinCrt;
      repeat
        Write('Entrez un caractère ("X" pour quitter) : ');
        Caractere := ReadKey;
        WriteLn;
        if Caractere in ['0'..'9']
           then
             WriteLn('C''est un chiffre.')
           else
             if Caractere in ['A'..'Z','a'..'z']
                then
                  begin
                    Write('C''est une lettre ');
                    if Caractere in ['A'..'Z']
                        then
                         WriteLn('majuscule.')
                       else
                         WriteLn('minuscule.');
                  end   (* <-- C'est ici ! *)
                else
                  WriteLn('C''est une lettre accentuée ou un caractère spécial.');
        WriteLn;
      until Caractere = 'X';
      DoneWinCrt;
    End.
    • Soyez cohérent(e) dans votre approche

    Essayez de traiter des problèmes identiques de manière similaire. Combien de fois voit-on qu'un bout de code a été écrit en utilisant un concept puis le bout de code suivant avec une autre approche, alors que la même approche aurait permis d'avoir un code cohérent.
    • Commentez votre code !

    Que ce soit pour vous relire vous-même plus tard ou pour qu'une autre personne lise et comprenne votre code, de grâce ajoutez suffisamment de commentaires là où c'est utile.

    Par exemple, dans une procédure, indiquez l'utilité de vos variables locales. Si, dans un traitement, vous effectuez un tri, précédez le tri d'un commentaire du genre "Tri du tableau par quick-sort" : vous serez bien content(e), six mois plus tard, d'avoir ce commentaire pour vous rappeler immédiatement ce que fait votre code sans avoir à le relire pour le comprendre.
    • Utilisez des identificateurs parlants

    Il est très utile de choisir des noms d'identificateurs qui donnent un maximum de renseignements sur l'utilité d'une variable, un type, une procédure ou fonction, etc.

    Par exemple, ce code :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Const AgeMajorite = 18;
    
    Type TTabEntiers = Array [1..20] of Integer;
    
    Var TableauAges : TTabEntiers;
    
    Function Age_Maximum (Const Tableau : TTabEntiers) : Integer;
    Begin
      (* ... *)
    End;
    
    Begin
      (* ... *)
      if Age_Maximum(TableauAges) > AgeMajorite
         then
      (* ... *)
    End.
    est beaucoup plus parlant que :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Type t = Array [1..20] of Integer;
    
    Var a : t;
    
    Function v (const tab : t) : Integer;
    Begin
      (* ... *)
    End;
    
    Begin
      (* ... *)
      if v(a) > 18
         then
      (* ... *)
    End.
    • Structurez vos programmes

    Le Pascal permet énormément de souplesse pour structurer son code. Isolez chaque traitement dans une procédure ou fonction et découpez vos programmes en unités. La structuration logique d'un programme facilite grandement sa compréhension, sa lecture et sa maintenance. De plus, vous pourrez plus aisément aisément réutiliser votre code dans d'autres programmes.
    • Une procédure ou fonction est une boîte hermétique

    Une procédure ou fonction doit recevoir comme paramètres tout ce dont elle a besoin et ne doit pas travailler directement avec des variables globales. Il faut considérer la procédure ou fonction comme une boîte hermétiquement fermée, qui reçoit d'un côté des paramètres et qui renvoie de l'autre côté un résultat et/ou une version modifiée des paramètres reçus en entrée.

    En appliquant systématiquement cette règle, on facilite la compréhension et le débogage d'un programme.
    • Transmettez vos paramètres invariables comme constantes

    Quand c'est possible (ce qui n'est pas le cas en Turbo Pascal), transmettez à vos procédures et fonctions des paramètres qui ne doivent pas être modifiés comme constantes. Si vous les transmettez par valeur, il sont inutilement recopiés sur la pile, ce qui est un non-sens en matière d'optimisation.

    Si l'on reprend un exemple cité plus haut :
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Const AgeMajorite = 18;
    
    Type TTabEntiers = Array [1..20] of Integer;
    
    Var TableauAges : TTabEntiers;
    
    Function Age_Maximum (Const Tableau : TTabEntiers) : Integer;
    Begin
      (* ... *)
    End;
    
    Begin
      (* ... *)
      if Age_Maximum(TableauAges) > AgeMajorite
         then
      (* ... *)
    End.
    La fonction Age_Maximum ne modifie pas le tableau TableauAges, elle ne fait qu'en extraire la valeur maximum. Il est donc logique de passer le tableau comme constante et seule son adresse est déposée sur la pile. Tandis que si on l'avait passé par valeur :
    Code :
    Function Age_Maximum (Tableau : TTabEntiers) : Integer;
    il serait intégralement recopié sur la pile, ce qui prendrait inutilement du temps machine et consommerait inutilement de l'espace sur la pile.
    • Utilisez des constantes pour représenter des valeurs numériques

    En déclarant des constantes pour représenter des valeurs numériques utilisées dans un programme, on se facilite la vie : il suffit de modifier la déclaration d'une constante en tête de programme ou d'unité pour que cela modifie automatiquement tous les exemplaires de sa valeur dans le programme ou l'unité.
  • Paul TOTH
    Expert éminent sénior
    autre bonne pratique, et c'est valable dans tous les langages, ne pas faire une fonction de 15 pages de long, c'est imbuvable. Et sur 15 pages on peut forcément identifier des sous-traitements à mettre dans des sous-fonctions afin de rendre le tout plus digeste.

    dans le même genre, il faut bannir le copier/coller de code (celui-là même qui produit des fonctions de 15 pages), si un code est suffisamment proche d'un autre pour permettre un copier/coller, c'est que le code peut être paramétré dans une sous-fonction qui sera utilisée deux fois.
  • Paul TOTH
    Expert éminent sénior
    j'ai changé 5 ou 6 fois de choix de mise en forme de mes sources au cours de ma longue carrière

    il faut savoir qu'un source est d'autant plus lisible qu'il est sous la forme à laquelle on est habitué.

    par exemple, le begin en fin de ligne ou à la ligne va être pratique ou pas selon l'habitude
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
     if test then begin
       ... 
     end;
    // si on s'attend à avoir le begin sous le if, 
    // on se demande ce que fait ce end ici
    
    
     if test then 
     begin
       ... 
     end;
    // si on a l'habitude d'avoir le begin en fin de ligne, 
    // le if semble inachevé car il n'a pas de end correspondant
     if test then begin
      ...
     end;
     begin
       ... 
     end;
    // on pire, il semble indépendant du begin/end
     if test then Inc(x);
     begin
       ... 
     end;
    J'ai longtemps privilégié l'écriture compacte, car elle permet d'avoir un maximum d'informations dans un minimum de place
    Code :
    1
    2
    3
    4
    5
      // tester les bornes
      if (x<0)or(y<0) then continue;
      if x>maxx then begin x:=0; Inc(y); end;
      if y>maxy then exit;
    aujourd'hui je préfère un code aéré bien qu'il occupe plus d'espace

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
      // point avant l'image
      if (x < 0) or (y < 0) then 
        Continue;
    
      // changement de ligne
      if x > MaxX then 
      begin 
        x := 0; 
        Inc(y); 
      end;
    
      // au delà de l'image on s'arrête
      if y > MaxY then
       Exit;
    Mais tout cela est sans doute moins important avec l'apparition des outils de refactoring.

    pour ce qui est de l'usage de With, c'est un outil comme un autre
    je l'utilise notamment avec les Canvas, ça permet en modifiant une ligne de changer les choses sans passer par une variable temporaire
    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {$IFDEF DIRECT_PAINT}
     with Canvas do
    {$ENDIF}
    {$IFDEF BITMAP_PAINT}
     with FBitmap.Canvas do
    {$ENDIF}
    {$IFDEF PAINTBOX_PAINT}
     with TPaintBox(Sender).Canvas do
    {$ENDIF}
     begin
      ...
     end;
  • popo
    Expert confirmé
    Pour ma part, je vais reprendre l'un des éléments de Alcatiz (le passage de paramètres) et développer un peu plus car, c'est selon moi un point essentiel à l'optimisation

    A l'exception des varaibles objet
    - Tout paramètre dont la valeur en utilisée seulement en entrée et qui n'est pas modifiée en sortie doit être passée en const
    - Tout paramètre dont la valeur est systématiquement modifiée et dont la valeur initiale n'est pas utilisée doit être passée en out
    - Tout paramètre dont la valeur est utilisée en entrée comme en sortie doit être passé en var

    On peut rapidement voir la différence par exemple lors du passage de paramètre de type chaine et en particulier les WideString

    Un autre point que je trouve particulièrement important, c'est d'éviter d'utiliser les composant dans une procédure de traitement qui ne nécessite pas leur utilisation.
    Exemple concret à ne pas faire :
    Code :
    1
    2
    3
    4
    5
    function EstDateSuperieurADateDuJour : Boolean;
    begin
      Result := False;
      if (MonDateTimePicker.Date > now) then Result := True;
    end;
    Préférer ceci :
    Code :
    1
    2
    3
    4
    5
    function EstDateSuperieurADateDuJour(Const UneDate : TDateTime) : Boolean;
    begin
      Result := False;
      if (UneDate > now) then Result := True;
    end;
    Cela évite de refaire une fonction si la date d'un autre composant doit être validée. Malheurement, on voit encore se genre de chose dans le monde professionnel !
  • nostroyo
    Membre éclairé
    Mettre un préfixe à ses variables en fonction du contexte
    -Paramètre A + nom de la variable
    -Variable local L + nom de la variable
    -Champs d'une classe F + nom de variable

    Lors de la création d'un objet pensez toujours à mettre (quand c'est possible) sa destruction dans un finally.
    Code :
    1
    2
    3
    4
    5
    6
      LData := TMemoryStream.Create;
      try
      // traitement
      finally
        LData.Free;
      end;
    Essayer de respecter fonction qui créer un objet = fonction qui détruit cet objet

    Je suis pour ma part totalement contre le with qui empêche l'inspection correct des variables en delphi et qui est source de bug.
  • EpiTouille
    Membre éprouvé
    Pensez à utilisé plusieurs fichiers pour de long code. Surtout avec Tp.
    C'est plus facile de changer de fenetre avec F6 que de parcourir 70 fonctions ou procedures .

    J'ai déja vu des codes avec 150 fonctions dans le main. C'est pas humain
    Ce que je fais c'est que je donne le 'U' a mon préfix d'unit, comme ça je sais directement ou est le main. comme par exemble Uoption ou Usauvegarde.

    Pour moi, le plus important a part l'indentation est le fait de mettre de nom explicite a vos variables. Comme j'ai aussi lut dans 'Code Proprement', les commentaire c'est bien mais il ne faut pas trop en mettre sinon, ça alourdie vachement le code. Certaines fois, une procedure avec de nom claire, et un bon code peut éviter une panoplie de commentaires mais les commentaires sont utiles aussi;

    Aussi éviter les GOTO et les Break. Il y d'autre moyen plus lisible de s'y prendre.

    Titeeee
  • bubulemaster
    Membre éclairé
    Bonjour,

    pour ma part, contrairement à l'article de Philippe Gormand sur l'écriture de code Pascal.
    Je suis pour toujours mettre begin end. Comme ça si on rajoute dans un block on est sûr que c'est pris et on est sûr où s'arrête le block

    Totalement contre le "with" comme teubies, car c'est illisible.

    La "ligne de séparation" à voir, c'est utile pour l'entête de fonction et encore. Ca alourdi la lecture du code je trouve.

    Pour l'utilisation du break voir au cas par cas, dès fois ça simplifie les choses et évite de mettre des conditions à rallonge.
  • Dr.Who
    Membre éprouvé
    Evitez les dépendances aux variables globale de classe dans l’implémentation de cette classe (FormX):

    NE PAS FAIRE :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var 
      Form1 : TForm1;
    
    implementation
    
    procedure TForm1.button1Click(Sender: TButton);
    begin
      Form1.caption := 'Bonjour';
      // dépendance à la variable : Form1 -> source de bugs
    end;

    FAIRE :

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var 
      Form1 : TForm1;
    
    implementation
    
    procedure TForm1.button1Click(Sender: TButton);
    begin
      Caption := 'Bonjour';
      // ou Self.Caption (temporairement en test, inutile en prod)
    end;
  • darrylsite
    Rédacteur
    j' ajouterai qu'il faut éviter de mélanger de la POO et du procédurale surtout dans une même unité (fichier). Le cas le plus courant se retrouve dans les forms.
  • Dr.Who
    Membre éprouvé
    Structurer les fichiers sources :



    - Nommer l'unité principale "Main" par exemple ou "TotoMain" pour votre projet "Toto".

    - Les fonctions d'outils peuvent être délocalisée dans une unité "Tools" ou "Utils" ou encore "TotoUtils", "TotoTools"

    - Chaque unité doit être nommée selon son utilisation, Outils (Tools, Utils), Traduction (Lang, Language), Importation/Exportation (Import, Export), gestion des données (BinFiles, DatFiles, ZipFiles, DbFiles) etc. Soyez clair, précis et concis.

    - Placez les ressources dans un sous-dossiers "Ressources" ou "Res" ou encore "Medias", hiérarchisez correctement vos projets en séparant chaques types de données (sons, images, scripts etc), exemple de structure claire :
    • \Toto
      • \Garbage
      • \Medias
        • \Sounds
        • \Sounds\open.ogg
        • \Sounds\close.ogg
        • \Sounds\click.ogg
        • \Graphics
        • \Graphics\logo.jpg
        • \Graphics\ground.jpg
        • \Graphics\splash.jpg
        • \Datas
        • \Datas\DataBase.db

      • \Setup
        • \Licence
        • \Licence\CeCill.fr.txt
        • \Licence\CeCill.en.txt
        • \Script
        • \Script\Toto.iss
        • \Script\Toto.ico
        • \Release
        • \Release\Toto-setup-v1.0.0.0.exe
        • \Release\Toto-setup-v1.0.1.0.exe
        • \Release\Toto-setup-v1.0.2.0.exe

      • \Setup\ChangeLog.txt

    • \Toto\Toto.dproj
    • \Toto\Toto.res
    • \Toto\Toto.dfm
    • \Toto\TotoMain.pas
    • \Toto\TotoTools.pas