Des raisons d’utiliser l’inférence de type

L’utilisation de l’inférence de type sur les variables locales en Java peut être source de débat sur son bien-fondé au sein des pratiquant(e)s. Cet article ne se veut pas objectif, une des réponses les plus pertinentes sur Reddit.com à la question “Is using var okay?” étant :

Let’s have a 4 hour company wide meeting to discuss this.

Histoire

En 2016 Brian Goetz propose via la JEP 286 (Java Enhancement Proposal) l’introduction d’un nouveau mot-clé dans le standard, qui permet l’inférence de type de lors de la déclaration-initialisation de variables locales, comme on le trouve dans certains langages (auto en C++, var/let/const en Javascript). L’objectif est alors d’alléger l’écriture tout en conservant le typage statique du langage.

Il propose également plusieurs choix de syntaxe, parmi lesquels (liste non exhaustive):

const x = expr
final x = expr
let x = expr
def x = expr
x := expr

Un sondage est alors réalisé parmi les développeurs Java pour récolter les avis sur la pertinence d’une telle fonctionnalité, ansi que sur la préférence syntaxique. Il en ressort des résultats résumés dans cette communication que 74% des personnes interrogées se déclarent favorables à son introduction, 12% moyennement convaincues et 10% contre avec comme principal argument la réduction de la lisibilité.

La proposition sera adoptée et incluse dans Java 10, publié en mars 2018.

Raison n°1 : on écrit moins, on lit moins, on se porte mieux…

La première raison pratique est évidememment la réduction de code à écrire. Dans l’exemple qui suit, on suppose que integerList est de type List<Integer> qu’on souhaite filtrer pour obtenir un générateur.

Sans l’inférence on écrirait :

// Nécessite également l'import de Stream
Stream<Integer> numbersGreaterThan4 = l.stream().filter((x) -> (x >= 4));

Avec l’inférence:

var numbersGreaterThan4 = integerList.stream().filter((x) -> (x >= 4));

Raison n°2 : …et de toute façon on ne lit pas vraiment…

Un des arguments principal contre le var c’est “on ne sait pas ce qu’on manipule”. Lors d’une assignation (et encore plus lors d’une déclaration-assignation), ce qui nous intéresse en premier c’est l’opérande de droite. Ce qu’on retient de la ligne c’est comment et où l’information qu’on cherche à stocker a été obtenue. On pourrait même dire que dans ce cas la lecture se fait de droite à gauche:

var users = db.getAllUsers();

Le type de users ici n’importe pas au moment où la ligne est lue, c’est le bien flux de données qui nous intéresse, à savoir que l’information provient de la base de données.

Raison n°3 : …parce que la vérité est ailleurs.

Les critères de lisibilité d’un code sont vastes, enclins à de nombreuses discussions, la recherche scientifique dispose d’ailleurs de papiers sur le sujet, et on ne cherchera pas à les définir ici.

Dans tous les cas, la lisibilité du code ne dépend certainement pas uniquement de l’utilisation de telle ou telle fonctionnalité d’un langage. La responsabilité d’écrire un code lisible appartient au développeur plus qu’au langage. Un des fondamentaux de ce métier est de savoir définir des types et des structures de données qui apportent d’eux-même une sémantique à l’écriture. C’est la définition de ces types qui permet de comprendre la nature des objets que l’on manipule, leur propriètes et parfois même une partie de leur fonctionnement, et c’est donc bien par une bonne définition des types que tout doit passer.

En exemple, on voit de plus en plus souvent, en Python notamment, l’utilisation de dictionnaires (équivalent des HashMap<> en Java) pour stocker toute sorte d’objet. Bien qu’ils soient aisément manipulables et qu’ils présentent une bonne performance générale, il s’agit là probablement d’une facilité qui tend plus à obscurcir un code qu’à en enrichir sa compréhension immédiate. Retour en Java avec un exemple de 2 classes qui permettent de stocker un objet lu à partir d’un JSON:

Avec un type bien défini, les propriètes sont visibles dès le départ,

// Définition
record Article(Integer id, Float price, String name) {}

// Utilisation
var article = new Article();

// Manipulation classique via les accesseurs...
int id = article.getId();
article.setPrice(12.50f);

Sans type, la définition n’a pas d’existence propre, et peut évoluer (ou dégénérer) au fil du code.

// Pas de définition de type particulier, on se base sur HashMap<String, Object>

// Utilisation
var article = new HashMap<String, Object>();

article.put("id", 14);
article.put("price", 12.50f);
article.get("name ",count);

L’utilisation de var n’est donc pas déterminante dans la charge cognitive allouée à la lecture.