jeudi 10 mars 2011

Principe ouvert/fermé

En programmation orientée objet, le principe ouvert/fermé signifie qu'une entité logicielle (classes, modules, fonctions, etc) devrait être ouverte pour extension, mais fermée pour modification. L'idée est que l'on puisse modifier le comportement d'une entité, sans en changer le code source.

Ce principe est particulièrement bien adapté à des environnements de production critiques, où tout changement du code source doit être approuvé par un processus jalonné par des revues de code, des tests unitaires, une phase de qualification, ... En effet, si modifier le code existant implique de détecter d'éventuelles régressions par une batterie de contrôles portant à la fois sur les anciennes et les nouvelles fonctionnalités, au contraire l'étendre peut réduire considérablement l'effort de qualification, puisque ce dernier ne portera que sur les nouveautés.

Depuis son apparition en 1988 dans l'ouvrage Object Oriented Software Construction, l'application de ce principe a largement évolué, et gagné en souplesse.

Principe ouvert/fermé selon Meyer :
On attribue généralement ce concept à Bertrand Meyer, l'auteur de Object Oriented Software Construction :
  • L'implémentation d'une classe en production ne doit pas être modifiée, sauf correction d'anomalies.
  • Ajouter ou changer une fonctionnalité implique de créer une nouvelle classe.
  • La nouvelle classe peut réutiliser le code de la classe existante, par héritage.
  • On est libre d'exposer la nouvelle implémentation par une nouvelle interface ou par l'interface existante.
D'après le principe de Meyer, une implémentation existante peut être réutilisée par héritage, alors que 2 interfaces ne devraient pas hériter l'une de l'autre.

Principe ouvert/fermé par polymorphisme :
Puis, entre 1996 et 2001, plusieurs personnes contribuent à redéfinir le principe ouvert/fermé en tirant profit de l'abstraction et du polymorphisme des langages objet :
  • Le comportement d'une entité peut reposer sur de multiples implémentations différentes.
  • Les implémentations adoptent toutes la même interface, ou héritent de la même classe de base abstraite.
  • L'interface (ou classe abstraite) existante ne peut pas être modifiée.
  • Ajouter ou changer une fonctionnalité implique de créer une nouvelle interface (ou classe abstraite).
  • Les implémentations peuvent être modifiées.
  • Les implémentations peuvent se substituer les unes aux autres par polymorphisme.
D'après le principe par polymorphisme, une interface (ou classe abstraite) existante peut être réutilisée par héritage, alors que 2 implémentations ne devraient pas hériter l'une de l'autre.

Synthèse et comparaison :

OCP selon MeyerOCP par Polymorphisme
Extension par héritage dela classe d'implémentationl'interface existante
Constantes entre 2 évolutionsimplémentation et interfaceinterface seulement
Modification possible pour-implémentations

On constate donc que le principe ouvert/fermé a évolué vers un niveau d'abstraction plus important, ainsi qu'une plus grande flexibilité. Une bonne illustration de ce principe est le design pattern Visiteur de la Guilde des Quatre.

Références :

mercredi 9 mars 2011

SOLID, 5 concepts orientés objet

Dans la conception d'application orientée objet, SOLID est un acronyme introduit par Robert Cecil Martin (alias "Oncle Bob") au début des années 2000. SOLID est la réunion de 5 concepts ou principes en programmation orientée objet. Appliqués ensemble, ils sont susceptibles de permettre de créer des systèmes logiciels plus faciles à maintenir et à faire évoluer. SOLID fait partie des pratiques agiles d'ingénierie logicielle, et peut être un complément puissant à la méthode de pilotage par les tests (Test-Driven Development).


Les 5 principes repris par SOLID sont :
  • S pour SRP, Single Responsibility Principle (principe de la responsabilité unique) :
    Un objet ne devrait avoir qu'une seule responsabilité.
  • O pour OCP, Open/Closed Principle (principe ouvert/fermé) :
    Une entité logicielle, telle que classes, modules, fonctions, etc, devrait être ouverte pour extension, mais fermée pour modification.
  • L pour LSP, Liskov Substitution Principle (principe de substitution de Liskov) :
    Il doit être possible de remplacer un objet par n'importe quelle instance de ses types dérivés, sans que cela affecte le reste de la fonction qui l'utilise.
  • I pour ISP, Interface Segregation Principle (principe de ségrégation des interfaces) :
    Mieux vaut avoir autant d'interfaces que de clients, plutôt qu'une interface à vocation générale.
    Autrement dit, les clients d'une interface ont besoin de connaître de cette interface seulement les méthodes qu'ils utilisent. Inversement, un client ne doit pas dépendre d'une interface dont il n'utilise pas une partie.
  • D pour DIP, Dependency Inversion Principle (principe d'inversion des dépendances) :
    L'inversion des dépendances s'énonce en 2 points :
    1. Des modules haut niveau ne devraient pas dépendre de modules bas niveau et des détails de leur implémentation. Tous devraient dépendre d'abstractions.
    2. Les abstractions ne devraient pas dépendre des détails, mais les détails devraient dépendre des abstractions.
    Exemple : L'Injection de Dépendances (Inversion of Control) est une méthode qui applique ce principe.
Références :

vendredi 5 novembre 2010

ABCdaire de la ponctuation anglaise

Histoire de ne pas se sentir désarçonné au moment de nommer des caractères traités dans un logiciel... Il est toujours utile de connaître le lexique propre à la ponctuation. C'est parti !

Ponctuation
FrançaisCaractèreAnglais
un point.a full stop
a period
une virgule,a comma
deux points:a colon
un point-virgule;a semi-colon
un slash/a slash
a solidus
un backslash\a backslash
a reverse solidus
un tiret-a dash
an hyphen (old fashion)
un point d'interrogation?a question mark
des points de suspension...dots
ellipsis
un point d'exclamation!an exclamation mark
des parenthèses()brackets
une parenthèse(a parenthesis
entre guillemets' '
" "
in inverted commas
in quotes (US)
un guillemet ouvrant (à la française)«a left angle quote
a guillemotleft
un guillemet fermant (à la française)»a right angle quote
un guillemet droit"a quotation mark
entre crochets[ ]in square brackets
un crochet ouvrant[a left square bracket
un crochet fermant]a right square bracket
entre accolades{ }in curly braces
une accolade ouvrante{a left curly brace
une accolade fermante}a right curly brace

Accents et autres
FrançaisCaractèreAnglais
un accent circonflexe^a circumflex accent
un accent aigu´an acute accent
un accent grave`a grave accent
une apostrophe'an apostrophe
une cédille¸a cedilla

Caractères spéciaux
FrançaisCaractèreAnglais
une astérisque*an asterisk
un dièse#a number sign
une esperluette ou perluette&an ampersand
arobe ou arobase@commercial at

Symbôles mathématiques
FrançaisCaractèreAnglais
égal=equals sign
inférieur à<less than
supérieur à>greater than
puissance 2²superscript two
puissance 3
au cube
³superscript three
racineradical
une sommea summation

vendredi 16 octobre 2009

Maîtriser par les Tests

S'il est vrai que les projets logiciels sont toujours soumis à des contraintes de temps, la réalisation des tests n'en devient pas pour autant un luxe : les tests jouent un rôle salvateur plus encore lorsque l'urgence est susceptible d'engendrer une part de chaos. Les tests unitaires sont le travail des développeurs. Ces derniers ne doivent pas les considérer comme une tâche superflus qui viendrait s'ajouter aux tâches de développement de leurs applications. Au contraire, ces tests font partie de leurs développements à part entière. Non seulement ils fournissent la garantie que leurs développements sont efficaces sur la durée, mais ils doivent aussi être livrés au même titre que les codes sources des applications. De là vient l'idée forte selon laquelle un développement opérationnel est un développement testé. Les tests unitaires font partie d'un processus rationalisé des développements logiciels.

Réaliser des tests pour une application est le seul moyen de s'assurer la maîtrise de ses développements. En effet, les tests sont là pour répondre à une question simple : "L'application remplit-elle ses objectifs en terme de fonctionnalités ?" Ces fonctionnalités sont auparavant définies par le dossier des spécifications, et chaque test mis en œuvre devra donc couvrir une fonctionnalité précise.

Une fonctionnalité correspond à un besoin de l'utilisateur final de l'application. D'abord, elle part d'un contexte, qui définit un cas d'utilisation de l'application ; elle décrit ensuite une réaction, qui doit aboutir à des résultats attendus. Un test se veut unitaire à partir du moment où il teste une seule fonctionnalité de l'application. Pour cela, un test reproduit le contexte, lance le processus associé à une fonctionnalité et vérifie l'ensemble des résultats.

Un test doit vérifier les résultats d'une application, lorsque celle-ci est stimulée en considération d'un contexte particulier. Un tel test n'est vraiment réalisable et probant que si le contexte au départ du test isole l'application du reste de son environnement. D'où parfois la nécessité de développer, pour les besoins des tests, des implémentations capables de simuler les services habituellement rendus par des processus étrangers à l'application. Une idée sous-tendue par l'isolation de l'application au sein d'un test est la définition claire des interfaces entre composants : les composants n'ont besoin d'être connus de ceux avec lesquels ils collaborent que par le biais des services qu'ils sont supposés leur rendre.

dimanche 4 février 2007

L'Abécédaire de Java

Les mots-clés de Java sont des mots réservés par le langage en vue d'une utilisation spéciale. On ne peut donc pas les utiliser en-dehors de l'usage prévu par Java, notamment comme identifiant de variable, nom de classe, de package ou de méthode.
  • abstract : modificateur de classe ou de méthode, afin de la rendre abstraite.
    abstract class MaClasse {
        abstract void maMethode();
    }
  • assert (depuis Java 1.4) : instruction qui exprime les conditions avant de continuer l'exécution d'une méthode ; permet de programmer par contrat. Si la condition n'est pas vérifiée, une exception AssertionError est levée.
    double diviser(double dividende, double diviseur) {
        assert diviseur != 0 : "Division par zéro interdite";
    }
  • boolean : type primitif, pouvant prendre une valeur booléenne : true (vrai) ou false (faux).
  • break : instruction qui provoque l'interruption d'un flux d'instructions et la sortie immédiate d'un bloc d'instructions : un bloc de contrôle (switch), un bloc itératif (do, for, while) ou un bloc try.
  • byte : type primitif, représentant un entier signé sur 8 bits, soit un octet.
  • case : instruction qui introduit un cas ou étiquette dans un bloc de contrôle switch (Cf. swith).
  • catch : instruction qui crée une clause dans laquelle attraper et traiter une exception précédemment levée dans un bloc try (Cf. try).
  • char : type primitif représentant un caractère.
  • class : instruction pour définir une classe.
    class MaClasse {
        ...
    }
  • const : actuellement inutilisé par Java.
  • continue : instruction qui provoque directement le passage à la prochaine itération d'un bloc itératif.
  • default : dans un bloc de contrôle switch, cette étiquette introduit l'instruction par défaut à effectuer, quand les conditions d'aucun des cas prévus n'ont été vérifiées.
  • do : instruction qui commence un bloc itératif do... while, autrement dit "Faire ... tant que la condition est vraie", où la condition d'itération est vérifiée à la fin du bloc. Une première itération est donc toujours accomplie.
    do {
        ...
    } while(condition);

  • double : type primitif, représentant un nombre réel signé sur 64 bits, soit 8 octets.
  • else : instruction qui propose une clause alternative dans une suite de blocs conditionnels (Cf. if).
    if (condition) {
        ...
    } else {
        ...
    }
  • enum (depuis Java 1.5) : instruction pour définir une énumération, c'est-à-dire un type qui comporte un nombre fini d'éléments.
    enum Jour {
        LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE
    }
  • extends : instruction qui définit un héritage entre une classe mère et une classe fille.
    classe MaClasseFille extends MaClasseMere { ... }
  • false : valeur booléenne pour une négation, signifiant faux.
  • final : modificateur de classe, de méthode ou de variable. Une classe final ne peut pas être étendue ; une méthode final ne peut pas être redéfinie par héritage ; une variable final est une constante et sera donc affectée d'une valeur une seule fois.
  • finally : instruction qui introduit la dernière clause faisant suite à un bloc try, sachant que les instructions de cette clause seront toujours exécutées, quelle que soit l'issue du bloc try (Cf. try).
  • float : type primitif, représentant un nombre réel signé sur 32 bits, soit 4 octets.
  • for : instruction qui introduit un bloc itératif "Pour". Sa déclaration se fait en 3 étapes séparées par des points-virgules :
    for (condition_initiale; condition_echappement; instruction_iteration) {
        ...
    }

    Par exemple :
    for (int compteur = 0; compteur < 10; compteur++) {     System.out.println(compteur);
    }
  • goto : actuellement inutilisé par Java.
  • if : instruction qui introduit un bloc conditionnel "Si". Les instructions du bloc sont exécutées si la condition qui suit if est vérifiée :
    if (condition) {
        ...
    }


    if (condition1) {
        ...
    } else if (condition2) {
        ...
    } else {
        ...
    }
  • implements : instruction qui, à la déclaration d'une classe, stipule qu'elle implémente une interface donnée.
    class MaClasse implements MonInterface { ... }
  • import : instruction qui définit un import de classe, qui permet de manipuler ensuite une classe sans fournir à chaque fois son nom de package.
    import unpackage.UneClasse;

    Les import permettent d'alléger l'écriture du code et de fixer les différentes dépendances d'une classe.
    Depuis Java 1.5, il existe aussi les import static, qui permettent d'importer les attributs et méthodes static d'une classe.
    import static unpackage.UneClasse.CONSTANTE;
    import static unpackage.UneClasse.uneMethode;
  • instanceof : instruction qui compare le type d'un objet à un type donné ; retourne true si les types sont identiques, false sinon.
    if (monObjet instanceof UneClasse)
  • int : type primitif, représentant un nombre entier signé sur 32 bits, soit 4 octets.
  • interface : instruction pour définir une interface.
  • long : type primitif, représentant un nombre entier signé sur 64 bits, soit 8 octets.
  • native : modificateur de méthode, qui indique que son implémentation n'est pas codée en Java, mais dans un langage dépendent d'une plateforme. Son code est fourni dans un autre fichier. Une méthode native ne possède pas de corps et sa signature se termine donc par un point-virgule :
    native void maMethodeNative();
  • new : opérateur pour instancier un objet à partir d'une classe donnée, immédiatement suivi d'un constructeur de cette classe.
    MaClasse monObjet = new MaClasse();
  • null : valeur spéciale pour des références qui ne pointent sur aucun objet.
    Exemple d'un objet initialisé à null :
    Object monObjet = null;
  • package : instruction pour définir le package d'appartenance d'une classe.
    package nomdupackagedemaclasse;

    class MaClasse { ... }
  • private : modificateur d'accès de classe, de méthode ou d'attribut, utilisé à sa déclaration, pour interdire tout accès direct depuis l'extérieur de la classe qui l'encapsule.
  • protected : modificateur d'accès de classe, de méthode ou d'attribut, utilisé à sa déclaration, pour ne le rendre accessible que par les classes en héritant ou par les classes du même package.
  • public : modificateur d'accès de classe, de méthode ou d'attribut, utilisé à sa déclaration, pour le rendre directement accessible par toute autre classe.
  • return : instruction pour retourner une valeur de retour dans le cas d'une méthode avec retour, ou bien pour provoquer une sortie immédiate d'une méthode sans retour.
    Exemple d'une méthode sans retour :
    void maMethode() {
        ...
        return;
    }


    Exemple d'une méthode avec retour :

    MaClasse maMethode() {
        MaClasse monObjet = null;
        ...
        return monObjet;
    }
  • short : type primitif, représentant un nombre entier signé sur 16 bits, soit 2 octets.
  • static : modificateur d'attribut, de méthode ou de bloc.
    Un attribut (respectivement une méthode) static est un attribut (respectivement une méthode) de classe, et non d'instance, c'est-à-dire partagé par toutes les instances de la classe :
    class MaClasse {
        static int nombreEntier = 0;
        static void maMethode() { ... }
    }

    System.out.println(MaClasse.nombreEntier);
    MaClasse.maMethode();


    Pour un bloc
    static :
    class MaClasse {
        static {
            ...
        }
    }
  • strictfp (depuis Java 1.2) : modificateur de classe, d'interface ou de méthode, qui force la JVM à adhérer au standard IEEE 754 pour toute opération sur des nombres réels, indépendamment de la plateforme sur laquelle tourne la JVM.
  • super : référence vers la classe de base depuis la classe dérivée.
  • switch : instruction qui introduit un bloc de contrôle sur la valeur d'une variable.
    L'instruction exécutée dépendra du cas correspondant à cette valeur :
    switch(number) {
        case 1:
            ...
            break;
        case 2:
            ...
            break;
        case 3:
            ...
            break;
        default:
            ...
    }
  • synchronized : instruction utilisée en programmation multi-tâche, pour désigner une section critique, de manière à ce qu'un thread accède de façon exclusive à cette méthode ou à ce bloc.
    Quand une méthode est la section critique, les 2 syntaxes suivantes sont possibles :
    void synchronized methode() { ... }

    et :
    void methode() {
        synchronized(this) { ... }
    }

    Quand une méthode static est la section critique, c'est la dernière syntaxe qui est reprise :
    class MaClasse {
        static void methode() {
            synchronized(MaClasse.class) { ... }
         }
    }

    Quand une section critique porte sur un objet :
    synchronized(monObjet) { ... }
  • this : référence vers l'instance courante d'une classe
  • throw : instruction pour lever une nouvelle exception. Une telle exception doit être une instance d'une classe dérivant de Throwable et devra être traitée par la méthode appelante.
    throw new Exception("Description de l'exception levée");
  • throws : instruction pour définir les différentes exceptions susceptibles d'être levées ou transmises par une méthode.
    void maMethode throws IOException { ... }
  • transient : modificateur d'attribut, indiquant qu'un tel attribut ne sera pas transmis par sérialisation.
  • true : valeur booléenne pour une affirmation, signifiant vrai.
  • try : instruction pour définir un bloc capable de traiter d'éventuelles exceptions.
    try {
        ...
    } catch (MonException1 exception1) { ... }
    } catch (MonException2 exception2) { ... }
    } finally { ... } // facultatif
  • void : utilisé à la place du type de retour pour les méthodes sans retour, signifiant "rien".
    void uneMethodeSansRetour { ... }
  • volatile : modificateur d'attribut, utilisé pour des attributs modifiables de façon asynchrone, c'est-à-dire que plusieurs threads peuvent y accéder en même temps. A chaque accès de l'attribut par un thread, la JVM doit rafraîchir la copie mise en cache par ce thread, de manière à ce qu'il dispose de la valeur correcte.
  • while : instruction pour définir un bloc itératif "Tant que la condition est vraie, faire ...".
    while(condition) {
        ...
    }


Types primitifs numériques
TypeBitsOctetsValeur minimaleValeur maximale
byte81-2^72^7-1
short162-2^152^15-1
int324-2^312^31-1
long648-2^632^63-1
float324n/an/a
double648n/an/a