Thématiques principales

dimanche 29 octobre 2017

Docker - publier

Nous avons vu dans le précédent post sur docker comment simplement installer docker sous linux (sous windows c'est relativement similaire), créer son DockerFile pour créer une image et ensuite utiliser cette image pour en instancier un conteneur.

Dans ce post ci nous allons nous intéresser a la publication des images afin d'utiliser celles des autres développeurs/intégrateurs et aussi leur fournir vos propres images.

Bien avant toute chose il vous faut un compte sur le site servant de repository docker. Un fois ce compte créé, reste a se loger dans le Shell docker et si l'on reprend l'image du post precedement creer, il va falloir la tagger comme suit:

1
2
$ docker login
$ docker tag image username/repository:tag

Ainsi vous spécifiez que l'image sera publier via votre repository (formé du username et du repo créé dans la page d'administration du site https://hub.docker.com) et d'un tag spécifiant la version de l'image. Reste a la pousser sur le serveur distant:

1
$ docker push username/repository:tag

Voila votre image est en ligne!! Coooool.... non?

Bien si ça c'est cool voyons encore plus cool, la republication..... et oui car très vite le besoin se fera sentir d'utiliser une image déjà créer la reconfigurer a notre besoin puis de la republier (pour la sauvegarder ou la partager...)

Prenons un exemple. Nous allons récupérer l'image artifactory de Matt

1
$ docker pull mattgruter/artifactory

Par défaut si l'on spécifie pas le tag, latest sera celui utiliser. on verifie:

1
2
3
4
5
$ docker image ls

REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE

mattgruter/artifactory                 latest              77905a728def        2 days ago          493MB

Bien on lance un container. Je rappele la commande

1
$ docker run -d -p 8081:8080 --name arti mattgruter/artifactory 

On se connecte sur l'interface administrateur d'Artifactory probablement a l'adresse http://192.168.99.100:8081/artifactory (Nous verrons a une autre occasion les procédure de configuration réseau possible avec docker) et nous y faisons quelques modifications.

Nous souhaitons sauvegarder ces modifications de façon a pouvoir les réutiliser dans le cas notre container serait défaillant ou qu'il faille le recréer ailleurs. Nous allons alors faire un commit de notre container afin de le transformer en image.

On va tout d'abord récupérer l'ID du container:

1
2
3
4
5
$ docker ps

CONTAINER ID        IMAGE                    COMMAND             CREATED             STATUS              PORTS                    NAMES

ce64f8df8020        mattgruter/artifactory   "catalina.sh run"   14 minutes ago      Up About a minute   0.0.0.0:8081->8080/tcp   arti

Puis faire notre commit (attention le container doit etre arreté):

1
$ docker commit -p ce64f8df8020 arti_configured

Ce qui va nous donner une nouvelle image:

1
2
3
4
5
6
7
 $ docker image ls

REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE

arti_configured                        latest              b5fd9f878fa2        3 seconds ago       538MB

mattgruter/artifactory                 latest              77905a728def        2 days ago          493MB

Tres bien on va donc tager l'image et la pousser dans notre repo:


1
2
3
$ docker tag arti_configured username/repository:latest

$ docker push username/repository:latest 

Voila notre nouvel artifactory en ligne prêt a être réutiliser au besoin! Pour le vérifier, il suffit de supprimer les images et containers locaux et de faire un pull sur notre repo et de vérifier que notre image contient bien notre configuration. (attention, a cause des log notre image peut tres vite grossir, l'exemple ci contre est pour illustrer... une meilleur méthode nécessiterai un nettoyage ou une configuration statique) Pour ceux qui ne souhaitent pas forcement pousser leur creations, le site ci contre vous expliquera comment l'exporter en local.

Voila, la prochaine fois on s’intéressera aux configurations réseaux.

jeudi 19 octobre 2017

Polymorphisme

Nous allons abordé une question très conceptuelle aujourd'hui : le polymorphisme.... Tout d'abord, pourquoi aborder ce sujet? En fait, a l'occasion d'un entretien de recrutement, j'ai eu cette désagréable question : "Qu'est ce que le polymorphisme?" Et j'avoue que j'ai été vraiment surpris et voila ce qui est passé par ma tête:
  • Quoi? On me pose cette question ? A quel moment j'ai réussi a laisser entendre que j’étais aussi nul que ça?
  • Hein?, c'est l’héritage quoi.... euh attend non ca se résume pas qu'a ca....
  • Merde c'est vraiment que ça le polymorphisme?
Bon passons sur le problème de l'ego... après tout je n’écrirais pas cet article si je n’étais pas prêt a me remettre en question...

Alors avant que tout le monde ne cri, je veux d'abord distinguer le concept de son implémentation. En effet le polymorphisme signifie: "qui a plusieurs formes...." Et la on est bien avancé. Cependant il est a noté que c'est bien ce que dit conceptuellement la définition. Alors mais qu'est ce qui peut avoir plusieurs formes? et bien l'objet, celui de la programmation orienté objet.

Je ne vais pas entrer dans le détail des différentes type de programmation mais il faut savoir qu'initialement la programmation est né sous la forme impérative, c'est a dire que l'on exécuter séquentiellement des listes d’instructions, puis face a la redondance des listings on s'est mis a créer des procédures et des fonctions nous amenant  a la programmation procédurale que nous avons fini par regrouper dans des modules afin de les classer et les cataloguer arrivant ainsi aux bibliothèques de fonctions.

L'objet est né à la suite de la programmation modulaire ou le besoin conceptuel d’étendre les fonctionnalités d'un module et de consolider un certain nombre de données propres au module s'est fait sentir et ce en fonction du contexte d'utilisation du module en question. Ainsi à partir d'un module de base, nous avons fait dérivé plusieurs modules ayant des objectifs différents et donc des fonctions complémentaires.

Donc un objet c'est:

  • une référence, ce qui va nous permettre de le manipuler, donc son adresse mémoire ou un pointeur vers celui ci, sans la référence, l'objet sera considérer comme susceptible d’être supprimé
  • un comportement, un ensemble de fonction accessible via la référence et permettant de faire évoluer son état
  • un état, ensemble de données traduisant de l'activité de l'objet dans le temps. 
En POO vont intervenir 4 concepts clefs :

  • la surcharge qui ne nous intéresse pas vraiment ici mais qui consiste a créer des fonctions portant le même nom mais avec des signatures différents
  • l’héritage permet a deux objets de disposer d'une relation de parenté qui donne a l'un la possibilité d'utiliser les fonction de l'autre
  • la redéfinition permet a un objet de reprendre complètement la signature d'une fonction de sa classe mère pour en re caractériser le comportement
  • le polymorphisme permet a l’exécution de faire le choix selon la nature réelle de l'objet a exécuter ses fonctions redéfinies ou non.

Donc voila ce qu'est le polymorphisme, illustrons le. Prenons un exemple simple en considérant deux objets A et B:

A défini la fonction toto.
B dérive A. (donc B connait toto)
B défini la fonction tata utilisant toto
B redéfini toto car celle ci ne fourni pas un comportement complet dans son cas d'utilisation.
Que se passera-t-il lorsque B appellera tata?
  • a) tata appellera toto de B
  • b) tata appellera toto de A
  • c) on sait pas
  • d) la réponse d
Bon c'est la réponse c car effectivement on a effectuer une redéfinition mais sans une définition du polymorphisme on ne peut prédire le comportement. Donc dans tata il faudra trouver quelques chose comme "B.toto" si on veut être sur du comportement. Mais si on fait une liste de A dans laquelle on place un B sous la forme d'un A et que l'on appelle itérativement toto, lorsque l'on sera sur le B, on appellera quelle définition de toto? et bien retour a la réponse c, car dans notre car si on aimerai bien appeler celle de B il y a des chance que ce soit plutôt celle de A.

Et donc le polymorphisme c'est ca, obliger l’exécution du toto de B lorsque l'on a un B même si en façade on ne voit qu'un A. Attention, ici je ne parle encore que conceptuellement et n'introduit pas l'implantation de ces concepts dans tel ou tel langage ... on va y venir.

Donc pour préciser le polymorphisme il faudrait fonction après fonction dire si une redéfinition peut être polymorphique ou non. On va donc s'aider d'un mot clef, on va prendre par pur hasard le mot clef virtual. Ainsi voici deux exemples d’utilisations:

Exemple 1 équivalent a l'exemple précèdent (on prendra -> pour défini, et les parenthèses pour dérive)
A -> toto
B(A) -> toto

Exemple 2
A -> virtual toto
B(A) -> virtual toto

Ainsi si on prend un B pour a A est que l'on appelle toto, alors :
  •  dans l'exemple 1 on appellera le toto de A.
  •  dans l'exemple 2 on appellera le toto de B 
Bingo, avec l'exemple 2 on a un comportement polymorphique car si on considère un C dérivant de A redéfinissant lui aussi (ou non d’ailleurs) la fonction toto, alors sur la liste de A incluant un A un B et un C, on aura 3 appels de toto différents alors que l'on manipule que des A... plusieurs comportements, plusieurs formes : polymorphisme!

Jusqu'ici on a pas parler de langages de programmation. Pourquoi? parce que le polymorphisme ne doit pas se définir au travers d'un langage. Il ne faut pas oublier que l'on parle juste d'objets ayant différentes formes. Et on peut voir des différences notables selon les langages:
  • Le C++ sait utiliser les deux cas des exemples 1 et 2. Il faut donc préciser si l'on souhaite que la redéfinition soit polymorphique ou non.
  • Par contre en java ou python, on sera forcement dans l'exemple 2 (la redéfinition est forcement polymorphique). 
Alors le polymorphisme, c'est quoi? et bien effectivement c'est intimement lié a l’héritage surtout dans l’implémentation que l'on en a aujourd'hui. Cela dit (et je reviens a l'entretien qui j'ai eu) certain chipoterons sur les différents types en parlant par exemple des interfaces Java.... Aie, effectivement, l’héritage par les interfaces java oblige la redéfinition donc par extension oblige à la mise en œuvre du polymorphisme... mais conceptuellement, c'est la même chose que de définir une classe abstraite (ou virtuelle pure en c++). Nous sommes la juste dans un contexte d’implémentation du concept de polymorphisme, pas du tout dans ce qu'il est, juste une caractéristique possible de la redéfinition de fonction.

Rappelons au passage que l’existence des interfaces Java ne provient que du besoin de permettre des héritages multiples (comme en c++ ou python) mais en limitant les effets de bords liés a l’héritage en diamant.... et que l'utilisation des interfaces est essentiellement préconisé pour l'adjonction de fonctionnalités techniques sur un objet (capacité a se sérialiser par exemple) et non pour la définition de spécificité naturelle a l'objet (la capacité d'avancer....).

Pour ceux qui veulent des infos rapides: Openclassrooms
Sinon pour les courageux : Amazon
Pour ceux qui veulent comprendre plus en profondeur : SiscoLaf

mercredi 18 octobre 2017

Comment or not comment ?

Je ressort d'une discussion avec des collègues concernant la pertinence des commentaires dans le code. C'est une question très terre a terre pour le développeur et si elle fait couler beaucoup d'encre, il en ressort souvent que finalement chacun reste sur son opinion malgré les recommandations et des remarques de bon sens.

En fait, c'est compréhensible, étant donnée que l'apprentissage de l'utilisation des commentaires fait partie ou presque des premières choses que l'on voit, il est normal que les premiers réflexes acquis soient les plus profondément inscrits.

Ainsi l’écriture des commentaires est presque culturelle et souvent les arguments allant en faveur de leur utilisation vont citer par exemple la nécessité de réaliser un code maintenable, de fournir une description plus fine des tests, de préciser les points susceptibles d'évoluer ou même d’être corrigés. Ainsi pour beaucoup, l'utilisation des commentaires dans le code a son importance et un code non commenté n'est pas un bon code.

Mais... autant le dire tout de suite, je ne suis pas de cet avis et avant de crier au scandale, de retourner la table et d'arracher les rideaux, laissez moi exposer mon point de vue.

Je vais commencer par préciser que je parle ici de commentaires dans le code source, pas de la pertinence de la documentation technique. Je ne considère pas non plus que la javadoc s'assimile a du commentaire même si la frontière est mince est que certaines critiques identiques a la production de commentaire pourrait également être faites.

Alors voila, je suis un grand fan de la POO, et en ce sens, je considére qu'un code bien écrit est
  • simple (on écrit que des choses évidentes), 
  • explicite (on nomme les chose par leur nom), 
et qu'il
  • respecte la règle des 7+/-2 de Miller (qui si elle n'est qu'une règle a suivre ne doit pas devenir une contrainte) donc on va avoir  7+/-2 lignes par méthodes, 7+/-2 méthodes par classe, 7+/-2 classes par package, 7+/-2 package par sous-package, etc.... 
  • limite les structures lourdes (par exemple, en java, on évitera l'utilisation, des switch/case, on préférera le polymorphisme a la cascade de if/then, etc)
  • fournit une documentation technique (ou dossier d'architecture) efficace.
(Alors bien sur pour les fan des approches fonctionnelles, ça vaut aussi bien sur mais adapté forcement au langage employé)

Donc a mon sens un code bien écrit respecte par lui-même un standard de lecture facile. Qui fait que si un commentaire "utile" doit être écrit c'est que probablement le code peut être simplifier ou revu afin de tendre vers un code explicitement évident. Je précise "utile" car malheureusement et souvent un code commenté n'est commenté que par des paraphrases inutile qui au contraire vont en alourdir la charge textuelle.

D'autre part il ne faut pas oublier qu'un langage de programmation est fait nous. En fait il ne s'agit que d'une interface homme machine dédié au développeur.... techniquement, notre ordi s'en moque que l'on écrive en C++, en Java, en SmallTalk ou en Scala... Dans l’idéal il veut de l'assembleur voir des 0 et des 1 (pour caricaturer....) Donc le langage a été élaborer afin de nous permettre un certain degré d'expressivité selon un certain nombre de concepts. Il ne faut pas non plus oublier qu'un langage est formelle dans sa définition et permet de décrire des comportements prédictibles ce que ne permet pas le langage naturel (qui de plus ne sera accessible qu'a ceux parlant votre langue....) Apres tout, lorsqu'il effectue une compilation, la première chose faites par le compilateur, c'est de supprimer les commentaires....

Certains vont continuer a défendre que malgré tout il existe des algorithmes qu'il est nécessaire de commenter. A mon sens, effectivement, l’emploie des commentaires peut n'avoir vraiment de sens que pour une chose: la description d'algorithmes optimisés et spécifiques.  Dans ce cas de figure on est en présence d'informations ne pouvant pas forcement faire partie d'un dossier de conception car relevant de détails d’implémentation trop précis. Alors bien sur, même ça, est discutable, et on pourra faire un billet également sur ce que doit contenir un dossier de conception.... mais c'est un autre problème. Il faut être pragmatique et savoir reconnaître que ces détails d'optimisation de la complexité et de performances ne peuvent tous être défini par l'architecture.

Cependant lorsque l'on considère ce besoin on oublie le contexte environnemental du développement logiciel. En effet, le développement logiciel actuel nécessite l'emploi d'un gestionnaire de source tel que Git, Subversion ou Mercurial. Ces outils sont indéniablement une avancée majeure dans le développement, l’intégration continu, ou la gestion de versions. Cependant, ils ont un bais lié a leur emploi, c'est qu'ils gèrent tous incrementalement les sources logicielles. Ça parait évident car c'est leur but, mais en fait dans la gestion des commentaires, ça pose un vrai problème. En effet contrairement au code qui est, si de bonnes pratiques sont appliquées, compilé testé avant d’être intégré dans une branche principale, les commentaires ne le sont pas. 

On va me répondre : "ba oui c'est normal, c'est pas le commentaire que l'on exécute! "

Oui effectivement, donc je vous poserai alors la question: qu'est ce qui vous permet de garantir que ce que le commentaire décrit correspond à la version du code présenté? Et bien rien; et a part faire de l’archéologie dans votre gestionnaire de sources pour garantir que à chaque modification du code, une modification du commentaire a été réalisée... et bien vous ne pouvez pas sauf en ayant foi en vos prédécesseurs.... tous....

Du coup.... vaut il mieux devoir comprendre un bout de code un peu trop complexe ou vaut il mieux chercher a savoir si un commentaire est fiable pour nous aider a comprendre ce code qu'il faudra finalement appréhender pour le modifier? Moi je pense que oui... car finalement les commentaires c'est pas fiable et ca ne pousse pas a écrire un code propre....

Mais alors faut il commenter son code? Et bien, pour dernier argument, pensez au temps que vous allez passer a écrire et maintenir ces commentaires en plus du code source et du coup que çà aura pour chacun de devoir le subir... et la  Si vous restez persuader du bien fait des commentaires, et bien poursuivez votre lecture avec  Ardalis et un Hinault, peut être arriveront ils a vous convaincre.

lundi 16 octobre 2017

La conception logicielle

Cet article a pour but d'introduire une série d'articles relatifs à la conception logicielle et plus généralement au développement des systèmes logiciels ou encore Génie logiciel.

La conception logicielle est entre autre une sous-activité du développement des systèmes logiciel. On la place généralement entre l'analyse des besoins et l'implantation du logiciel. Son objectif est de répondre aux objectifs suivant:

  • Maîtrise de la complexité 
  • Amélioration de la productivité et de la qualité
  • Maîtrise des coûts
  • Maîtrise de l’évolution du logiciel et de son obsolescence

Pour partir sur une définition de la conception logicielle, nous pourrions en donner celle-ci:
Processus permettant, a partir d'un besoin fonctionnel donné, l'élaboration d'un ensemble de concepts métiers et techniques reconstituant la logique du besoin en vue d'une implantation logicielle. Le produit de ce processus est l'architecture logicielle.
On voit bien dans cette définition qu'il s'agit d'une activité et qu'elle permet de caractériser des concepts qui naturellement seront associé aux éléments de l'architecture. Ainsi, sur cette base, nous pouvons donner une définition pas si évidente que ça de l'architecture logicielle:
Organisation générale d'un système en tant que tout, matérialisé pas ses composants, leurs relations mutuelles et des relations avec l'environnement, étayé par des principes guidant la conception et l'évolution du système.
Bien sur cette définition est celle fournie par l'IEEE et est donc très formelle surtout que finalement ça ne nous donne pas vraiment de clefs pour faire de la conceptions logicielles même si on en voit mieux l'objectif, on a pas encore forcement de méthode ou de processus pour mener a bien cette tache.

Ainsi divers approches existent pour concevoir un système logiciel:

  • Utilisation de patterns de conception et d'architectures qui définissent des standard de construction déjà éprouvés
  • Utilisation de frameworks consolidant des aspects métiers ou techniques 
  • La modélisation afin de rationaliser l'architecture dans une spécification plus proche des aspects d'implantation et de fournir une méthodologie pour capturer les concepts du besoin (par exemple en traitant séparément des aspects métier et aspects technique)
Voila, après avoir abordé ces quelques notions, je traiterai plus en avant des sujets évoqués comme les patterns de conception, les frameworks, la modélisation (entre autre avec l'IDM), etc...

samedi 14 octobre 2017

Evolution Java 7

Nous voila donc arrivé a la version 7. Cette version, tout comme la version 5 amène ses bouleversements mais dans une optique de simplification. Voyons en quoi.

Globalement je me suis inspirer de l'article de JmDoudou donc si vous voulez des détails, n’hésitez pas a aller le consulter.

Donc dans les grandes lignes, l'idée de cette version de Java est de simplifier et comme souvent, pour simplifier, on change la syntaxe afin de rendre plus lisible le code sauf pour le premier point qui lui consiste a permettre l’écriture des types entiers en binaire. Bon en dehors d'un besoin très spécifique, comme de traiter un protocole très bas niveau... je ne m’entendrais pas sur ce point.

Autre point de réécriture, celui de permettre d'utiliser des anderscores dans les nombres afin de faire des groupes de chiffres et simplifier la lecture.

Exemple:

1
2
3
4
5
int value = 1_146_789;
value = 4_3;
value = 4___3;
value = 0b1001110_10001011_01001101_01000101;
float pi = 3.141_593f;

Comme je l'avais évoqué dans la partie sur les enum  le switch/case n’était pas capable de traiter autre chose que des types primitifs ou des enum. Maintenant ces derniers pourront traiter des String.

Exemple:

1
2
3
4
5
6
7
String s="toto";
switch(s)
{
    case "toto" : toto();break;
    case "titi" : titi();break;
    default: defaut();break;
}


Bon après honnêtement moi les switch/case j'aime pas ça et souvent on peut les éviter par une bonne hiérarchie de classe (et ça va de même avec les if/then/else imbriqué) mais on retraitera de ce genre de sujet.

L’opérateur <>. alors la, clairement, il s'agit de Ze Sucre Syntaxique. En fait il n'apporte que de la lisibilité et c'est franchement pas mal. Je m'explique, le problème est simple, on est partie dans la construction d'une hiérarchie de classe avec un beau pattern composite et op on se dit tiens utilisons des generiques! Dans le principe c'est pas une mauvaise idée, au contraire sauf que souvent on se retrouve a declarer des listes de truc, machin avec des listes de chouettes shumrtz.

Exemple:

1
Map<Integer, Map<String,List<Truc>>> maStructure=new HashMap<Integer, Map<String,List<Truc>>>();

Bon je dis pas j'ai un peu exageré le trait....  quoique.... Enfin et bien l'operateur diamant permet d'ecrire juste:

1
Map<Integer, Map<String,List<Truc>>> maStructure=new HashMap<>();

Et le compilateur fait le reste du travail de résolution des types s'il en est capable et ca marche aussi pour les retours de fonctions:


1
2
3
private static Map<Integer, Map<String,List<Truc>>> titi() {
     return new HashMap<>();
}

Gestion des ressources. Il s'agit de permettre au bloc try de gérer lui-même la fermeture d'une ressource au lieu de devoir le faire via le finally employé jusqu’à la version 6. On aura alors une écriture plus simple, plus lisible et plus fiable avec laquelle on pourra continuer a gérer les erreurs et exceptions éventuelles mais ou la fermeture des flux se fera automatiquement.

Exemple:


1
2
3
4
5
6
7
8
try (BufferedReader bufferedReader = new BufferedReader(new FileReader("monfichier.txt"))) {
    String ligne = null;
    while ((ligne = bufferedReader.readLine()) != null) {
        System.out.println(ligne);
    }
} catch (IOException ioe) {
    ioe.printStackTrace();
}


Enfin pour ceux qui voudront créer leur propre flux compatible avec le bloc try, il faudra implémenter l'interface java.lang.AutoCloseable

La gestion des exceptions a également été revu en java 7, et c'est une bonne chose car cette nouvelle gestion permet une gestion plus concise des anomalies et de leur type leur de reemissions d'exceptions.

En effet, maintenant, un bloc catch peut être défini pour un groupe d'exception possible permettant de factoriser leur traitements et d’éviter la multiplication des blocs:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
try{ //TODO
}catch (Exception1 e1) {
    //TODOX
}
catch (Exception2 e2) {
    //TODOX
}
catch (Exception3 e3) {
    //TODOX
}
finally {
    //TODO
}

devient si les blocs TODOX sont identiques


1
2
3
4
5
6
try{ //TODO
}catch (Exception1 | Exception2 | Exception3 e) {
    //TODOX
}finally {
    //TODO
}


C'est mieux quand même!

Pour la reemissions d'exceptions c'est simple, si vous catcher une exception a l'aide d'un sur-type mais que vous devez la reemettre avec un throw, et bien sur la définition de la méthode (a la suite du mot clefs throws) vous pourrez toujours utiliser la définition de type de l'exception initiale et non de son sur-type, sauf.... si vous imbriquez deux blocs try/catch.... Dans l'absolu ça permet de garder l'information initiale malgré un filtrage "trop globale".

java.nio.Path en remplacement de java.io.File.... bon ca parle de soi-même, c'est pour accéder plus simplement aux chemins comme sait le faire Python.... et oui dans les autres langages on trouve des trucs bien, il faut savoir en tirer parti... et couplé aux classes Files, DirectoryStream, Filter and coe, on obtient toutes les infos pour traiter les fichiers et toutes leurs proprietés. Dommage que ca n'ai pas été fait déjà a la version 6 qui avait déjà tenté une mise a jour.... a oui pour info tout cela s’appelle l'API NIO 2 et en plus elle intègre la possibilité de traité des flux de façon asynchrone.

Sinon encore des updates dans awt/Swing .... je rentre dans le détail?

Ah si! un truc très sympas, l'ajout du package java.lang.invoke qui enrichi la réflexion deja existante avec des capacités de recherche de méthodes en respectant la visibilité. Cela fournira donc des objets, les MethodHandle, sortes de pointeur de fonction sur les méthodes ciblés. Il faudra faire un petit billet dédié a cette API associé a l'API réflexion. Ca sera très intéressant.

L'API de concurrence est peut être l'apport le plus significatif de Java pour cette version. Par contre son utilisation est loin d’être trivial car nécessitant de bien comprendre les conséquences de son utilisation. Ainsi cette API propose via la classe RecursiveTask de diviser le traitement de processus et les repartissant via des Thread dédiés sur les processeurs physiques de la machine. Ceci va permettre d'exploiter au mieux des ressources cependant attention une limitation en terme de synchronisation peu survenir ainsi une division trop élevé d'une tache en sous-tache pourra être contre productive car passant plus de temps a resynchroniser les résultats de Thread qu'a exécuter leur propre tache. A noter que l'approche doit être forcement récursive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Fibonacci extends RecursiveTask<Integer> {
    public static void main(final String[] args) {
        final ForkJoinPool p = new ForkJoinPool();
        final Fibonacci f = new Fibonacci(1_000_000_000);
        p.execute(f);
    }
    final int n;
    Fibonacci(final int n) {
        this.n = n;
    }
    @Override
    protected final Integer compute() {
        if (this.n <= 1) {
            return this.n;
        }
        final Fibonacci f1 = new Fibonacci(this.n - 1);
        f1.fork();
        final Fibonacci f2 = new Fibonacci(this.n - 2);
        return f2.compute() + f1.join();
    }
}


Voila un petit tour d'horizon de java 7, on approche de la dernière version officiellement sortie, la version 9 mais il nous reste encore la version 8 qui a apporté une nouvelle fois de nouveaux concepts très important.

Informations complementaires sur java 7

vendredi 13 octobre 2017

Evolution Java 6

Je reviens avec les évolutions de Java  au fil du temps. Aujourd'hui nous allons nous intéresser aux évolutions de Java 6.

L'integration d'interpreteurs de script est une évolution tres intéressante dans l'optique de rendre interopérable Java avec les langages de scripting courant et de plus pour le rendre en plus extensible et capable de largement s’étendre.

Basiquement la plateforme n’intègre par défaut qu'un interpreteur Javascript  mais est extensible avec les langages comme perl, python, ou plus connu dans ce contexte Groovy.

Pour accéder a l'API on passe par la classe javax.script.ScriptEngineManager, exemple:


1
2
3
4
5
6
7
8
9
int val1 = 5;
int val2 = 12;
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.getBindings(ScriptContext.ENGINE_SCOPE).put("val1", val1);
engine.getBindings(ScriptContext.ENGINE_SCOPE).put("val2", val2);
Object result = engine.eval(" var sum= function(val1,val2){return val1+val2;};"
    + "sum(val1,val2);");
System.out.println("Résultat : " + result);

Maintenant en dehors de l’intégration possible d’interpréteur de script, globalement les évolutions de la version 6 de Java consiste essentiellement en des mise a jour ou en l'ajout d'API, aucune modification sensible du langage n'est a noter.

Une API pour le compilateur java pour les adeptes des outils de la compilation java dans Java Cette API se trouve dans le package javax.tools. L'idée principale est de permettre la recompile plus facile de code java directement sans avoir a passer par une plateforme intermédiaire (bash, etc...).

Accès Base de données : une mise a jour de JDBC qui passe a la version 4.0. Je ne rentre pas dans le détail, ce sujet pourra faire l'objet d'un autre billet.

API XML et WebServices. Sur ce point Java 6 opère une grosse évolution. Avec la mise a jour de JAXB et de JAX-WS en 2.0 accompagné des annotations qui vont bien, il est plus simple de mettre en place de façon standard un Webservice. A noter l’intégration de StAX comme parseur XML dont le fonctionnement se situe entre DOM et SAX en gardant que les avantages ainsi que XML Digital-Signature API permettant la sécurisation des WebServices. Je ne rentrerai pas la non plus dans le détail de ces API. Ceci pourra faire l'objet d'un futur billet.

Mise a jour AWT et SWING.... bon... ok

L'API Collection a été enrichi avec de nouvelles interfaces sur les files et sur la navigation dans les Map et les Set avec les implémentations en standard qui vont bien.

Coté Réseau et IO, on notera un enrichissement de la classe java.net.NetworkAdress pour fournir plus d'info réseau (masque reseau, adresse Mac...) et également plus de capacité de gestion des droits sur la classe java.io.File.

java.utils.ServiceLoader : Cette classe n'y parait pas mais a mon sens, elle est extrêmement importante car a elle seule elle permet de conjuguer l'injection de dépendance et le processeur d'annotations AT RUNTIME, ce qui permet de rendre l'utilisation des annotations beaucoup plus efficace et surtout utile plutôt que de juste en faire une utilisation de générateur de fichier de configuration. Je reviendrais la dessus car lorsque l'on traitera OSGI on y trouvera beaucoup d’intérêt. Petit avant gout de la chose ici dans cet article.

Voila, cet article est déjà fini, pas que les évolutions du langage java soit peu significative dans cette version mais il s'agit surtout d'une version ou il a ete important de consolider et de mettre a jour l'existant afin de fournir des fonctionnalités stables et pérennes. Apres les changements fournis avec la Version 5 il fallait bien cela!

Evolution Java 5

Suite a quelques échanges professionnel sur Java, je me suis dit qu'il ne serait pas si mal de faire un petit topo illustré de l’évolution de Java avec quelques exemples. Je me suis inspiré de l'article du blog de Xebia qui liste l'ensemble des évolutions majeures du langage au debut de son histoire. Pour le consulté ici

Je ne vais pas traiter les évolutions antérieur a Java 5 car la version 1.4 a été l'age de raison en fournissant l'essentiel de ce que l'on de l'API Java et de sa syntaxe. Pour rappel, l'API java 1.4 se constituait de ceci:



Apports de Java 5:

Bien sur les génériques!  C'est forcement ce que l'on en retient le plus. Souvent comparés aux Templates c++, les deux sont fondamentalements differents. Par exemple, les génériques Java sont résolus lors de l’exécution alors que les templates c++ le sont a la compilation. Ils ont amener beaucoup de complexité dans la manipulation du typage mais ils ont aussi permis la simplification de l’écriture de code qui initialement impliquer d'utiliser des cast et des instanceof.

voici un petit exemple mais pour ceux qui en veulent plus je vous conseille les cours suivants d'openclassroom et de jmdoudou.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class GeneriqueTest<T> {

    private T truc;

    public GeneriqueTest(T truc)
    {
        this.truc=truc;
    }
    
    public T getTruc() {
        return truc;
    }

    public void setTruc(T truc) {
        this.truc = truc;
        
    }
    
    public <S> S functionGen(S s)
    {
        return s;
    }
    
}

L'autoboxing/unboxing qui permet la construction a la volée d'un Wrapper lors de la manipulation d'une variable primitive comme d'un entier ou réciproquement de récupérer automatiquement un entier primitif a partir de son Wrapper. Cette pratique nécessite des précautions car elle masque la création de l'objet Wrapper et généralement tout ce qui est implicite est dangereux  si l'on ne comprend pas le fonctionnement sous-jacent.

Exemple:

1
2
3
4
5
6
7
List<Integer> l=new ArrayList<Integer>();

public int addInt(int i)
{
    l.add(i);
    return l.get(0); 
}

Les imports statiques qui a mon sens si elles permettent de simplifier l’écriture ne facilitent pas forcement la relecture du code si plusieurs imports statique sont employés en même temps. Un abus sera probablement une erreur, une utilisation avec parcimonie une bonne idée.

On écrira

1
System.out.println(PI); 

a la place de

1
System.out.println(Math.PI); 

en ajoutant  

1
import static java.lang.Math.PI; 

en tête de fichier.

Les varargs permettent la définition de fonctions comportant un nombre indéfini de paramètres d'un même type. Il ne s'agit ici que d'un sucre syntaxique équivalent a l'emploie explicite d'un tableau.

Ainsi le code suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static int sum(int[] values)
{
    int result=0;
    for(int i=0; i< values.length;i++)
        result+=values[i];
    return result;
}

public static void main(String[] args)
{   
    int[] ta1={1,2,3,4,5,6};
    int[] ta2={1,2,3,4,5,6};
    System.out.println(sum(ta1));
    System.out.println(sum(ta2));
}

deviendra:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static int sum(int ... values)
{
    int result=0;
    for(int i=0; i< values.length;i++)
        result+=values[i];
    return result;
}

public static void main()
{
    System.out.println(sum(1,2,3,4,5,6));
    System.out.println(sum(1,2,3));
}

Les annotations ont été une évolution majeure de Java 5 au même titre que les générique. A ce titre, elles permettent l'ajout de méta-données sur les éléments du code (Classes, méthodes, ou données membres) en vue de traitements spécifiques soit a la compilation (génération de code ou de fichier de configuration) ou au runtime via l'introspection. Un article dédié serait nécessaire pour en faire le tour (tout comme pour les génériques au passage) et ce n'est pas le but. Cependant il faut en retenir que les annotations permettent la décoration du code tout comme le faisait XDoclet mais permettent d'aller franchement plus loin et y associant également des comportements via les processeurs d'annotation.

Petit exemple sur une méthode afin d'en supprimer le warning a la compilation:

1
2
3
4
5
@SuppressWarnings(value = { "static-method" })
public <S> S functionGen(S s)
{
    return s;
}

La simplification des boucles est la aussi un sucre syntaxique, mais celui ci est vraiment pratique car sans ambiguïté. La nouvelle écriture permet de se passer de l'utilisation de l'iterateur issu de la liste sur laquelle on veut itérer.

Ainsi si en Java 1.4 on aurait ecrit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
List l=new ArrayList();
Iterator it=l.iterator();
for (;it.hasNext();)
{
    Object o=it.next();
    if(o instanceof Truc)
    {
        Truc c=(Truc)o;
        c.montruc();
    }
}

en java 5 on écrira plutôt (a noter l'utilisation intéressant du générique pour éviter l'emploie du instanceof)

1
2
3
4
5
List<Truc> l=new ArrayList<Truc>();
for (Truc c:l)
{
    c.montruc();
}

Il faut admettre que c'est plus court... et autre chose bien c'est que ça marche également sur les tableaux

1
2
3
4
5
Truc[] l = {new Truc(),new Truc()};
for (Truc c:l)
{
    c.montruc();
}

Et enfin les enumerations. La aussi nous sommes face a un gros changement dans Java. Elles constituent la dernière grosse évolution de Java 5. Le besoin des enum est venu du problème a déclarer en java 1.4 un ensemble fini de valeur possible sauf en déclarant des constantes. Et encore, il devient vite lourd de devoir créer un objet spécifique ne pouvant prendre que les valeurs définies par ces constantes.

Les enumerations permettent de consolider cela en limitant pour un objet de type enum les valeurs qu'il peut prendre:

1
2
3
4
5
public enum MaCouleur {
    BLANC,BLEU,ROUGE
}

MaCouleur b=MaCouleur.BLANC;

En plus de cela, la construction de certaines structures s'en trouve simplifier comme dans l'emploi du switch/case (a noter qu'il faut attendre la version 7 de java pour pouvoir utiliser des objets dans un switch/case mais nous y reviendrons).

A noter qu'une enum vue par le compilateur n'est qu'une classe héritant de la classe java.lang.Enum. Je vous passe la transformation réalisée par le compilateur mais il est intéressant de savoir que du coup il est possible d'ajouter des comportement a notre enumeration.

Exemple:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public enum MaCouleur {
    BLANC("white"),BLEU("blue"),ROUGE("red");
    
    private final String trad;
    
    private MaCouleur(String trad)
    {
        this.trad=trad;
    }

    public String inEnglish()
    {
        return this.trad;
    }
}

On  pourrait aller plus loin en décrivant le mécanisme de classe interne/anonyme permettant de spécifier des comportements spécifiques pour chaque valeur de l'enum. Cependant ce n'est pas le propos de ce billet qui d’ailleurs arrive a sa fin. Le prochain traitera de Java 6.

JMX : Java Management Extension qui est une API intégré de façon standard afin de faciliter la supervision et le monitoring des applications Java. L'exemple le plus simple de son utilisation est lorsque l'on cherche a exploiter les consommations mémoires et CPU d'une JMV a l'aide d'outils tels que la JConsole ou JVisualVM qui se connectent en JMX Remote sur le processus a l'aide de son PID.