Le contexte
Travaillant pour Médiavision, nous avons plusieurs applications dont les dates et calendriers sont basés sur le calendrier cinéma des sorties de films, dont les semaines débutent le mercredi.
La numérotation des semaines cinéma est définie de la même façon que pour les semaines civiles de la norme ISO 8601 : la première semaine cinéma de l’année N cinéma débute un mercredi et contient au moins 4 jours dans l’année civile N".
dans la pratique, la première de l’année cinéma N est la première semaine dont le samedi appartient à l’année civile N. C’est également la première semaine de l’année qui inclut le 4 janvier. |
-
Le mardi 3 janvier 2017, nous sommes en semaine 53 de l’année 2016 dans le calendrier cinéma.
-
Le mercredi 30 décembre 2015, nous sommes au premier jour le la semaine 1 de l’année cinéma 2016.
Jusqu’ici, nous utilisions pour calculer les semaines cinéma un code maison historique, d’environ 100 lignes (qui
utilisait uniquement l’API java.util.Date
et java.util.Calendar
… sinon ça serait trop facile), pour calculer
la semaine cinéma correspondant à une date donnée.
Java 8 à la rescousse
Suite à la migration de toute notre base de code Java vers Java 8, je suis retombé sur cette fameuse classe de calcul
et me suis demandé si la nouvelle API java.time
ne permettrait pas de simplifier ce code…
Les tests
Avant tout refactoring, on commence par s’assurer que le code existant est bien couvert par les tests, afin de garantir qu’on ne casse rien dans l’opération.
Je triche dans le code que je vais présenter ici, en utilisant uniquement les classes de l’API java.time .
Dans la pratique, les méthodes refactorées utilisaient en entrée/sortie les classes de l’API java.util.Date , qui
étaient converties ultérieurement. Cette partie ne nous intéressant pas ici, j’omets volontairement le code de
conversion.
|
package org.courtine.cinema;
import org.testng.annotations.Test;
import java.time.Month;
import java.time.LocalDate;
import static org.assertj.core.api.Assertions.assertThat;
public class CalendrierCinemaTest {
@Test
public void le_03_janvier_2017_doit_etre_dans_la_semaine_cinema_53_de_2016() {
LocalDate date = LocalDate.of(2017, Month.JANUARY, 3);
assertThat(CalendrierCinema.semaineCinema(date)).isEqualTo(53);
assertThat(CalendrierCinema.anneeCinema(date)).isEqualTo(2016);
}
@Test
public void le_30_decembre_2015_doit_etre_dans_la_semaine_cinema_1_de_2016() {
LocalDate date = LocalDate.of(2015, Month.DECEMBER, 30);
assertThat(CalendrierCinema.semaineCinema(date)).isEqualTo(1);
assertThat(CalendrierCinema.anneeCinema(date)).isEqualTo(2016);
}
// Autres cas de test…
}
La classe WeekFields
Jusqu’à cette problématique, je n’avais jamais eu affaire à cette classe. Son but est de permettre de configurer la définition d’une "semaine", en fournissant :
-
Le jour de départ de la semaine.
-
Le nombre minimal de jours que la première semaine doit contenir dans l’année civile N.
Ces contraintes correspondent justement à la façon dont nous avons défini "l’année cinéma" ci-dessus. Nous pouvons donc nous en servir pour remplacer l’implémentation maison du calcul. Celui-ci devient alors trivial :
package org.courtine.cinema;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
public class CalendrierCinema {
// La première semaine cinéma de l'année débute un mercredi et contient au moins 4 jours inclus dans l'année.
public static final WeekFields SEMAINE_CINEMA_WEEK_FIELDS = WeekFields.of(DayOfWeek.WEDNESDAY, 4);
public static int semaineCinema(LocalDate date) {
return date.get(SEMAINE_CINEMA_WEEK_FIELDS.weekOfWeekBasedYear());
}
public static int anneeCinema(LocalDate date) {
return date.get(SEMAINE_CINEMA_WEEK_FIELDS.weekBasedYear());
}
}
Conclusion
Ce refactoring nous a permis de supprimer 100 lignes de code historique, difficilement maintenable, et de le remplacer
par le code trivial ci-dessus. Il nous a également permis d’entamer une migration plus globale vers l’API java.time
.
Rendons tout de même à César ce qui est à Jules : bien que difficilement lisible/maintenable, le code historique avait été éprouvé par plusieurs année d’utilisation. Les tests mis en place ont d’ailleurs prouvé que le nouveau code donnait exactement le même résultat que l’ancien, y compris sur les cas aux limites.