Sébastian Le Merdy

Aller au contenu | Aller au menu

04 November 2012

How to master hibernatemode on your mac

Do you know that you can turn your mac into hibernation mode whenever you want ? Not after it runs out of battery power. With this blog entry I will describe how do I used to do this until Snow Leopard and how do I do it now with Mountain Lion.

Lire la suite

09 October 2012

I finally found a better GitX which is GitX

Last day I was unattentively listen last podcast from javaposse and they were talking about a great peace of software called SourceTree from Atlassian. It gave me the idea about finding a better git browser for my mac.

Since first time I was using git I installed and started to like GitX much. I was really happy with this simple yet powerful tool. But I was really confused about some screens on mac’s friends of mine that show me a different GitX. I wasn’t ever understand why my last updated GitX was really something else compared by their last updated GitX. Then time has passed.

So javaposse’s guy wake up my motivation about maybe replace GitX with a better tool. Unfortunately after a dowload and install of source tree then extreme simplicity of GitX just missed me very much.

I decided to dig deeply into GitX versions and realize very soon that there are many forks onto github. But how to choose the better/maintained/stable fork ? gitx.org help me really efficiently in this task : I choose GitX (L). At last UI reminds me what I have seen onto my friends screens.

Now I can code.

16 September 2012

According to Eric Evans we do it wrong and he's right

I just started to read Domain-Driven Design by Eric Evans and some part of this book exactly describes what we do wrong at work :

Other projects use an iterative process, but they fail to build up knowledge because they don’t abstract. Developers get the experts to describe a desired feature and then the go build it. They show the experts the result and ask what to do next. If the programmers practice refactoring, they can keep the software clean enough to continue extending it, but if programmers are not interested in the domain, they learn only what the application should do, not the principles behind it. Useful software can be built that way, but the project will never arrive at a point where powerful new features unfold as corollaries to older features.

To avoid what it is described it seems that we need to think about building a strong model with behaviors :

Developing a knowledge-rich model. The objects had behavior and enforced rules. The model wasn’t just a data schema; it was integral to solving a complex problem. It captured knowledge of various kinds.

It will be long and painful to catch up but I think that it worth it.

10 September 2012

How to install git subtree with a former home brew installation

Since 1.7.11 git subtree has been released as an official contrib. Here are commands that I had to type in into my Terminal in order to makes this script available:

$ cd /usr/local/share/git-core/contrib/subtree
$ make
$ sudo make prefix=/usr/local/Cellar/git/1.7.12 install

I think that theses commands may be more accurate (prefix is dependent to current version of git that is currently installed) but does the job. Hopes this help.

07 September 2012

Can regexp loop?

I have a very simple use case and I’ve come to conclusion that it is not possible to use regular expression to achieve it.

I want to parse this string and get all numbers in it :

(string) "what is the largest number : 384, 90, 6, 15"

That is the best I can with regex:

(regexp) "what is the largest number : ((?:\d+, )+ \d+)"
(group1) "384, 90, 6, 15"

Then result have to be splitted manually. This it clearly not the best pattern matching I have ever seen.

The thing is if I reverse the first group by using a capturing group into a non-capturing group then this is the last captured group that I just can get it back:

(regexp) "what is the largest number : (?:(\d+), )+ \d"
(group 1) "6"
(group 2) "15"

Anyway.

08 March 2012

Le singleton statique

J’aime bien les singletons et je n’ai jamais vraiment pris le temps de comprendre pourquoi tant de gens déconseillent ce design pattern (d’ailleurs le singleton est-il un design pattern ?). J’ai eu un exemple très récemment des dangers que cela peut provoquer.

Le projet sur lequel je travaille a une base de code plutôt impressionnante et plutôt pas trop mal testée (de l’ordre de 13000 classes et 56% de couverture de code). Tous ces tests exécutés chaque jour n’auront finalement permi de détecter ce problème de singleton qu’aujourd’hui. Je résume la situation : une classe héritant de ArrayList<MaClasse> a été écrite il y a plusieurs années afin d’ajouter des comportements à ce type de listes bien particulières. Jusque là : pourquoi pas. Problème : cette classe déclare une variable de classe (donc static) nommée EMPTY. Elle permettait à l’origine d’économiser les instanciations pour l’utilisation de la liste vide à l’extérieur. Cela ne choque pas plus de ça - d’autant plus que des classes du JDK) offrent ce genre de fonctionnalités.

Une chose primordiale à noter dans la documentation est la notion d’immuabilité. La liste EMPTY découverte dans le code n’est pas immuable et n’interdit donc pas ce type d’instructions :

MaListe.EMPTY.add(new MonObjet());

Inévitablement, ce code a fini par être écrit (en fait, l’ajout dans la liste vide était fait beaucoup plus loin que son initialisation). Et les effets de bord néfastes se sont donc produits. Pour corriger ce problème, il est nécessaire de créer une implémentation spécifique de MaListe<MaClasse> qui soit immuable.

Moralité : de grands pouvoirs impliquent de grandes responsabilités. Il faut toujours bien savoir ce que l’ont fait quand on parle de singleton, surtout dans un environnement concurrent.

Un article qui couvre une autre dimension du singleton : le lazy singleton.

12 October 2011

Un résumé super concis de la soirée "Le métier de développeur"

Pour les pressés, un résumé super concis de la soirée qui s’est tenue le mardi 11 octobre organisée par le Paris JUG.

  • Didier Girard : C’est Internet qui permet au développeur de se surpasser.
  • Vincent Massol : Faites quelque chose qui vous passionne, et faites le bien.
  • Jean Laurent de Morlhon : Apprendre à s’améliorer chaque jour grâce au Software Craftmanship.
  • Régis Médina : Permettre aux organisations d’aboutir aux meilleurs produits : ceux qui décuplent la puissance des utilisateurs.
  • Gregory Weinbach : À la fin de chaque journée, il faut avoir appris, transmis ou réalisé quelque chose dont on est fier.

06 February 2011

Comment tester en Java un client HTTP sans se connecter ?

C’est cet article datant d’octobre 2000 (!) qui m’a montré la voie. Il explique en détail le mécanisme utilisé par la JVM pour invoquer le bon composant permettant d’interpréter correctement un protocole décrit dans une java.net.URL.

Pour montrer l’utilité de ce mécanisme dans un cas de programmation orientée par les tests, je vais prendre l’exemple de l’écriture d’une classe permettant de géocoder une adresse à travers l’API Google Maps. Cette API est accessible à travers le protocole HTTP. Pour ne pas solliciter constamment le service web nous allons donc mocker l’appel en se basant sur la documentation du service.

Remplacer le composant HTTP standard

Lors de l’appel à java.net.URL.openStream() la JVM interprète le protocole contenu dans l’URL. Si c’est "http" la JVM instanciera par défaut une sun.net.www.protocol.http.HttpURLConnection qui gèrera le dialogue avec le serveur. En définissant la propriété java.protocol.handler.pkgs avec un nom de package, la JVM introspectera ce package suivi du nom du protocole et de la classe nommée Handler et devant étendre java.net.URLStreamHandler.

Cela se traduit par le code suivant dans notre test unitaire :

@BeforeClass
public static void setupMockHTTP() {
    System.setProperty("java.protocol.handler.pkgs", "name.lemerdy.sebastian.mock");
}

Et par les deux classes suivantes dans le package name.lemerdy.sebastian.mock.http :

package name.lemerdy.sebastian.mock.http;

import java.io.IOException;
import java.net.URL;
import java.net.URLStreamHandler;

public class Handler extends URLStreamHandler {

    @Override
    protected URLConnection openConnection(URL url) throws IOException {
        return new URLConnection(url);
    }

}
package name.lemerdy.sebastian.mock.http;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class URLConnection extends java.net.URLConnection {

    protected URLConnection(URL url) {
        super(url);
    }

    @Override
    public void connect() throws IOException {
        connected = true;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        if (!connected) {
            connect();
        }
        return this.getClass().getResourceAsStream(url.getPath());
    }

}

Cette seconde classe ouvre un fichier dont le chemin dans le classpath est la même que celle de l’URL.

Mise en oeuvre dans l’exemple

Maintenant qu’on a remplacé l’envoi d’une requête HTTP par l’ouverture d’un fichier du classpath on peut donc tester notre service dont voici un extrait du code :

public static double[] geocode(String address) {
    double[] coordinates = null;
    if (address != null && !address.isEmpty()) {
        try {
            address = URLEncoder.encode(address, Charset.defaultCharset().name());
            final URL url = new URL("http://maps.googleapis.com/maps/api/geocode/xml?address=" + address + "&sensor=false");
            final XPath xPath = XPathFactory.newInstance().newXPath();
            XPathExpression xPathExpression = xPath.compile("/GeocodeResponse/result/geometry/location/lat|/GeocodeResponse/result/geometry/location/lng");
            final NodeList location = (NodeList) xPathExpression.evaluate(new InputSource(url.openStream()), XPathConstants.NODESET);
            if (location != null) {
                coordinates = new double[2];
                for (int i = 0; i < 2; i++) {
                    if (location.item(i) != null) {
                        coordinates[i] = Double.parseDouble(location.item(i).getTextContent());
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
        } catch (MalformedURLException e) {
        } catch (IOException e) {
        } catch (XPathExpressionException e) {
        }
    }
    return coordinates;
}

Enfin voici le test du code ci-dessus :

@Test
public void testGeocode() {
    assertThat(GeocodeService.geocode("1600 Amphitheatre Parkway, Mountain View, CA"), is(new double[] { 37.4217550d, -122.0846330d }));
}

Puisque le code à tester se connecte à l’URL http://maps.googleapis.com/maps/api/geocode/xml il suffit donc maintenant de créer un fichier nommé xml dans le package maps.api.geocode et dont le contenu sera envoyé lors de l’éxécution du test. Ce fichier XML pourra donc ressembler à ceci :

<GeocodeResponse>
    (...)
    <result>
    (...)
        <geometry>
            <location>
                <lat>37.4217550</lat>
                <lng>-122.0846330</lng>
            </location>
            (...)
        </geometry>
        (...)
    </result>
    (...)
</GeocodeResponse>

04 February 2011

Tester une expression régulière

J’ai découvert assez récemment une nouvelle méthode de JUnit 4 :

public static <T> void org.junit.Assert.assertThat(T actual, org.hamcrest.Matcher<T> matcher)

Elle permet d’écrire des tests assez lisibles du genre :

assertThat("toto", notNullValue());
assertThat("toto", is("toto"));

Le propos ici est d’utiliser un Matcher spécifique qui valide que la valeur correspond au Pattern.

Exemple :

assertThat("01 02 04 05 06", matchPattern(Pattern.compile("\\d{2}(?:\\s\\d{2}){4}"));

Voici donc le code de la classe de Matcher :

import java.util.regex.Pattern;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;

public final class MatchPattern extends BaseMatcher<String> {

    private final Pattern pattern;

    private MatchPattern(final Pattern pattern) {
        if (pattern == null) {
            throw new IllegalArgumentException("pattern cannot be null");
        }
        this.pattern = pattern;
    }

    @Override
    public boolean matches(Object arg0) {
        return pattern.matcher((CharSequence) arg0).matches();
    }

    @Override
    public void describeTo(Description arg0) {
        arg0.appendText("String that matches pattern \"").appendText(pattern.pattern()).appendText("\"");
    }

    @Factory
    public static Matcher matchPattern(final Pattern pattern) {
        return new MatchPattern(pattern);
    }

}

17 November 2010

Déterminer les dépendances d'un projet maven

Rappel de l’importance des dépendances dans un projet

Il fut un temps où gérer les dépendances d’un projet JEE consistait à copier tous les jars téléchargés au fil de l’eau depuis sourceforge vers le répertoire magique de son application web : le fameux WEB-INF/lib. Le temps où les ClassNotFoundException ou les NoSuchMethodError pouvaient arriver à tout moment (ou presque). Comment gérait-on les migrations des librairies tierces ? Comment déterminions-nous le degré de dépendance de notre code avec les frameworks ? Mon avis est qu’on ne s’en souciait peu – Java ayant une forte tradition de non-régression dans ses évolutions.

Tout le monde s’accorde pour dire que la puissance de la plateforme Java est justement la multitude de librairies tierces et la réactivité potentielle d’une communauté très présente. Mais ce foisement implique de fait une gestion plus rationnelle des liens de dépendances tissées entre ces différents produits. C’est entre autre ce que maven essaie de faire en mettant en place des conventions dans la construction et le packaging des librairies.

Nous allons donc voir comment maven peut nous aider à optimiser voire minimiser les dépendances afin de consommer moins d’espace mémoire dans la JVM pendant l’exécution d’un programme.

Les niveaux de dépendances définis par maven

Dans tout langage il existe plusieurs niveaux de dépendance. Nous allons voir les principaux niveaux que maven défini pour java :

  • Le premier niveau est le plus important : compile. Si une dépendance n’est pas satisfaite à ce niveau, le projet ne compile pas.
  • Le second niveau permet justement d’éviter les ClassNotFoundException et les NoSuchMethodError citées plus haut : runtime. Il indique que certaines librairies tout comme votre projet peut invoquer du code de manière dynamique (c’est la réflexion). C’est le cas de Spring-context par exemple qui essaie d’instancier des classes sur la base d’une chaîne de caractères qu’il aura parsé dans un fichier XML.
  • Les troisième et quatrième niveaux ont été introduits suite aux différentes absorbtions de librairies vraiment centrales dans le cœur de la plateforme ou bien dans le classpath global d’une JVM : system et provided. Ce niveau ne nous intéresse pas vraiment.
  • Enfin le dernier niveau permet de définir des dépendances qui ne sera jamais embarqués dans les applications de production mais sont nécessaires pour tester le logiciel : test. Le meilleur exemple de ce niveau est bien entendu la librairie JUnit.

Pour chaque librairie (appelons-les artifact), ces dépendances permettent de construire un graphe. Ce graphe permet de gérer de manière cohérente et reproductible les classpath lors de la compilation, les tests, la construction et l’exécution de l’artifact. L’ensemble des dépendances contenues dans le graphe sont déterminées en résolvant les dépendances de chacuns des artifacts récursivement en commençant par l’artifact initial et cela récursivement jusqu’à ce qu’il ne reste plus aucune dépendance transitive à résoudre.

exemple de graphe de dépendances

À la main

Commencer par supprimer toutes les dépendances (les mettre en commentaires dans le pom). Le scope le plus proche du code étant compile, la première étape est donc de pouvoir faire compiler le projet. Toute dépendance qui n’aide pas à la compilation doit avoir un scope différent de compile ou supprimée si elle ne sert à rien.

Une fois que le projet compile on doit pouvoir valider le scope runtime. Pour cela deux solutions plus ou moins élégantes :

  1. Soit votre projet dispose d’une bonne couverture de tests unitaires et l’exécution de ces tests vous permet de valider qu’un maximum d’instanciations dynamiques seront réussies.
  2. Soit votre projet ne dispose pas de tests unitaires auquel cas vous devrez exécuter le projet dans un environnement proche de la production. Vous noterez qu’en fonction de l’ordre avec lequel vous ajoutez au fur et à mesure vos dépendances il se peut que vous puissiez oublier des dépendances de niveau compile (utilisées directement dans votre code) mais résolues par une dépendance transitive. Or cela est problématique car rien ne vous garantie que la dépendance transitive ne soit pas un jour supprimée par la librairie qui la porte.

À l’aide d’outils

Maven dispose d’un outil très utile pour minimiser les dépendances inutiles mais aussi pour garantir une bonne qualité des dépendances de niveau compile : il s’agit de maven-dependency-plugin.

Placez-vous dans un projet mavenisé et exécutez le goal suivant :

mvn dependency:analyze

L’exécution doit vous indiquer :

  • Les dépendances de niveau compile qui ne sont pas déclarées dans votre projet
  • Les dépendances qui sont déclarées mais qui ne servent pas à la compilation

Ces informations vous seront très utiles pour optimiser les dépendances. Exemple pour un projet fwk :

[INFO] [dependency:analyze {execution: default-cli}]
[WARNING] Used undeclared dependencies found:
[WARNING]    org.springframework:spring-core:jar:2.0.8:compile
[WARNING]    org.hibernate:hibernate-core:jar:3.3.0.SP1:compile
[WARNING]    org.springframework:spring-dao:jar:2.0.8:compile
[WARNING]    aopalliance:aopalliance:jar:1.0:compile
[WARNING]    org.springframework:spring-beans:jar:2.0.8:compile
[WARNING]    org.apache.axis:axis-jaxrpc:jar:1.4:compile
[WARNING]    commons-logging:commons-logging:jar:1.0.4:compile
[WARNING]    org.hibernate:ejb3-persistence:jar:1.0.2.GA:compile
[WARNING] Unused declared dependencies found:
[WARNING]    javax.faces:jsf-impl:jar:1.2_04-p02:compile
[WARNING]    org.slf4j:slf4j-api:jar:1.5.8:compile
[WARNING]    org.springframework:spring-jpa:jar:2.0.8:compile
[WARNING]    net.sf.ehcache:ehcache:jar:1.5.0:compile
[WARNING]    org.hibernate:hibernate-validator:jar:3.1.0.GA:compile
[WARNING]    javax.servlet:jstl:jar:1.2:compile
[WARNING]    com.sun.facelets:jsf-facelets:jar:1.1.11:compile
[WARNING]    org.slf4j:slf4j-simple:jar:1.5.8:compile
[WARNING]    commons-collections:commons-collections:jar:3.2:compile
[WARNING]    org.slf4j:slf4j-log4j12:jar:1.4.2:test
[WARNING]    org.springframework.security:spring-security-acl:jar:2.0.4:compile
[WARNING]    javax.persistence:persistence-api:jar:1.0:provided
[WARNING]    org.hibernate:hibernate-entitymanager:jar:3.4.0.GA:compile

Dans ce rapport on voit que 8 dépendances sont utilisées par le code source sans être déclarées (le code compile car il se repose sur les dépendances transitives). 13 dépendences sont déclarées alors qu’elles ne sont pas utilisées par le code source. Il faut toutefois nuancer ce deuxième résultat : en effet seule le niveau compile est intéressant pour cette mesure. Le niveau provided ou test reste dans bien des cas utile à déclarer.

10 August 2010

Restaurer son iPhone 3G en iOS 3.1.3

Je viens de suivre cette procédure. Ça a fonctionné parfaitement pour moi.

capture décran de iTunes montrant le numéro de version de liPhone

J’apporte quelques précisions par rapport à l’article :

  1. J’ai téléchargé iOS 3.1.3 grâce à cette page. Le md5 du fichier est 82e93e4e51b0e4503a8165507b8a3df2.
  2. Une fois dézippé, RecBoot propose deux binaires. Il faut évidemment exécuter RecBoot exit only.
  3. Ajouter les droits d’écriture de mon compte sur /etc/hosts n’a pas suffit à pouvoir l’enregistrer après modifications avec TextEdit. J’ai dû faire un $ sudo vi /etc/hosts...
  4. Une fois l’iPhone débloqué j’ai supprimé cette entrée dans mon fichier hosts sans que cela ne pose problème.  J’espère que ces indications supplémentaires vous permettrons enfin de récupérer votre iPhone 3G dans le meilleur de sa forme. J’attends maintenant une mise à jour de iOS 4 en tenant compte du processeur et de la RAM vieillissants de mon iPhone 3G.

Cette expérience de mise à jour m’aura au moins appris à ne pas faire une confiance aveugle dans les logiciels d’Apple - confiance qu’ils avaient pourtant su gagner assez facilement lors de mon passage sur Mac OS X.