Cómo crear interfaces de usuario de JavaScript resistentes

Aceptar la fragilidad de la web nos permite crear interfaces de usuario capaces de adaptarse a la funcionalidad que pueden ofrecer, sin dejar de brindar valor a los usuarios. Este artículo explora cómo la degradación elegante, la codificación defensiva, la observabilidad y una actitud saludable hacia las fallas nos equipan mejor antes, durante y después de que ocurra un error.
Las cosas en la web pueden fallar: las probabilidades están en nuestra contra. Muchas cosas pueden salir mal: una solicitud de red falla, una biblioteca de terceros falla, una función de JavaScript no es compatible (suponiendo que JavaScript esté disponible), una CDN falla, un usuario se comporta inesperadamente (hace doble clic en un botón de envío), el La lista continúa.
Afortunadamente, nosotros, como ingenieros, podemos evitar, o al menos mitigar, el impacto de las roturas en las aplicaciones web que creamos. Sin embargo, esto requiere un esfuerzo consciente y un cambio de mentalidad para pensar tanto en escenarios infelices como en escenarios felices.
La experiencia del usuario (UX) no tiene por qué ser todo o nada, solo lo que es utilizable. Esta premisa, conocida como degradación elegante, permite que un sistema continúe funcionando cuando algunas partes de él no funcionan, de manera muy parecida a como una bicicleta eléctrica se convierte en una bicicleta normal cuando se agota la batería. Si algo falla, solo la funcionalidad que depende de eso debería verse afectada.
Las interfaces de usuario deben adaptarse a la funcionalidad que pueden ofrecer y, al mismo tiempo, proporcionar el mayor valor posible a los usuarios finales.
¿Por qué ser resiliente?
La resiliencia es intrínseca a la web .
Los navegadores ignoran las etiquetas HTML no válidas y las propiedades CSS no compatibles. Esta actitud liberal se conoce como Ley de Postel, que Jeremy Keith transmite magníficamente en Resilient Web Design :
"Incluso si hay errores en HTML o CSS, el navegador seguirá intentando procesar la información, omitiendo cualquier parte que no pueda analizar".
JavaScript es menos indulgente. La resiliencia es extrínseca. Le indicamos a JavaScript qué hacer si sucede algo inesperado. Si una solicitud de API falla, recae en nosotros la responsabilidad de detectar el error y, posteriormente, decidir qué hacer. Y esa decisión impacta directamente en los usuarios.
La resiliencia genera confianza con los usuarios. Una experiencia con errores refleja mal la marca. Según Kim y Mauborgne, la conveniencia (disponibilidad, facilidad de consumo) es una de las seis características asociadas con una marca exitosa, lo que hace que la degradación elegante sea sinónimo de percepción de la marca.
Una UX sólida y confiable es una señal de calidad y confiabilidad, las cuales alimentan la marca. Un usuario que no puede realizar una tarea porque algo está roto, naturalmente, se enfrentará a una decepción que podría asociar con su marca.
A menudo, las fallas del sistema se consideran "casos de esquina": cosas que rara vez suceden; sin embargo, la Web tiene muchas esquinas. Los diferentes navegadores que se ejecutan en diferentes plataformas y hardware, respetan nuestras preferencias de usuario y modos de navegación (Safari Reader/tecnologías de asistencia), y que se ofrecen a ubicaciones geográficas con diferentes latencia e intermitencia aumentan la apariencia de que algo no funciona según lo previsto.
Igualdad de errores
Al igual que el contenido de una página web tiene jerarquía, las fallas (las cosas que salen mal) también siguen un orden jerárquico. No todos los errores son iguales, algunos son más importantes que otros.
Podemos clasificar los errores por su impacto. ¿Cómo es que XYZ no funciona impide que un usuario logre su objetivo? La respuesta generalmente refleja la jerarquía de contenido.
Por ejemplo, una descripción general del panel de control de su cuenta bancaria contiene datos de diversa importancia. El valor total de su saldo es más importante que una notificación que le solicita que revise los mensajes dentro de la aplicación. El método de priorización de Moscú clasifica el primero como algo imprescindible y el segundo como algo que es bueno tener.
Si la información primaria no está disponible (es decir, si falla la solicitud de red), debemos ser transparentes e informar a los usuarios, generalmente mediante un mensaje de error. Si la información secundaria no está disponible, aún podemos brindar la experiencia principal (que es imprescindible) mientras ocultamos elegantemente el componente degradado.
a href='/notifications'
al centro de notificaciones. ( Vista previa grande )
Saber cuándo mostrar o no un mensaje de error se puede representar mediante un árbol de decisión simple:
La categorización elimina la relación 1-1 entre fallas y mensajes de error en la interfaz de usuario. De lo contrario, corremos el riesgo de bombardear a los usuarios y saturar la interfaz de usuario con demasiados mensajes de error. Guiados por la jerarquía de contenido, podemos seleccionar qué fallas aparecen en la interfaz de usuario y qué sucede sin que los usuarios finales lo sepan.
Es mejor prevenir que curar
La medicina tiene un dicho que dice que más vale prevenir que curar.
Aplicado al contexto de la creación de interfaces de usuario resistentes, evitar que ocurra un error en primer lugar es más deseable que tener que recuperarse de uno. El mejor tipo de error es el que no ocurre.
Es seguro asumir que nunca se deben hacer suposiciones, especialmente cuando se consumen datos remotos, se interactúa con bibliotecas de terceros o se utilizan funciones de lenguaje más nuevas. Las interrupciones o los cambios de API no planificados junto con los navegadores que los usuarios eligen o deben usar están fuera de nuestro control. Si bien no podemos evitar que se produzcan roturas fuera de nuestro control, podemos protegernos contra sus efectos (secundarios).
Adoptar un enfoque más defensivo al escribir código ayuda a reducir los errores del programador que surgen al hacer suposiciones. El pesimismo sobre el optimismo favorece la resiliencia. El siguiente ejemplo de código es demasiado optimista:
const debitCards = useDebitCards();return ( ul {debitCards.map(card = { li{card.lastFourDigits}/li })} /ul);
Se supone que existen tarjetas de débito, el punto final devuelve una matriz, la matriz contiene objetos y cada objeto tiene una propiedad denominada lastFourDigits
. La implementación actual obliga a los usuarios finales a probar nuestras suposiciones. Sería más seguro y más fácil de usar si estas suposiciones estuvieran integradas en el código:
const debitCards = useDebitCards();if (Array.isArray(debitCards) debitCards.length) { return ( ul {debitCards.map(card = { if (card.lastFourDigits) { return li{card.lastFourDigits}/li } })} /ul );}return "Something else";
Usar un método de terceros sin verificar primero si el método está disponible es igualmente optimista:
stripe.handleCardPayment(/* ... */);
El fragmento de código anterior supone que el stripe
objeto existe, tiene una propiedad denominada handleCardPayment
y que dicha propiedad es una función. Sería más seguro y, por lo tanto, más defensivo si verificáramos estas suposiciones de antemano:
if ( typeof stripe === 'object' typeof stripe.handleCardPayment === 'function') { stripe.handleCardPayment(/* ... */);}
Ambos ejemplos comprueban que algo esté disponible antes de usarlo. Quienes estén familiarizados con la detección de características pueden reconocer este patrón:
if (navigator.clipboard) { /* ... */}
Simplemente preguntarle al navegador si es compatible con la API del Portapapeles antes de intentar cortar, copiar o pegar es un ejemplo simple pero efectivo de resiliencia. La interfaz de usuario puede adaptarse con anticipación ocultando la funcionalidad del portapapeles de navegadores no compatibles o de usuarios que aún no han concedido permiso.
Los hábitos de navegación de los usuarios son otro ámbito que escapa a nuestro control. Si bien no podemos dictar cómo se utiliza nuestra aplicación, podemos establecer barreras de seguridad que impidan lo que percibimos como "uso indebido". Algunas personas hacen doble clic en los botones, un comportamiento en su mayoría redundante en la web, pero que no constituye un delito punible.
Hacer doble clic en un botón que envía un formulario no debería enviar el formulario dos veces, especialmente para métodos HTTP no idempotentes . Durante el envío del formulario, evite envíos posteriores para mitigar las consecuencias de la realización de múltiples solicitudes.
Evitar el reenvío de formularios en JavaScript junto con el uso aria-disabled="true"
es más útil y accesible que el disabled
atributo HTML. Sandrina Pereira explica con gran detalle Cómo hacer que los botones deshabilitados sean más inclusivos .
Respondiendo a los errores
No todos los errores se pueden prevenir mediante programación defensiva. Esto significa que responder a un error operativo (los que ocurren dentro de programas escritos correctamente) recae sobre nosotros.
La respuesta a un error se puede modelar mediante un árbol de decisión. Podemos recuperar, retroceder o reconocer el error:
Ante un error, la primera pregunta debería ser: "¿podemos recuperarlo?" Por ejemplo, ¿volver a intentar una solicitud de red que falló por primera vez tiene éxito en intentos posteriores? Los microservicios intermitentes, las conexiones a Internet inestables o la eventual coherencia son motivos para volver a intentarlo. Las bibliotecas de recuperación de datos como SWR ofrecen esta funcionalidad de forma gratuita.
El apetito por el riesgo y el contexto circundante influyen en los métodos HTTP con los que se siente cómodo reintentando. En Nutmeg reintentamos lecturas fallidas (solicitudes GET), pero no escrituras (POST/ PUT/ PATCH/ DELETE). Múltiples intentos de recuperar datos (rendimiento de la cartera) son más seguros que modificarlos (volver a enviar un formulario).
La segunda pregunta debería ser: si no podemos recuperarnos, ¿podemos ofrecer un recurso alternativo? Por ejemplo, si falla un pago con tarjeta en línea, podemos ofrecerle un medio de pago alternativo, como PayPal o Open Banking.
Las alternativas no siempre tienen que ser tan elaboradas, pueden ser sutiles. La copia que contiene texto que depende de datos remotos puede recurrir a texto menos específico cuando falla la solicitud:
La tercera y última pregunta debería ser: si no podemos recuperarnos o retroceder, ¿qué importancia tiene este fracaso (que se relaciona con el “Error de Igualdad”). La interfaz de usuario debe reconocer los errores principales informando a los usuarios que algo salió mal y al mismo tiempo proporcionar indicaciones prácticas, como ponerse en contacto con el servicio de atención al cliente o vincular a artículos de soporte relevantes.
Observabilidad
La adaptación de las UI a algo que va mal no es el final. Hay otra cara de la misma moneda.
Los ingenieros necesitan visibilidad sobre la causa raíz detrás de una experiencia degradada. Incluso los errores que no llegan a los usuarios finales (errores secundarios) deben propagarse a los ingenieros. Los servicios de monitoreo de errores en tiempo real, como Sentry o Rollbar, son herramientas invaluables para el desarrollo web moderno.
La mayoría de los proveedores de monitoreo de errores capturan automáticamente todas las excepciones no controladas. La configuración requiere un esfuerzo de ingeniería mínimo que rápidamente genera dividendos para un entorno de producción saludable y mejorado y MTTA (tiempo medio de reconocimiento).
El verdadero poder surge cuando nosotros mismos registramos explícitamente los errores. Si bien esto implica un mayor esfuerzo inicial, nos permite enriquecer los errores registrados con más significado y contexto, los cuales ayudan a solucionar problemas. Cuando sea posible, busque mensajes de error que sean comprensibles para los miembros no técnicos del equipo.
Extender el ejemplo anterior de Stripe con una rama else es el candidato perfecto para el registro de errores explícito:
if ( typeof stripe === "object" typeof stripe.handleCardPayment === "function") { stripe.handleCardPayment(/* ... */);} else { logger.capture( "[Payment] Card charge — Unable to fulfill card payment because stripe.handleCardPayment was unavailable" );}
Nota : este estilo defensivo no tiene por qué estar vinculado al envío del formulario (en el momento del error), puede ocurrir cuando un componente se monta por primera vez (antes del error), lo que nos da a nosotros y a la interfaz de usuario más tiempo para adaptarnos.
La observabilidad ayuda a identificar las debilidades del código y las áreas que pueden reforzarse. Una vez que surge una debilidad, observe si y cómo se puede endurecer para evitar que vuelva a suceder lo mismo. Observe las tendencias y las áreas de riesgo, como las integraciones de terceros, para identificar qué podría incluirse en un indicador de función operativa (también conocido como interruptores de apagado).
Los usuarios advertidos de que algo no funciona se sentirán menos frustrados que aquellos sin previo aviso. Conocer las obras viales con antelación ayuda a gestionar las expectativas, lo que permite a los conductores planificar rutas alternativas. Cuando se enfrente a una interrupción (con suerte, descubierta mediante el monitoreo y no reportada por los usuarios), sea transparente.
Retrospectivas
Es muy tentador pasar por alto los errores.
Sin embargo, brindan valiosas oportunidades de aprendizaje para nosotros y nuestros colegas actuales o futuros. Es fundamental eliminar el estigma de la inevitabilidad de que las cosas salgan mal. En el pensamiento de caja negra, esto se describe como:
"En organizaciones altamente complejas, el éxito sólo puede ocurrir cuando confrontamos nuestros errores, aprendemos de nuestra propia versión de una caja negra y creamos un clima donde sea seguro fallar".
Ser analítico ayuda a prevenir o mitigar que vuelva a ocurrir el mismo error. Al igual que las cajas negras en la industria de la aviación registran incidentes, debemos documentar los errores. Como mínimo, la documentación de incidentes anteriores ayuda a reducir el MTTR (tiempo medio de reparación) en caso de que vuelva a ocurrir el mismo error.
La documentación, a menudo en forma de informes RCA (análisis de causa raíz), debe ser honesta, reconocible e incluir: cuál fue el problema, su impacto, los detalles técnicos, cómo se solucionó y las acciones que deben seguir al incidente.
Pensamientos finales
Aceptar la fragilidad de la web es un paso necesario hacia la construcción de sistemas resilientes. Una experiencia de usuario más confiable es sinónimo de clientes satisfechos. Estar preparado para lo peor (proactivo) es mejor que apagar incendios (reactivo) desde el punto de vista empresarial, del cliente y del desarrollador (¡menos errores!).
Cosas para recordar:
- Las UI deben adaptarse a la funcionalidad que pueden ofrecer, sin dejar de ofrecer valor a los usuarios;
- Piensa siempre en lo que puede estar mal (nunca hagas suposiciones);
- Categorizar los errores según su impacto (no todos los errores son iguales);
- Prevenir errores es mejor que responder a ellos (codificar a la defensiva);
- Cuando se enfrente a un error, pregunte si hay disponible una recuperación o un respaldo;
- Los mensajes de error que enfrenta el usuario deben proporcionar indicaciones procesables;
- Los ingenieros deben tener visibilidad de los errores (utilizar servicios de seguimiento de errores);
- Los mensajes de error para ingenieros/colegas deben ser significativos y proporcionar contexto;
- Aprenda de los errores para ayudarnos a nosotros mismos y a los demás en el futuro.
(vf, il)Explora más en
- interfaz de usuario
- experiencia de usuario
- Flujo de trabajo
- javascript
Deja un comentario