Dépôt Github avec le rendu : https://github.com/fabiovandewaeter/GL_Projet1_Groupe5_Fabio_VANDEWAETER
Explication des dossiers et fichiers du dépôt du Projet1 :
bac_a_sable
contient l'archive du dépôtgson
à analyser ainsi qu'un main Java pour pouvoir expérimenter (lancer avec la commandemake main
)README.md
sert de compte rendu à ce Projet 1
Il s'agit du logiciel GSon qui a été forké : https://github.com/fabiovandewaeter/gson
Ce programme permet, en Java, de convertir des objets Java en JSON, et inversement, avec des méthodes fonctionnant comme un toString()
Par exemple, on peut convertir du Java en Json grâce à la méthode gson.toJson()
en passant en paramètre l'objet Java à convertir :
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Main{
public static void main(String[] args){
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
// Convertion d'éléments simples
System.out.println("1 => " + gson.toJson(1));
System.out.println("test => " + gson.toJson("test"));
final int[] list = { 1, 2, 3 };
System.out.println("{1,2,3} => " + gson.toJson(list));
// Convertion d'objets complets
Cube cube = new Cube("red", 10, 50);
System.out.println("Cube(\"red\", 10, 10) => " + gson.toJson(cube));
}
public static class Cube{
private String color;
private int height;
private int width;
public Cube(String color, int height, int width){
this.color = color;
this.height = height;
this.width = width;
}
}
}
Ce qui affiche :
1 => 1
test => "test"
{1,2,3} => [1,2,3]
Cube("red", 10, 10) => {"color":"red","height":10,"width":50}
Dans l'autre sens, il est possible de convertir du Json, sous forme de chaîne des caractères, en un objet Java, grâce à la méthode gson.fromJson()
en passant en paramètre la chaine de caractère Json à convertir ainsi que la classe de l'objet à retourner :
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class Main{
public static void main(String[] args){
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
// Convertion à partir du Json
Cube cube = new Cube("red", 10, 50);
String convertedCube = gson.toJson(cube);
System.out.println("Cube(\"red\", 10, 10) => " + convertedCube);
Cube cube2 = gson.fromJson(convertedCube, Cube.class);
System.out.println("Cube(\"red\", 10, 10) => " + gson.toJson(cube2));
System.out.println("Même objet ? " + cube.equals(cube2));
}
public static class Cube{
private String color;
private int height;
private int width;
public Cube(String color, int height, int width){
this.color = color;
this.height = height;
this.width = width;
}
}
}
Ce qui nous donne bien un nouvel objet avec les mêmes valeurs en attributs que l'ancien, même après convertion en Json :
Cube("red", 10, 10) => {"color":"red","height":10,"width":50}
Cube("red", 10, 10) => {"color":"red","height":10,"width":50}
Même objet ? false
A la racine du projet, faire mvn clean verify
pour obtenir l'archive dans le dossier gson/gson/target/gson-2.10.2-SNAPSHOT.jar
Le dépôt contient divers dossiers, par exemple gson/extras
qui contient des fonctionnalités non fournies par défaut ou gson/metrics
qui permet aux développeurs de faire des benchmarks de leur côté, mais le dossier le plus important, avec plus de 200 classes, est le dossier gson/gson
, qui permet d'obtenir l'archive principale
D'après le Readme, il suffit d'ajouter cette dépendance :
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
}
Toujours d'après le Readme, il faut ajouter ceci :
<dependencies>
<!-- Gson: Java to JSON conversion -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
Le dépôt contient un Readme à jour malgré le fait que le projet soit en "maintenance mode" (d'après le Readme), donc qu'il ne soit plus prévu d'ajouter des fonctionnalités
En plus du fichier README.md
, le fichier GsonDesignDocument.md
donne des détails sur les choix faits lors de la conception du programme
Le fichier Troubleshooting.md
, quant à lui, détaille le fonctionnement des exceptions
Le fichier UserGuide.md
vient compléter le Readme dans tous ses aspects
Ces informations sont suffisantes pour permettre d'utiliser le programme
Le projet compte 145 contributeurs mais les 6 plus gros contributeurs ont participé significativement plus que les autres
La grande majorité des contributions ont eu lieu entre 2008 et 2015
Le projet a été crée en 2008 et étant en "maintenance mode" depuis 2014 (ce qui signifie ici qu'il n'y aura pas de nouvelles fonctionnalités ajoutées), il est donc normal de voir que pratiquement aucun commit n'a eu lieu depuis 2014
Il y a eu 18 branches au total et 8 branches sont encore actives dont :
- la branche
main
- 6 branches gérée par
dependabot
, un programme qui met automatiquement à jour les dépendances du projet Github - une branche gérée par
OSSF Scorecard
qui gère la sécurité du projet
Les pulls requests sont utilisées depuis 2015 sur le dépôt, avec des labels en fonction du type de modifications à apporter ; il y a actuellement 105 pulls requests ouvertes, notamment pour mettre à jour des dépendances, et 874 fermées
Le tableau ci-dessus présente le nombre de classes dont dépend une classe (Dcy) ainsi que le nombre de packages dont elle dépend (PDpt) ; on constate que la moyenne est assez faible dans les deux cas, mais que des classes très importantes comme com.google.gson.Gson
dépend de beaucoup plus de classes ou packages, ce qui était prévisible
Les classes dépendent souvent de packages du projet, notamment des packages de com.google.gson.internal
Le projet gson/gson
est composé de 9 packages, qui se trouvent dans le dossier gson/gson/src/main/java
:
com.google.gson
contient tous les autres packages
com.google.gson.annotations
qui fournit des annotations qui peuvent être utilisées avec le projet
com.google.gson.internal
qui permet au projet de fonctionner mais ne doit pas être accédé directement
com.google.gson.internal.bind
com.google.gson.internal.bind.util
com.google.gson.internal.reflect
com.google.gson.internal.sql
com.google.gson.reflect
qui donne des informations sur les typescom.google.gson.stream
qui fournit des classes pour traiter le JSON de manière efficace
Dans le dossier gson/gson/src/test/java
on retrouve globalement la même structure avec quelques changements :
com.google.gson.annotations
n'est pas testécom.google.gson.common
est un nouveau package qui ajoute des méthodes pour les autres testscom.google.gson.functional
est un nouveau package qui test le projet de façon plus globale avec des tests fonctionnelscom.google.gson.metrics
est un nouveau package qui test les performancescom.google.gson.regression
est un nouveau package
Globalement, les packages ont des noms pertinents et séparent les fonctions du projet de façon pertinente, en plus de rester cohérent avec la structure des test
Les classes du package com.google.gson.internal
semblent utiliser des Factory pattern ou Adapter pattern si on se base sur leurs nom et sur le package dans lequel elles sont placées
Le projet comporte 252 classes au total dont 202 dans gson/gson
, avec 83 classes dans gson/gson/src/main
et 119 classes de test dans gson/gson/src/test
La majorité des classes se trouvent uniquement dans le packetage com.google.gson
mais le le package com.google.gson.reflect
ne contient qu'une classe, ce qui montre que ce choix de répartition en package est pertinent
Le tableau ci-dessus présente, pour les classes, le nombre de dépendences (Dcy), de dépendences de packages (PDpt) ainsi que le couplage entre deux objets
On constate que les classes sont très dépendantes les unes des autres et que l'on ne peut pas réellement les réutiliser ou les modifiers sans impacter les autres
Ce second tableau, quant à lui, donne le couplage afférent (Ca), le couplage efferent (Ce) et l'instabilité (I) des packages ; ceci va dans le sens de la conclusion tirée du premier tableau
On lance les tests dans le dossier gson/gson
grâce à la commande mvn test
Le tableau ci-dessus présente des statistiques sur les tests JUnit du dossier gson/gson/src/test
avec le nombre d'assertions de test (JTA), le nombre de classes de test (JTC) et le nombre de méthodes de test (JTM)
Comme on pouvait s'y attendre les packages com.google.gson.regression
et com.google.gson.metrics
et com.google.gson.common
comportent peu de tests car ils sont là pour compléter les autres tests ou n'ont pas besoin d'être testés
Sonarqube indique 89.6% du code de gson/gson/src/main
est testé, ce qui est satisfaisant car les classes essentielles sont bien testées
Les tests sont des tests unitaires sauf dans le package com.google.gson.functional
qui contient des test fonctionnel, ce qui permet de tests les méthodes mais aussi de vérifier qu'elles intéragissent bien entre-elles du début à la fin
Les tests 1360 tests passent sans erreurs et même si 19 ne sont pas exécutés, cela est très satisfaisant
On pourrait ajouter des tests aux classes com.google.gson.JsonParser
et com.google.gson.JsonElement
qui ont environ 50% de coverage, puis compléter certaines classes de tests pour tester des cas relativements simples qui ne sont pas encore testés
Le tableau ci-dessus présente différentes statistiques sur la javadoc de gson/gson
, avec le coverage des classes (Jc), le coverage des attributs (Jf), le nombre de lignes de javadoc (JLOC) ainsi que le coverage des méthodes (Jm)
Le code est largement commenté dans le format de la Javadoc, avec les licences en début de fichier
Le code est donc largement commenté en dehors des packages com.google.gson.internal
, car ces derniers ne sont pas censé être accédés directement lors de l'utilisation de l'API ; cela peut poser problème pour les développeurs eux-mêmes qui pourraient ne pas s'y retrouver par manque de documentation, et il serait pertinent d'ajouter des commentaires à certaines méthodes, notamment pour expliquer dans quel contexte elles sont utilisées dans le projet
Le code déprécié est voué à ne plus être utilisable, c'est donc une bonne raison de le remplacer ; on trouve du code déprécié dans ces classes :
java.security.AccessController.doPrivileged(PrivilegedAction<Field[]> action)
ligne 969 qui est marquée comme dépréciée
public final void setLenient(booblean lenient)
ligne 312 est marquée comme dépréciée
public final void setLenient(boolean lenient)
ligne 332 est aussi marquée comme dépréciée
public Excluder excluder()
ligne 408 est marquée comme dépréciée
public GsonBuilder setLenient()
ligne 548 est marquée comme dépréciée
public char getAsCharacter()
ligne 371 est marquée comme dépréciée
public JsonElement()
ligne 40 est marquée comme dépréciéepublic char getAsCharacter()
ligne 275 est marquée comme dépréciée
public JsonNull()
ligne 40 est marquée comme dépréciée
public JsonParser()
ligne 38 est marquée comme dépréciéepublic JsonElement parse(String json) throws JsonSyntaxException
ligne 118 est marquée comme dépréciéepublic JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException
est marquée comme dépréciéepublic JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException
est marquée comme dépréciée
Il n'y a pratiquement pas de duplication de code, mais on peut trouver du code dupliqué dans le fichier gson/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java
, de façon très légère, par exemple pour les lignes 197-209 et 232-244, mais cela se répère dans les méthodes de type public statis final TypeAdapter<T>
:
public static final TypeAdapter<Number> SHORT =
new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
int intValue;
try {
intValue = in.nextInt();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
public static final TypeAdapter<Number> BYTE =
new TypeAdapter<Number>() {
@Override
public Number read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
int intValue;
try {
intValue = in.nextInt();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
Il semble possible d'améliorer cela en créant de petites fonctions qui gèrent les différentes parties communes à ces méthodes
Même si la majorité des classes sont peu complexes, quelques-unes sont particuliérement imposantes ; par exemple ces classes ont une complexité (WMC) particuliérement élevée :
Il en va de même pour les fichiers de tests de ces classes, mais cela est moins impactant
Avec beaucoup de temps il serait possible de séparer ces classes en sous-classes plus spécialisées et simples à maintenir, même si cela demanderai beaucoup de temps
Le tableau ci-dessus nous donne la complexité cyclomatique (v(G)) totale et moyenne, et même si cette dernière basse, certaines méthodes sont beaucoup plus complexes, comme par exemple la méthode com.google.gson.stream.JsonReader.doPeek()
qui a une complexité cyclomatique de 40 avec la même méthode de calcul, ce qui est problématique (il semble important de réduire la taille de cette fonction en en créant d'autres plus petites, car le développement de cette méthode risque d'être fastidieux) :
int doPeek() throws IOException {
int peekStack = stack[stackSize - 1];
if (peekStack == JsonScope.EMPTY_ARRAY) {
stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
} else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
// Look for a comma before the next element.
int c = nextNonWhitespace(true);
switch (c) {
case ']':
return peeked = PEEKED_END_ARRAY;
case ';':
checkLenient(); // fall-through
case ',':
break;
default:
throw syntaxError("Unterminated array");
}
} else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
stack[stackSize - 1] = JsonScope.DANGLING_NAME;
// Look for a comma before the next element.
if (peekStack == JsonScope.NONEMPTY_OBJECT) {
int c = nextNonWhitespace(true);
switch (c) {
case '}':
return peeked = PEEKED_END_OBJECT;
case ';':
checkLenient(); // fall-through
case ',':
break;
default:
throw syntaxError("Unterminated object");
}
}
int c = nextNonWhitespace(true);
switch (c) {
case '"':
return peeked = PEEKED_DOUBLE_QUOTED_NAME;
case '\'':
checkLenient();
return peeked = PEEKED_SINGLE_QUOTED_NAME;
case '}':
if (peekStack != JsonScope.NONEMPTY_OBJECT) {
return peeked = PEEKED_END_OBJECT;
} else {
throw syntaxError("Expected name");
}
default:
checkLenient();
pos--; // Don't consume the first character in an unquoted string.
if (isLiteral((char) c)) {
return peeked = PEEKED_UNQUOTED_NAME;
} else {
throw syntaxError("Expected name");
}
}
} else if (peekStack == JsonScope.DANGLING_NAME) {
stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
// Look for a colon before the value.
int c = nextNonWhitespace(true);
switch (c) {
case ':':
break;
case '=':
checkLenient();
if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
pos++;
}
break;
default:
throw syntaxError("Expected ':'");
}
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
if (strictness == Strictness.LENIENT) {
consumeNonExecutePrefix();
}
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
} else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
int c = nextNonWhitespace(false);
if (c == -1) {
return peeked = PEEKED_EOF;
} else {
checkLenient();
pos--;
}
} else if (peekStack == JsonScope.CLOSED) {
throw new IllegalStateException("JsonReader is closed");
}
int c = nextNonWhitespace(true);
switch (c) {
case ']':
if (peekStack == JsonScope.EMPTY_ARRAY) {
return peeked = PEEKED_END_ARRAY;
}
// fall-through to handle ",]"
case ';':
case ',':
// In lenient mode, a 0-length literal in an array means 'null'.
if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
checkLenient();
pos--;
return peeked = PEEKED_NULL;
} else {
throw syntaxError("Unexpected value");
}
case '\'':
checkLenient();
return peeked = PEEKED_SINGLE_QUOTED;
case '"':
return peeked = PEEKED_DOUBLE_QUOTED;
case '[':
return peeked = PEEKED_BEGIN_ARRAY;
case '{':
return peeked = PEEKED_BEGIN_OBJECT;
default:
pos--; // Don't consume the first character in a literal value.
}
int result = peekKeyword();
if (result != PEEKED_NONE) {
return result;
}
result = peekNumber();
if (result != PEEKED_NONE) {
return result;
}
if (!isLiteral(buffer[pos])) {
throw syntaxError("Expected value");
}
checkLenient();
return peeked = PEEKED_UNQUOTED;
}
Même si nous avons vu que le coverage Javadoc du code est élevé, cette méthode com.google.gson.stream.JsonReader.doPeek()
n'a aucun commentaires alors qu'elle est très complexe, ce qui aggrave le problème
Plusieurs méthodes complexes de cette classe ne sont pas testées non-plus, ce qui est un vrai problème
D'après les outils d'analyses d'IntelliJ, il y a en moyenne 14.50 lignes de code par méthode, ce qui est élevé et est expliqué par le fait qu'il existe beaucoup de méthodes complexes comme com.google.gson.stream.JsonReader.doPeek()
pour analyser le code et le traduire du Java au Json ou inversement
La moyenne du nombre d'arguments est à 1.01, cependant il existe 14 méthodes avec plus de 3 paramètres, ce qui est faible au vu du nombre de méthodes, cependant le constructeur com.google.gson.Gson.Gson()
demande 21 paramètres, ce qui est très élevé
En dehors des constructeurs, on peut trouver les méthodes com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createrBoundField()
avec 7 paramètres et com.google.gson.internal.bind.MapTypeAdapterFactory.Adapter.Adapter()
avec 6 paramètres ; de façon plus générale, les méthodes du package com.google.gson.internal
ont souvent plus de 3 paramètres, ce qui peut s'expliquer par le fait qu'elles ne soient pas destinées à être utilisées en dehors du développement du projet, même si cela est également problématique car complexifie la maintenance du code
Sonarcube détecte 382 code smells mais estime que cela est peu
Même si le code fonctionne, il serait très intéressant de supprimer ces code smells et sonarcube estime que cela pourrait demander moins de 30 minutes pour une grande partie des code smells, et à l'extrême la classe com.google.gson.stream.JsonReader
pourrait demander plus d'un jour de travail pour traiter cette dette technique
On peut trouver des éléments qui ne respectent pas les conventions de nommage de Java, ce qui peut créer de l'ambiguité et des quiproquo pour un développeur qui ne connait pas déjà le code
private static final int majorJavaVersion
qui être doit écrit en majuscule et snake case
public static volatile JsonReaderInternalAccess INSTANCE
qui devrait être passé en final ou renommé
private String locationString()
devrait passer en publique pour remplacer la méthode du même nom de sa classe parent
private JsonElement peek
devrait aussi passer en public
abstract T finalize(A accumulator)
devrait être renommée pour éviter de la confondre avecObject.finalize()
T finalize(T accumulator)
ligne 562, pour les mêmes raisonsT finalize(T accumulator)
ligne 642, pour les mêmes raisons
final TypeAdapter<Date> dateTypeAdapter
ligne 37 devrait être écrit en majuscule et snake case
En dehors de ces problèmes de conventions Java, les variables ou méthodes du code principal ou des tests ont en grande majorité des noms pertinents qui reflètent leur utilité dans le projet et respectent les conventions de nommage
Pratiquement tous les nombres sont utilisés à travers de constantes avec des noms pertinents (il n'y a pas de nombres magiques dans les tests par exemple), cependant il existe quelques exemples de nombres magiques, ce qui risque de poser problème aux prochaines personnes qui auront à maintenir ce projet, car elles ne comprendront pas forcément la signification de ces nombres :
int value = src.get(i) ? 1 : 0;
ligne 142, où il serait possible de donner des noms au 1 et au 0, pour connaitre leur signification directement// Allow up to 255 to support unsigned values if (intValue > 255 || intValue < Byte.MIN_VALUE)
ligne 210, où le 255 pourrait être remplacé par une constante, avec un nom pertinent, au lieu d'ajouter un commentaire au dessus, par exemple Byte.MAX_VALUE// Allow up to 65535 to support unsigned values if (intValue > 65535 || intValue < Short.MIN_VALUE)
ligne 245, où le 65535 pourrait aussi être remplaçé par une constante, par exemple Short.Max_VALUE
if (Math.abs((long) decimal.scale()) >= 10_000)
ligne 26, qui n'utilise pas la constanteprivate statis final int MAX_NUMBER_STRING_LENGTH = 10_000
déjà définie au début de la classe pour remplacer le 10_000 (si le nom ne convient pas, il faudrait alors créer une seconde constante ou en changer le nom)
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
ligne 525, où les 0 devraient être placés dans une constante pour signifier qu'ils sont la valeur par défaut
return majorJavaVersion >= 9;
ligne 93, où le 9 devrait être placé dans une constante pour signifier la version minimale acceptée
return (lowerBound != null ? 31+ lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode());
ligne 671, où les 31 et 1 devraient être placés dans des constantes
builder = new StringBuilder(Math.max(estimatedLength, 16));
ligne 1091 et 1105, on peut remplacer les 16 par une constantebuilder = new StringBuilder(Math.max(i, 16));
ligne 1160, pour la même raisonSystem.arraycopy(buffer, pos, buffer, 0, limit);
ligne 1416, pour expliquer ce qu'est le 0
if (c < 128)
ligne 744, qui devrait être remplacé par une constante pour préciser qu'il s'agit de la limite supérieure de la valeur d'un char signé
On vérifie que :
- les variables d'instance sont au début de leur classe
- les méthodes publiques sont en haut et les privées plus bas
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
- les méthodes publiques et privées sont mélangées
On pourra placer les variables d'instances en début de classe et les méthodes publiques avant les méthodes privées, afin d'avoir un code mieux ordonné, pour ne pas se perdre en cherchant des informations
Comme il y a une API, beaucoup de code n'est pas appelé en interne, mais ce n'est pas un problème
On constate que beaucoup de méthodes avec une grande complexité cyclomatique consistent en des listes de if/else ou de switch/case, ce qui rend leur lisibilité et maintenance compliqué ; il serait donc pertinent de réduire la taille de ces méthodes, par exemple avec de plus petites méthodes ou en délégant ce travail à des sous classes, pour profiter du polymorphisme
On reprend ici les modifications qui ont été annoncées dans la première partie de ce projet et qui ont bien été appliquées
Les modifications sont faites sur ce dépôt Github : https://github.com/fabiovandewaeter/gson
Les noms des variables ou fonctions sont modifiées pour respecter les conventions de nommage Java
Commit : https://github.com/fabiovandewaeter/gson/commit/21ef7a7bec422cb2b6900484f70117d74165758a
private static final int majorJavaVersion
est renommée enprivate static final int MAJOR_JAVA_VERSION
private String locationString()
est changée en public pour remplacer la méthode du même nom de sa classe parent
private JsonElement peek()
est changée en public pour remplacer la méthode du même nom de sa classe parent
Commit : https://github.com/fabiovandewaeter/gson/commit/a10bbe7236d89a554995fb7c3fa6ee14485f9d0c
Les nombres magiques 1
, 0
, 255
, 65535
sont remplacés par des constantes
10000
est remplacé par la constante déjà présente dans la classe
9
est remplacé par une constante pour pouvoir facilement changer la version minimale
Les 31
sont remplacés par une constante pour pouvoir facilement changer la valeur utilisée dans le calcul du hash code
builder = new StringBuilder(Math.max(estimatedLength, 16));
ligne 1091 et 1105, les 16 sont remplacés par desbuilder = new StringBuilder(Math.max(i, 16));
ligne 1160, pour la même raison
if (c < 128)
ligne 744, qui est remplacé par une constanteprivate static final int MAX_SIGNED_CHAR_VALUE = 128
Commit : https://github.com/fabiovandewaeter/gson/commit/bf9c3e0e8cb4230ff36b01065b4a73de01ade3c4
L'objectif de cette section est de réorganiser le code des classes pour avoir les méthodes publiques avant celles privées, afin de s'y retrouver plus facilement dans le code avec beaucoup de méthodes ; comme beaucoup de classes ne respectent pas ce principes, on réarrangera uniquement 5 classes en guise d'exemple :
com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory
com.google.gson.internal.bind.JsonTreeWriter
com.google.gson.internal.JavaVersion
com.google.gson.internal.bind.TreeTypeAdapter
com.google.gson.JsonArray
Commit : https://github.com/fabiovandewaeter/gson/commit/32f453637fb7c6ce873ccea1b1d021b27e17ecc2
Les modifications de code déprécié sont réparties entre petites et moyennes modifications mais sont liées au même commit ; on prend 5 cas de dépréciations dans ceux trouvés lors de la partie 1 du projet en guise d'exemple, avec 2 petites modifications et 3 moyennes modifications :
com.google.gson.JsonArray
:public char getAsCharacter()
est renommée enpublic char getFirstCharacter()
pour éviter la confusion décrite dans le commentaire justifiant la dépréciationcom.google.gson.JsonElement
:public char getAsCharacter()
est changée pour la même raison
Commit : https://github.com/fabiovandewaeter/gson/commit/20836f0ccb76ba07154a6fc2eb95a3a90c86246d
com.google.gson.MixedStreamTest
ligne 90 et 94 au niveau desCar unused1
qui ne sont jamais utilisées, même si ces variables ont bien une valeur suite à l'appel de la méthodecom.google.gson.Gson
: modificationJsonToken unused = reader.peek();
pour la même raison
Commit : https://github.com/fabiovandewaeter/gson/commit/b3b1521ba556171c34f9eb3e408a6ad8dfa5e3e1
On modifie les noms des classes com.google.gson.$Gson$Types
et com.google.gson.$Gson$Preconditions
pour enlever les symboles '$', ce qui demande de modifier du code dans plusieurs autres classes du projet
On modifie la méthode private boolean isLiteral(char c) throws IOException
qui utilisait de nombreux switch/case, ce qui rendait le code peu lisible et demandait de supprimer un warning de type fall-through
; on ajoute ainsi la constante static final String NON_LITERAL_CHARACTERS = "/\\;#={}[]:, \t\f\r\n"
représentant les caractères non litéraux et qui est utilisé pour simplifier les cas
Ce commit réemploie ce qui a été modifié au dessus pour supprimer les switch/case, les suppressions de warnings fall-through
et du flag findNonLiteralCharacter
Commit : https://github.com/fabiovandewaeter/gson/commit/32f453637fb7c6ce873ccea1b1d021b27e17ecc2
2 autres modifications plus petites se trouvent dans la section 'Déprécation' des petites modifications et sont contenues dans le même commit que cette section :
com.google.gson.stream.JsonWriter
:public final void setLenient(boolean lenient)
est déprécié et doit être remplacé parpublic final void setStrictness(Strictness strictness)
, une méthode déjà implémentée mais qui n'a pas encore remplacé l'ancienne méthode partout dans le projet ; on modifie donc les appels à la méthode dépréciée dans le projet puis on la supprime de la classecom.google.gson.stream.JsonReader
:public final void setLenient(boolean lenient)
est changée dans le projet pour les mêmes raisonscom.google.gson.GsonBuilder
:public final void setLenient(boolean lenient)
est changée dans le projet pour les mêmes raisons
Commit : https://github.com/fabiovandewaeter/gson/commit/05b74474dc3b079339db76ac19a7d4ae9aacd43a
On décompose la classe com.google.gson.internal.bind.TypeAdapters
, qui est contituée de nombreuses classes statiques pour adapter à un type particulier, en des fichiers de classe séparés dans le package com.google.gson.internal.bind.adapters
L'objectif est de permettre d'ajouter ou de modifier facilement de nouveaux types d'adaptateurs, ou de pouvoir faire des tests unitaires plus simplement
com.google.gson.internal.bind.TypeAdapters
:java.security.AccessController.doPrivileged(PrivilegedAction<Field[]> action)
est déprécié depuis Java 17 mais il n'est pour le moment pas donné d'alternative, donc ce code ne peut pas encore être remplacécom.google.gson.Gson
:public Excluder excluder()
ligne 408 devait être modifié mais il n'y a pas d'alternative implémentée par les développeurs du projet
- Les 0 dans
com.google.gson.internal.LinkedTreeMap
car ce nombre est facilement compréhensibles sans ajouter une constantes - Dans
com.google.gson.stream.JsonReader
le 0 decom.google.gson.stream.JsonReader
pour la même raison
- Renommage de com.google.gson.internal.JsonReaderInternalAccess
- Renommage de abstract T finalize(A accumulator)
- Renommage de final TypeAdapter dateTypeAdapter car c'est pas en static final mais juste final
- Comme le problème de structuration du code est présent dans beaucoup de classes, on le corrige uniquement dans 5 classes en guise d'exemple
com.google.gson.JsonElement
: il s'agit d'une classe abstraite qui n'a pas de comportement particulier, donc la classe de test n'a même pas été crée et n'est pas vraiment utile
- Enlever les symboles
$
de$Gson$Type
et$Gson$Preconditions
- Décomposer la classes
com.google.gson.internal.bind.TypeAdapters
en créant un package pour les différents types d'adaptateurs
Je retiens de cette matière qu'elle nous a permis de nous améliorer sur ces 2 points :
- C'est la première fois durant notre licence que nous avons l'occasion de travailler sur un vrai projet, qui est composé de beaucoup de classes, ce qui nous permet de nous rendre compte d'à quel point la maintenance d'un projet devient compliquée au fil des années
- Nous sommes allé plus loin que dans les autres matières, où l'objectif est d'abord d'obtenir un programme qui fonctionne, en cherchant à éviter les code smells qui rendent des programmes fonctionnels compliqués à maintenir, ce qui n'est pas acceptable non-plus
Cela nous permet, même lors de nos projets plus petits, de comprendre l'importance des bonnes pratiques de développement et de savoir les mettre en place