Flutter: Qu’est-ce que le Null Safety?

Introduction

Récemment, Dart 2.12 a été livré dans Flutter 2. Cette mise à jour contient l’une des fonctionnalités les plus importantes du langage qui est le Null Safety.
Si vous programmez depuis un certain moment, vous avez surement dû subir au moins une fois dans votre vie un crash d’application provoqué par l’utilisation d’une variable dont vous ignoriez qu’elle était nulle.
Le Null Safety (connu sous le nom des optionnelles en Swift) permet au compilateur de vous aider à trouver et corriger ces problèmes avant l’exécution de votre code.

L’absence de données fait partie de la vie de tous les jours, par exemple, certains n’ont qu’un seul prénom.
Dire qu’une variable est null est un excellent moyen de modéliser des données que l’on n’a pas.
Le problème n’est pas d’assigner une variable à null mais bien de s’en servir alors que celle-ci ne contient rien.

Null Safety répartit les variables en deux catégories : non nullable et nullable. Par défaut, les variables sont non nullables.
Oublier d’en initialiser une ou affecter null à une méthode, conduit à une erreur de compilation.

Par exemple ici, le compilateur ne nous laissera pas passer null à la méthode suisJeMajeur car le paramètre n’est pas nullable.

L’opérateur nullable (?)

Et même si nous rendions le paramètre nullable (ou optionnel), en ajoutant le point d’interrogation au type, nous aurions tout de même une erreur de compilateur car nous ne pouvons comparer une valeur null avec une vraie valeur (ici un entier) :

Une manière de faire, serait d’utiliser l’opérateur ??.
Voici un exemple :

 

Dans cet exemple, nous utilisons l’opérateur ?? pour dire au compilateur: « Assigne à ageVerifie la valeur contenue dans la variable age si elle existe, sinon 0« . De cette manière, nous sommes certains que notre condition sera exécuté quoiqu’il arrive.

Le mot clé required

Le fait d’avoir un paramètre nommé (voir ci-dessous) nous oblige à préciser si le paramètre est optionnel ou non.

Précédemment, nous avons vu comment déclarer une propriété optionnelle (le point d’interrogation).
Maintenant, voyons comment dire au compilateur que nous souhaitons que cet argument soit obligatoire :

Avec le mot clé « required » nous explicitons le fait que ce paramètre est obligatoire.

L’analyse  statique

Le système derrière le Null Safety s’appelle l’analyse statique. Cette analyse examine toutes les manières dont votre code pourrait s’exécuter, chaque flux de contrôle qui pourrait être pris, valeur qu’une variable pourrait avoir, et si, lors de cette analyse il y a un problème, vous avez une erreur de compilation.
L’analyse est extrêmement prudente et pessimiste car parfois, nous sommes sûrs de notre code et nous souhaitons pouvoir utiliser une variable à priori null, sans avoir besoin de la vérifier, mais le système n’est pas assez intelligent pour le comprendre.

Prenons cet exemple :

Ici, nous avons une méthode qui prends en paramètre une liste d’arguments. Si cette liste d’arguments n’est pas vide, non aassignons la variable prénom.
Puis par la suite, encore une fois, si cette liste d’arguments n’est pas vide, nous utilisons cette variable.
Nous savons que cette liste d’arguments ne changera pas et n’utilisera la variable que lorsqu’elle aura été initialisée. Mais le compilateur n’a aucun moyen de savoir si les deux conditions renverront toujours la même valeur.
Donc l’analyse statique permet de bloquer les potentiels problèmes mais également des programmes corrects comme celui-ci qui pourraient frustrer les utilisateurs.

Pour pallier ce problème, Dart a étendu une fonctionnalité déjà existante du langage qui s’appelle la promotion de type. Prenons cet exemple :

Si vous effectuez un test de type sur une variable, une expression is dans un if, puis l’utilisez dans le corps de l’instruction if, alors Dart favorisera la variable pour avoir ce type plus spécifique.
Par exemple, ici nous vérifions qu’Object est de type String, et, étant donné que c’est le cas, nous pouvons utiliser la méthode isEmpty appartenant au type String.

Ce qui se passe ici, c’est ce qu’on appelle l’analyse du flux de contrôle :
Le système de type examine les manières dont l’exécution circule dans le programme. Si un morceau de code ne peut être atteint qu’en passant par un test de type réussi, alors il peut prouver que la valeur de la variable doit avoir ce type.

Comme je le disais auparavant, Dart a étendu cette fonctionnalité avec les valeurs nullables dont voici un exemple :

Ici, le paramètre peutEtreUneString est une chaîne de caractère pouvant être nullable.
Dart regarde si cette variable n’est pas nulle, et si ça n’est pas le cas, alors il promeut la variable vers un type non nullable que l’on peut ensuite utiliser.

De cette manière, la majorité du code écrit avant la nouvelle version de Dart est déjà compatible avec cette nouvelle version. Car, de base, nous vérifions déjà qu’une variable ne soit pas nulle avant de l’utiliser.
Je ne dis pas que vous n’aurez pas de changements à faire de votre côté car, il y a des endroits où l’analyse statique ne pourra pas voir que le code est sécurisé.
Cependant, la majorité du travail sera déjà mis en place.
Chez Agilidée, nous avons passés principalement du temps sur l’ajout de points d’interrogations ainsi que de la correction de certains tests (la manière dont nous générions des mocks également, mais c’est un autre sujet).

Mais, au-delà de ça, nous n’avons pas eu de difficultés majeures à effectuer la migration.

L’opérateur d’assertion non nul (!)

Pour compléter la fonctionnalité, d’autres outils ont été mis en place comme l’opérateur d’assertion non nul : le point d’exclamation.

Dans cet exemple, nous sommes sûr que la variable reponses nous donnera une valeur, peu importe le paramètre (true ou false) et nous affirmons cette certitude avec l’opérateur !
C’est en quelque sorte un downcast explicite.

Le mot clé late

Un autre outil de Null Safety est l’opérateur late :

Late permet de dire au compilateur : « Je sais que cette variable n’est pas instanciée, mais lorsque je l’utiliserais, elle le sera ».

Et c’est ce que nous avons dans cet exemple :

  • On déclare notre variable _color dans notre Widget avec late
  • Elle est initialisée dans l’initState()
  • Puis, on l’utilise dans la méthode build

Ainsi, il reporte les vérifications de variables non initialisées à l’exécution.

Conclusion

Dans la vie de tous les jours, nous suivons un cycle qui se compose généralement de cette manière :

  1. Nous éditons du code
  2. On compile le programme
  3. Le code s’exécute
  4. On valide les changements

Et on recommence …
Grâce à Null Safety, inutile de faire toutes ses étapes pour se rendre compte que notre variable est null, le compilateur nous avertis directement à l’étape 1 (à l’édition du code).

Flutter via le langage Dart vient une nouvelle fois d’accélérer ce cycle.
Pour rappel, nous avons déjà le hot reload qui permet d’éviter de recompiler notre application lorsqu’elle est déjà lancée.

Aujourd’hui, nous venons de voir les nouveautés principales qu’apportent Null Safety au langage Dart :

  • Le point d’interrogation, permettant de dire qu’une variable est nullable
  • Le mot clé « required » permettant de dire au compilateur qu’une propriété est obligatoire
  • L’analyse statique ou la condition sur une variable pouvant être nulle, qui permet de pouvoir l’utiliser dans le cas où elle ne l’est pas
  • Le point d’exclamation, permettant d’affirmer au compilateur que nous sommes certains que cette variable existe
  • Le mot clé « late » permettant de dire au compilateur que la variable l’utilisant ne sera pas instanciée tout de suite, mais que, lorsque nous l’utiliserons, elle contiendra une valeur.

Ces améliorations permettent aux développeurs Flutter d’avoir une tranquillité d’esprit mais pas seulement.
Le fait d’avoir une preuve de certaines propriétés du programme, donne au compilateur la possibilité d’être optimisé.

Avant Null Safety, le compilateur devait agir avant chaque appel de méthode tout comme le font les compilateurs pour des langages avec des références nullable. Ces vérifications rendent l’exécutable plus grand et plus lent.

Avec Null Safety, le compilateur peut rejeter ces vérifications lorsque le type statique de l’expression n’est pas Nullable car il sait que null ne peut jamais atteindre ce point.
De plus, il peut stocker des variables directement dans des registres ou sur la pile au lieu de les allouer sur le tas.
Ainsi, on obtient une application plus rapide avec moins de mémoire.

Et toi, que penses-tu de cette fonctionnalité?
Y aurait-il d’autres problématiques dont je n’aurais pas parlé auxquelles tu penses?
As-tu participé à la Google I/O 2021?

Comme d’habitude, je serais curieux d’avoir ton avis et de te lire dans les commentaires.
À bientôt !

Photo by Piotr Chrobot on Unsplash

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.