Les API changent, évoluent, s’améliorent, et ne sont pas toujours compatibles entre leurs versions.
C’est ce qui fait qu’un logiciel vieux de deux ans ne fonctionne parfois plus correctement, souvent après une mise à jour du système d’exploitation ou de la plate-forme (par exemple les bibliothèques) sur laquelle s'appuie le logiciel.
Le problème est général à la programmation, donc il concerne tous les systèmes d’exploitation et toutes les plate-formes.
Au final, ce qui cause ces problèmes, particulièrement énervants pour l’utilisateur final, c’est avant-tout un choix.
Choix de la compatibilité entre versions d’une API
[ Ici, je parle de versions majeures des API, et non des mises à jour mineures ]
Pour clarifier un peu le problème, voici une liste des différents choix que j’ai pu rencontrer jusqu’à présent.
Aucune compatibilité
Ça peut paraître absurde à beaucoup, mais certains programmeurs ou architectes font le choix de ne tout simplement pas permettre à un logiciel prévu pour une version A d’une API de fonctionner avec la version B de la même API.
Schématiquement, ça ressemble à ceci:
Ici les différentes versions ne proposent aucune compatibilité.
C’est évidemment ce qui demande le moins de travail aux programmeurs.
Par contre, un logiciel basé sur une API A devra être livré avec la même version A de la bibliothèque de fonctions, ce qui peut représenter une grosse taille, du temps de téléchargement et des incompatibilités à l’exécution.
Redirection vers la version immédiatement suivante
Note: C’est un concept différent de la compatibilité descendante.
Schématiquement:
Ici, lorsqu’on crée une nouvelle version (B) de l’API, la 1ère version (A) est réécrite pour renvoyer vers la version suivante (B). Et ainsi de suite.
L’intérêt de cette technique, c’est qu’un logiciel écrit pour une très vieille version de l’API (A) peut utiliser l’API la plus récente (E), sous forme de sa bibliothèque de fonctions.
Cela évite le problème de l’Enfer des bibliothèques, c’est à dire essentiellement qu’on n’a pas besoin de livrer chaque logiciel avec ses bibliothèques associées.
Avantage supplémentaire: les anciennes API étant maintenues, par le biais des renvois vers des versions plus récentes, on trouve moins de bogues.
Du point de vue du travail, cela oblige, à chaque nouvelle version d’un API, de revoir la précédente pour remplacer ses fonctions obsolètes par des renvois vers la nouvelle.
Ça peut sembler représenter beaucoup de travail, mais en pratique ce n’est pas tellement le cas, car:
- En général une faible partie de l’API change d’une version à une autre.
Les plus gros changements ont lieu dans l’implémentation des fonctions, ce qui en soi ne change rien à l’API. - Il est classique que les fonctions et les concepts d’une nouvelle version d’une API englobent et surpassent ceux de l’ancienne version.
Par exemple, une fonction pouvant calculer le sinus d’un nombre flottant sera étendue pour pouvoir calculer aussi le sinus d’un nombre imaginaire (pourquoi pas ?).
Dans un tel cas, l’ancienne fonction peut tout simplement renvoyer vers la nouvelle, avec des paramètres par défaut. Car qui peut le plus peut le moins.
Ce système, bien que limité à des paires de versions, permet tout de même à la plus ancienne version (A) de renvoyer, par bonds successifs, vers la plus récente (E). Pour une somme de travail acceptable, ça reste d’un intérêt non négligeable.
Redirection vers la dernière version
Schématiquement:
L’avantage de ce principe, c’est que toutes les anciennes versions renvoient vers la dernière, ce qui les maintient le plus à jour possible, évitant les vieux bogues et utilisant les toutes dernières techniques.
Par exemple, si c’est l’API d’un système de fenêtrage, alors même une très vieille application utilisera la toute dernière bibliothèque et donc elle aura un affichage dernier cri, tout-à-fait compatible avec les applications les plus récentes, et elle sera bien intégrée dans le système.
L’inconvénient est bien sûr que le travail est de plus en long à chaque version.
De plus il faut avoir des programmeurs connaissant bien les plus anciennes versions de l’API, ce qui est de plus en plus rare au fil du temps car les plus jeunes gens sont formés sur les API les plus récentes.
Par contre, du point de vue de l’utilisateur final, c’est la meilleure solution.
Réutilisation d’une plus ancienne version
Schématiquement:
Ici, une nouvelle version renvoie vers une plus ancienne.
Étrangement, ce concept rétrograde est très présent dans les systèmes d’exploitation.
Il est d’ailleurs systématiquement utilisé dans cet autre concept: les plateformes.
Exemple de Windows (8)
Ce choix parait justifié par la grande différence qui existe entre l’API de la nouvelle plateforme et celle de l’ancienne.
Toutefois, je ne peux pas m’empêcher de penser qu’il serait bien plus logique de choisir un des autres concepts de compatibilité.
Dans ce dernier exemple, DotNet aurait pu être créé à partir de zéro et les parties obsolètes de Win32 réécrites pour n’être plus qu’un renvoi vers l’API la plus récente.
Idem pour WinRT.
Bien entendu, il est clair que cela aurait demandé bien plus de travail et causé bien plus de difficultés.
Il aurait fallu réfléchir longuement et mettre au point de vrais nouveaux concepts, les expérimenter tout aussi longuement. Et ensuite il aurait fallu réécrire toute la partie de Win32 devant renvoyer vers WinRT (par exemple).
Ceci dit, le résultat aurait été d’un grand avantage: Windows 8 aurait été un système homogène, au lieu de proposer deux types d’interface graphique différents et séparés.
On peut aussi remarquer que pour Windows 8 Microsoft a dû réécrire de nombreux logiciels et panneaux de configuration pour WinRT, ce qui représente également du travail. D’un certaine façon, ils ont économisé du travail avec ce choix de sur-plateforme, mais en ont gaspillé avec la réécriture des applications.
Toutefois, il faut aussi prendre en compte le bénéfique renouvellement des applications. Une application réécrite l’est en tentant compte de nouveaux concepts et des dernières techniques (par nécessairement liés à la plateforme).
Conclusion
Il me semble clair qu’il n’existe aucune méthode parfaite, et qu’on doit faire des choix difficiles.
Toutefois si on a une vision d’une informatique pérenne, alors il existe au moins une façon de faciliter l’évolution d’une API: la créer la plus simple et de plus haut niveau d’abstraction que possible, pour éviter que ses fonctions et ses concepts ne soient vite périmés.
Il me semble nécessaire d’éviter les fonctions trop spécialisées.
Nous avons une culture de l’optimisation qui nous vient des temps anciens lorsque les ordinateurs étaient si peu puissants qu’il fallait compter chaque octet et chaque temps d’horloge.
Il faut donc désormais penser différemment, plus tournés vers l’avenir et donc vers des solutions plus universelles et plus pérennes, au moins pour les API.
Cela ne nous empêchera pas d’optimiser l’implémentation des fonctions. Après tout, l’API n’est qu’une façade, une porte d’accès.