Si no vas a hacer nada, al menos haz correctamente la nada
(devblogs.microsoft.com)- Aunque no se pueda ofrecer funcionalidad real por una plataforma específica o porque algo fue retirado, para evitar que las apps existentes se rompan hay que diseñar un comportamiento de “no hacer nada” dentro del contrato de API documentado
- En entornos sin infraestructura de impresión, como Xbox, una respuesta exitosa con 0 impresoras es más segura para apps escritas con PC en mente que lanzar
NotSupportedException - Este enfoque se conoce como API inert, un diseño que mantiene la superficie y la especificación de la API, pero evita que realice trabajo útil en la práctica
- Si en una API de widgets en retirada
CreateWidgetdevuelve éxito pero retornanullptr, el llamador queda en un estado contradictorio; por eso, la ruta de error documentada conERROR_CANCELLEDes más consistente - Una API inert bien diseñada hace desaparecer solo la funcionalidad y usa las rutas de error que el código existente ya sabe manejar para reducir excepciones, handles inválidos y crashes
Aunque la funcionalidad desaparezca, el contrato de la API sigue ahí
- A veces hay que hacer que una API no haga nada, pero incluso esa inacción debe ajustarse al código existente y al comportamiento documentado
- Una inacción mal diseñada puede generar excepciones, valores de retorno contradictorios y estados inválidos, lo que termina en crashes de apps o errores innecesarios para el usuario
Cómo desactivar la API de impresión en Xbox
- Windows cuenta con una amplia infraestructura de impresión, pero Xbox no dispone de ella
- Si una app intenta imprimir en Xbox y la función de impresión lanza
NotSupportedException, el problema puede agravarse- Es probable que las apps instaladas en Xbox se hayan probado principalmente en PC
- En PC, imprimir siempre es posible, así que una excepción no controlada puede terminar en un crash de la app
- Incluso si se captura la excepción, el usuario podría ver mensajes de error como “proporcione un incident code al equipo de soporte”
- Un mejor diseño es que la función de impresión tenga éxito, pero informe que no hay impresoras instaladas
- La app muestra la UI de selección de impresora y presenta una lista vacía
- El usuario ve que no hay impresoras y puede cancelar la solicitud de impresión
- Si una app intenta ayudar a instalar una impresora, la función de instalación puede regresar de inmediato con un código de resultado que indique que el usuario canceló la operación
Condiciones de una API inert
- A este tipo de comportamiento de “no hacer nada” se le llama inert
- Una API inert mantiene la superficie de la API y se comporta según la especificación, pero en la práctica no realiza ningún trabajo útil
- Lo clave es no hacer nada de una forma consistente con la documentación y que minimice la posibilidad de romper el código existente
- En una API de impresión, también podría añadirse una función para comprobar si realmente se puede imprimir
- La app puede usar esa función para ocultar el botón Print en sistemas que no soportan impresión en absoluto
- Las apps que simplemente asumen que se puede imprimir seguirán comportándose como en un sistema “sin impresoras y donde intentar instalarlas no tiene efecto”
Diseño que debe evitarse en una API de widgets en retirada
- La API que se va a retirar tiene una función para crear un handle de widget, funciones que reciben ese handle y una función para cerrarlo
- La propuesta inicial era que
CreateWidgetdevolvieraS_OKmientras establecía*widget = nullptr- La llamada tendría éxito, pero sin entregar un handle válido
- En código de prueba real había casos que determinaban el éxito revisando si el handle era
null, no el valor de retorno
- Un diseño donde
EnableWidgetdevolviera “handle inválido” también confunde al llamador- La app pasa a
EnableWidgetel handle recibido tras el éxito deCreateWidget - La API responde que ese handle no es válido
- El llamador queda en un estado contradictorio: “pedí un widget, me lo dieron y luego me dicen que no era un widget”
- La app pasa a
Diseño corregido con una ruta de error documentada
- La documentación existente ya incluye el valor de retorno
ERROR_CANCELLED, que significa que el usuario canceló la creación del widget - Como la app ya debe manejar la posibilidad de que el widget no se cree por condiciones externas, puede hacerse que la creación del widget siempre falle como cancelación del usuario
- El flujo del diseño corregido es simple
CreateWidgetdeja*widget = nullptry devuelveHRESULT_FROM_WIN32(ERROR_CANCELLED)- Como la creación del widget siempre falla, no existe ningún handle de widget válido
GetWidgetAliases,EnableWidgetyClosedevuelvenE_HANDLE, indicando que el handle no es válido
- Con este enfoque tampoco hace falta crear un alias ficticio
- Como no existe un widget válido, tampoco hay un caso en el que la app pueda pedir un alias de forma legítima
Criterio al llevar APIs de escritorio a otros entornos
- En escritorio, la API de impresión siempre ha existido y la función para “obtener la lista de impresoras” está documentada para no lanzar excepciones
- Si se lleva esta API a Xbox y se quiere que las apps de escritorio existentes sigan funcionando, el comportamiento inert correcto es devolver honestamente que “Xbox no tiene impresoras”
- Lanzar una excepción ante la pregunta “¿cuántas impresoras hay?” no respeta el contrato de API documentado
- El mismo principio se aplica al retirar una API existente
- Aunque ya no haga trabajo útil, el comportamiento de la API debe seguir siendo consistente con su contrato
- Es importante usar valores de retorno y estados que el código existente ya pueda manejar
2 comentarios
Opiniones en Lobste.rs
Bromas aparte, no estoy de acuerdo con este tipo de programación excesivamente defensiva ni con esa experiencia de usuario. Así, el software deja de cumplir su función sin que nadie sepa por qué, y además no hay forma de averiguarlo. La app debería capturar el error y, si es posible, mostrar un mensaje amigable para el usuario, o al menos enseñarle el mensaje de error original. Si es una tarea en segundo plano, debería haber un registro de errores
Admito que este texto está escrito desde la perspectiva de un desarrollador de API, no de un desarrollador de aplicaciones. Así que hay que documentar los errores de la API y ofrecer mensajes de error sobre los que quien la invoca pueda actuar
Tampoco me gusta que se oculten botones en la UI solo porque no se tienen permisos de acceso. Si el espacio lo permite, me parece mejor mostrar el botón pero desactivado, y cuando el usuario pase el mouse por encima, mostrar un mensaje que explique cómo puede habilitarlo
En general, es mejor exigir corrección. Pero si ya tienes mil millones de usuarios existentes, es muy sensato evitar romper las cosas tanto como sea posible, y desde la perspectiva del usuario eso genera un valor real a nivel de sistema porque simplemente funciona. Al final, la actitud debería ser fallar rápido, pero no provocar fallos masivos
Esto se parece más a estabilidad de ABI que de API. En Windows, incluso el software compilado hace 15 años debe seguir funcionando en un sistema operativo nuevo siempre que sea posible. Como no puedes cambiar la firma de la función, si quieres que incluso una API que ya no tiene sentido siga funcionando, tienes que recurrir a mentiras piadosas
Por ejemplo, la API todavía finge que Active Desktop existe. La alternativa sería romper en masa software heredado antiguo
Uno ya no sabe si la función desapareció o si quedó escondida en otra pantalla mientras tanto
Pero si la app no lo hace, la gente que usa esa app culpa a Windows. Culpan a Windows, no a la app, e incluso pasa lo mismo cuando la app se cae
Por eso Microsoft crea soluciones alternativas. Es mucho más fácil dejar que el trabajo de impresión simplemente desaparezca, y así el usuario se queda pensando un momento y luego lo acepta con un “ah, claro, no había impresora”
ifpara comprobar si algo es null. Cada vez que lo veo me acuerdo de este textoAsí que les indiqué a los agentes que no repitan verificaciones de null, que usen funciones inocuas, y que validen una sola vez al momento de la declaración que el valor nunca será null
Opiniones en Hacker News
Lo aprendí como una forma de tragarse los errores, y lo considero una mala práctica.
No solo no resuelve el problema real de que no se puede imprimir desde Xbox, sino que oculta qué tan roto está el software, lo que hace mucho más difícil encontrar bugs y probar.
Me gusta que el
panicde Go no esté pensado para abusarse en tiempo de ejecución ni para recuperarse de él, sino como una herramienta que hace sonar fuerte la sirena durante las pruebas para avisar que el programador se equivocó.Cuando los actores dentro de un sistema ocultan activamente sus propios defectos o errores, encontrar y corregir la causa se vuelve exponencialmente más difícil.
En los sistemas B2B, de microservicios y centrados en API con los que trabajo, lo correcto es hacer fallar rápido las solicitudes fuera de especificación.
Pero Windows se ha esforzado muchísimo por mantener compatibilidad hacia atrás, e incluso hay casos en los que mantuvo vivo software famoso y roto como SimCity aplicando parches de memoria: https://arstechnica.com/gadgets/2022/10/windows-95-went-the-...
En un entorno de escritorio donde el usuario no puede corregir el código del sistema, muchas veces la experiencia del usuario final importa más que la corrección.
A Microsoft no le importaba tanto por qué ocurría un BSOD; aprendió que el software o hardware malo dañaba la reputación de Microsoft.
Personalmente prefiero diseños que fallen rápido o se detengan ante entradas o condiciones inválidas, pero también entiendo el valor de este enfoque en ese entorno.
Lo importante es el contexto, no aplicar dogmáticamente una u otra postura.
Por ejemplo, no deberías llevar el enfoque de Chen al diseño de un reactor nuclear o de trading de alta frecuencia, pero puede ser un enfoque excelente para un motor de juegos.
Decidir si dejar un componente del runtime como “inofensivamente inactivo” es una decisión de diseño anterior a si la implementación hará
panico no.En este caso, le corresponde a Microsoft definir qué significa “imprimir desde Xbox”.
Puede definirlo como un error, o teóricamente como una función que podría soportarse más adelante pero que ahora no tiene una forma real de ejecutarse.
Si se conecta una impresora USB y un “juego”, como por ejemplo un juego de Game Boy que soportaba la GB Printer mediante el puerto de expansión, sabe cómo imprimir, en teoría también podría imprimirse desde Xbox.
Que un juego de Xbox intente imprimir no es necesariamente un error intrínseco; definirlo como error también es solo una elección con trade-offs, como la portabilidad de aplicaciones.
Podrías mostrar una pantalla de selección sin impresoras seleccionables, como en Xbox; hacer que la API mienta diciendo que imprimió; o soportar la impresión real.
Solo después de definir el significado de “imprimir desde Xbox” como un error, el hecho de que ese error no se lance y se trague pasa a ser un problema de implementación o depuración.
Hay dos tratamientos posibles: como la máquina nunca tendrá impresora, devolver un error; o, como la máquina nunca tendrá impresora, devolver una lista vacía de impresoras.
Ambos son válidos, pero solo uno no rompe el espacio de usuario.
“Hacer sonar fuerte la sirena” es bueno para testers o desarrolladores, pero no tanto para el usuario final.
Desde la perspectiva del usuario final, que se trague el error es mejor que que la app se cierre.
No se ha avanzado nada en saber qué falló: si fue el software de mi computadora, el hardware, el cable Ethernet o el Wi-Fi, el switch, el router, el módem de cable, el cable de Internet que llega a mi casa, el ISP o el sistema remoto al que intento conectarme.
Es previsible que haya reacciones negativas, pero este tipo de cosas sucias es precisamente la razón por la que un documento de Word ’97 o un juego compilado para MS-DOS hace 30 años se abre como esperabas cuando le das clic.
La compatibilidad hacia atrás siempre es messy, y solo hay dos opciones: hacerla de forma imperfecta o no hacerla en absoluto.
Al final termino usando GOG, y GOG básicamente virtualiza el entorno.
Por ejemplo, no creo que nadie logre instalar el Sim City original en Windows 11 con éxito.
El formato dependía del driver de impresora instalado.
En cambio, un archivo LaTeX de hace 35 años sigue funcionando bien hoy.
Word no podía garantizar que el formato no cambiara al volver a abrirlo en la misma computadora y con la misma versión, y por eso aprendí TeX.
En juegos, Microsoft también rompió muchísimos títulos cuando cerró GFWL.
El Dark Souls original no se podía jugar hasta que fue relanzado en Steam, así que había que usar una copia pirata.
Aparece un cuadro de diálogo de “no compatible”, y puedes descargar dosbox, pero eso no es “exactamente como esperabas”.
Casi siempre me rendí o los volví a comprar en GOG; en la práctica, terminé pagando por el trabajo de haber resuelto los problemas de compatibilidad.
No hay nada tan frustrante como una UI de dispositivos que da a entender que podrían existir, pero luego dice que no hay ninguno en este momento
Al final uno tiene que dedicar tiempo para descubrir que esos dispositivos no son compatibles y que esa pantalla no era más que un mockup que alguien hizo
Si la app no contempló el caso de que la impresión no esté soportada y la salida lanza una excepción, probablemente la app se muera tal cual
Una app que sí contempló que la impresión no esté soportada debería comprobar primero con una API explícita si la impresión está soportada y, si no lo está, no mostrar la UI de impresión
El artículo no trata sobre esas apps, sino sobre cómo manejar apps que no lo comprueban antes
El bug está en la app, pero una buena plataforma debería intentar que incluso las malas apps funcionen lo más posible, en vez de simplemente dejarlas morir
El papel de Microsoft es evitar que la app se muera solo porque el proveedor del software no tuvo en cuenta las impresoras
Que cuando una app intenta imprimir se le pida al usuario elegir una impresora y se le muestre una lista vacía... parece que Microsoft no aprendió nada en 30 años: https://www.reddit.com/r/hacking/comments/djvzd/windows_nt_l...
La dirección concreta que propone el artículo —que los componentes deben sufrir antes que los usuarios— es correcta, pero el encuadre resulta muy molesto
Cosas como “a veces hay que hacer que una API no haga nada” o “está mal que una función de impresión lance
NotSupportedException” de ninguna manera son consejos generalesEsto es un hack para dar soporte a clientes pésimos y, aunque a veces sea necesario, no es un consejo normal ni generalizable
Ese tipo de formulaciones deja ver huellas de haber interiorizado el sufrimiento de ser desarrollador de Microsoft
Si el cliente no cambió y la API lo rompe, lo pésimo no es el cliente, sino la API
En cualquier plataforma ampliamente adoptada que se tome en serio la compatibilidad hacia atrás, esto es completamente normal y generalizable
Windows es el caso representativo, pero pasa lo mismo con la ABI del kernel de Linux, glibc, la plataforma web, Java, etc.
No se puede volver el tiempo atrás para recompilar o reescribir los clientes
Si el cliente ya está comprobando
NotSupportedException, entonces hay que devolver eso; pero si no, hace falta otro enfoqueEsto es Windows
No se deben romper las aplicaciones de las que dependen los usuarios
Si hay apps cliente “pésimas” o que se comportan mal, hay que usar todos los workarounds, shims y hacks de compatibilidad necesarios para que se comporten como se espera
Incluso si eso implica meter código especial de gestión de memoria para que SimCity funcione
Tu cliente depende de ese cliente, y si deja de funcionar por algo que tú cambiaste, te va a culpar a ti
Aquí todos se obsesionan con “no hacer nada” y “manejo de errores”, pero parece que no se explicó bien que el artículo no propone tragarse todos los problemas y cantar kumbaya
El contexto real es emulación y compatibilidad para software que no va a cambiar, y eso es muy distinto de la mayoría de las situaciones
Microsoft mismo a menudo difumina la frontera entre ambas cosas, lo que confunde más, pero aquí no se habla del manejo de errores en general
La emulación consiste, en esencia, en mentir
En una Xbox no hay forma real de conectar una impresora, estás ejecutando juegos de Windows en Linux, y un iPhone falso puede ser en realidad un servidor ARM en AWS
Si vas a mentir, tienes que hacerlo de forma convincente, para que las aplicaciones existentes sigan funcionando
Cuando Microsoft dice que se pueden portar apps existentes de Windows a Xbox, las apps de Windows tenían la promesa de que podían imprimir, así que Xbox tiene que mentir para que eso parezca posible
En cambio, si un programa está haciendo un use-after-free y se lo esquiva en silencio fingiendo que no hay bug, eso no es un manejo correcto
Porque nadie prometió que hasta los use-after-free existentes serían compatibles bug por bug en la nueva plataforma
Solo asumiste esa interpretación porque respalda la postura que querías defender
De hecho, lo que dice el artículo se aplica igual si mañana escribo un programa para Xbox e intento imprimir
No hay nada que distinga que este comportamiento solo aplica a software viejo y sin mantenimiento
Este enfoque me gusta y no me gusta a la vez
Instintivamente no me gusta tratar los problemas mediante cumplimiento malicioso
Por otro lado, si el objetivo es permitir que más usuarios ejecuten más software en la plataforma, aunque la impresión esté rota, estoy totalmente de acuerdo en que es una buena decisión
Este hilo de comentarios fue raro
Claramente hay gente a la que le disgusta cualquier cosa que haga Microsoft
El punto central del blog es hacer que llevar apps (UWP) a XBOX sea un proceso sin fricciones
Que la app “simplemente funcione” sin recompilar, sin
ifdefsucios y sin trabajo adicional, y luego el desarrollador ya pueda ajustarla a la plataforma XBOXLo importante es que el desarrollador pudo llevar la app a XBOX sin ningún esfuerzo, y eso es bueno tanto para la plataforma como para el desarrollador
Para el usuario, que la lista de impresoras esté vacía es una señal clara de que no puede imprimir
Sobre todo, la app no se muere ni muestra el peor tipo de error, como “Ocurrió un problema. Inténtalo de nuevo más tarde”
Ese mensaje no es una mejor experiencia de usuario
El desarrollador puede recompilar la app y poner un mensaje más explicativo, pero entonces volvemos al punto de partida
Por ejemplo, la acusación de que se tragan los errores no viene al caso en absoluto
El punto clave que pasan por alto quienes critican este artículo es que aquí no hay una situación excepcional en la que haya que lanzar una excepción.
No hay una impresora conectada a este dispositivo y, en este caso, por definición es así, por lo que la app debería manejar esa situación limpiamente, no morirse.
Una laptop no lanzaría una excepción solo porque no puede acceder a una impresora.
Si agregas una impresora en Configuración y luego, durante la instalación, pasas a la pestaña “Color Profiles”, con el timing adecuado gnome-control-center se muere.
Si lo miras de cerca, intentó enumerar las impresoras disponibles; sabe que debería haber una impresora, pero como la información aún no existe, simplemente se muere.
Por suerte, basta con esperar unos segundos a que GNOME termine de instalar la impresora y volver a abrir Configuración.
Aun así, si se trata de una app clave que ayuda a usuarios finales de todo tipo de contextos a usar bien el escritorio, en un mundo ideal nunca debería morirse.
Pensar en las condiciones límite es difícil, y es todavía más difícil imaginar que el propio manejo de condiciones límite también tiene otras condiciones límite.
Con razón o sin ella, al menos el autor está pensando con más profundidad que “es una mala app, hay que reescribirla para que soporte Wayland”.
Hubo una época en que se consideraba una gran estrategia que los navegadores hicieran su mejor esfuerzo por mostrar una página aunque el código HTML tuviera errores.
La idea era adivinar lo mejor posible la intención del autor y seguir adelante, porque los errores son malos y los usuarios no quieren errores.
Creía que habíamos aprendido de esa experiencia, pero parece que no.
El intento de XHTML de reemplazarla con validación estricta fracasó; en cambio, HTML5, que sí tuvo éxito, volvió atrás y definió explícitamente cómo interpretar todas las secuencias inválidas posibles, para que al menos los navegadores se comportaran de manera consistente.
Si los primeros navegadores hubieran mostrado un error apenas se encontraban con una etiqueta desconocida, las nuevas funciones de los navegadores se habrían asfixiado al nacer.
HelloyWorld!, puedes comprobar si, gracias a la sabiduría que adquirimos, ahora aparece un error.Pero si todavía se renderiza bien, quizá significa que aún tenemos mucho que aprender, o que tiene sentido manejar con elegancia ciertos tipos de errores y morirse ante situaciones excepcionales.
Siento que si viajara en el tiempo a 1999 no extrañaría demasiado.