Blogounage

Aller au contenu | Aller au menu | Aller à la recherche

Technique

Tout ce qui parle de programmation et même un peu plus large que ça...

Fil des billets - Fil des commentaires

lundi 30 juin 2008

Comment réinitialiser manuellement les pages chargées par firefox au démarrage

Si, comme moi depuis 10 minutes, vous êtes dans l'impossibilité de relancer Firefox parce que la page sur laquelle vous étiez provoquait un plantage de FF, ce qui suit est pour vous.

Dans les options de Firefox, vous pouvez demander à ce que celui-ci réouvre les onglets ouverts avant la dernière fermeture (Onglet Général, le premier combobox). Une fois cette option sélectionnée, mon firefox qui gelait avait pour bonne idée de redémarrer sur la page en question et de regeler...

Et là, bizarrement, la proposition de "restaurer les onglets" ou de "démarrer une nouvelle session" n'était même pas offerte. Donc, bien bien bloqué.

La solution que j'ai trouvée est la suivante :

  • Tuez firefox
  • dans votre répertoire profile (dans quelque chose comme $HOME/.firefox/1d5s1.default sous Linux ou C:\Documents and Settings\votreuser\Application Data\Mozilla\Firefox\Profiles\1d5s1.default sous windows), supprimez simplement le fichier sessionstore.js.
  • Relancez firefox...

Voilà, en espérant que ça serve...

mardi 24 juin 2008

Quelques déclarations XSD ou DTD de formats XML connus

C'est le genre de chose qu'il est sympathique d'avoir écrit en tête de ce type de fichier pour disposer de l'auto-complétion XML dans son IDE favori. Lorsque j'utilise un nouveau format XML, la première chose que je cherche est en effet à ajouter cet entête pour me faciliter la vie (et aux autres une fois commité :)).

Note : dans la liste ci-dessous, je ne mets pas le prologue XML pour gagner en concision. Toutefois, ça ne fait jamais pas de mal de le mettre, personnellement j'essaie de le mettre systématiquement.

Hibernate : mappings hbm.xml

Même s'il vaut mieux à mon avis passer aux annotations lorsqu'on en a la possibilité, voici une déclaration de DTD qui fonctionne chez moi pour ceux qui ont encore des mappings hibernate à la sauce XML :

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="net.batmat.domaine">
...
</hibernate-mapping>

Java Persistence : persistence.xml

Pour ceux qui utilisent Java Persistence, cette "sous-spécification" des EJB3 devenue une spécification autonome dans sa version 2 :

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
                http://java.sun.com/xml/ns/persistence
                http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"

        version="1.0">

...
</persistence>

Spring : fichier de contexte XML

Depuis la version 2, ils ont subdivisé leur format XML en différents namespaces. Voici un exemple fonctionnel qui met en œuvre un certain nombre de ces espaces de nommages XML. Je laisse en exercice le passage à Spring 2.5 (que nous utilisons, d'ailleurs, mais on n'a pas mis à jour le XML) ou l'ajout d'un autre namespace dont vous auriez besoin :-).

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:util="http://www.springframework.org/schema/util"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
                http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                http://www.springframework.org/schema/util
                http://www.springframework.org/schema/util/spring-util-2.0.xsd
                http://www.springframework.org/schema/tx
                http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
                http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
>

...
</beans>

Maven : pom.xml

Celui-là, il est sacrément pratique vu la taille du truc :

<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/maven-v4_0_0.xsd"
>

...
</project>

Voilà déjà pour cette fois. Une fois cet entête ajouté, dans Eclipse par exemple, tapez juste "<" quelque part et vous verrez la liste des balises apparaître et leur documentation si les auteurs l'ont indiquée (après qu'Eclipse ait pu récupérer le XSD sur Internet, bien sûr) : Auto-complétion du XML avec Eclipse

Si vous en avez d'autres, n'hésitez pas à les poster (ou à les demander, si je suis dans un bon jour) dans les commentaires.

samedi 2 février 2008

Optimisation GWT 1.4

Wouah, je viens de regarder l'interview de Bruce Johnson par InfoQ à propos de GWT.

Bruce explique les nombreuses optimisations qu'ils effectuent au niveau du compilateur GWT. Il explique notamment qu'ils (les développeurs GWT) cherchent sans cesse à optimiser le compilateur, pour cela il comparent par exemple les requêtes qu'une webapp classique effectue vers le serveur et les requêtes que GWT fait.

Et en exemple, autour de la dixième minute, il explique l'optimisation concernant les images : l'ImageBundle. Dans une application classique, il y a des dizaines d'images, souvent toutes petites (icônes, fonds...). Le problème, c'est que le navigateur, même pour se voir répondre que l'image est à jour ouvre très souvent des connexions, d'autant plus qu'il y a d'image ! Ça peut créer (dixit Bruce) une contention en raison du grand nombre de sockets ouvertes côté serveur.

Et bien si vous utilisez cet objet ImageBundle, GWT construit côté serveur une image composite. Comme ça, le navigateur n'a qu'une seule image à télécharger puis vérifier, et les morceaux sont déconstruits côté client en récupérant les bouts de la grosse image et en les plaçant au bon endroit (cf. 10m40s). C'est quand même génial comme idée.

Bon après, il faut que la machine sur laquelle le navigateur tourne dépote d'autant plus, mais on n'a rien sans rien.

PS : Ce que je viens de dire n'est que le reflet de ce que j'ai compris de l'interview. Je n'ai pas testé cette fonctionnalité.

Ça y est, @Override fonctionne enfin comme je l'ai toujours voulu :-)

Depuis Java 5, les annotations ont été ajoutées. L'une des premières a été @Override, une annotation destinée à informer le compilateur que la méthode ainsi annotée redéfinit une méthode parente.

Le problème, c'est qu'en Java 5, cette annotation ne pouvait être utilisée que sur des méthodes en provenance d'une classe parente, pas d'une interface.

Je l'ignorais jusqu'à hier soir, mais en posant la question de la justification de ce choix d'implémentation sur ma liste de diffusion favorite, c'est mon ancien prof de Java et de Génie Logiciel qui m'a répondu que c'était possible depuis Java 6. Cf. le blog de Peter Ahé.

En clair :

public interface MonInterface
{
        void methode();
}

class MonImpl implements MonInterface
{
        // L'annotation suivante est valide depuis Java6, mais produit à la compilation le
        // message suivant en Java5 :
        // "The method methode() of type MonImpl must override a superclass method"
        @Override
        public void methode()
        {
        }
}

Ça fait un outil de plus pour augmenter la qualité du code, en l'occurrence en diminuant le risque de se retrouver avec du code mort.

dimanche 13 janvier 2008

How to load a XML Spring context lazily by default

Say you want to change the default-lazy-init attribute programmatically. The most common use case for this seems to appear with unit-testing. In fact, you probably want to load everything when starting a production server, even if it costs you more time. But when testing, and particularly on your machine, you might want to inverse this behaviour. In fact, you might be interested in not loading every 1000 beans of your context when you just need less than 10 for example...

A (beginning of) solution

If you crawl Google, you might find this entry that links to this other one that explains how to programatically modify the lazy-init attribute of each bean of your context. In fact, before the loading occurs, you can iterate through the bean list and call setLazyInit using the method AbstractBeanDefinition.setLazyInit(true). This will be roughly equivalent to manually put lazy-init="true" on every single bean of your context files.

The problem

The problem with this is that you override everything that was declared in the XML context file. You might want a particular bean be instantiated lazily or eagerly for some reason. But iterating through the whole list like this won't let you keep what was explicitly defined and only redefine the default value. Another problem is that you may have to exclude some bean from this loop, ending up with a condition that really looks like an ugly hack :

for (String beanDefinitionName : context.getBeanDefinitionNames()) {
    AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition)context
            .getBeanDefinition(beanDefinitionName);
    System.err.println(beanDefinitionName);
    // FIXME : ugly hack
    if (!"org.springframework.aop.config.internalAutoProxyCreator".equals(beanDefinitionName)) {
        beanDefinition.setLazyInit(true);
    }
}

Having to exclude the one bean that's (according to its name) responsible for proxying beans that have to be lazy-loaded seems quite reasonable. But the thing is you obviously don't have to do it when you put the default-lazy-init manually...

So I looked for a way to simply replace the default value of this attribute instead. After some time digging into Spring code, I finally found it.

When parsing context definition written in XML (since writing it in XML is no requirement), Spring will use an instance of BeanDefinitionReader that will then call DefaultBeanDefinitionDocumentReader to parse the Document instance. Searching a bit more lets us find where the default values are initialized : BeanDefinitionParserDelegate.initDefaults().

LazyInitByDefaultBeanDefinitionDocumentReader

public class LazyInitByDefaultBeanDefinitionDocumentReader extends DefaultBeanDefinitionDocumentReader
{
        @Override
        protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root)
        {
                root.setAttribute(BeanDefinitionParserDelegate.DEFAULT_LAZY_INIT_ATTRIBUTE, "true");
                return super.createHelper(readerContext, root);
        }
}

I would have preferred to use the seemingly more dedicated method preprocessXml(Node root)], but this method is called after createHelper(), so the default values are already initialized when calling preprocessXml()... So overriding preprocessXml() is "too late".

I think I'm going to submit a patch into the Spring bug tracker about this problem.

Unit-testing

When you do unit-testing with Spring, you generally inherit the AbstractDependencyInjectionSpringContextTests class. Add this method redefinition to enable the "lazy by default" behaviour :

@Override
protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context)
{
        XmlBeanDefinitionReader r = new XmlBeanDefinitionReader(context);
        r.setDocumentReaderClass(LazyInitByDefaultBeanDefinitionDocumentReader.class);
        return r;
}

Plain old Java (Spring) code

When you want to create an XML context, you generally use a ClassPathXmlApplicationContext instance. If you want to modify the default behaviour like here, you have to use a GenericApplicationContext. Note that this is what is adviced in the javadoc of ClassPathXmlApplicationContext :

This is a simple, one-stop shop convenience ApplicationContext. Consider using the GenericApplicationContext class in combination with an org.springframework.beans.factory.xml.XmlBeanDefinitionReader for more flexible context setup.

Here is the code using this GenericApplicationContext that enables the lazy-loading :

GenericApplicationContext context = new GenericApplicationContext();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
reader.setDocumentReaderClass(LazyInitByDefaultBeanDefinitionDocumentReader.class);
reader.loadBeanDefinitions("/yourApplicationContext.xml");
context.refresh();

When is it needed ?

At the moment, I see two reasons for enabling the lazy-loading by default :

  1. When unit-testing, this lets you test a part of the code a bit more quickly, since not every single beans needs to be instantiated.
  2. When using code-coverage tools to evaluate a part of the code, this prevents totally unrelated code to appear with non-null percentage.

Hope this small article will help some other people :-).

vendredi 4 janvier 2008

The Mythical 5%

Via InfoQ, voici un article de Bruce Eckel sur l'informatique aujourd'hui.

Il commence par parler du "fascinant" 5% des programmeurs sont 20 fois plus productifs que les autres 95%., et élargit ensuite au monde de l'informatique : jeune, immature, ...

Note : Le titre de l'article de Bruce fait très certainement référence au célèbre livre "The Mythical Man-Month" de Frederick P. Brooks. C'est de ce livre qu'est issu la plus célèbre encore "loi de Brooks" :

Ajouter des ressources à un projet en retard le rend encore plus en retard

Je vous conseille aussi la lecture de cet ouvrage, ça se lit très bien et c'est très instructif.

vendredi 21 décembre 2007

Quelles méthodes je peux mettre dans mes « objets du domaine » ?

Gavin King, le créateur du projet Hibernate, dont le hobby est maintenant devenu Seam, a récemment publié un billet intéressant sur ce sujet.

Souvent, la question se pose : quelles méthodes doit-on modéliser dans un objet métier ? Concrètement, où dois-je mettre mon code métier ? Par exemple, est-ce que la méthode de sauvegarde d'une facture doit se trouver dans l'objet Facture lui-même ? Si je veux récupérer le nombre de clients d'une facture, dois-je mettre la méthode dans la Facture, etc. ?

Gavin a formulé d'une façon très concise une règle à laquelle je souscris totalement :

the domain model (entity classes) are the most reusable classes in my codebase

[...]

In particular, I would never write code that calls out to external services, or accesses the database, or calls an EJB/Seam/Spring component in my entity class. I want my domain model to be completely self-contained!

So anytime you find yourself wishing that entities supported injection, or find yourself writing a JNDI lookup in a method of an entity, please consider that your domain model is no longer self-contained, and will be less reusable in different execution environments.

Rien de plus à ajouter.

dimanche 16 décembre 2007

Do Interfaces have semantics ?

When I saw the title of this entry in my feed reader, my first thought was who the f*** could be wondering if interfaces have semantics ?. Happily, that's not Gavin who thinks this.

The assertion is the following :

Interfaces in Java don't have any semantics attached. Only a concrete class can define the semantics of a method.

In my opinion, that's even what interfaces are designed for, defining semantics...

I totally agree with Gavin when he says semantics is defined in the Javadoc and the "name of the things". I always find myself taking a long time to carefully choose the naming of my objects, since I want those names to be the most meaningful possible (same for method parameters, method names, and so on. Naming meaninfully local variable is also important, but it goes beyond the scope of the current subject).

Using polymorphism let you see different implementations as the same thing, thanks to the defined contract. Maybe I'm only viewing interfaces throught the Liskov principle. But for me contract is a synonymous for semantics when it comes to programming, although I'd be glad to hear explanations of where it differs in your opinion.

Even more, I think that today, as everybody gets more and more used to using the dependency injection pattern, some concrete classes are not even known to the code they're used by. It's becoming common to use classes only through their interfaces only (services, for example). In those case, semantics resides in the interface only, and implementations obviously have to respect it very carefully.

vendredi 30 novembre 2007

Continuum : envoyer le mail après le build dans tous les cas

Nous avons récemment installé et configuré Continuum. Comme nous démarrons avec, nous avons configuré nos projets pour recevoir systématiquement un mail lorsque le build est terminé, qu'il ait réussi ou non. Nous voulons en effet nous assurer que le build est bien effectué par ce biais. Une fois que nous aurons rôdé le processus, dans quelques mois par exemple, je pense que nous modifierons effectivement la configuration pour rendre le serveur d'intégration moins bavard...

Voici le bloc qui permet de dire au serveur d'intégration d'envoyer les mails dans tous les cas. À mettre dans le pom.xml de votre projet :

<ciManagement>
        <system>continuum</system>
        <url>http://mvnrepo.mipih.fr:8080/continuum</url>
        <notifiers>
                <notifier>
                        <type>mail</type>
                        <sendOnError>true</sendOnError>
                        <sendOnFailure>true</sendOnFailure>
                        <sendOnSuccess>true</sendOnSuccess>
                        <sendOnWarning>true</sendOnWarning>
                        <configuration>
                                <address>notre-adresse@mipih.fr</address>
                        </configuration>
                </notifier>
        </notifiers>
</ciManagement>

Dans tous les cas, j'ai dit !

Mais en fait, même comme ça, continuum ne vous enverra pas toujours la notification. En fait, il ne l'enverra que si l'état de la construction de votre projet a changé depuis la dernière fois. De base, donc, continuum n'enverra pas de mail si dexu builds successifs ont réussi.

J'ai donc cherché à savoir comment configurer continuum pour envoyer le mail inconditionnellement à la fin de chaque build. Comme je n'ai pas trouvé cette information dans la documentation de continuum, j'ai posé la question sur la liste de diffusion. J'ai eu la réponse d'Emmanuel Venisse, le projet lead de continuum :

You can configure "alwaysSend to true in WEB-INF/classes/META-INF/plexus/application.xml in the mail notifier component descriptor.

By default, we don't send notifications if the state doesn't change to not spam users.

Emmanuel

Dans continuum 1.0.3, c'est là : $CONTINUUM_HOME/apps/continuum/conf/application.xml. Et effectivement, après modification du bloc <alwaysSend/> à true du composant mail notifier comme suit, ça marche !

<!--
        The mail notifier
-->

<component>
        <role>org.codehaus.plexus.notification.notifier.Notifier</role>
        <role-hint>mail</role-hint>
        <implementation>
                org.apache.maven.continuum.notification.mail.MailContinuumNotifier
        </implementation>
        <requirements>
                <requirement>
                        <role>org.codehaus.plexus.velocity.VelocityComponent</role>
                </requirement>
                <requirement>
                        <role>org.apache.maven.continuum.store.ContinuumStore</role>
                </requirement>
                <requirement>
                        <role>org.codehaus.plexus.mailsender.MailSender</role>
                </requirement>
                <requirement>
                        <role>org.apache.maven.continuum.configuration.ConfigurationService</role>
                </requirement>
        </requirements>
        <configuration>
                <from-mailbox></from-mailbox>
                <from-name></from-name>
                <timestamp-format>EEE, d MMM yyyy HH:mm:ss Z</timestamp-format>
                <includeBuildResult>true</includeBuildResult>
                <alwaysSend>true</alwaysSend>
        </configuration>
</component>

En espérant que ça en aide certains...

jeudi 22 novembre 2007

44 balises en 5 minutes...

Via Prendre un café, un petit test rigolo sur vos connaissances HTML4 :

44

Pas trop mal. J'ai honte d'avoir oublié de citer acronym, abbr que j'ai déjà pas mal utilisé, et toutes les balises des formulaires ;-).

vendredi 26 octobre 2007

Limitation Oracle antédiluvienne

Je découvre avec une relative stupeur que la taille maximale des noms de tables, de colonnes, de contraintes est limitée sur Oracle à 30 caractères seulement !

Imaginez que vous ayez une contrainte d'intégrité référentielle entre deux tables :

  • PRODUIT
  • CATEGORIE_PRODUIT

Par convention, on préfixe souvent ces contraintes de clés étrangères par FK (voire C_FK par exemple, pour Constraint et Foreign Key). Ça me donne quelque chose comme ça :

  • FK_PRODUIT_CATEGORIE_PRODUIT

Là, j'ai 28 caractères, je passe limite. Si je rajoute "C_", je suis à fond...

Imaginons maintenant que votre fonctionnel vous indique qu'il y a une relation de réflexivité sur CategorieProduit. Les catégories de produit forment en effet un arbre avec des catégories, des sous-catégories, etc. Ben là, vous oubliez le nommage ci-dessus :

  • FK_CATEGORIE_PRODUIT_PRODUIT_CATEGORIE_PRODUIT, ça passe pas...

Autre cas, si vous avez deux relations entre deux mêmes tables. Par exemple, Adresse et Client : un client peut avoir une adresse de facturation et une adresse de livraison. En base, on doit donc retrouver deux contraintes d'intégrité. La première idée qui vient est de concaténer par exemple le rôle au nommage ci-dessus. Euuu, ça commence à exclure pas mal de cas.

Franchement, à l'occasion du passage de la mise à jour de Oracle v10, Oracle aurait pu supprimer ou au moins augmenter cette limitation antédiluvienne !!! Pour la petite histoire, cette limitation n'existe pas sur les autres SGBD que nous pouvons utiliser : HSQLDB, MySQL, PostgreSQL... Je comprends mieux pourquoi les schémas d'entreprise sont truffés de noms absolument incompréhensibles. L'auto-documentation du nommage est impossible avec une limitation aussi importante.

À l'heure de l'auto-complétion omniprésente, il est courant d'avoir des variables qui atteignent 30 caractères. Je ne dis pas qu'il faut forcément des noms de variable à rallonge, mais si la longueur apporte en clarté, en maintenabilité, en compréhension, ne vous en privez surtout pas !

Très très déçu par Oracle sur ce coup là.

samedi 16 juin 2007

Utiliser les objets du domaine directement dans la couche présentation

Nous utilisons encore Struts 1 actuellement. Ce n'est normalement qu'une question de temps avant de passer à autre chose. Nous sommes sûrs de ne plus l'utiliser à terme. Nous connaissons donc notre source, mais pas encore la cible. Voici les nombreuses solutions qui s'offrent à nous :

  • Struts 2
  • GWT
  • .Net
  • Flex
  • ...

Toutefois, je ne suis pas très chaud pour .Net ou Flex ou toute technologie qui nous contraigne forcément à passer par les WS pour accéder à nos services (développés en Java). Je suis réticent à devoir obligatoirement passer par une couche WS alors que nous pourrions garder du java de bout en bout, et ainsi économiser le marshalling/unmarshalling incessant qui va avec les WS, sans parler du problème de l'identification du graphe d'objets à sérialiser pour le renvoyer. Quoi qu'on en dise, utiliser systématiquement des WS n'est pas neutre et amène de nombreuses questions qu'on ne se pose jamais sinon (hein Séb...).

Donc, pour revenir au sujet, nous ne sommes pas encore fixés sur notre future couche présentation (ou plutôt nos futures couches de présentation). Pour le moment, nous avons donc décidé de continuer à fonctionner tant bien que mal Struts 1.x avec la version 2 du socle technique que nous sortons.

Or, la différence fondamentale entre notre version 1 et notre version 2 est que nous avons supprimé une "transformation" systématique entre deux couches. Auparavant, en effet, nos entités persistantes de couche DAO étaient systématiquement transformés en objet de couche service. Entre les deux couches, nous avions systématisé un bidouillage destiné à régler le problème que nous avons dans la couche Web dans la gestion des multiplicités 0..1.

La gestion du null de Struts 1.x

Avec Struts 1, si vous demandez par exemple le prénom d'un client null, vous n'aurez pas quelque chose de null, mais une exception. La question a été discutée il y a déjà un moment sur la liste de Struts et posée sur le tracker de Struts en février 2001 (quand je vous disais que ça faisait un moment), et Craig Mc Clanahan d'expliquer que le principe était que si vous vouliez afficher le prénom d'un client, eh bien il fallait protéger le code pour n'afficher le prénom que si le client est non null :

<logic:present name="beanname" property="client">
  <bean:write property="client.prenom"/>
</logic:present>

Si dans l'exemple ci-dessus, on met un champ de saisie à la place du <bean:write />, on est dans une impasse : certes, il n'y aura pas de plantage, mais il devient alors impossible de saisir le prénom du client puisque le textfield n'apparaîtra même pas...

Si votre objectif est de créer le client seulement si l'utilisateur a saisi des données (exemple : le formbean en cours gère un objet Facture qui a une multiplicité 0..1 vers un objet client), vous êtes obligé de créer dans votre FormBean un objet destiné à recevoir ces données. Le problème qui survient alors, c'est que si vous voulez conserver des objets "propres"[1], vous devez ensuite vérifier l'instance de Client pour la remettre à null ou l'associer manuellement (si pas encore fait) à la Facture avant de transmettre la Facture à la couche service pour mettre à jour. Ouf...

La longueur de l'explication ci-dessus par rapport à la complexité de ce qu'il faut gérer me paraît parler d'elle-même. Et le code qu'il faut écrire pour gérer ce cas est toujours le même... :

  1. avant affichage de la JSP : instancier les propriétés null pour pouvoir les afficher. Eh oui, rappel comme je l'ai dit plus haut : la demande de la propriété "a.b" déclenche une exception si a est null, il faut donc s'assurer que a est non null et l'instancier le cas échéant.
  2. après soumission du formulaire de la JSP et avant de passer les données à la couche application : supprimer les objets 0..1 non modifiés dans la JSP.

Struts 2

Dans Struts 2, le premier point est géré différemment : la demande de "a.b" si a est null renvoie simplement null. Par contre, le deuxième point est toujours à gérer manuellement et plantera si on cherche à valoriser a.b.

Une étape a déjà été franchie, pourquoi ne pas aller plus loin ?

Une solution

Pourquoi ne pas instancier automatiquement a s'il est null lorsqu'on cherche à valoriser a.b ? La question se pose d'autant que nous ne sommes pas les seuls à nous être posés la question : ce patch apportait déjà les modifications nécessaires aux commons-beanutils (utilisé par Struts, c'est cette bibliothèque qui impose le comportement de lever une exception si une propriété imbriquée est demandée à l'intérieur d'un objet null)...

Une autre solution (sans patch, cette fois) intermédiaire a été proposée pour la partie "get" : mettre en place un handler externe pour gérer le cas des propriétés imbriquées (nested properties) dans un conteneur null. Cette solution est un intermédiaire intéressant parce qu'elle est très courte à implémenter et permet de faciler renvoyer null ou de lancer une autre exception dans le cas en question.

Cette deuxième solution ne gère toutefois que le cas get. Peut-être le même principe serait-il utilisable pour le "set". Cela permettrait de même de positionner une sorte de NestedSetterHandler qui pourrait s'occuper d'instancier les propriétés intermédiaires si besoin. Avec un fonctionnement de ce type, on pourrait même envisager de définir un handler qui serait observable afin de gérer à la fois l'instanciation "dynamique" et de pouvoir suivre les instanciations effectuées. C'est à creuser.

Ce mode de fonctionnement aurait toutefois quelques inconvénients : lorsqu'on instancie manuellement a, on sait qu'on risque de devoir l'enlever s'il n'est pas vraiment saisi avant de l'envoyer à la couche service. Et que faire si on décide de faire gérer ça en standard par le service ? Imaginons par exemple qu'une adresse ait été automatiquement instanciée pour un Client. À quel composant, à quelle couche, incombe le rôle de supprimer l'adresse créée dynamiquement si le fonctionnel indique qu'elle n'est pas correcte ?

Conclusion

Dommage que l'équipe de commons-beanutils soit peu réactive sur cette question à mon avis primordiale de gestion des propriétés. C'est aussi très dommage que l'idée d'avoir derrière la classe statique BeanUtils une instance de BeanUtilsBean ne soit pas poussée à son terme : en l'état, il est en effet impossible d'utiliser de façon transparente une sous-classe BeanUtilsBean qu'on aurait adaptée à ses besoins.

Je nous vois bien maintenir en interne une version patchée de commons-beanutils afin de résoudre le problème tant que nous utiliserons Struts 1, en espérant bien sûr que le patch en question soit finalement appliqué.

En même temps, il reste la question de la gestion de la suppression des propriétés instanciées dynamiquement. Mais je ne suis pas sûr que ce soit un véritable problème, c'est un code qu'il faut de toute façon écrire. Au final, il y a actuellement les deux points suivants à gérer :

  1. instanciation manuelle pour gérer l'affichage ;
  2. vérification des données et suppression des créations incorrectes si les données n'ont par exemple pas été saisies.

C'est à mon sens un point qu'il serait intéressant de creuser. Avec Struts 2 par exemple, écrire un Interceptor chargé de l'instanciation dynamique ferait l'affaire pour enlever le point 1 ci-dessus.

Le tout serait ensuite de définir un emplacement de choix pour faire la validation, puis la suppression des propriétés fonctionnellement "inacceptables". À part ce point, je ne vois aucun frein à cette évolution. Cette question se posant pour n'importe quel formulaire de saisie, le monde de l'informatique aurait à mon sens beaucoup à gagner à trouver un véritable pattern pour gérer ce cas...

Notes

[1] c'est à dire que facture.getClient() renvoie bien null si la facture en question n'a pas de client, et non une instance bâtarde de Client qu'il vous faut ensuite analyser pour savoir si c'est un vrai client ou bien simplement une instance positionnée pour gérer des contraintes de présentation.

lundi 14 mai 2007

Toplink better than Hibernate?

Today, we received Oracle. They were coming to speak about their business Intelligence offer, Oracle Application Server and so on.

When speaking about Toplink, the guy said something like: "We could say Toplink is kind of a father to Hibernate. Obviously, it's already almost 15 years of existence." Well, why not. Though I didn't see any article interviewing Gavin King confirming that, but apart from reusing their ideas or not, Toplink was here before, OK.

The assertion that surprised me was another one, something like: As Toplink is older than Hibernate, it's obviously more robust and has more functions.

Well, I'm not sure I would agree with this one. In fact, as Toplink Essentials was only released in the OpenSource world very recently (may 2006 in Glassfish, so the JPA implementation even more, if I'm right), and experts seems to agree that about 90% of the projects today use Hibernate for the persistance layer (Didier Girard says it in the TV4IT Webcast. I think this is more of a feeling for him, so if anybody has some statistics about, I'm very interested. I should also ask Sami Jaber for his feeling about it.).

Webcast (in french, see about 9/10 of the video for the Hibernate is 90% of the current projects) :

So, what is the right indicator about software robustness?

Is this the software that's been released fifteen years ago, used by 10 people around the word since this time, or is this this other one, that's been released 6 years ago, used by 90 people since then? Who was the most testable? The one that only few people could download to give it a try, or the one that's been downloaded and tested tons of time by a lot different people?

In my opinion, but I also agree I only know Hibernate, I'm pretty sure Hibernate has already been thoroughly tested. At least, as much as Toplink has. My point is that the maturing of Hibernate was a lot quicker than Toplink's one. Once more, it would be very interesting to put numbers in the debate. Hibernate has been opensourced since its start, so it always benefited from code reviews, patches and so on, and evolved very quickly.

To sum up, because of the fundamental differences between both development styles, I'm not sure that today Hibernate is still behind Toplink speaking about robustness, functions scope and so on. Though I'd be delighted to learn about interesting differences, if you know about it.

dimanche 25 mars 2007

Sun Tech Days, mercredi 21

Avec un peu de retard, je prends enfin le temps de faire un billet sur mon deuxième jour aux Tech Days. Cette journée a été très chargée, mais j'ai appris pleins de nouvelles choses. Évidemment, ce sont des choses à approfondir, mais je vais essayer de vous donner aussi envie de le faire. En cherchant un logiciel pour faire des vidéos de démo comme on en voit partout fleurir sur le net, je suis tombé sur Wink. C'est simplissime d'utilisation : avec ça, plus aucune excuse pour ne pas faire de vidéo explicative de l'installation ou d'une démo d'un logiciel (et on peut générer du flash ou un .exe pur windows).

Sun Technical Keynote

Cette introduction a été l'occasion de nous montrer le langage F3 : Form follows function, censé simplifier le développement d'interfaces graphiques. C'est vrai que la partie de la démo montrant comment mettre un fond en dégradé de rouge vers bleu vers jaune (ou d'autres couleurs) a montré que c'était effectivement très simple.

Speach VMWare

En tant que sponsor Platinium, VMWare a eu droit à un speach devant tout le monde. On nous a présenté ce qu'était VMWare, pour ceux qui l'ignoraient encore. Ça a été l'occasion de parler de Lab Manager, une sorte de dépôt central dans lequel on peut stocker des machines virtuelles et les lancer depuis des postes clients. Ça a l'air intéressant dans le cas des recettes par des équipes de tests. En effet, le syndrôme du Chez moi ça marche est effectivement connu de tout informaticien. Avec ce système qui simplifie le partage d'une configuration complète (du logiciel jusqu'à l'OS et sa configuration précise), il devient possible de débugguer un problème dans les conditions qui garantissent la reproductibilité, ce qui est la première difficulté pour un développeur : trouver les conditions permettant de reproduire le problème (essayez de corriger un problème que vous n'avez pas, pour voir !).

Java Jacket Give away

20 minutes pour un mini-show de 4 d'entre nous. Je n'ai pas osé y aller. J'ai eu une idée un peu tard, mais j'aurais dû la préparer la veille. Peut-être pour l'année prochaine :-).

Le gagnant a chanté une petite chanson sur Java et a donc gagné la veste en cuir avec le logo Java :-).

Java scripting: One VM, Many Languages

Cette session a été très intéressante. La démo de Guillaume Laforge m'a notamment donné envie de me mettre au Groovy, pour les apports en rapidité d'écriture par rapport au Java, l'ajout des fermetures, mais aussi pour sa proximité syntaxique et son intégration avec le Java. Ça peut en effet fonctionner directement avec la JVM, être mis dans un jar..., puisqu'on peut générer du pur bytecode Java. L'idée n'est pas de remplacer le Java, mais d'apporter une alternative plus puissante aux scripts shell et une syntaxe plus habituelle pour le développeur Java qui veut déployer un script de batch sur le serveur, par exemple.

Cette présentation a été l'occasion de découvrir la JSR223, la spécification qui permet d'intégrer n'importe quel langage de script à la plate-forme Java. Javascript l'est déjà (cf. la présentation flash plus bas), JRuby, Groovy, etc. En gros, l'architecture mise en place permet à n'importe qui d'intégrer n'importe quel langage de script en implémentant simplement les interfaces de la spéc (pensez à JDBC si vous ne voyez pas ce que je veux dire).

Code Quality issues: Prevention and Detection

C'est certainement l'une des sessions dans lesquelles je me suis senti le plus à l'aise, parce que j'avais déjà mis en place un grand nombre des outils cités. Ça a toutefois été l'occasion de découvrir de nouveaux outils (Selenium, notamment).

La présentation a commencé en citant Dijkstra : Testing can only show you bug presence, not their absence. Et c'est vrai que c'est bien de rappelant l'intérêt essentiel des outils de qualité de code... Ce n'est pas magique.

Pour résumer :

  • Test unitaire : Junit, TestNg
  • Couverture de code : Emma (+ pleins d'autres), ce type d'outil permet de savoir quelles parties de votre code ont été testées par les tests unitaires. En gros, vous lancez tous les tests de votre projet et l'outil monitore par où ça passe. Ensuite, vous pouvez généralement savoir quelles parties ont été testées, voire même lesquelles l'ont été plusieurs fois et d'autres une seule. La plupart de ces outils possèdent des plugins pour les principaux IDE qui permettent de voir graphiquement la couverture. Bien sûr, il existe aussi des plugins pour des outils comme Maven, permettant de sortir des statistiques.
  • Montée en charge : JMeter, LoadRunner (j'ajouterais OpenSTA)
  • Mock objects : EasyMock, JMock
  • Qualité du code : PMD, Checstyle
  • Test fonctionnel d'une webapp : Selenium
  • P6Spy pour voir la valeur des "?" dans les requêtes SQL paramétrées.

TP : Les nouveautés de Java 6

Comme j'en ai parlé plus haut, il est possible d'interfacer la JVM avec d'autres langages. Avec Java 6, vous disposez de jrunscript, un interpréteur Ecmascript (javascript normalisé).

En voici une petite démo :(cliquez sur l'image pour voir la démo flash) : jrunscript-presentation.jpg

JMX, Performance et concurrence

JMX

J'ai appris au cours de cette session que l'équipe de développement de JMX était située à Grenoble.

L'objet de cette session a été principalement de développer un MBean et de l'exposer via le MBeanServer. Ceux qui, comme moi, ont déjà développé un MBean et joué avec JConsole juste pour voir n'auront pas été surpris.

Par contre, j'en ai appris sur les différentes espaces mémoires de la JVM :

  • Eden : c'est l'espace mémoire où les objets à courte durée durée de vie sont stockés (la plupart en général) ;
  • Survivor : pour les objets qui ont une durée de vie un peu plus grande ;
  • Tenured : ben euu, encore plus :-).

Autre chose que les conférenciers ont dit, je n'ai pas vérifié, mais à propos des options -client et -server :

  • -client est toujours utilisé sous Windows, quelle que soit la machine
  • -server est utilisé automatiquement sous Linux ou sous Solaris si la machine possède plus de 2Go de mémoire vive.

Truc génial : la possibilité de repérer des deadlocks avec un simple bouton ! (je crois que c'est un ajout dans le JConsole embarqué dans Mustang, parce que je ne me souviens pas de l'avoir dans Tiger).

JSR 262

La façon dont on accède à JMX, JMX Remote, n'est pas liée fortement à JMX. En effet, l'implémentation par défaut dans Java 5 est effectivement en RMI, mais il reste très faisable de permettre l'accès aux fonctionnalités d'un serveur JMX par n'importe quelle autre implémentation.

C'est justement l'objet de la JSR 262 : implémenter une couche WebServices d'accès à JMX. Comme il existe une norme appelée WS-Man qui n'est pas liée au Java. Cette implémentation permettra donc l'accès au management de la JVM d'une façon normalisée. WiseMan est ainsi l'implémentation Java de WS-Man.

Lors de la séance de Q/R, j'ai posé la seule question de ces deux jours à Paris : pourquoi n'y a-t-il pas pour les MBeans, de la même façon qu'il y a l'annotation @WebService pour les Webservices, une annotation @MBean qui indiquerait automatiquement aux MBeanServer quels sont les MBeans à exposer ? Éamonn McMannus a répondu que c'était une question qu'on leur posait souvent, et que c'était justement prévu.

Performance

Bon, ils ont parlé du classique StringBuffer à la place de concaténer des String. Attention : depuis la version 5 de Java, la classe StringBuilder est à privilégier : de la même manière qu'il est recommandé d'utiliser HashMap à la place de Hashtable, il faut utiliser StringBuilder dans un contexte qui ne nécessite pas de synchronisation. En effet, la synchro n'est pas gratuite, donc l'utiliser de façon systématique est une grave erreur.

Concurrence

Ils ont présenté la nouvelle API de gestion de la concurrence ajoutée au JDK5, encore peu connue.

Java Puzzle

Pour finir, on a eu droit à un Java Puzzle (en anglais, puzzled correspond à surpris, étonné), c'est un bout de code qui surprend ou pour lequel on n'a généralement pas une réponse évidente pour tout le monde :

public class PingPong {
    public static synchronized void main(String[] a) {
        Thread t = new Thread() {
            public void run() { pong(); }
        };
        t.run();
        System.out.print("Ping");
    }

    static synchronized void pong() {
        System.out.print("Pong");
    }
}

Qu'affiche ce bout de code ?

  1. PingPong
  2. PongPing
  3. Ça varie

J'avoue sans honte que j'ai répondu correctement à cette question :-). Je mettrai la réponse dans les commentaires un peu plus tard (si personne d'autre ne le fait avant, d'ailleurs).

mardi 2 janvier 2007

Différence entre Session.save() et Session.persist()

Dans la version 3 d'Hibernate, d'un point de vue strictement utilitaire, il y a deux méthodes qui ont le même objectif : sauvegarder un objet en base (ou le rendre persistant, comme on dit).

Le problème est donc de savoir laquelle il faudrait plutôt utiliser. Et bien la réponse est simple, euuuu...

Traduction de ce qu'on peut lire dans la FAQ :

Je ne sais pas si je dois utiliser save() ou persist().

Ces deux méthodes rendent persistante une instance éphémère (NdT : transient). Toutefois, la méthode persist() ne garantit pas que la valeur de l'identifiant sera assignée immédiatement, l'assignement pourrait arriver au moment du flush.

La méthode persist() garantit aussi qu'aucun INSERT ne sera exécuté si elle est appelée en dehors d'un contexte transactionnel. Ceci est utile pour les conversations longues avec un contexte de persistance/session étendu.

La méthode save() ne garantit pas la même chose. Elle retourne un identifiant, et si un INSERT doit être exécuté pour obtenir l'identifiant it returns an identifier (e.g. générateur "identity", pas "sequence"), cet INSERT arrive immédiatement, peu importe que vous soyez à l'intérieur ou à l'extérieur d'une transaction. Ce n'est pas bon pour les conversations longues avec un contexte de persistance/session étendu

En résumé, si vous faites des conversations longues, utilisez plutôt persist(). Sinon il y a peu de différence, mis à part toutefois le cascading. Celui-ci n'est propagé dans le cas de save() que si la relation est mappée cascade="save-update", alors que c'est cascade="persist" pour persist().

Notez que si vous mappez cascade="all", cela inclue à la fois save-update et persist. Donc pour les relations mappées "all", la sémantique de persist() et de save() sont à mon avis extrêmement proches.

mardi 5 décembre 2006

Ajax pour les nuls, Scriptaculous

Franchement génial ce qu'on peut faire en trois coups de cuillère à pot grâce à Scriptaculous. J'avais fait, il y a un moment déjà, un petit test pour voir comment fonctionnait Ajax au marteau et au burin, sans aller bien loin. Ça avait donné ça.

Après en avoir entendu parlé un peu, ce soir, j'ai testé la bibliothèque Scriptaculous. Et voilà ce que ça donne. Franchement, en partant du wiki de scriptaculous, il m'aura fallu moins d'une heure, le temps de refaire un bout de php et trouver la fonction stristr à utiliser ! J'ai par ailleurs testé les effets (bibliothèque Effects) et c'est aussi très impressionnant : rien à faire ou presque et on peut tout de suite obtenir de superbes effets de fondus sur n'importe quel élément HTML.

Ajax en mode marteau/burin, j'y crois mais alors pas du tout. Faut être chtarbé pour envisager de faire coder à des dizaines de développeurs un truc aussi difficile à maintenir et à débugguer. Par contre, avec des bibliothèques comme celles-ci, ça devient tout de suite plus crédible. Toutefois, attention, comme c'est souvent rappelé, il ne faut pas s'emballer à en faire à bloc partout tout le temps : ça tape quand même beaucoup sur le serveur à coup de requêtes HTTP (exemple avec l'autocomplétion : à chaque lettre un échange avec le serveur est effectué, ben oui, c'est pas magique non plus), donc ça peut vite devenir gênant en terme de performance. Ne pas en abuser et à surveiller, donc.

Bravo pour cet outil si bien documenté et simple à utiliser, c'est pas toujours comme ça :-).

samedi 13 mai 2006

Mail encoder

J'ai écrit une page qui vous permet d'encoder vos adresses email. Je l'ai fait parce que celle qui était auparavant disponible chez Olivier semble ne plus l'être.

Cette technique permet de réduire le nombre de spams reçus. Toutefois, bien évidemment, il ne s'agit en aucun cas d'une technique infaillible. Je parle bien d'encodage et non de cryptage. Un robot un peu élaboré n'aura donc pas plus de mal à récupérer pouet@free.fr que %70%6f%66%40%66%72%65%65%2e%66%72.

Il s'agit en l'occurrence simplement d'une sorte de petite bidouille qui met seulement hors-jeu les robots collecteurs les moins élaborés. Et l'expérience semble montrer que la plupart des robots ne s'embêtent pas à tenter de récupérer les adresses encodées, il doit y en avoir déjà largement suffisamment qui ne le sont pas... Il existe un nombre important de façon de rendre plus difficile aux robots la récupération des adresses email. La technique en question d'encodage présente surtout l'intérêt d'être directement utilisable dans un lien mailto.

Ça me fait penser à autre chose : personnellement, lorsque j'écris une adresse au format texte, du style moi chez free point fr, je le fais en essayant d'utilisant des modèles pas trop standards, mélangeant les langues, at avec point, chez et dot, écrivant POWAIN au lieu de POINT lorsque le sérieux de l'endroit me le permet (e.g. pas sur un CV). Je vous conseille de faire de même si vous êtes paranoïaques. En effet, il semble envisageable d'écrire un programme qui récupère simplement tout ce qui ressemble à l'expression suivante : "(.*POINT)+CHEZ(.*POINT).*" ou quelque chose de ce genre afin de récupérer sur la toile un grand nombre d'adresses écrites dans ce format à la mode... Il serait peut-être intéressant d'écrire un pareil robot pour voir la faisabilité de la chose, mais ça me semble hautement faisable [1]...

Notes

[1] Notez que je parle ici uniquement de prouesse technique, et en aucun cas je ne pense à cela dans le but de devenir un spammer professionnel. Je vous encourage grandement à ne pas le faire non plus dans ce but si vous ne souhaitez pas recevoir ma plus grande inimitié et celle de la plupart des internautes de la planète (cf. mon rêve de désouder un spammer)

samedi 11 mars 2006

De l'importance du nommage, état atteignable ou état invariant ?

Il y a quelques temps, j'ai eu l'occasion de rencontrer un cas qui m'a donné à réfléchir sur la signification des suffixes anglais able et ed. En l'occurrence, le cas portait sur une classe qui dérivait en Java de ArrayList qui s'appelait SortedArrayList.

Cette classe ajoutait la possibilité de tri à sa super-classe ArrayList. Elle possédait pour ce faire la méthode sort(), et c'est justement ça que je le lui reprochais.

Le suffixe able signifie qu'un objet peut atteindre un état, sans y être nécessairement constamment, par l'appel par exemple d'une méthode qui lui fera atteindre cet état à un instant t. Pour une propriété X, le sufixe ed signifie quant à lui qu'un objet est constamment Xed.

C'est peut-être un peu abstrait comme ça, je vais donc illustrer avec la classe SortedArrayList dont j'ai parlé ci-dessus.

SortableArrayList et non SortedArrayList

Une classe en ed, comme sorted, est triée. Cela signifie qu'elle l'est constamment, en considérant un point de vue extérieur à l'objet : en interne, l'objet peut ne plus respecter l'état, mais au retour de la méthode, il doit y être revenu. Exemple : dans le corps d'une méthode add d'un conteneur trié, il peut se faire que l'objet soit non trié à un instant t, mais jamais au retour de la méthode add en question.

Toute action occasionnant une modification de son contenu doit laisser l'objet ...Sorted trié à l'issue de l'opération. Par exemple, un add sur un SortedSet en Java n'a pas besoin d'être suivi par un tri pour qu'une itération ultérieure sur l'instance se fasse toujours dans l'ordre de tri.

À l'inverse, un objet suffixé par able est par exemple Triable et non Trié, ce qui n'est pas du tout la même chose. Ainsi, une classe nommée SortableArrayList possédera une méthode sort() et l'ajout d'un objet à cette liste pourra ne pas forcément garantir que l'objet soit constamment dans un état trié. Il sera alors possible d'appeler la méthode sort() pour rétablir l'état trié au moment voulu.

Quand utiliser able ou ed ?

C'est assez simple. Je dirais que ça peut d'ailleurs souvent être lié à des questions de performances. Il sera en effet certainement plus coûteux de chercher à maintenir un état particulier constamment si ce n'est pas absolument nécessaire. Auquel cas, il semblera alors plus approprié d'utiliser le suffixe able et la méthode permettant de revenir à cet état.

Si par contre, vous avez souvent, voire toujours besoin d'accéder à l'objet alors que celui-ci est dans l'état voulu, utilisez le suffixe ed pour que toute opération touchant à la structure de l'objet conserve l'état à son retour.

samedi 21 janvier 2006

Mediawiki, utilisation d'un wiki en entreprise, coloration syntaxique avec GeSHI et pages secrètes

Depuis maintenant un peu moins d'un an, Mediawiki a été installé dans mon entreprise.

C'est un peu amusant, parce que je pense en être le principal instigateur, mais je ne peux pas le dire avec certitude. En effet, à l'époque, j'en parlais en effet souvent à celui qui allait devenir mon responsable aujourd'hui. Je discutais avec lui de toutes ces technologies, de tous ces outils modernes, connus ou moins connus, à la mode pour des raisons justifiées ou non.

Je lui parlai donc de ce qu'était un wiki, lui expliquai le principe. Une bonne démo valant mieux que de longs discours, ainsi pourrait-on détourner la formule consacrée, je lui montrai successivement ChuWiki, puis Mediawiki. Je me souviens que c'est à ce moment que, sentant que de l'intérêt était présent face à moi, j'ai beaucoup cherché des arguments me permettant de mieux défendre l'idée d'utiliser un wiki dans l'entreprise. Je cherchais surtout ces arguments pour convaincre si besoin les responsables de l'entreprise.

Pour moi, l'intérêt majeur d'un wiki d'entreprise réside dans la centralisation de la documentation. Toutefois, il ne fallait pas que le wiki impacte le processus de certification qualité en cours de mise en place, utilisant déjà largement word et ses modèles... Je défendai donc la pertinence du wiki pour ce que j'estime être sa plus grande utilité : la rédaction de toutes ces petites documentations, ces FAQ, qui devraient être utilisables et modifiables par tous, tout le temps. Mais aussi SURTOUT qu'on puisse être certain de disposer toujours de la dernière version d'une doc, ce qui est loin d'être trivial lorsque vous travaillez avec des gens qui ont pris l'habitude depuis longtemps de passer leur temps à s'échanger des fichiers word par mail. Personnellement, ma plus grande hantise lorsque je dois écrire une documentation avec un traitement de texte est de savoir où je vais bien pouvoir la mettre... Avec un wiki, je ne m'occupe que de choisir un nom de page qui exprime le plus clairement possible son contenu (sémantique, mon enfant, sémantique, ya qu'ça de vrai !), le stockage n'est plus mon problème.

Un jour, Lionel est revenu me voir en me disant : Bon, la direction veut qu'on installe un wiki, rapidement.. Je pense que ceci est arrivé parce qu'à l'époque, 01 Informatique commencait à en parler. Alors, comme ça faisait un peu plus "DSI-hype" qu'avant, on a eu carte blanche pour le monter. Dès la semaine suivante, la moitié du centre de développement était invitée à une présentation/formation sur le sujet, faite par Lionel, à laquelle j'ai assisté pour l'assister :-).

Formation Wiki

La formation devait partir de très loin. Une partie non négligeable des développeurs de l'entreprise utilisent encore leur navigateur de façon très épisodique, et ne connaissent que les outils qu'ils utilisent depuis longtemps. Il fallait donc parfois non seulement expliquer ce qu'est le code wiki, mais aussi montrer qu'on pouvait changer le nom par la barre d'adresse du navigateur, etc.

Cette formation avait évidemment pour objectif de présenter la philosophie générale d'un wiki, en plus de son utilisation. Ceci nous paraissait indispensable de présenter notre wiki de la sorte afin d'éviter que l'auditoire ne prenne par exemple le wiki pour un simple marque-page vers les documents word actuels ou futurs qui continueraient à être disséminés sur les serveurs de l'entreprise...

Coloration syntaxique avec GeSHI

Le wiki a continué à grossir et à prendre sa place. Il a été modifié très majoritairement par Lionel ou moi jusqu'à récemment. Les projets actuels ont fait que nous l'avons beaucoup utilisé pour documenter notre processus de développement J2EE. Nous mettons ainsi beaucoup de code Java et (X)HTML.

Plus cela allait, plus il nous apparaissait indispensable de chercher à ajouter la coloration syntaxique au wiki, afin d'améliorer la lisibilité des différents morceaux de code présents. J'ai donc cherché une solution et trouvé l'extension GeSHI pour mediawiki. En suivant précisément la documentation fournie, l'installation de l'extension prend 5 minutes maximum. Appliquez bien les bugfix spécifiés sur le lien ci-dessus.

La première question qui vient à l'esprit pour tout utilisateur de CSS est de se demander quelle est la forme du code généré par GeSHI. Et bien, c'est très propre. Par défaut, le code généré utilise les styles inline pour colorer le texte. Il est toutefois simplissime de modifier ce comportement. En lisant la documentation de GeSHI, on trouve l'appel à la méthode enable_classes(true).

Ajoutez simplement $geshi->enable_classes(true); avant le return de la fonction formatCode de l'extension et ça marche ! Après, il vous reste bien sûr à écrire le code CSS pour mettre la forme et les couleurs qui vous conviennent.

Note : J'ai essayé l'extension sur la dernière version actuelle (Mediawiki 1.5.5). Pour faire ça, j'ai dû migrer de la version 1.4.3 vers la dernière. Suivez la documentation de mise à jour à la lettre (notamment la mise à jour de latin1 vers utf8 si besoin, primordiale) si vous êtes dans le même cas que moi.

Ajouter un bouton pour encadrer votre code Java

J'ai voulu ajouter un bouton pour ajouter rapidement les balises <code type="java"></code> autour du code sélectionné, à la manière de ce que permettent les autres boutons pour mettre en valeur, faire un lien, etc.

C'est très simple à faire :

  • Ouvrez le fichier EditPage.php et cherchez la fonction getEditToolbar().
  • Copiez-collez un bloc qui définit un outil, à la fin par exemple :
array(	'image'	=>'button_hr.png',
					'open'	=>	"\
\
",
					'close'	=>	'',
					'sample'=>	'',
					'tip'	=>	wfMsg('hr_tip'),
					'key'	=>	'R'
				)

Et remplacez ce qui doit l'être pour faire ce que vous voulez :

array(	'image'	=>'button_java.png',
					'open'	=>	"<code type="java">\
",
					'close'	=>	'</code>',
					'sample'=>	'',
					'tip'	=>	wfMsg('codeJava_tip'),
					'key'	=>	'J'
				)

Ceci fait, il vous reste deux choses à faire :

  • Ajoutez l'image que vous voulez pour faire votre bouton pour le code Java
  • Ouvrez le fichier de localisation correspondant à la configuration de votre wiki et ajoutez, derrière celle du hr_tip par exemple, la ligne :
'codeJava_tip'        => 'Code Java',

Ça y est, ça marche. Vous avez la coloration syntaxique et un joli bouton pour gagner du temps :-). Je vous laisse en exercice la manipulation précédente pour insérer un bouton pour du code XML :-).

Cacher des pages

Revenons à mon entreprise. Il a été question récemment de savoir où nous allions bien pouvoir stocker les mots de passe utilisables par notre équipe... Ça commence à faire du monde... Les utilisateurs/mots de passes de :

  • connexion ssh au serveur web du wiki ;
  • connexion phpmyadmin à la base mysql du wiki ;
  • connexion ssh au serveur cvs (oui, je sais, bientôt passer à svn, c'est dans le tube :p)
  • etc.

Étant donné que le wiki est devenu l'emplacement le plus logique pour nos documentations communes, j'ai naturellement pensé à utiliser des pages invisibles à d'autres que nous. Étant donné que nos comptes sont tous sysop, cela doit bien être possible sans trop d'efforts.

Après quelques investigations, il s'est avéré que c'était un peu moins simple que prévu, étant donné que ce n'est pas un comportement standard et intégré à mediawiki. En effet, il existe la possibilité de définir des pages en lecture seule, mais pas d'empêcher carrément la lecture. La raison énoncée est que ce n'est pas la volonté d'un wiki de permettre le masquage d'une partie de l'information. Toutefois, cette fonction est maintenue sous forme de patch pour certains contextes, tels qu'une entreprise, où ce type de page peut s'avérer très utile.

J'ai donc testé le patch en question sur mediawiki 1.5.5. J'ai utilisé le principe de namespace secret. Celui-ci fonctionne parfaitement. Si vous souhaitez mettre en place cette fonctionnalité sur votre mediawiki, voici une version raccourcie et simplifiée de la configuration à ajouter en fin de votre LocalSettings.php :

$wgExtraNamespaces = array(100 => "Secret", 101 => "Discussion_secrète");
$wgRestrictedNamespaces = array(100 => "namespacePriv", 101 => "namespacePriv");
$wgGroupPermissions['sysop']['namespacePriv']      = true;

Ces trois lignes définissent un namespace Secret (exemple : la page bonjour du namespace Secret s'appelle Secret:Bonjour) et sa page de discussion associée "Discussion secrète" (attention à enregistrer votre fichier en utf8 si vous voulez voir l'accent de secrète s'afficher correctement). Tous les utilisateurs appartenant au groupe standard sysop pourront voir et éditer ces pages.

Notons que le patch est bien fait : si vous n'êtes pas de ce groupe, vous ne verrez pas apparaître les modifications sur ces pages dans vos "Modifications récentes". Ça peut paraître évident, mais heureusement toutefois que c'est fait afin d'éviter que des utilisateurs puissent avoir envie de hacker la lecture de ces pages en les voyant sans cesse dans les pages modifiées. Bien sûr, si un utilisateur qui n'est pas du bon groupe tape directement le nom d'une page cachée, il ne pourra tout de même pas la voir, donc tout va bien :).


Voilà, ce sera tout pour aujourd'hui. Je vous laisse passer une agréable fin de week-end et à bientôt.

samedi 26 novembre 2005

Surcharge ou redéfinition ?

Comme si un billet en appelait un autre :-).

Je suis en train d'effectuer la relecture de la traduction d'une documentation d'un framework Java très connu et je tombe sur une phrase qui me choque :

La classe identifiante doit surcharger equals() et hashCode() et implémenter Serializable

Je me dis que l'erreur vient peut-être de la version anglaise. Je vérifie immédiatement que non :

The identifier class must override equals() and hashCode() and implement Serializable.

Les équivalents anglais de surcharge et redéfinition sont overloading et overriding. C'est pourtant simple à se souvenir : overload contient le mot load, dont tout le monde sait, même les plus anglophobes, qu'il veut dire charge (ou charger, selon le contexte, bref).

Donc :

  • Overload => Surcharge
  • Override => redéfinition

Rappel

Surcharge (overload)

Il y a surcharge lorsqu'on utilise le même nom qu'une autre méthode, mais en changeant la signature des paramètres.

Exemple :

class MaClasse
{
   public void maMethode(int i) { ... }
   public void maMethode(float f) { ... }
}

Ici, la méthode maMethode est surchargée.

Redéfinition

On touche ici à ce qu'on appelle le polymorphisme. Un des apports de la POO qui permet d'appeler une méthode spécialisée dans une hiérarchie de classes. La redéfinition d'une méthode est étroitement liée à la notion de dérivation (ou d'héritage) et consiste à définir dans une sous-classe une méthode déjà définie dans une classe parente. La méthode en question doit posséder exactement la même signature, sinon ce n'est justement plus de la redéfinition, mais de la surcharge !

Exemple :

public class A
{
   public void direDesBetises()
   {
      System.out.println("Hey, ça foire fouille ?");
   }
}

public class B extends A
{
   public void direDesBetises()
   {
      System.out.println("Hey, vNr ?");
   }

   public void direDesBetises(String msg)
   {
      System.out.println("Alors, comme ça tu veux dire "+msg+" ?");
   }
}

Avec le code ci-dessus, le code main suivant affiche quoi d'après vous ?

public class Test
{
	public static void main(String args[])
	{
		A a = new A();
		B b = new B();		
		A ab = new B();
		
		a.direDesBetises();
		b.direDesBetises();
		ab.direDesBetises();
	}
}

Ici B.direDesBetises() est une redéfinition (override) de A.direDesBetises(). En plus, j'ai surchargé (overloaded) dans la classe B la méthode en question avec la méthode B.direDesBetises(String).

Résultat
baptiste@pumte:~/Temst$ java Test
Hey, ça foire fouille ?
Hey, vNr ?
Hey, vNr ?

Mon propos ici étant simplement de récapituler brièvement ce que sont surcharge et redéfinition en plus de rappeler la correspondance des termes français et anglais, je n'entrerai pas plus dans les détails. Si vous souhaitez en savoir plus, Vincent a par contre déjà écrit un article très complet sur le polymorphisme.

- page 1 de 2