Idiomes pour Android

Auteur

Feanorin

L'objectif du document est de centraliser les bonnes pratiques de développement sur Android pour :

- harmoniser les codes sources et projets ;

- suivre les grandes tendances des développeurs ;

- optimiser les temps de développements mais surtout les temps de corrections d'anomalies / reprises de code ;

- éviter des erreurs grossières sources de bug.

- Titre : Idiomes pour Android

- Auteur : Feanorin

- Parution : 19 mai 2014

- Mise à jour : 10 juillet 2014

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 Feanorin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Participants

- II. Architecture

- III. Nommage

- III-A. Côté JAVA

- III-B. Côté XML

- III-C. Nom des fichiers

- III-D. Nom des packages

- IV. Implémentation

- IV-A. Création d'objet initialisation

- IV-B. Exception

- IV-C. Annotation

- V. Documentation et Commentaires

- V-A. JAVA

- V-B. XML

- VI. Coding Style

- VI-A. JAVA

- VI-B. XML

- VI-C. Style

- VII. Autres : meilleures pratiques (éviter les anti-patterns)

- VIII. Le plus : Cas d'usage en entreprise

I - Participants

Florian Fournier (29 ans, Toulouse)

- Expert en Android .

- Responsable de la rubrique Android du site developpez.com.

Kilian Lamberdière (25 ans, Montpellier)

- Développeur mobile.

- Modérateur de la rubrique Android du site developpez.com.

Joël Drigo (44 ans , Paris)

- Chef de produit pour la société Wedia.

- Developpeur java / Eclipse RCP.

- Modérateur de la rubrique Java du site developpez.com.

Mickaël Le Trocquer

- Expert en Android pour la société Open.

Nicolas Romantzoff (42 ans, Lyon)

- Formation Systèmes & Réseaux (ENSIMAG).

- Expert Android pour la société Anyxis.

- Expert architecture logicielle pour la société Ars-Media.

- Rédacteur/Modérateur de la rubrique Android du site developpez.com.

Thomas Andres Gomez

- Développeur Android / iOs pour la société Novedia Group.

Sébastien Knopff

Yannick Eglem

Florian Le Ficher (23 ans, Paris)

- Développeur Android pour la société Valtech.

- Chef de projet technique pour la société Saint-Gobain.

- A fréquenté l'EPITA, promotion 2013 en alternance.

David Silvera (24 ans, Paris)

- Expert en Android pour la société Webtyss.

- Rédacteur/Modérateur de la rubrique Android du site developpez.com.

- A fréquenté l'EPITA, promotion 2013 en alternance.

Sébastien Grimault

Mathias Seguy

II - Architecture

1) Bien packager les sources par type de classe :

- haut niveau GCM/ Application / Constante :

- Pour les constantes, cela peut se positionner soit dans une classe statique permettant de centraliser les données ou directement depuis sa classe en privé ou protected selon le besoin ;

- activité :

- générique,

- par UC :

- fragment,

- service,

- adapter,

- view,

- autres ;

- fragment générique ;

- service générique ;

- adapter générique ;

- utils ;

- provider ;

- view générique :

- par Type de View (View, ViewGroup, etc.) ;

- BD :

- Pojo ;

- com / WS :

- Pojo ;

- listener / BroadCast ;

- animations.

2) Si des sources externes doivent être importées, les placer dans un projet à part et les inclure au projet courant.

3) Toujours avoir des images sous différents formats selon le type d'écran de mobile cible :

- hdpi ;

- ldpi (plus utilisé par les constructeurs mais les vieux téléphones les utilisent encore) ;

- mdpi ;

- xhdpi ;

- xxhdpi (représente en général les tablettes qui sont, elles, reconnues par la dimension sw-600).

Tips : Essayer au maximum d'utiliser les drawables sous format XML pour ne pas surcharger l'application en ressource ( voir shape ).

4) Les fichiers XML sur les animations ou selector doivent se trouver dans le dossier drawable par défaut si ceux-ci ne sont pas dépendants de la configuration du téléphone, avec un nommage pour les différencier. Ex : animation_fade_in, selector_action, etc.

5) Toutes les chaînes de caractères doivent être définies dans le fichier “res/values/strings.xml”, ce qui permet de modifier les libellés sans toucher aux sources. Cela a aussi pour but de pouvoir développer en multi-langage assez rapidement.

Tips : Si votre projet est assez lourd en ressources, vous pouvez également diviser vos données en plusieurs fichiers, par exemple par activity ou Use Case pour faciliter l'accessibilité à ces données.

6) De la même manière que pour le point précédent, déclarer les couleurs et les dimensions dans leurs fichiers respectifs “res/values/colors” et “res/values/dimens”.

7) Supprimer les fichiers non utilisés dans votre programme. En effet, en fin de développement, nous retrouvons souvent des images que nous n'utilisons plus dans notre application. Pour retrouver facilement ces fichiers, utiliser Lint (cf, chapitre “Le plus”).

III - Nommage

III-A - Côté JAVA

1) Tous les identifiants sont en CamelCase (les mots sont attachés, chaque mot commence par une majuscule). Le caractère “_” (underscore) est interdit (sauf constantes voir #7).

2) Le premier mot des identifiants de types (classes, enum, interface) commence avec une majuscule (voir points #3 et #4). Les autres identifiants commencent par une minuscule, que ce soient des variables (voir points #5 et #6) ou des fonctions.

3) Toujours commencer le nom d'une interface par la lettre I.

4) Toujours commencer le nom d'une classe abstraite par Abstract. Pour ne pas avoir de nom à rallonge, ce nom peut commencer par la lettre A.

5) Les membres de classe non statiques doivent commencer par un ‘m' et/ou précéder l'utilisation d'un membre de classe par “this”, le but étant de pouvoir les différencier des variables locales.

6) Les membres de classe statiques non final doivent commencer par un ‘s'.

7) Les variables de type public static final doivent être en majuscules. De plus, le nom de la variable contenant plusieurs mots doit séparer ces derniers par un underscore :

public static final String MON_NOM_DE_VARIABLE_STATIC_FINAL;

8) Les noms de variables doivent commencer par une minuscule.

9) Les noms de fonctions doivent être explicites. Nous devons savoir ce que fait une fonction sans même lire le code.

De même, les noms de variables doivent aussi être explicites. Il faut favoriser les noms discriminants.

Exemple : Cas rencontré dans une discussion de forum :
bleu = new JMenuItem("Bleu"),
rouge = new JMenuItem("Rouge"),
vert = new JMenuItem("Vert");
Plus loin dans le code :
red = new JButton(new ImageIcon("images/rouge.jpg")),
green = new JButton(new ImageIcon("images/vert.jpg")),
blue = new JButton(new ImageIcon("images/bleu.jpg"));
Le problème rencontré est que la personne en question a confondu sa variable appelée “blue” avec celle appelée “bleu”. S'il avait plutôt eu le code suivant, il ne se serait sûrement jamais trompé et la lisibilité du code aurait été meilleur e :
menuBlue = new JMenuItem("Bleu"),
menuRed = new JMenuItem("Rouge"),
menuGreen = new JMenuItem("Vert");
Plus loin dans le code :
buttonRed = new JButton(new ImageIcon("images/rouge.jpg")),
buttonGreen = new JButton(new ImageIcon("images/vert.jpg")),
buttonBlue = new JButton(new ImageIcon("images/bleu.jpg"));

III-B - Côté XML

Les identifiants XML ne doivent pas contenir de majuscule. La bonne pratique consiste à remplacer la majuscule par une minuscule précédée d'un tiré bas. Par exemple, “monIdentifiant” devient “mon_identifiant”.

Il faut éviter les doublons au niveau des identifiants. Une pratique pour cela peut être de préfixer l'identifiant par le fichier de layout.

Les identifiants XML commencent par une minuscule.

III-C - Nom des fichiers

Astuce : Les fichiers de ressource des layout sont souvent difficiles à retrouver parmi nos dizaines de fichiers. Il est préférable de préfixer le nom du fichier de ressource par le type de layout. Par exemple :

- Un fichier de ressource pour une activité sera préfixé par “activity_” suivi du nom de l'activité l'utilisant. Si l'activité est “AuthenticationActivity.java”, le layout sera “activity_authentication”.

- Un fichier de ressource pour un fragment sera préfixé par “fragment_” suivi du nom du fragment l'utilisant. Si le fragment est “ListEtudiantFragment.java”, le layout sera “fragment_list_etudiant”.

- Un fichier de ressource pour une vue sera préfixé par “view_” suivi du nom de la vue l'utilisant. Si la vue est “MyCardView.java”, le layout sera “view_my_card”.

- Un fichier de ressource pour un item sera préfixé par “item_” suivi du type d'item. Si l'item correspond à une ligne d'une ListView affichant des noms d'étudiants, le nom du fichier sera “item_student_name”.

Ainsi nommés, il sera facile de rechercher le layout d'une activité ou autre.

III-D - Nom des packages

1) Le nom des packages ne doit pas contenir d'underscore (_) ni de majuscule !

2) Le nom des packages ne doit pas commencer par com.android. En effet, cette appellation est réservée aux applications créées par Google pour Android.

3) Le nom du package par défaut est décomposé en 3 parties :

- l'abréviation du pays : fr (France), uk (Angleterre), ca (Canada), com (International -> le plus souvent utilisé) ;

- la société qui développe l'application ;

- le nom de l'application.

Exemple : “com.android.sms” représente une application nommée “sms”, créée par la société “Android” à destination internationale (com).

IV - Implémentation

1) Toujours implémenter le else quitte à retourner false.

2) Un seul return.

3) Préférer à un while(true), un boolean conditionBoucle = true, avec while(conditionBoucle) { };.

4) Simplifier les if (maCondition == true) en if (maCondition).

5) Utiliser la fonction equals lors de la comparaison d'Objet, implémenter la fonction si cela est nécessaire ! Par cohérence, faire de même avec compareTo.

6) Favoriser les types primitifs au lieu des Objets (int != Integer), mais faire attention à l'auto-unboxing avec les passages en bundle/extra.

7) Écrire des méthodes courtes. Une méthode faisant plus de 40 lignes peut toujours être découpée en deux minimums.

Conseil :
Il peut arriver que des méthodes métier (voir certaines méthodes techniques) utilisent beaucoup de lignes et beaucoup de variables. On peut arriver facilement à 1000 lignes pour faire un traitement métier, en utilisant 25 ou 30 variables.
Il faut absolument découper cette méthode en plusieurs méthodes : il y a toujours une forte probabilité qu'un traitement qui prend autant de lignes soit une combinaison de traitements plus limités réutilisables, à quelques variantes près que l'on résout par des paramètres. Cependant, on peut avoir besoin de la plupart de ces variables dans chacune des nouvelles méthodes qu'on va devoir créer pour découper le processus. Faire des méthodes avec 25 ou 30 arguments est source d'erreurs. De plus, c'est lourd à appeler (on peut facilement inverser 2 paramètres, et lorsqu'on doit ajouter un nouveau paramètre, il faut le faire dans plusieurs appels). Il est aussi plus lourd à déboguer. Pour finir, les performances sont dégradées par la copie des paramètres.
On peut résoudre ce problème en créant une classe qui représente un contexte : les paramètres deviennent simplement des variables de cette classe. Si l'instance est créée dans la première méthode d'appel, elle ne sera pas partagée, et, donc, on peut se permettre d'avoir des variables public, ce qui facilitera la manipulation.

8) Commenter le code ! Voir le chapitre sur les Commentaires.

9) Les dimensions doivent être de préférence en “dp”, mis à part pour la taille des textes qui doivent être en “sp”.

10) Pour convertir une chaîne de caractères en entier, flottant, double ou booléen, utiliser la fonction static “valueOf” de la primitive désirée. Par exemple, pour convertir une chaîne de caractères en entier, nous ferons :

Integer.valueOf (maChaine);

et non :

new Integer (maChaine);

11) Éviter au maximum les poids (weight) dans vos fichiers XML.

12) Supprimer les importations inutiles.

13) User et abuser des Thèmes et Styles pour vos applications. Cela permet d'habiller automatiquement les composants graphiques, de factoriser les déclarations graphiques et de pouvoir réagir rapidement au changement. Pour cela, vous pouvez vous référer à ce tutoriel : http://nicroman.developpez.com/tutoriels/android/styles_themes/ .

14) Ne pas instancier d'objets lorsque l'on n'en a pas besoin. Cela peut paraître bête, mais il peut arriver que l'on oublie ce genre de choses après un refactoring.

IV-A - Création d'objet initialisation

1) Interdiction d'initialiser les variables membres en dehors de leur classe.

2) Les variables de classe doivent être définies de préférence au début du fichier. Si elles ne peuvent pas y être pour une quelconque raison, elles doivent être déclarées avant la fonction les utilisant.

3) Les variables de boucle doivent être déclarées directement dans la boucle :

for (int i = 0; i < size; i++)

et non :

int i;
for (i = 0; i < size; i++)

4) Ne pas réutiliser une même variable dans un même scope pour représenter plusieurs choses différentes (même si elles se ressemblent).

Exemple à ne pas suivre :

Date date = reservation.getStartDate();
if (newDate.before(date)) {
reservation.setStartDate(newDate);
// ... 2 ou 3 lignes de codes ici
}
// ... 2 ou 3 lignes de codes ici
date = reservation.getEndDate();
if( newDate.after(date) {
reservation.setEndDate(newDate );
// ... 2 ou 3 lignes ici
}
/*
On peut facilement ajouter ici du code qui manipule date, en pensant que c'est la date de début, si on ne regarde pas tout le code de la méthode. De plus, si le code est repris par un autre développeur, il comprendra difficilement à quoi sert la variable. */

IV-B - Exception

1) Utiliser au maximum les exceptions JAVA existantes (ArithmeticException par exemple, lorsqu'un calcul ne peut être fait parce qu'un opérande est invalide), et ne jamais faire de throw new Exception(“...”), mais faire sa propre classe d'exceptions.

2) Pensez à centraliser vos exceptions si vous voulez les gérer. Pour cela, créer une classe ExceptionManager permettant de réaliser les tâches à faire lors d'une remontée d'exception (Log, affichage de dialogue, etc ..). Cette opération peut s'avérer difficile par type d'exception, une interface ou un paramètre vous seront peut-être utiles pour différencier de quel Use Case l'exception a été remontée.

IV-C - Annotation

1) Ne jamais appeler de méthode marquée deprecated : une méthode marquée deprecated est toujours accompagnée d'un commentaire indiquant quelle autre méthode appeler à la place. Toujours dans le scope de votre API minimum, essayer au possible d'utiliser les nouvelles fonctionnalités en fonction de l'API installé sur votre device.

Exemple :

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
// only for gingerbread and newer versions
}

Vous pouvez également passer par un boolean depuis les ressources pour savoir sur quel API vous êtes.

2) Utiliser les annotations Android plutôt que de laisser des warnings. Typiquement, pour les utilisations de méthodes ou d'objets disponible dans une version ultérieure de la cible, encadrer par le #1, et rajouter l'annotation @TargetApi(XX).

V - Documentation et Commentaires

V-A - JAVA

1) Tous les documents ayant une licence doivent commencer par un commentaire contenant la définition du copyright. Chaque bloc doit être séparé par un espace. Par exemple, si l'on veut contribuer aux sources Android, on écrira :

/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

2) Avant chaque interface ou chaque classe, une description de celles-ci doit être écrite en commentaire. Le commentaire doit être “JavaDoc” (commencer par /** )

Par exemple:

/**
* Un étudiant correspond à une personne en cours d'apprentissage
*/
public class Student {
}

3) Commenter chaque fonction publique avec la notation de la javadoc:

/**
*
* @param
* @return
*/

Ceci à pour but de savoir :

- à quoi sert la fonction (1ère ligne) ;

- avec quels paramètres l'appeler (à placer après le @param) ;

- que retourne la fonction (à placer après le @return).

Cette notation permet aussi de générer facilement une documentation du code.

4) Lorsque des parties de code seront implémentées par la suite, laisser un commentaire expliquant ce que doit faire le code manquant précédé de “TODO”. La plupart des IDE gère automatiquement ce genre de tags, dans une TodoList : ceci ajoutant en plus de l'information utile un moyen pratique de suivre ses tâches à faire. Pour l'utiliser efficacement, ne pas oublier de supprimer les “TODO” générés automatiquement par les IDE, pour ne pas se retrouver avec X vrais “TODO” et Y bruits “// TODO auto-generated method stub”.

5) De la même façon que pour le tag TODO, le tag FIXME peut être utilisé pour marquer un commentaire qui indique qu'on sait qu'un code pose problème dans certains cas, mais qu'on n'a pas forcément le temps de traiter dans l'immédiat.

6) Pensez à marquer deprecated les méthodes que vous avez besoin de modifier de façon importante, en indiquant en commentaire de déprécation la méthode de remplacement. Corollaire : dans un code réutilisé (une bibliothèque par exemple), ne jamais modifier les paramètres d'une méthode ; en créer une nouvelle, que l'ancienne appelle, et rendre deprecated l'ancienne éventuellement : si les paramètres ajoutés ne peuvent avoir de valeur par défaut facilement déterminable, laisser les 2 codes. Les IDE modernes permettent de visualiser les appels de méthodes marquées deprecated de manière globale afin d'aider le développeur à intégrer une nouvelle version d'une bibliothèque. Ceci est applicable également aux classes.

V-B - XML

1) Pensez à structurer vos fichiers xml en créant des blocs par activité à l'aide des commentaires. Par exemple, l'ensemble des chaînes de caractère de l'activité d'authentification seront précédé d'un commentaire simple en xml m'indiquant que ces chaînes de caractères sont utilisées dans cette activité :

<!-- AUTHENTICATION ACTIVITY -->
<string name="hello">Hello</string>

VI - Coding Style

VI-A - JAVA

1) Au niveau du formatage automatique dans les IDE, Google met à disposition des fichiers ici : https://android.googlesource.com/platform/development/+/master/ide .

2) Déclaration d'une variable à la fois.

3) Déclarer les variables types tableau selon cette convention :

int[] array;

et non

int array[];

qui est elle sujette à confusion et tromperie.

4) Ne pas écrire une fonction sur la même ligne qu'une déclaration.

5) Les accolades suivant une condition doivent être présentes sur la même ligne que la condition, séparées d'un espace.

Exemple :

if ( toto == 1) {
//...
} else {
//...
}

6) Une ligne doit faire au maximum 100 caractères.

7) Aérer le code. Ne pas hésiter à ajouter un espace après et avant chaque opérateur.

Ce code :

1 + 2 * 3 / 4

est plus lisible et agréable à lire que :

1+2*3/4

Surtout si nous remplaçons les chiffres par des noms de variables.

De même, sauter des lignes entre des blocs de code ou des suites d'instructions pour rendre le code moins compact et plus lisible, ainsi que faire ressortir des suites d'instructions liées.

VI-B - XML

1) Ajouter un retour à la ligne après chaque paramètre d'un widget. Par exemple, cette déclaration :

<ImageView
android:id="@+id/iv_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/marginLeft"
android:layout_marginRight="@dimen/marginRight"
android:contentDescription="@string/app_name"
android:src="@drawable/ico_back" />

et plus lisible que :

<ImageView android:id="@+id/iv_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_centerVertical="true" android:layout_marginLeft="@dimen/marginLeft" android:layout_marginRight="@dimen/marginRight" android:contentDescription="@string/app_name" android:src="@drawable/ico_back" />

VI-C - Style

Comme vu dans la section XML, nous devons rendre les plus lisibles possibles les fichiers de ressources. Il s'avère que souvent nos composants ont x paramètres. Les styles sont là justement pour permettre au développeur de pouvoir factoriser le code des ressources et ainsi rendre plus lisibles et plus simples les changements à opérer sur le graphique.

Pour cela, vous devrez créer un style par composant graphique pour votre application. Ces styles pourront être personnalisés par types de charte graphique que vous pourrez trouver dans votre application.

Utilisez les thèmes pour personnaliser les composants par défaut d'Android (Listes, Dialogs,...).

Par exemple, si nous avons un composant Button, nous pourrons trouver comme style :

- Button Action ;

- Button NetWork ;

- etc.

VII - Autres : meilleures pratiques (éviter les anti-patterns)

Toujours préférer typer avec l'interface, ou la classe, la plus générique possible les résultats et les arguments de méthodes, pour permettre une plus grande souplesse dans la modification de l'implémentation interne, sans impact sur l'appelant, comme  :

public static <T> Collection<? extends T> filter(Collection<? extends T> collection, IFilter<? super T> filter) {
/* personnellement, j'ai pris l'habitude également de tester mes arguments en premier, surtout dans les méthodes utilitaires, plutôt que de laisser les exceptions standard se soulever le cas échéant, mais je ne suis pas sûr que cela soit une bonne pratique */
if ( collection == null ) throw new IllegalArgumentException("Collection to be filtered must be not null");
if ( filter == null ) throw new IllegalArgumentException("Filter must be not null");
List<T> filteredCollection = new ArrayList<>();
for( T element : collection ) {
if ( filter.accept(element) ) {
filteredCollection.add(element);
}
}
return filteredCollection;
}

avec pour exemple d'utilisation :

List<Integer> exampleList = Arrays.asList(10, -10, 20, -20); // exemple de liste d'entier
// exemple : filtrer les nombres positifs
IFilter<Integer> exampleFilter = new IFilter<>() {
public boolean accept(Integer element) {
return element.intValue()>0;
};
};
for(Integer element : FilterTools.filter(exampleList, exampleFilter )) {
// traitement
}

VIII - Le plus : Cas d'usage en entreprise

1) Utilisez les bonnes fonctions de Log. Encapsuler les logs au sein d'une classe pour ne pas les afficher sur l'application en production.

2) Pensez à créer la possibilité d‘écrire les logs sur la sdcard si vous avez besoin depuis votre projet de récupérer les données terrain autres que les exceptions (par exemple les données GPS). Ceci peut se faire avec Acra ou Crashlytics, mais ils ne sont pas prévus pour cela.

2) N'utilisez jamais System.out.println() ni Exception#printStackTrace().

4) Utilisez lint pour vérifier :

- que toutes les chaînes décrites dans les XML sont utilisées dans le code ou layout ;

- que toutes les chaînes possèdent une traduction dans les langues supportées par l'application ;

- que toutes les images possèdent bien leurs dérivées dans les résolutions supportées par votre application ;

- qu'il n'y a pas d'utilisation de méthodes disponibles dans un API supérieur au minimum supporté par le projet.

5) Vérifiez toujours la mise à jour d'une application avant de la pousser sur le store. Pour ce faire, installez l'application via Google Play, ayez en local l'APK signé prêt à aller sur le store et faites la mise à jour via adb avec adb install -r NouvelleVersionApk.apk.