Stop with serialVersionUID!

Je ne compte plus les projets où la moitié des classes commencent par le fameux

private static final long serialVersionUID = 875713023L;

et ça m’énerve !

Alors bien sûr, les intentions des développeurs sont louables. L’IDE (Eclipse, I’m looking at you) émet un avertissement disant que la classe est sérialisable mais n’a pas de serialVersionUID. Pour avoir un avertissement de moins et faire plaisir à leur IDE, ils ajoutent un serialVersionUID à la classe.

Il y a un autre moyen de ne plus avoir d’avertissement : désactiver cette règle dans les options de compilation de l’IDE. Comme je suis sympa, je vais même vous dire où ça se trouve (dans Eclipse) :

Eclipse Java compiler preferences

Maintenant, ce qui serait encore mieux, ce serait de comprendre à quoi cette constante sert, pourquoi elle est parfois nécessaire, et pourquoi l’immense majorité du temps, elle ne l’est pas et ne fait que rajouter du bruit dans les sources.

Explication

Quand un objet est sérialisé, il est transformé en suite de bytes. Ce flux binaire est destiné à être retransformé, plus tard ou ailleurs, en une copie de l’objet original. On peut par exemple l’envoyer sur le réseau à destination d’un client. Ou on peut l’enregistrer sur disque pour être désérialisé plus tard.

Lorsque l’objet est sérialisé, en plus du nom de la classe de l’objet, la version de cette classe (la valeur de serialVersionUID) est aussi écrite avant l’état à proprement parler de l’objet. Cela permet, à la lecture, de détecter un conflit entre la classe de l’objet sérialisé et la classe présente dans le classpath à la désérialisation.

Si aucun serialVersionUID n’est défini dans la classe, la JVM en calcule un automatiquement à partir des noms de classe, d’interfaces, des champs et méthodes contenus dans la classe. Si la classe est modifiée (par exemple, des champs ou des méthodes sont ajoutés, supprimés, ou permutés), relire l’objet sérialisé ne fonctionnera pas : la JVM va comparer le serialVersionUID présent dans la suite de bytes avec celui généré pour la classe chargée en mémoire, va détecter une différence et va lever une InvalidClassException.

Ajouter un serialVersionUID à la classe, et lui laisser la même valeur même après une modification de la classe revient à dire : je sais que la classe a changé, mais je garantis que les changements apportés sont compatibles et permettent de lire une ancienne version à partir de la nouvelle, et vice-versa.

Le problème, c’est qu’il ne suffit pas de le dire : il faut aussi être capable de le faire ! Et le faire, ça nécessite de savoir quels changements sont compatibles et quels changements ne le sont pas. Ca nécessite de réfléchir très attentivement à chacune des évolutions apportées à la classe. Ca nécessite éventuellement d’implémenter des méthodes telles que readResolve(), readObject(), writeObject() pour conserver la compatibilité. Et curieusement, ça, personne ne le fait. Au mieux, tenter de lire une ancienne version de l’objet va donc lever une exception. Au pire, ça va fonctionner, mais retourner un objet dans un état complètement incohérent, incapable de remplir son contrat.

La plupart du temps heureusement, la sérialisation n’est pas utilisée comme mode de persistance à long terme, devant être capable de s’adapter à des changements de structure des classes. Lorsqu’elle est utilisée comme moyen de communication, les deux parties (le client et le serveur) partagent la même version des classes. Il est rare de déployer un serveur en version 1.2 qui doit toujours pouvoir parler à des clients en version 1.1.

Donc, TL;DR : si vous ne vous servez pas de la sérialisation comme moyen de persistance à long terme, et si vos clients et votre serveur sont déployés de manière homogène, désactivez cette option de compilation et ne mettez pas de champ serialVersionUID dans vos classes. Un bon coup de regexp-replace, et vous pouvez même les enlever.



blog comments powered by Disqus