Programmation C avanc ´ee
Portabilit ´e, maintenabilit ´e et r ´eutilisabilit ´e
Licence informatique 3`emeann ´ee
Universit ´e de Marne-la-Vall ´ee
Portabilit ´e
I qu’est-ce qu’un code portable ?
I ind ´ependance vis- `a-vis : - du compilateur
- du syst `eme - de la machine
I pourquoi le faire ?
- force `a coder proprement en prenant du recul - facilite la diffusion des applications
Un code qui compile partout
I pour qu’un code compile toujours, il faut : - ´eviter les choses exotiques comme #pragma - respecter les normes-ansi -Wall
- cerner le code qui d ´epend du compilateur, du syst `eme, de la machine
- ´eviter les d ´ependances vers des biblioth `eques non portables
- faire des Makefile portables
Cerner le code variable
I tout code pouvant varier doit ˆetre cern ´e avec des directives pr ´eprocesseurs, et si possible, isol ´e dans des fichiers `a part
1 /∗ System−dependent f u n c t i o n
2 t h a t compares f i l e s names ∗/
3 i n t fcompare (const char∗ a , const char∗ b ){
4 # i f d e f WINDOWS LIKE
5 r e t u r n s t r c m p i g n o r e c a s e ( a , b ) ;
6 # else
7 r e t u r n strcmp ( a , b ) ;
8 # e n d i f
9 }
Cerner le code variable
I m ˆeme chose pour les inclusions, les types, les constantes, etc.
1 # i f d e f WINDOWS LIKE
2 # include ” mygetopt . h ”
3 # else
4 # include <g e t o p t . h>
5 # e n d i f
1 # i f d e f WINDOWS LIKE
2 # d e f i n e PATH SEPARATOR CHAR ’ / ’
3 # d e f i n e PATH SEPARATOR CHAR ” / ”
4 # else
5 # d e f i n e PATH SEPARATOR CHAR ’\ \’
6 # d e f i n e PATH SEPARATOR CHAR ”\ \”
7 # e n d i f
Noms de fichiers
I casse importante sous certains syst `emes
I il faut ˆetre soigneux:
motor.c :
1 # include ” Motor . h ”
2
3 /∗ . . . ∗/ motor.h :
1 # i f n d e f motorH
2 # d e f i n e motorH
3 /∗ . . . ∗/
4 # e n d i f
compile souswindows, pas souslinux
D ´ependances au syst `eme
I 2 types de d ´ependances :
- valeurs/types (s ´eparateur de fichier /ou\) - comportementales (casse des noms de fichiers)
I pour le premier type, la gestion par#ifdef suffit
I pour le second, pas toujours
D ´ependances au syst `eme
I chaque fois que quelque chose d ´epend du syst `eme, il faut l’expliciter
I exemple : longueur des noms de fichiers - mauvaise solution : constante arbitraire
- bonne solution : utiliser la constante FILENAME MAX (stdio.h) adapt ´ee au syst `eme courant.
Tailles des types
I anticiper les diff ´erences d’architecture
I utiliser les constantes de limits.h
I utilisersizeof
I exemple non portable :
1 void∗ ∗ n e w p t r a r r a y (i n t n ){
2 void∗ ∗ p t r = m a l l o c ( n∗4 ) ;
3 i f ( p t r == NULL ){
4 /∗ . . . ∗/
5 }
6 r e t u r n p t r ;
7 }
Tailles des types
I si on a besoin d’un type avec une taille en octets fixe, utilisez les constantes et types de stdint.h
I exemple : si on veut g ´erer Unicode non ´etendu, on a besoin d’un type sign ´e sur 2 octets
1 # include <s t d i n t . h>
2
3 typedef u i n t 1 6 t u n i c h a r ;
Les sauts de ligne
I Windows : \r \n
I Linux : \n
I MacOS :\r
I il ne suffit pas que le programme soit portable
I est-ce que les donn ´ees doivent l’ ˆetre ?
- comment faire pour lire/ ´ecrire un fichier texte multi-plateforme ?
Endianness
I x86-like : little-endian
I Motorola-like : big-endian
I il ne suffit pas que le programme soit portable
I est-ce que les donn ´ees doivent l’ ˆetre ?
- comment lire/ ´ecrire un fichier binaire contenant des int d’une fac¸on multi-plateforme ?
- exemple de solution : encodage utf8
Makefile
I ´ecrire des Makefile portables avec une variable qui d ´epend du syst `eme
I informations concern ´ees : - compilateur `a utiliser - options de compilation - r ´epertoire (include, lib, etc)
- commandes d’installation et de nettoyage - noms des sorties (exemple: .exeou pas ?) - et bien d’autres...
Makefile
CCFLAGS=-Wall -ansi ifeq ($(OS),Windows_NT)
CCFLAGS += -D WIN32
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) CCFLAGS += -D AMD64
endif
ifeq ($(PROCESSOR_ARCHITECTURE),x86) CCFLAGS += -D IA32
endif else
UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux)
CCFLAGS += -D LINUX endif
ifeq ($(UNAME_S),Darwin) CCFLAGS += -D OSX endif
UNAME_P := $(shell uname -p) ifeq ($(UNAME_P),x86_64)
CCFLAGS += -D AMD64 endif
ifneq ($(filter %86,$(UNAME_P)),) CCFLAGS += -D IA32
endif endif
Makefile
I les versions de windows depuis 2000 poss `edent une variable d’environnement OSdont la valeur est
Windows NT
I les syst `emes bas ´es sur Unixont une commande shell uname qui donne une chaˆıne d ´ecrivant le syst `eme
I en stockant dans une variable du Makefile la description de l’OS, on peut alors donner des instructions d ´edi ´ees pour chaque OS (-fPICest inutile sous Windows pour les biblioth `eques dynamiques par exemple...)
I une entreprise commercialisant des softs a tout int ´er ˆet
`a ne pas d ´evelopper deux fois le m ˆeme logiciel
De l’art de d ´evelopper rapidement et efficacement
I science appel ´ee le g ´enie logiciel
I pour d ´evelopper un logiciel : - on r ´efl ´echit
- on produit du code
I La production du code :
- implantation de l’application avec ses fonctionnalit ´es (10% du temps de travail)
- d ´ebogage, documentation, affinement, tests, optimisations (90% du temps de travail)
fait : La seconde partie de la production est d’autant plus r ´eduite que l’on a bien r ´efl ´echit avant de passer `a l’action
Dans l’imaginaire collectif
“Pour ˆetre un bon d ´eveloppeur en langagefoo, il suffit d’ ˆetre bon en algo et de maˆıtriser la syntaxe defoo”
Proverbe ´etudiant
“Quand on est con, on est con”
Georges Brassens
Qu’est ce que le g ´enie logiciel ?
I ensemble de bonnes pratiques et de conseils `a m ´editer
I objectifs : d ´evelopper mieux et plus vite*
- mieux = moins de bugs, code r ´eutilisable, ´evolutif, etc.
- plus vite = acc ´el ´erer le d ´ebogage, ´eviter de tout casser tout le temps, etc.
*(comment devenir des feignants efficaces)
R `egle de modularit ´e
´ecrire des ´el ´ements simples et les relier par des interfaces propres
I pour faire des applications complexes, il faut pouvoir contr ˆoler la complexit ´e du code
I principes d’encapsulation en biblioth `equesboˆıtes noires
R `egle de clart ´e
pr ´ef ´erer la clart ´e `a l’intelligence
I penser `a celui qui relira le code
I un algorithme trop rus ´e a moins de chance d’ ˆetre lisible qu’un classique
- risque plus ´elev ´e de bugs - moins facile `a entretenir
I style obscurantiste `a bannir absolument
R `egle de composition
concevoir des programmes `a connecter `a d’autres programmes
I ´eviter les programmes ”Dieu” qui font tout
I esprit d’Unix: de multiples petits programmes bien taill ´es assemblables facilement via des pipes
I ne pas r ´einventer la roue, la plupart du temps on en fait une roue carr ´ee
- utilisez les choses faites par ceux qui savent - utiliser les d ´ependances proprement
R `egles de s ´eparation
s ´eparer m ´ethodes et m ´ecanismes s ´eparer moteur et interfaces
I bonne pratique : ne rendre visible que les interfaces et pas les impl ´ementations en utilisantstatic
I exemple : frontal GUI (Graphical User Interface) dissoci ´e des t ˆaches de fond (affichages et calculs sont deux choses diff ´erentes)
I 1 module = 1 responsabilit ´e
R `egles de transparence
concevoir un comportement lisible pour faciliter l’investigation et le d ´ebogage
I n’utiliser que de bonnes interfaces
I tout ce qui facilite le d ´ebogage est bon
I mettre des sorties de debug
I ne pas l ´esiner sur les tests
I ne pas cacher les bugs
R `egle de robustesse
engendrer de la robustesse par la transparence et la simplicit ´e
I penser aux conditions d’utilisation non normales - contr ˆoles des plages de valeurs, corner cases, ...
- tester, tester, tester
I pas de donn ´ees cach ´ees en dur:
- fichiers de configuration
I ´eviter les cas particuliers dans le code
R `egle de robustesse
I exemple : les jeux de plateau
I comment tester les voisins dans un 8x8 ?
Cas g ´en ´eral cas particulier 1
cas particulier 2 cas particulier 3
cas particulier 4 cas particulier 5
cas particulier 6 cas particulier 7 cas particulier 8
R `egle de robustesse
I bonne solution : ajouter une bordure inerte (des cases vides par exemple)
I taille du jeu = 8x8
I taille du tableau = 10x10
un seul cas : le cas g ´en ´eral avec 8 voisins bordure inerte
R `egle de repr ´esentation
placer le savoir dans les donn ´ees, afin d’obtenir des algorithmes disciplin ´es et
robustes
I pr ´evoir de bonnes structures de donn ´ees
I si l’on utilise des structures, l’ajout d’une information (d’un champ...) ne modifie pas le code
1 void m i n i m i z e ( S t a t e s [ ] , i n t n s t a t e s , T r a n s i t i o n t [ ] ) ;
1 t y p d e f s t r u c t{ 2 S t a t e s [ ] ; 3 i n t n s t a t e s ; 4 T r a n s i t i o n t [ ] ; 5 }Automaton ;
6 void m i n i m i z e ( Automaton∗ a ) ;
R `egle de la moindre surprise
´eviter les surprises dans la conception des interfaces
I ´eviter les nouveaut ´es inutiles
I respecter les habitudes :
- des programmeurs (pourquoi changer si for(i=0;i<n;i++)convient ?)
- des utilisateurs (ne pas appeler .txtun fichier binaire)
R `egle de silence
n’afficher des informations que si n ´ecessaire
I informations inutiles = pollution
I les informations internes (debug) doivent pouvoir ˆetre d ´esactiv ´ees
I contre-exemple : un programme qui peut durer
longtemps peut donner des signaux pour montrer qu’il n’est pas plant ´e (ex: gros calcul qui affiche un
progression...)
R `egle de r ´eparation
r ´eparer ce qui est possible, mais en cas de probl `eme, faire ´echouer rapidement et
clairement
I “ ˆetre tol ´erant avec ce qu’on rec¸oit, exigeant avec ce qu’on envoie” (J. Postel)
I mais : `a vouloir trop g ´erer les erreurs, on cr ´ee des usines `a gaz
I ne pas h ´esiter `a faire exit lors d’une erreur fatale - erreur d’allocation du plateau pour un jeu de dames
⇒ rien ne sert de continuer
- erreur d’allocation du nom du joueur en fin de partie
⇒ tant pis, on ne sauvegarde rien mais on termine normalement
R `egle d’ ´economie
pr ´ef ´erer l’ ´economie du temps de programmation `a celle du temps machine
I automatiser autant que possible
I exemple : inutile de garder en cache la taille des chaˆınes de caract `eres
- dans l’immense majorit ´e des cas,strlen suffit
R `egle de g ´en ´eration
´eviter le travail manuel, ´ecrire plut ˆot des programmes de g ´en ´eration (de programme,
de donn ´ees, etc)
I exemples : flex,bison,automake,doxygen, etc
I si un programme utilise un fichier d’entiers, ´ecrire un autre programme pour g ´en ´erer des fichiers de tests pour le premier
R `egle d’optimisation
cr ´eer des prototypes avant d’affiner;
rechercher un bon fonctionnement avant d’optimiser
I “on devrait repousser les petites am ´eliorations de l’efficacit ´e dans environ 97% des cas, car une optimisation pr ´ematur ´ee est la racine de tout le mal”
(C. A. R. Hoare)
I “dans 90% des cas, la meilleure optimisation consiste
`a ne rien faire”
I L’intuition est mauvaise conseill `ere - Il faut utiliser des outils de profilage
R `egle de diversit ´e
se m ´efier de la bonne solution unique
I en utilisant des interfaces propres, on laisse la possibilit ´e d’utiliser un jour une autre impl ´ementation (plus rapide, mois gourmande, dans un autre langage, etc)
1 void f o o ( Automaton∗ a ){ 2 S t a t e∗ s=a−>s t a t e s [ 0 ] ; 3 i f ( s−>c o n t r o l & FINAL ){
4 /∗ . . . ∗/
5 }
6 }
1 void f o o ( Automaton∗ a ){ 2 S t a t e∗ s= g e t s t a t e ( a , 0 ) ; 3 i f ( i s f i n a l ( s ) ){
4 /∗ . . . ∗/
5 }
6 }
R `egle de simplicit ´e
concevoir des syst `emes simples;
n’introduire de la complexit ´e que si n ´ecessaire
I “La simplicit ´e est la sophistication supr ˆeme” (L ´eonard de Vinci)
I pas d’abstraction inutile
- pas de hashtable g ´en ´erique si on ne manipule que des entiers
I ne pas trop anticiper
- pas de param `etres inutiles, au cas o `u, plus tard, ...
R `egle d’extensibilit ´e
concevoir en pensant `a l’avenir, qui sera l `a plus t ˆot qu’on ne l’imagine
I format de fichiers g ´en ´eriques
I utilisation de num ´eros de version
I exemples : gestion des encodages de caract `eres
R `egle d’extensibilit ´e
I bonne solution : utiliser une biblioth `eque qui les g `ere avec possibilit ´e d’en ajouter, ´eventuellement par plugins.
1 /∗ T h i s l i b r a i r y i s designed t o manage 2 v a r i o u s i m p l e m e n t a t i o n s o f
3 f p u t c f o r v a r i o u s encoding . ∗/ 4 # i f n d e f encodings H
5 # d e f i n e encodings H 6
7 t y p d e f i n t (∗encoder ) (i n t, FILE∗) ; 8
9 void add encoder (char∗ name , encoder f ) ; 10 encoder g e t e n c o d e r (char∗ name ) ;
11 # e n d i f
Plugins automatiques
I Lorsque des plugins poss `edent tous les m ˆemes sp ´ecificit ´es (m ˆemes symboles pour le linker) visant `a enrichir une application, ces derniers peuvent ˆetre charg ´es automatiquement.
- on d ´eclare les types pointeurs de fonctions
correspondants aux symboles `a r ´ecup ´erer dans les .so
- on r ´ecup `ere tous les fichiers .socontenu dans un joli r ´epertoire pluginsavecscandir
- on remplit un tableau de symboles (pointeurs de fonctions) r ´ecup ´er ´es avecdlsym
- lors de l’utilisation d’une fonctionnalit ´e, on recherche si elle est disponible dans le tableau des plugins charg ´es.
exemple : dc avec plugins
I dcpour Desk Calculator est la fameuse calculatrice en Polonais invers ´e d’Unix.
I toutes les op ´erations arithm ´etiques dedcd ´epilent un certain nombre d’arguments, calculent un r ´esultat et empilent ce r ´esultat
I on peut imaginer un m ´ecanisme de plugins automatiques recherchant toute les op ´erations disponibles pour l’application
I Une op ´eration est d ´efinie par :
- une fonction d’ ´evaluation :int eval(int* args);
return args[0] + args[1];
- une arit ´e :int arity(void);
return 2;
- un symbole :char symbole(void);
return ’+’;
Et tout le reste...
I coder et commenter en anglais
I utiliser des formats lisibles, et si possible standards et ouverts
I toujours penser `a ceux qui reliront le code (m ˆeme si c’est vous...)
I penser `a l’utilisateur qui n’a pas forcement les m ˆemes rep `eres
I respecter les sp ´ecifications donn ´ees