Cela fait maintenant 8 ans que j'ai découvert le language de programmation Java. Ce qui m'a d'abord séduit : la programmation très simple d'une interface graphique, comparée à la programmation d'une interface graphique en C ou en C++, que ce soit avec la l'API Win32 sous Windows, ou les différentes API disponibles sous Unix et Linux.

La société Oracle, ces dernières années, acquis d'autres sociétés qui avaient la maîtrise de logiciels majeur. Ceux qui m'intéressent sont en l'occurrence MySQL (base de données), OpenOffice.org (bureautique) et Java (programmation).

MySQL et OpenOffice.org sont des logiciels libres, et ont finalement été «forkés», donnant MariaDB et LibreOffice afin d'offrir une alternative réellement libre –sous-entendu : débarassé de l'emprise d'Oracle–.

En revanche, la situation de Java est plus problématique : bien que Sun a fini par «libérer» sa machine virtuelle via le projet OpenJDK, cela n'a pas empêché la récente attaque juridique d'Oracle contre Google sur la violation de brevets logiciel pour avoir utilisé une autre implémentation du language, le projet Harmony, qui se veut une implémentation réalisé par «rétro-ingénierie propre» (clean room reverse engineering).

Cette affaire commence à être suivi par le site Groklaw, et ça ne va pas être joli joli. Étant fan de ce language, qu'est-ce que cela entraîne pour moi ?

À priori, pas grand-chose dans l'immédiat : openjdk est diffusé selon les termes de la licence GPL, et IBM s'est rallié à Oracle, abandonnant le projet Harmony à son sort. Donc, pour le travail, Java a encore de belles années devant lui. Du côté de mes projets personnels, certains de ces projets ont pour objet le jdk en lui-même, car ce sont des librairies de fonctions pour utiliser plus facilement ou mettant à profit les spécificités du JDK. Enfin, après toutes ces années de pratique, il serait dommage de jeter au orties cette aisance que j'ai acquise.

Cependant, j'ai des projets qui ne nécessitent pas d'être développé en Java, même si j'aurais préféré le faire pour capitaliser mon expérience. Les péripéties récentes rappellent une chose, le piège Java n'a finalement pas été neutralisé définitivement –et peut-être ne le sera-t-il jamais–. Il me faudra donc une alternative, et ça fait un moment que je lorgne sur la combinaison Python et GTK –via PyGTK– : en effet, ces deux éléments sont libres, et ne dépendent pas du bon vouloir d'une société (ce qui disqualifie Qt et PyQt/PySide par exemple, bien qu'actuellement l'attitude de Nokia soit à mon avis plus engageante que celle d'Oracle ; peut-être que Nokia a simplement compris qu'on n'attrape pas les mouches avec du vinaigre ? ).

Habituellement, les accesseurs en écriture aux propriétés d'un JavaBean sont implémentés à l'aide d'une simple affectation. Lorsque le propriété est un Objet, on a donc à envisager le cas d'une valeur nulle. Cela peut avoir son utilité, mais quand la propriété est une collection de sous-objet, la valeur nulle est généralement plus une gêne qu'un choix de conception : avant tout traitement sur les élément de la liste ou ses propriété, il faut tester qu'elle n'est pas nulle, alors que d'un point de vue fonctionnel cette nullité n'a aucun sens : ce qui a du sens, c'est le contenu de la liste -ou le fait que la liste soit vide-.

Dans ce cas, il faut que les accesseurs de la propriété soit implémenté de telle sorte que la non nullité soit garantie.

Deux approches sont possible : soit la classe est responsable de la gestion de la collection, et c'est l'accesseur en écriture qui devra remplacer le contenu de la collection interne par celui de la collection fournie.

Soit on veut une simple garantie de non nullité, car ce sont les traitements qui veulent changer la collection qui ont la responsabilité du choix de l'implémentation et de la construction de la nouvelle collection.

Licence d'utilisation

Ce code est diffusé selon les termes de la licence GNU GPL dans sa version 3

Garantir la non nullité

import java.util.HashSet;
import java.util.Set;

/**Démonstration d'une propriété de type Collection.
 * Ici, la classe s'assure simplement que la propriété retournée est non nulle.
 *
 * (c)David SPORN
 * Ce code est diffusé selon les terme de la licence GPL version 3.
 */
public class DemoClass
{
	private Set myDatas = new HashSet() ;
		
	public Set getDatas()
	{
		if (null == myDatas)
		{
			myDatas = new HashSet() ;
		}
		return myDatas;
	}

	public void setDatas(Set datas)
	{
		myDatas = datas ;
	}
}

Gérer la collection

import java.util.HashSet;
import java.util.Set;

/**Démonstration d'une propriété de type Collection.
 * Ici, la classe est en charge de la gestion de la collection interne.
 *
 * (c)David SPORN
 * Ce code est diffusé selon les terme de la licence GPL version 3.
 */
public class DemoClass
{
	private Set myDatas = new HashSet() ;
		
	public Set getDatas()
	{
		return myDatas;
	}

	public void setDatas(Set datas)
	{
		if (null == datas)
		{
			myDatas.clear() ;
		}
		else
		{
			if (datas != myDatas )
			{
				myDatas.clear() ;
				myDatas.addAll(datas) ;
			}
		}
	}
}

L'idée de ce code est est qu'on récupère le nom du champ d'une manière ou d'une autre, via un paramétrage.

Ce code est diffusé selon les termes de la licence GNU GPL dans sa version 3

/**Retourne la valeur objet d'un champ de classe.
 *
 * (c)David SPORN
 * Ce code est diffusé selon les terme de la licence GPL version 3.
 *
 * @param classe la classe contenant le champ
 * @param fieldName le nom du champ
 * @param defaultValue la valeur par défaut en cas de problème (inexistant, permission d'accès, ...)
 * @return un objet
 */
private Object getStaticFieldFromClass (Class classe, String fieldName, Object defaultValue)
{
	try {
		Field field = classe.getDeclaredField(fieldName) ;
		return field.get(null) ;
	} catch (Exception e) {
		return defaultValue ;
	} 
}

Ce code est diffusé selon les termes de la licence GNU GPL dans sa version 3

/**Duplication d'une collection.
 * Plus précisément, c'est l'état de la collection fournie qui est dupliqué.
 * Les éléments référencée par la collection ne sont pas dupliqués.
 *
 * (c)David SPORN
 * Ce code est diffusé selon les terme de la licence GPL version 3
 * @param collection la collection à dupliquer
 * @return une collection de même type, référençant les mêmes élements
 * @throws CloneNotSupportedException en cas de problème.
 */
public Collection clone(Collection collection) throws CloneNotSupportedException
{
	try {
		Collection collection_result = null ;
		Class collection_class = collection.getClass() ;
		Constructor collection_constructor = collection_class.getConstructor(null) ;
		collection_result = (Collection) collection_constructor.newInstance(null) ;
		collection_result.addAll(collection) ;
		return collection_result ;
	} catch (Exception e) {
		CloneNotSupportedException exception_to_throw = new CloneNotSupportedException("An exception was raised :"+e.getClass().getName()+":"+e.getLocalizedMessage()) ;
		exception_to_throw.initCause(e) ;
		throw exception_to_throw ;
	} 
}

Résumé

Checkstyle est un outil d'analyse de source configurable. En l'intégrant à ses projets, cela facilite l'uniformisation des conventions de programmation. Cet article présente un manière d'intégrer cet outil

Présentation de l'outil

Checkstyle est un outil d'analyse de code source Java configurable. Son but est de vérifier qu'un code source respecte les conventions de programmation choisies pour le projet. Cela contribue à une meilleure lisibilité du code, ce qui peut permettre de faciliter la compréhension du code et sa maintenance.

Intégration de l'outil

Checkstyle peut être piloté par ant. On va donc utiliser cette méthode de préférence à des plug-in pour les environnement intégrés, afin de s'affranchir de l'environnement de développement. Afin de pouvoir réutiliser l'intégration dans tous les projets à vérifier, on va utiliser les mécanismes d'importations de ant. L'intégration de checkstyle se décomposera donc en les étapes suivantes

  1. Préparation d'un dépôt pour la définition de nouvelles tâches ant
  2. Définition de la tâche checkstyle, initialisation de valeurs par défaut, et fichiers par défaut (règles et feuille de style XSL)
  3. Intégration de la tâche checkstyle à un projet ant existant
  4. Appel de la tâche checkstyle

Préparation d'un dépôt pour la définition de nouvelles tâches ant

Créer un répertoire __Tasks situé au même endroit que le répertoire de votre projet. Créer le sous-répertoire checkstyle. Copier dans ce dernier répertoire le ficher checkstyle-all-<version>.jar.

Définition de la tâche checkstyle, initialisation de valeurs par défaut, et fichiers par défaut (règles et feuille de style XSL)

La dernière version de Ant permettent l'importation de scripts. On prépare donc un tel script, sauvegardé dans __Tasks sous le nom import_checkstyle.xml. Ici on utilise la version 4.10 beta de checkstyle.

<?xml version="1.0"?>
<project name="checkstyle" basedir="." default="default">
	<dirname property="checkstyle.basedir" file="${ant.file.checkstyle}"/>
	<taskdef resource="checkstyletask.properties" classpath="${checkstyle.basedir}/checkstyle/checkstyle-all-4.0-beta1.jar"/>
	<property name="default.checkstyle.conf" value="${checkstyle.basedir}/checkstyle/sporniket_style.xml"/>
	<property name="default.checkstyle.xsl" value="${checkstyle.basedir}/checkstyle/checkstyle_simple_report.xsl"/>
	
	<target name="default"/>
</project>

La variable default.checkstyle.conf référence un fichier de règles que checkstyle va vérifier. Checkstyle est livré avec un fichier de règles permettant de suivre les règles édictées par Sun, l'éditeur du language Java. On peut donc utiliser ce fichier comme base de travail.

La variable default.checkstyle.xsl référence un fichier de transformation xslt pour transformer un rapport XML de checkstyle en autre chose, généralement une page web, grace à la tache ant style. Voici un exemple très simple de transformation, qui affiche pour chaque fichier les erreurs détectées.

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="checkstyle">
	  <html>
	  <head>
	  	<title>Rapport d'erreur de checkstyle</title>
	  </head>
	  <body>
	  	<h1>Rapport d'erreur de checkstyle</h1>
	  	<p>Nombre d'erreurs : <xsl:value-of select="count(file/error)"/></p>
	  	<p>Proportion de fichiers avec erreurs : <xsl:value-of select="count(file[count(error)>0])"/>/<xsl:value-of select="count(file)"/></p>
	  	<dl>
	  		<xsl:apply-templates/>
	  	</dl>
	  </body>
	  </html>
	</xsl:template>

	<xsl:template match="file">
	</xsl:template>

	<xsl:template match="file[count(error)>0]">
		<dt><a>
			<xsl:attribute name="href"><xsl:value-of select="@name"/></xsl:attribute>
			<xsl:value-of select="@name"/>
		</a></dt>
		<xsl:apply-templates/>
	</xsl:template>

	<xsl:template match="error">
		<dd><em><xsl:value-of select="@severity"/> Ligne <xsl:value-of select="@line"/></em> : <xsl:value-of select="@message"/></dd>
		<xsl:apply-templates/>
	</xsl:template>
</xsl:stylesheet>

Intégration de la tâche checkstyle à un projet ant existant

Rajouter simplement la directive suivante dans votre script ant :

<import file="../__Tasks/import_checkstyle.xml"/>

Appel de la tâche checkstyle

Voici un exemple d'utilisation de la tache checkstyle, avec la génération d'un rapport xml qui est ensuite transformé en rapport html.

	<property name="build.src" value="src"/>
	<property name="build.compile" value="bin"/>
	<property name="build.dest" value="build"/>
	<property name="build.report" value="reports"/>
	<property name="build.tmp" value="tmp"/>
	<property name="javadoc.dest" value="doc"/>
	<property name="project" value="sporniket_core_base"/>



	<!-- 
	=========================================================== 
	Audit the code
	-->
	<target name="audit">
		<checkstyle config="${default.checkstyle.conf}" failureProperty="checkstyle.failure" failOnViolation="false">
			<fileset dir="${build.src}" includes="**/*.java"/>
		    <!-- Location of cache-file. Something that is project specific -->
			<property key="checkstyle.cache.file" file="${build.tmp}/cachefile"/>
			<formatter type="xml" toFile="${build.report}/${project}.xml"/>
		</checkstyle>
		<style in="${build.report}/${project}.xml" out="${build.report}/${project}.html" style="${default.checkstyle.xsl}"/>
	</target>

Conventions de nommage

Le 22 Avril 2005 à 20:21

Introduction

Pour maintenir et faire évoluer un logiciel, un programmeur doit en comprendre tous les rouages. Cette compréhension nécessite plusieurs éléments : des connaissances générales sur les tâches accomplies par le logiciel et la motivation pour accomplir ces tâches d'une certaines façon. Certains de ces éléments peuvent être explicités dans un cahier des charges et des spécifications, mais parfois le programmeur peut se retrouver avec le code source du programme pour unique référence. Si le code source est bien écrit, le programmeur pourra facilement l'étudier, ce qui aidera à la compréhension.

Le language Java a implémenté dès sa conception un outil permettant de générer une documentation à partir des fichiers source, pour peu que ces fichiers comportent des commentaires spéciaux : javadoc. Cet outil incite naturellement à expliquer le code source au plus près. De plus le Java a aussi fortement poussé les développeur à regrouper le code dans des paquetages et à isoler les modélisation en utilisant des concepts simple : un fichier pour une classe, un dossier pour un paquetage.

En revanche, pour le nommage des différentes entités (paquetages, classes, fonctions, variables), il n'y aucun mécanisme de contrôle fourni, juste des conventions basés sur l'expérience et les usages dans d'autres langages de programmation. Les conventions de nommages permettent de faciliter la lecture en permettant d'identifier la nature des entités nommées.

Les conventions existantes

Les conventions sur lesquelles je me base sont celles de Sun, le créateur du language Java [1], et celles d'Ambisoft [2]. Cependant, mes goût personnels et ma propre réflexion m'on conduit à préciser certains usage, ou bien d'utiliser ma propre convention [3].

Les bases

Le principe général est d'utiliser des descriptions en anglais en utilisant des mots complets, sauf pour des abréviations largement reconnu (HTML, par exemple). On utilisera les minuscules et on mettra la première lettre de chaque mot ou abréviation en majuscules. L'utilisation des majuscules et du caractère « souligné » (« _ ») sera explicitement précisé si besoin. Enfin on autorise l'utilisation des chiffres.

On distingue les classes de noms suivantes :

  • Nom de variable
  • Nom de type (paquetage, classe)
  • Nom de fonction (méthode, constructeur, destructeur)

Enfin on rappellera la convention concernant le nommage et l'organisation des fichiers sources : un paquetage correspond à un dossier ayant le même nom, l'imbrication des paquetages se traduit par l'imbrication des dossiers, et le code source d'une classe est placé dans un fichier portant le même nom que la classe.

Nom de variable

Un nom de variable consiste en un groupe nominal décrivant la nature de la variable (adresse, compteur, compte client, etc...) et sa fonction (point de départ, compte client à créditer, message d'erreur, etc...).

On utilisera le singulier pour les objets à l'unité, et le pluriel accompagné du terme« collection » pour les collections d'objets.

L'objectif des différentes normes sur les noms de variables est de pouvoir distinguer immédiatement la portée d'une variable, et d'éviter les collisions de nom entre deux variables de portée différentes.

On distingue les portées suivantes avec entre parenthèses des cas particuliers :

  • Variable locale (exception, variables de boucle)
  • Paramètre d'une fonction
  • Variable d'instance
  • Variable de classe modifiable
  • Variable de classe constant

Variable locale

Convention

On utilisera une description complète en anglais, tout en minuscules, les mots séparés par un caractère « souligné ». Pour les descriptions tenant sur un seul mot, on rajoutera « a » ou « an » (« un » en anglais) au début de la description.

Pour les boucles for, on tolèrera les noms de variable classique : i, j, etc...

Pour le traitement des exceptions, on tolèrera les noms de variable en e* : e, e1, exc, etc...

Exemples
an_address
customer_account
customer_account_to_credit
starting_point
error_message
html_document
customers_collection
for (i=...; ... ; ...)
{
    ...
}
try
{
    ...
}
catch (IOException e)
{
    ...
    try
    {
        ...
    }
    catch (IOException ee)
    {
        ...
    }
}

Paramètre d'une fonction

Convention

On utilisera une description complète en anglais, la premières lettre des mots sera en majuscules, à l'exception du premier mot qui sera en minuscules.

Exemples
address
customerAccount
customerAccountToCredit
startingPoint
errorMessage
htmlDocument

Variable d'instance

Convention

On suivra la même convention que pour les paramètres, mes on préfixera le nom de variable avec « my ».

Exemples
myAddress
myCustomerAccount
myCustomerAccountToCredit
myStartingPoint
myErrorMessage
myHtmlDocument

Variable de classe modifiable

Convention

On suivra la même convention que pour les paramètres, mes on préfixera le nom de variable avec « the ».

Exemples
theAddress
theCustomerAccount
theCustomerAccountToCredit
theStartingPoint
theErrorMessage
theHtmlDocument

Variable de classe constante

Convention

On utilisera une description complète en anglais, tout en majuscule, les mots séparés par un caractère « souligné ».

Exemples
ADDRESS
CUSTOMER_ACCOUNT
CUSTOMER_ACCOUNT_TO_CREDIT
STARTING_POINT
ERROR_MESSAGE
HTML_DOCUMENT

Nom de type

Le nommage des types regroupe deux classes :

  • Les paquetages (package), qui servent à regrouper logiquement ou par fonctionnalité les types.
  • Les classes (class, interface), qui modélisent un concept.

Les paquetages

Convention

On utilisera une description complète en anglais, tout en minuscules, les mots séparés par un point. Si on dispose d'un nom de domaine, le premier mot sera le domaine racine (TLD, Top Level Domaine) et le deuxième de nom de domaine.

Exemples
application.ide.widget
com.sporniket.component.io

Les classes

Convention

On utilisera une description complète en anglais, en minuscules avec la première lettre des mots en majuscules.

Plus la description sera longue, plus la classe sera spécifique. Inversement, plus la description sera courte, plus la classe sera générique

Exemples
Display
ColorDisplay
LiquidCrystalDisplay

Cas des classes abstraites

Convention

Une classe abstraite suivra la convention de nommage des classes, mais on préfixera le nom avec le terme « abstract ».

Exemples
AbstractDisplay
AbstractWidget

Cas des interfaces

Convention

Une interface suivra la convention de nommage des classes, et on distingue deux possibilités :

  • L'interface peut être décrite par un terme dénotant une capacité (mot en « -able »), auquel cas on ne rajoute rien.
  • Les autres cas, où on suffixera le nom avec le terme « interface ».
Exemples
Displayable
WidgetInterface

Nom de fonction

Un nom de fonction consiste en verbe et ses complément (« fait ceci et cela »).

On distinguera les cas suivant :

  • Cas général
  • Constructeurs
  • Destructeur
  • Accès en lecture (JavaBean)
  • Accès en écriture (JavaBean)

Cas général

Convention

Le nom de la fonction utilisera une description complète en anglais, commençant par un verbe en minuscules et éventuellement suivi de mots en minuscules avec la première lettre en majuscule.

Exemples
doThisAndThat(...)
execute(...)
addAccount(...)
openFile(...)

Constructeurs

Convention

Les spécifications du langage Java imposent aux constructeur d'avoir le même nom que la classe.

Exemples
Public class RequestBuilder
{
    public RequestBuilder()
    {
        //initialisation
    }

    public RequestBuilder(Connection connection, String request)
    {
        //initialisation
    }

    //etc...
}

Destructeur

Convention

Les spécifications du langage Java imposent au destructeur (si cela est nécessaire) le nom « finalize() ».

Accès en lecture

Convention

La convention JavaBean pour accéder à une propriété en lecture est d'utiliser le verbe « get » suivi du nom de la propriété, à l'exception des propriétés booléennes qui peuvent utiliser le verbe "is". Il y a découplage entre l'accès en lecture d'une propriété dans le cadre d'un JavaBean et son implémentation : variable d'instance ou résultat d'un traitement à effectuer.

Exemples
Public class RequestBuilder
{
    public String getRequest()
    {
        //retourne la requête
    }

    public String getResultSet()
    {
        //execute la requête et retourne le résultat
    }

    //etc...
}

Accès en écriture

Convention

La convention JavaBean pour accéder à une propriété en lecture est d'utiliser le verbe « set » suivi du nom de la propriété. Il y a découplage entre l'accès en écriture d'une propriété dans le cadre d'un JavaBean et son implémentation : affectation directe à une variable d'instance ou encore vérification et notification préalable.

Exemples
Public class RequestBuilder
{
    public void setRequest(String request)
    {
        //met à jour la requête interne
    }

    public void setNameParameter(Object value)
    {
        //effectue des tests de conformité avant l'affectation effective
    }

    //etc...
}