Si vous ne faites rien, faites au moins rien correctement
(devblogs.microsoft.com)- Même si une plateforme donnée ou l’abandon d’une fonctionnalité empêchent de fournir la fonctionnalité réelle, il faut concevoir un comportement qui « ne fait rien » dans le cadre du contrat d’API documenté afin d’éviter de casser les applications existantes
- Dans un environnement sans infrastructure d’impression, comme la Xbox, une réponse de succès indiquant 0 imprimante est plus sûre pour les applications écrites en supposant un PC qu’une
NotSupportedException - Cette approche est celle d’une API inerte : la surface d’API et la spécification sont conservées, mais l’API n’effectue en pratique aucune opération utile
- Dans une API de widgets en cours de retrait, si
CreateWidgetréussit tout en renvoyantnullptr, l’appelant se retrouve dans un état contradictoire ; le chemin d’échec documentéERROR_CANCELLEDest donc plus cohérent - Une API inerte bien conçue fait disparaître uniquement la fonctionnalité et utilise les chemins d’échec que le code existant est déjà censé gérer, afin de réduire les exceptions, handles invalides et crashs
Même sans fonctionnalité, le contrat d’API reste en place
- Il arrive qu’une API doive être conçue pour ne rien faire, mais cette absence d’action doit rester conforme au code existant et au comportement documenté
- Une absence d’action mal conçue peut produire des exceptions, des valeurs de retour contradictoires ou des états invalides, entraînant des crashs d’applications ou des erreurs utilisateur inutiles
Comment neutraliser les API d’impression sur Xbox
- Windows dispose d’une vaste infrastructure d’impression, mais Xbox n’a pas cette infrastructure
- Si une application tente d’imprimer sur Xbox et que la fonction d’impression lance une
NotSupportedException, le problème peut s’aggraver- Les applications installées sur Xbox ont très probablement été testées principalement sur PC
- Sur PC, l’impression étant toujours possible, une exception non gérée peut provoquer le crash de l’application
- Même si l’exception est interceptée, l’utilisateur peut voir un message d’erreur du type « fournissez un incident code au support »
- Une meilleure conception consiste à faire réussir la fonction d’impression tout en signalant qu’aucune imprimante n’est installée
- L’application affiche l’interface de sélection d’imprimante avec une liste vide
- L’utilisateur constate qu’il n’y a pas d’imprimante et peut annuler la demande d’impression
- Pour les applications qui tentent d’aider à installer une imprimante, la fonction d’installation peut retourner immédiatement avec un code de résultat indiquant que l’utilisateur a annulé l’opération
Conditions d’une API inerte
- Ce comportement qui « ne fait rien » est appelé inerte
- Une API inerte conserve la même surface d’API et se comporte conformément à sa spécification, mais n’effectue en réalité aucune opération utile
- L’essentiel est de ne rien faire d’une manière cohérente avec la documentation et présentant le moins de risques possible pour le code existant
- L’API d’impression pourrait aussi ajouter une fonction permettant de vérifier si l’impression elle-même est disponible
- Les applications peuvent utiliser cette fonction pour masquer le bouton Print sur les systèmes qui ne prennent pas du tout en charge l’impression
- Les applications qui supposent simplement que l’impression est possible se comporteront comme sur « un système sans imprimantes et où les tentatives d’installation n’ont aucun effet »
Conception à éviter pour une API de widgets en cours de retrait
- L’API à retirer comprend une fonction qui crée un handle de widget, une fonction qui reçoit un handle de widget et une fonction qui ferme un handle de widget
- La proposition initiale consistait à faire renvoyer
S_OKparCreateWidgettout en définissant*widget = nullptr- L’appel réussit, mais aucun handle valide n’est reçu
- Dans certains codes de test réels, la réussite était déterminée non pas par la valeur de retour, mais par le fait que le handle soit
null
- Une conception où
EnableWidgetrenvoie « handle invalide » perturbe aussi l’appelant- L’application transmet à
EnableWidgetle handle reçu après le succès deCreateWidget - L’API répond que ce handle n’est pas valide
- L’appelant se retrouve dans un état contradictoire : « j’ai demandé un widget et je l’ai reçu, mais quand je le remontre, on me dit que ce n’est pas un widget »
- L’application transmet à
Conception corrigée via un chemin d’échec documenté
- La documentation existante prévoit une valeur de retour
ERROR_CANCELLED, indiquant que l’utilisateur a annulé la création du widget - Les applications doivent déjà gérer la possibilité que le widget ne soit pas créé en raison de conditions externes ; on peut donc faire en sorte que la création du widget échoue toujours comme une annulation par l’utilisateur
- Le flux de la conception corrigée est simple
CreateWidgetlaisse*widget = nullptret renvoieHRESULT_FROM_WIN32(ERROR_CANCELLED)- Comme la création du widget échoue toujours, aucun handle de widget valide n’existe
GetWidgetAliases,EnableWidgetetCloserenvoientE_HANDLE, indiquant que le handle n’est pas valide
- Avec cette approche, puisqu’il n’y a pas de widget, il n’est pas nécessaire de créer d’alias factice
- Comme aucun widget valide n’existe, il n’y a pas non plus de situation où l’application pourrait normalement demander un alias
Critère à appliquer quand on porte des API de bureau vers un autre environnement
- Sur desktop, les API d’impression ont toujours existé, et la fonction qui « récupère la liste des imprimantes » est documentée comme ne lançant pas d’exception
- Quand cette API est portée sur Xbox, pour que les applications desktop existantes continuent de fonctionner, le bon comportement inerte consiste à renvoyer honnêtement le fait que « la Xbox n’a pas d’imprimante »
- Lancer une exception à la question « combien y a-t-il d’imprimantes ? » n’est pas conforme au contrat d’API documenté
- Le même principe s’applique lorsqu’on retire une API existante
- Même si elle ne fait rien d’utile, le comportement de l’API doit rester cohérent avec son contrat
- Il est important d’utiliser des valeurs de retour et des états que le code existant sait déjà gérer
2 commentaires
Avis sur Lobste.rs
Blague à part, je ne suis pas d’accord avec ce genre de programmation excessivement défensive ni avec cette expérience utilisateur. Dans ce cas, le logiciel ne fait pas son travail sans qu’on sache pourquoi, et il n’y a aucun moyen de le découvrir. L’application devrait intercepter l’erreur et, si possible, produire un message convivial pour l’utilisateur, ou au moins lui afficher le message d’erreur d’origine. S’il s’agit d’une tâche en arrière-plan, il devrait y avoir des journaux d’erreurs
Je reconnais que cet article est écrit du point de vue d’un développeur d’API plutôt que d’un développeur d’applications. Il faut donc documenter les erreurs d’API et fournir des messages d’erreur exploitables par la partie appelante
Je n’aime pas non plus le fait de masquer un bouton dans l’interface sous prétexte qu’on n’a pas les droits d’accès. Si l’espace le permet, il vaut mieux afficher le bouton mais le désactiver, puis fournir un message au survol indiquant comment l’activer
Globalement, il vaut mieux exiger de la rigueur. Mais quand on a un milliard d’utilisateurs existants, éviter autant que possible de tout casser est extrêmement judicieux, et du point de vue de l’utilisateur, cela crée aussi une vraie valeur au niveau système puisque « ça marche ». Au final, l’attitude devrait être : échouer vite, mais sans faire échouer trop de choses
Cela relève moins de l’API que de la stabilité ABI. Sous Windows, même un logiciel compilé il y a 15 ans doit continuer à fonctionner autant que possible sur un nouvel OS. Comme on ne peut pas changer la signature d’une fonction, il faut parfois recourir à un mensonge pieux pour qu’une API qui n’a plus vraiment de sens continue tout de même de fonctionner
Par exemple, l’API fait encore comme si Active Desktop existait. L’alternative serait de casser en masse de vieux logiciels existants
On ne sait plus si la fonctionnalité a disparu ou si elle a simplement été enfouie ailleurs entre-temps
Mais si elle ne le fait pas, les gens qui utilisent cette application reprocheront cela à Windows. Ils accuseront Windows, pas l’application, et c’est pareil même quand l’application plante
C’est pour cela que Microsoft met en place des contournements. Il est bien plus simple de laisser la tâche d’impression disparaître discrètement, et l’utilisateur finit alors par se dire après un instant : « Ah oui, c’est vrai, il n’y a pas d’imprimante »
ifpour vérifier la nullité. Chaque fois que je vois ça, cet article me revient en têteJ’ai donc demandé à l’agent de ne pas répéter les vérifications de nullité, d’utiliser des fonctions inoffensives, et de vérifier une bonne fois au moment de la déclaration que la valeur ne sera jamais nulle
Commentaires Hacker News
J’ai appris à avaler les erreurs, et je considère ça comme une mauvaise pratique
Non seulement cela ne résout pas le vrai problème — l’impossibilité d’imprimer sur Xbox — mais cela masque aussi à quel point le logiciel est cassé, ce qui rend la détection des bugs et les tests bien plus difficiles
J’aime le fait que
panicen Go n’ait pas été conçu pour être abusé à l’exécution ni pour être récupéré, mais comme un outil qui déclenche une forte sirène au moment des tests pour signaler qu’un programmeur a fait une erreurQuand les acteurs d’un système cachent activement leurs propres défauts ou erreurs, trouver la cause et corriger le problème devient exponentiellement plus difficile
Dans les systèmes B2B, microservices et centrés sur les API avec lesquels je travaille, une requête hors spécification doit échouer rapidement
Mais Windows s’est énormément contorsionné au nom de la compatibilité, au point d’aller jusqu’à patcher la mémoire de logiciels notoirement cassés comme SimCity pour les maintenir en vie : https://arstechnica.com/gadgets/2022/10/windows-95-went-the-...
Dans un environnement desktop où l’utilisateur ne peut pas corriger le code du système, l’expérience utilisateur finale est souvent plus importante que la justesse
Peu importe pourquoi un BSOD s’est produit ; Microsoft a appris que c’est sa réputation qui en souffre à cause de mauvais logiciels ou matériels
Personnellement, je préfère les conceptions qui échouent rapidement ou s’arrêtent face à une entrée ou une condition invalide, mais je comprends aussi la valeur de cette approche dans cet environnement
Ce qui compte, c’est le contexte, pas l’application dogmatique d’un seul camp
Par exemple, il ne faudrait pas appliquer l’approche de Chen à la conception d’un réacteur nucléaire ou du trading haute fréquence, alors que cela peut être une excellente approche pour un moteur de jeu
La question de savoir si un composant du runtime doit rester « inoffensivement désactivé » est une décision de conception en amont, avant même de savoir si l’implémentation doit produire un
panicou nonDans ce cas, c’est à Microsoft de définir ce que signifie « imprimer sur Xbox »
Ils peuvent le définir comme une erreur, ou en théorie comme une fonctionnalité potentiellement prise en charge plus tard mais sans moyen réel d’exécution pour l’instant
En branchant une imprimante USB, et si un « jeu » comme le port d’extension des jeux Gameboy compatibles avec la GB Printer sait comment imprimer, alors il serait théoriquement possible d’imprimer sur Xbox
Le fait qu’un jeu Xbox tente d’imprimer n’est pas forcément une erreur intrinsèque ; le définir comme une erreur n’est qu’un choix avec des compromis, notamment sur la portabilité des applications
On pourrait afficher un écran de sélection sans aucune imprimante disponible, comme sur Xbox, faire mentir l’API en lui faisant dire qu’elle a imprimé, ou prendre en charge une vraie impression
Ce n’est qu’après avoir défini « imprimer sur Xbox » comme une erreur que la question de savoir si cette erreur est levée ou avalée devient un sujet d’implémentation ou de débogage
Il y a deux traitements possibles : renvoyer une erreur parce qu’il n’y aura jamais d’imprimante sur la machine, ou renvoyer une liste d’imprimantes vide pour la même raison
Les deux sont valables, mais un seul ne casse pas l’espace utilisateur
« Déclencher une forte sirène » est bien pour les testeurs ou les développeurs, mais beaucoup moins pour l’utilisateur final
Du point de vue de l’utilisateur final, mieux vaut avaler l’erreur que voir l’application planter
On ne sait pas ce qui a échoué : le logiciel de mon ordinateur, le matériel, le câble Ethernet ou le Wi-Fi, le switch, le routeur, le modem câble, le câble Internet jusqu’à la maison, le FAI ou le système distant auquel on essaie de se connecter ; il n’y a eu aucun progrès
Je m’attends à des réactions négatives, mais c’est précisément ce genre de saleté qui explique pourquoi un document Word ’97 ou un jeu compilé il y a 30 ans pour MS-DOS s’ouvre comme prévu quand on clique dessus
La rétrocompatibilité est toujours messy : soit on la fait imparfaitement, soit on ne la fait pas du tout
Au final, on se retrouve à utiliser GOG, qui virtualise lui aussi l’environnement
Par exemple, je ne pense pas que beaucoup de gens réussiraient à installer le Sim City original sur Windows 11
La mise en forme dépendait du pilote d’imprimante installé
En revanche, un fichier LaTeX vieux de 35 ans fonctionne toujours très bien aujourd’hui
Word ne pouvait même pas garantir qu’en rouvrant le document sur le même ordinateur avec la même version, la mise en forme resterait inchangée, et c’est pour cela que j’ai appris TeX
Microsoft a aussi cassé d’innombrables jeux en arrêtant GFWL
Le Dark Souls original était injouable jusqu’à sa ressortie sur Steam, ce qui obligeait à recourir au piratage
Une boîte de dialogue « non pris en charge » s’affiche, et même si l’on peut télécharger dosbox, ce n’est pas « exactement comme prévu »
Presque à chaque fois, j’ai fini par abandonner ou par les racheter sur GOG ; en pratique, j’ai payé pour le travail de mise en ordre des problèmes de compatibilité
Il n’y a pratiquement rien de plus frustrant qu’une interface d’appareil qui laisse entendre, via l’UI, que quelque chose pourrait exister, pour finalement indiquer que ce n’est pas disponible pour le moment
Au final, il faut perdre du temps avant de découvrir que ces appareils ne sont pas pris en charge et que cet écran n’était qu’une maquette fabriquée par quelqu’un
Si l’app n’a pas prévu le cas où l’impression n’est pas prise en charge et que l’impression lève une exception, elle va probablement planter telle quelle
Si l’app a été conçue pour gérer l’absence de prise en charge de l’impression, elle devrait d’abord vérifier explicitement via une API si l’impression est supportée et, si ce n’est pas le cas, ne pas afficher d’UI d’impression
L’article parle non pas de ce type d’app, mais de la manière de gérer les apps qui ne font pas cette vérification au préalable
Le bug est dans l’app, mais une bonne plateforme devrait essayer de faire en sorte que même les mauvaises apps continuent à fonctionner autant que possible, plutôt que de simplement les laisser planter
Le rôle de Microsoft est d’éviter qu’une app plante simplement parce que son éditeur n’a pas pris les imprimantes en compte
Faire en sorte qu’une app demande à l’utilisateur de choisir une imprimante, puis lui montrer une liste vide, donne l’impression que Microsoft n’a rien appris en 30 ans : https://www.reddit.com/r/hacking/comments/djvzd/windows_nt_l...
La direction concrète proposée dans l’article — à savoir que le composant doit souffrir avant l’utilisateur — est juste, mais le cadrage est très agaçant
Des formulations du genre « il y a des moments où l’API ne doit rien faire » ou « c’est une erreur qu’une fonction d’impression lève
NotSupportedException» ne constituent absolument pas un conseil généralC’est un hack pour prendre en charge des clients médiocres ; c’est parfois nécessaire, mais ce n’est ni une pratique normale ni un conseil qu’on peut généraliser
Ce type de formulation trahit une intériorisation de la souffrance propre au métier de développeur chez Microsoft
Si le client n’a pas changé mais que l’API le casse, alors ce n’est pas le client qui est mauvais, c’est l’API
C’est quelque chose de parfaitement normal et généralisable sur toute plateforme largement adoptée qui prend la rétrocompatibilité au sérieux
Windows en est le cas emblématique, mais on retrouve la même situation avec l’ABI du noyau Linux, glibc, la plateforme web, Java, etc.
On ne peut pas remonter le temps pour recompiler ou réécrire ces clients
Si le client vérifie déjà
NotSupportedException, alors il faut le renvoyer ; sinon, il faut adopter une autre approcheIci, c’est Windows
On ne doit pas casser les applications dont les utilisateurs dépendent
S’il existe des apps clientes « médiocres » ou mal conçues, il faut utiliser tous les contournements, shim et hacks de compatibilité nécessaires pour qu’elles se comportent comme prévu
Même s’il faut ajouter un code spécial de gestion mémoire pour que SimCity fonctionne
Votre client en dépend, et si ça s’arrête de fonctionner à cause d’un changement que vous avez introduit, c’est vous qu’il blâmera
Ici, tout le monde s’obsède sur le fait de « ne rien faire » et de « gérer les erreurs », mais l’article semble avoir mal expliqué qu’il ne s’agit pas d’avaler tous les problèmes en chantant kumbaya
Le vrai contexte, c’est l’émulation et la compatibilité pour des logiciels qui ne vont pas changer, ce qui est très différent de la plupart des situations
Microsoft lui-même brouille souvent la frontière entre les deux, ce qui rend la chose encore plus confuse, mais il n’est pas question ici de gestion d’erreurs au sens général
L’émulation consiste par nature à mentir
Il n’existe en réalité aucun moyen de brancher une imprimante à une Xbox, un jeu Windows tourne sur Linux, et un faux iPhone peut en fait être un serveur ARM sur AWS
Si l’on va mentir, il faut le faire de manière crédible, afin que les applications existantes continuent à fonctionner
Quand Microsoft dit qu’on peut porter des apps Windows existantes sur Xbox, ces apps Windows avaient la promesse qu’elles pouvaient imprimer ; Xbox doit donc mentir de façon à faire croire que c’est possible
En revanche, si un programme fait un use-after-free et qu’on le contourne discrètement comme si le bug n’existait pas, ce n’est pas la bonne manière de faire
Personne n’a jamais promis qu’un use-after-free existant serait aussi compatible, bug pour bug, sur la nouvelle plateforme
C’est simplement une hypothèse adoptée parce qu’elle soutient la position souhaitée
En réalité, le contenu de l’article s’applique tout autant si j’écris demain un programme pour Xbox et que j’essaie d’imprimer
Rien n’y distingue ce comportement comme réservé à des logiciels anciens et non maintenus
J’aime et je n’aime pas cette approche à la fois
Instinctivement, je n’aime pas traiter ce genre de problème par conformité malveillante
En revanche, si l’objectif est de permettre à davantage d’utilisateurs d’exécuter davantage de logiciels sur la plateforme, même avec une impression défaillante, alors je suis tout à fait d’accord pour dire que c’est une bonne décision
Ce fil de commentaires était étrange
Il y a manifestement des gens qui détestent tout ce que fait Microsoft
Le cœur du billet consiste à rendre le passage d’une app (UWP) vers XBOX sans friction
Que l’app « fonctionne tout simplement », sans recompilation, sans
ifdefsale et sans travail supplémentaire, puis que le développeur l’ajuste ensuite à la plateforme XBOXL’important, c’est que le développeur ait pu porter son app sur XBOX sans effort, ce qui est bénéfique à la fois pour la plateforme et pour le développeur
Pour l’utilisateur, la liste d’imprimantes vide est un signal clair qu’il ne peut pas imprimer
Surtout, l’app ne plante pas et n’affiche pas la pire erreur possible du type « Un problème est survenu. Veuillez réessayer plus tard »
Ce message n’offre pas une meilleure expérience utilisateur
Le développeur peut certes recompiler l’app pour ajouter un message plus explicite, mais on revient alors au point de départ
Par exemple, l’accusation selon laquelle il s’agirait d’avaler les erreurs passe complètement à côté du sujet
Le point essentiel que rate le camp critique de cet article, c’est qu’il n’y a ici aucune situation exceptionnelle qui justifierait de lever une exception
Aucun périphérique d’impression n’est connecté à cet appareil et, dans ce cas, c’est vrai par définition ; l’application doit donc gérer proprement cette situation, pas planter
On ne lèverait pas d’exception simplement parce qu’un ordinateur portable ne peut pas accéder à une imprimante
Après avoir ajouté une imprimante dans les paramètres, si l’on passe à l’onglet « Color Profiles » pendant l’installation, gnome-control-center plante si le timing s’y prête
En regardant de plus près, il essayait d’énumérer les imprimantes disponibles ; il sait qu’une imprimante doit exister, mais les informations ne sont pas encore là, donc il plante tel quel
Heureusement, il suffit d’attendre quelques secondes que GNOME termine l’installation de l’imprimante, puis de rouvrir les paramètres
Cela dit, pour une application essentielle qui permet à des utilisateurs finaux d’horizons variés d’utiliser correctement leur bureau, dans un monde idéal elle ne devrait jamais planter
Penser aux cas limites est difficile, et imaginer qu’il existe encore d’autres cas limites dans la gestion des cas limites l’est encore plus
Qu’il ait raison ou tort, l’auteur réfléchit au moins plus en profondeur que « c’est une mauvaise appli, donc il faut la réécrire pour prendre en charge Wayland »
À une époque, on considérait comme une excellente stratégie le fait que les navigateurs affichent au mieux les pages même quand le code HTML contenait des erreurs
L’idée était de deviner autant que possible l’intention de l’auteur, d’avancer quand même, et de considérer que les erreurs sont mauvaises et que les utilisateurs n’en veulent pas
Je pensais qu’on avait tiré des leçons de cette expérience, mais visiblement non
La tentative XHTML, qui voulait la remplacer par une validation stricte, a échoué ; à la place, le HTML5 qui a réussi est revenu en arrière et a explicitement défini comment interpréter toutes les séquences invalides possibles, afin que les navigateurs se comportent au moins de manière cohérente
Si les premiers navigateurs avaient affiché une erreur dès la première balise inconnue, les nouvelles fonctionnalités des navigateurs auraient été étouffées dès leur naissance
HelloetWorld!pour voir si, grâce à la sagesse que nous avons acquise, cela produit désormais une erreurMais si cela se rend toujours correctement, cela veut dire soit qu’il nous reste encore beaucoup à apprendre, soit qu’il y a peut-être du sens à gérer élégamment certains types d’erreurs tout en plantant dans des situations véritablement exceptionnelles
Si l’on remontait en 1999, il ne semble pas qu’il y aurait tant de choses à regretter