Bien programmer en langage C
Date de publication : 28 mai 2008
V. Concevoir et réaliser un composant logiciel en C
V-A. Introduction
V-B. Stratégie de développement d'un composant logiciel
V-B-1. Modélisation d'un composant logiciel
V-B-2. Implémentation du modèle.
V-C. Etude de cas : Un gestionnaire d'alarme à niveaux
V-C-1. Spécifications
V-C-2. Conception
V-C-3. Réalisation en C
V-C-3-a. Nommage du composant logiciel
V-C-3-b. Interface
V-C-3-c. Implémentation
V. Concevoir et réaliser un composant logiciel en C
V-A. Introduction
Lors du développement d'un projet logiciel, il est courant d'écrire des portions de codes qui pourraient
être utiles dans d'autres projets. Pour ce faire, il faut définir une stratégie de développement
de modules de logiciels dont le comportement est clairement spécifié, les performances et les limites
connues, et qui soient facilement intégrables.
Ce type de module logiciel est appelé 'composant logiciel', par analogie avec les composants utilisés
dans l'industrie de l'électronique, par exemple.
V-B. Stratégie de développement d'un composant logiciel
Les qualités requises pour un composant logiciel sont :
-
Une spécification claire
-
Comportement
-
Performances
-
Interface (entrées, sorties)
-
Gestion des erreurs
-
Une grande fiabilité
-
Respect des normes du langage
-
Respect des normes de codage
-
Vérification du domaine des paramètres
-
Elimination des pratiques douteuses
-
Testabilité (indépendance vis à vis des sorties)
-
Jeu de tests de non regression
-
Une facilité d'integration
-
Portabilité
-
Respect des normes de nommage
-
Elimination des pratiques empêchant la réentrance
-
Elimination des pratiques empêchant l'instanciation
-
Simplicité des points de sorties
V-B-1. Modélisation d'un composant logiciel
L'expérience montre que la plupart des composants logiciels servent à effectuer un traitement sur des
données.
---------------
Entrées | | Résultat
---------> | Traitements | ----------->
| |
---------------
|
Certains évènements extérieurs peuvent agir sur les données (entrées). Il est bien sûr possible de fournir
un résultat immédiat par valeur de retour ou par un paramètre de sortie, mais certains traitements
peuvent aussi donner lieu à des réactions en fonction des résultats et/ou de l'état courant (seuil,
erreur etc.). Se sont les sorties.
On peut donc modéliser un composant logiciel de la façon suivante :
----------------------------------------
| Composant logiciel |
|----------------------------------------|
| |
| --------------- |
| Entrées | | Sorties |
------------> | Traitements | ----------->
| Résultat | | |
<------------ | | |
| --------------- |
| |
----------------------------------------
|
V-B-2. Implémentation du modèle.
Les entrées sont implémentées par de simples appels de fonctions appelées 'points d'entrées'. Vu de l'extérieur,
il s'agit d'une interface (API). Chaque interface de fonction est décrite par son nom, ses paramètres
(nom et type) et son type de retour.
Les traitements sont le corps des fonctions. Certains traitements sont des actions directes sur les données,
et sont retournées immédiatement, d'autres nécessitent de la mémoire permanente pour gérer des
configurations, états, compteurs, files etc. Il peut aussi y avoir des fonctions de lecture qui
permettent d'accéder à certaines données internes. Enfin, selon des critères bien définis, le traitement
peut générer des appels à des fonctions de sorties (évènements).
Les fonctions de sorties sont extérieures au composant. Leur appel se fait donc via une variable contenant
l'adresse de la fonction. Par contre, la spécification de l'interface des fonctions de sorties
appartient à la définition du composant. Il est probable que dans le cadre de l'intégration du
composant dans un projet, les fonctions de sorties doivent être développées projet par projet.
C'est une opération simple qui nécessite de fournir l'interface spécifiée, et l'appel à la fonction
extérieure (qui peut très bien être un point d'entrée d'un autre composant logiciel, voire du même),
parfois au prix d'un transcodage de valeur.
La modélisation détaillée d'un composant logiciel et de son environnement est donc la suivante :
--------------------------------------------------------------------
| Application |
|--------------------------------------------------------------------|
| ---------- ------------------ |
| | Appelant | | | |
| ---------- | v |
| |^ | -------- |
| || | | Sortie | |
| || | -------- |
------------||-----------------------|------------------------------
v| |
-------------------------- |
------| Entrées |---|------
|C L | -------------------------- | |
|o o | |^ | |
|m g | || | |
|p i | v| | |
|o c | ---------------- | |
|s i | | | --------- |
|a e | | Traitements | -----> | Sorties | |
|n l | | | --------- |
|t | | | |
| | ---------------- |
| | |^ |
--------------||----------------------------
||
v|
--------------
| Bibliothèque |
| standard |
--------------
|
V-C. Etude de cas : Un gestionnaire d'alarme à niveaux
V-C-1. Spécifications
On dispose d'un système qui fait des mesures de grandeurs physiques. On souhaite ajouter un mécanisme
qui déclenche une alarme lorsque un certain seuil est dépassé, et qui supprime l'alarme lorsqu'on
repasse en dessous d'un autre seuil.
La gamme de mesure acceptable couvre les valeurs de -32767 à 32767. Les seuils sont programmables sur
toute la gamme de mesure. Le seuil de retour doit être inférieur au seuil de déclenchement.
V-C-2. Conception
A partir du modèle de composant logiciel standard, on ajoute les spécificités du projet :
----------------------------------------------
| Composant logiciel 'Alarme' |
|----------------------------------------------|
| |
| --------------- |
| Entrées | Traitements | Sorties |
| | | |
| | | Alarme (ON/OFF) |
| Réglage | | ------------------>
| seuils | | |
---------------> | | |
<--------------- | | |
| | | |
| Mesure | | |
---------------> | | |
<--------------- | | |
| --------------- |
| |
----------------------------------------------
|
Le traitement peut être décrit par un algorithme textuel :
Si la mesure est supérieure ou égale au seuil de déclenchement,
et que l'on est pas en alarme, declencher l'alarme.
Si l'alarme est déclenchée et que la mesure est inférieure au seuil
de retour, arréter l'alarme.
|
Ce que l'on peut traduire en langage algorithmique de la façon suivante :
IF NOT in_alarme
IF mesure >= seuil_declenchement
in_alarme := TRUE
sortie_alarme (ON)
ENDIF
ELSE
IF mesure < seuil_retour
in_alarme := FALSE
sortie_alarme (OFF)
ENDIF
ENDIF
|
Les données permanentes sont donc
-
Les données de configuration
-
Le seuil de déclenchement
-
Le seuil de retour
-
L'adresse de la fonction de sortie
-
Les données d'état
-
L'état courant de l'alarme
Les fonctions nécessaires à l'exploitation sont :
-
Les fonctions de configuration
-
Enregistrement de la fonction de sortie 'alarme'
-
Réglage du seuil de déclenchement
-
Réglage du seuil de retour
-
Les fonctions d'évaluation
-
Evaluation de la mesure courante
V-C-3. Réalisation en C
V-C-3-a. Nommage du composant logiciel
Le composant logiciel est nommé 'alarme'. Le prefixe est donc 'alarme_'
V-C-3-b. Interface
Les données sont rassemblées dans la structure suivante.
typedef struct
{
int seuil_declenchement;
int seuil_retour;
unsigned active:1 ;
}
alarme_s;
|
Les seuils sont de type int, qui couvre la plage requise par la spécification. Le champ 'active', qui
contient l'état courant de l'alarme, peut prendre les valeurs 0 et 1, d'où l'utilisation d'un
champ de bits de 1 bit.
Il manque le champ qui contient l'adresse de la fonction de sortie. Il est très pratique d'utiliser un
typedef pour spécifier le type de cette fonction, surtout qu'à ce stade de la réalisation, on
n'a pas une vision très claire de cette fonction. Le type risque donc d'évoluer, d'où l'intérêt
du typedef qui concentre la définition du type en un seul endroit. Dans un premier temps, je propose
:
typedef void alarme_out_f (int on);
|
La structure peut donc s'écrire
typedef struct
{
int seuil_declenchement;
int seuil_retour;
alarme_out_f * pf_out;
unsigned active:1 ;
}
alarme_s;
|
Il reste à définir le prototype des fonctions d'exploitations. Il est d'usage d'implémenter un système
de gestion d'erreur minimum, qui consiste à retourner 0 quand tout va bien, et 1 en cas de problèmes.
int alarme_install_out (alarme_s * this, alarme_f * pf);
int alarme_trigger_level (alarme_s * this, int level);
int alarme_return_level (alarme_s * this, int level);
int alarme_eval (alarme_s * this, int value);
|
V-C-3-c. Implémentation
Maintenant que l'interface des fonctions est spécifiée, on peut écrire l'implémentation des fonctions
publiques (points d'entrée). Il suffit de recopier l'interface et d'écrire le corps des fonctions.
Il est d'usage de sécuriser les fonctions en effectuant des tests de validité (valeur, domaine)
sur les paramètres. D'autre part, afin d'éviter des erreurs à venir, il est fortement recommandé
de définir les paramètres 'non-modifiables' à l'aide du qualificateur 'const'. Ca évite de perdre
une valeur qui aurait pu être précieuse.
int alarme_install_out (alarme_s * const this, alarme_f * const pf)
{
int err = 0 ;
if (this ! = NULL )
{
this- > pf = pf;
}
else
{
err = 1 ;
}
return err;
}
int alarme_trigger_level (alarme_s * const this, int const level)
{
int err = 0 ;
if (this ! = NULL )
{
if (level >= ALARME_VALUE_MIN
& & level <= ALARME_VALUE_MAX)
{
this- > seuil_declenchement = level;
}
else
{
err = 1 ;
}
}
else
{
err = 1 ;
}
return err;
}
int alarme_return_level (alarme_s * const this, int const level)
{
int err = 0 ;
if (this ! = NULL )
{
if (level >= ALARME_VALUE_MIN
& & level <= ALARME_VALUE_MAX)
{
this- > seuil_retour = level;
}
else
{
err = 1 ;
}
}
else
{
err = 1 ;
}
return err;
}
int alarme_eval (alarme_s * const this, int const value)
{
int err = 0 ;
if (this ! = NULL )
{
if (valuel >= ALARME_VALUE_MIN
& & value <= ALARME_VALUE_MAX)
{
if (! this- > active)
{
if (value > this- > seuil_declenchement)
{
this- > active = 1 ;
out (this, 1 );
}
}
else
{
if (value < this- > seuil_retour)
{
this- > active = 0 ;
out (this, 0 );
}
}
}
else
{
err = 1 ;
}
}
else
{
err = 1 ;
}
return err;
}
|
Pour terminer l'implémentation, il reste à définir les constantes ALARME_VALUE_MIN et ALARME_VALUE_MAX
...
# define ALARME_VALUE_MIN - 32767
# define ALARME_VALUE_MAX 32767
|
... et la fonction interne (privée) out().
static void out (alarme_s * this, int on)
{
if (this- > pf_out ! = NULL )
{
this- > pf_out (on);
}
}
|
L'ensemble du composant logiciel 'alarme' est stocké ici (Ver 1.0) :
alarme.halarme.c avec le fichier de
test rudimentaire (rien à voir avec un véritable test unitaire)
test.c
Nous disposons d'une sorte de 'maquette' qui permet de faire quelques tests, de vérifier l'algorithme
etc.
Tant qu'il n'a pas passé une séance de validation unitaire intense, ce n'est certainement pas un produit
fini.
Copyright © 2008 Emmanuel Delahaye.
Aucune reproduction, même partielle, ne peut être faite
de ce site ni 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.