Todos nos esforzamos por crear software de alta calidad, eficaz y seguro. Tanto si se está preparando para una ronda de inversión crucial como si pretende acelerar su ciclo de desarrollo, una base de código sólida es absolutamente imprescindible. Pero, ¿cómo puede asegurarse de que su equipo entrega constantemente un código excepcional? La respuesta está en una revisión eficaz del código.
En este artículo, repasaremos los aspectos esenciales del desarrollo de aplicaciones JavaScript sólidas y las mejores prácticas de revisión de código. Siguiendo nuestra lista de comprobación de revisión de código JavaScript, puede reducir el tiempo dedicado a la corrección de errores, mejorar la eficiencia de su equipo y, en última instancia, ofrecer mejores productos más rápidamente. En este artículo, vamos a cubrir:
Estilo y formato coherentes del código
Al centrarse en la coherencia del estilo y el formato del código, está estableciendo una base sólida para una base de código saludable. Una base de código uniforme no sólo tiene un aspecto profesional, sino que también facilita la colaboración de su equipo, la detección de errores y la incorporación de nuevos desarrolladores. Establecer guías de estilo y herramientas de automatización puede requerir un poco de esfuerzo inicial, pero la recompensa en eficiencia mejorada y calidad del código merece la pena.
Guía de estilo y herramientas de automatización:
- Elija una guía de estilo establecida, como las guías de estilo de Airbnb, Google o JavaScript Standard.
- Si es necesario, personalícela para adaptarla mejor a los requisitos específicos de su proyecto.
- Siga la guía de estilo para garantizar que todos los fragmentos de código de su proyecto tengan el mismo aspecto, independientemente de quién los haya escrito.
- Para garantizar un formato coherente en toda su base de código, integre una herramienta de formateo de código como Prettier.
- Prettier puede formatear automáticamente el código para que coincida con la guía de estilo elegida, liberando a los desarrolladores de la carga del formateo manual.
// ESLint configuration with Airbnb style guide and Prettier
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'airbnb-base',
'prettier'
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-console': 'warn',
'import/prefer-default-export': 'off'
}
};
// Code formatted with Prettier
// Before
function calculateTotal(price,quantity,tax)
{
let total=price*quantity;
total+=total*tax;
return total;
}
// After
function calculateTotal(price, quantity, tax) {
let total = price * quantity;
total += total * tax;
return total;
}
Convenciones de nomenclatura claras:
- Utilice nombres que indiquen claramente lo que representa la variable o función. Por ejemplo, utilice getUserData() en lugar de getData()
- En JavaScript, es habitual utilizar camelCase para variables y funciones, y PascalCase para clases
- Evite las abreviaturas: Aunque pueden ahorrar algunas pulsaciones, las abreviaturas pueden resultar confusas
- Utilice los mismos términos en todo el código. Si utiliza fetch en un lugar, no utilice retrieve en otro para la misma acción
Mala práctica:
function calc(n) {
// complex calculation
}
function calculateTotalPrice(numberOfItems) {
// complex calculation
}
Legibilidad y mantenimiento del código
El código limpio reduce la carga cognitiva de los desarrolladores, minimiza los errores y acelera los ciclos de desarrollo. El código limpio es la práctica de escribir código de forma que los demás (y su futuro yo) puedan entenderlo, mantenerlo y desarrollarlo fácilmente.
Estructura de código modular:
- Utilice nombres descriptivos de archivos y funciones que transmitan su propósito
- Agrupe las funciones relacionadas en carpetas o módulos lógicos (por ejemplo, utilidades, servicios, controladores)
- Aproveche la programación funcional u orientada a objetos, según las preferencias de su equipo y las necesidades del proyecto
Mala práctica:
// Bad: A single function handling multiple responsibilities
function handleUserRequest(req, res) {
// Validate request
if (!req.userId) {
res.status(400).send('User ID is required');
return;
}
// Fetch user data
database.query(`SELECT * FROM users WHERE id = ${req.userId}`, (err, user) => {
if (err) throw err;
// Process user data
const processedData = processData(user);
// Send response
res.send(processedData);
});
}
// Good: Separate functions for each responsibility
function validateRequest(req) {
if (!req.userId) {
throw new Error('User ID is required');
}
}
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
database.query(`SELECT * FROM users WHERE id = ${userId}`, (err, user) => {
if (err) reject(err);
resolve(user);
});
});
}
function processUserData(user) {
// Process and return user data
return {
id: user.id,
name: user.name.toUpperCase(),
// more processing...
};
}
async function handleUserRequest(req, res) {
try {
validateRequest(req);
const user = await fetchUserData(req.userId);
const data = processUserData(user);
res.send(data);
} catch (error) {
res.status(400).send(error.message);
}
}
Prevención del anidamiento profundo:
- Utilice bibliotecas de flujo de control: Las bibliotecas como async pueden ayudar a gestionar el código asíncrono
- Refactorice condiciones complejas: Divida las sentencias if complejas en funciones más pequeñas o utilice cláusulas de protección
- Utilice retornos anticipados: Salga de una función antes de tiempo si se cumplen ciertas condiciones para evitar el anidamiento adicional
Malas prácticas:
// Bad: Deeply nested if-statements reduce readability and increase complexity
if (user) {
if (user.isActive) {
if (user.hasPermission) {
// Execute action
} else {
// No permission
}
} else {
// User not active
}
} else {
// No user found
}
// Good: Using early returns to simplify logic and improve readability
if (!user) {
// No user found
return;
}
if (!user.isActive) {
// User not active
return;
}
if (!user.hasPermission) {
// No permission
return;
}
// Execute action
Comentarios y documentación:
- Documente las funciones y clases: Utilice comentarios para explicar la finalidad, los parámetros y los valores de retorno
- Explique la lógica compleja: Si has tenido que pensarlo mucho, coméntalo
- Mantén los comentarios actualizados: Revise los comentarios durante las revisiones y actualizaciones del código
- Evite comentar el código: Elimine el código que no utilice en lugar de comentarlo; el control de versiones le cubre las espaldas
/**
* Calculates the final price by applying tax and discount.
* @param {Object} params - Calculation parameters
* @param {number} params.basePrice - Base price before calculations
* @param {number} params.taxRate - Tax rate as a decimal (e.g., 0.2 for 20% tax)
* @param {number} params.discount - Discount amount to apply
* @returns {number} Final price after calculations
* @throws {ValidationError} If any parameters are invalid
*/
function calculateFinalPrice({ basePrice, taxRate, discount }) {
// Input validation
if (!isValidPrice(basePrice)) {
throw new ValidationError('Invalid base price');
}
// Calculate tax
const taxAmount = basePrice * taxRate;
// Apply discount
const discountedPrice = basePrice + taxAmount - discount;
// Ensure final price is not negative
return Math.max(0, discountedPrice);
}
// Usage example
try {
const finalPrice = calculateFinalPrice({
basePrice: 100,
taxRate: 0.2,
discount: 25
});
console.log(`Final price: $${finalPrice}`);
} catch (error) {
logger.error('Price calculation failed:', error);
}
Gestión eficaz de errores
Los errores son inevitables en el desarrollo de software. La forma de gestionar estos errores es lo que marca la diferencia entre una experiencia de usuario agradable y un bloqueo frustrante. Con una gestión eficaz de los errores, se refuerza de forma proactiva la resistencia de la aplicación. Esto también ayuda a los desarrolladores a depurar y mantener el código.
Uso de bloques Try-Catch:
- Utilícelos con moderación: Envuelve sólo el código que pueda lanzar excepciones, no funciones o módulos enteros
- Código asíncrono: Recuerde que try-catch no detectará errores en código asíncrono (por ejemplo, dentro de setTimeout o callbacks Promise). Utilice .catch() para las promesas o para gestionar errores en funciones asíncronas
- No suprima los errores: Captura errores para manejarlos adecuadamente, no para ocultarlos
Mala práctica:
// Bad: Not handling potential errors
function parseJSON(jsonString) {
return JSON.parse(jsonString);
}
const data = parseJSON(userInput);
console.log(data.name);
// Good: Handling errors with try-catch
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
console.error('Failed to parse JSON:', error);
return null;
}
}
const data = parseJSON(userInput);
if (data) {
console.log(data.name);
} else {
console.log('Invalid input provided.');
}
Mensajes de error personalizados:
- Proporcione mensajes de error personalizados para facilitar la depuración y mejorar la experiencia del usuario
- Indique claramente la naturaleza del error
class ValidationError extends Error {
constructor(message, { cause, ...extra } = {}) {
super(message);
this.name = 'ValidationError';
this.cause = cause;
this.extra = extra;
}
}
function calculateDiscount(price, discountPercentage) {
if (price <= 0) {
throw new ValidationError('Price must be a positive number', {
cause: 'Invalid price',
price,
});
}
if (discountPercentage < 0 || discountPercentage > 1) {
throw new ValidationError('Discount percentage must be between 0 and 1', {
cause: 'Invalid discount percentage',
discountPercentage,
});
}
return price * (1 - discountPercentage);
}
try {
const discountedPrice = calculateDiscount(-100, 0.2);
console.log(`Discounted price: $${discountedPrice}`);
} catch (error) {
if (error instanceof ValidationError) {
console.error(`${error.message} (${error.cause})`);
console.error(error.extra);
} else {
console.error('An unexpected error occurred:', error);
}
}
Registro:
- Registre en un formato estructurado (por ejemplo, JSON) para facilitar el análisis sintáctico
- Tenga cuidado de no registrar contraseñas, tokens o información personal de los usuarios
- Utilice niveles de registro adecuados: Error (para problemas graves que requieren atención inmediata), Warn (para problemas potenciales o avisos importantes), Info (entradas operativas generales sobre lo que está pasando), Debug (información detallada utilizada para depuración)
- Utilice sistemas de gestión de registros como ELK Stack (Elasticsearch, Logstash, Kibana) o servicios en la nube para agregar y analizar registros
Mala práctica:
// Bad: Using console.log in production code
function fetchData(url) {
fetch(url)
.then((response) => response.json())
.catch((error) => console.log('Error fetching data:', error));
}
// Good: Using a logging library
const logger = require('your-preferred-logger');
function fetchData(url) {
fetch(url)
.then((response) => response.json())
.catch((error) => {
logger.error('Error fetching data:', {
message: error.message,
stack: error.stack,
url,
});
});
}
Optimización del rendimiento
Los usuarios esperan que las aplicaciones sean rápidas como el rayo y tengan capacidad de respuesta. Así que te preguntarás, ¿cómo de rápido debería cargarse un sitio web en 2024? Según Google, todo lo que supere los 2,5 segundos debe mejorarse, y todo lo que esté por debajo de los 4 segundos se considera una velocidad de página deficiente. La optimización del rendimiento es la clave para mantener a los usuarios contentos y comprometidos. He aquí algunas estrategias fiables para optimizar el rendimiento de sus aplicaciones JavaScript.
Manipulación eficiente de datos:
- Seleccione las estructuras de datos adecuadas:
- Las matrices son ideales para colecciones ordenadas, y cuando necesite realizar operaciones como map, filter o reduce
- Los objetos (o mapas) son mejores para pares clave-valor con tiempos de búsqueda rápidos
- Utilice Set para valores únicos y comprobaciones de existencia rápidas
- Utilice Map para pares clave-valor en los que las claves pueden ser de cualquier tipo
- Minimice el procesamiento de datos: Procese sólo los datos necesarios; utilice la paginación o la carga lenta para grandes conjuntos de datos
- Evite copiar objetos en profundidad innecesariamente: Las copias profundas pueden resultar caras; utilice copias superficiales cuando sea posible o estructuras de datos inmutables
- Utilice web workers: Descargue los cálculos pesados en web workers para evitar el bloqueo del hilo principal
Mala práctica:
// Bad: Using an array to check for the existence of a value
const items = ['apple', 'banana', 'orange'];
function hasItem(item) {
return items.indexOf(item) !== -1;
}
// Good: Using a Set for efficient existence checks
const itemsSet = new Set(['apple', 'banana', 'orange']);
function hasItem(item) {
return itemsSet.has(item);
}
Programación asíncrona:
- A menos que la compatibilidad con entornos antiguos sea una preocupación, utilice async/await para un código más limpio
- Utilice siempre try/catch con funciones asíncronas para gestionar errores
- Limítese a un patrón asíncrono en un bloque de código determinado para mantener la coherencia
Mala práctica:
// Bad Practice: Promise Hell, avoid this pattern at any cost
function getUserData(userId) {
fetchUser(userId)
.then(user => {
fetchUserPosts(user.id)
.then(posts => {
fetchPostComments(posts[0].id)
.then(comments => {
console.log(comments);
})
.catch(err => console.error(err));
})
.catch(err => console.error(err));
})
.catch(err => console.error(err));
}
// Good Practice: Promise Chaining
function getUserData(userId) {
return fetchUser(userId)
.then(user => fetchUserPosts(user.id))
.then(posts => fetchPostComments(posts[0].id))
.then(comments => comments)
.catch(err => {
logger.error('Failed to fetch user data:', err);
throw err;
});
}
// Better Practice: Async/Await with Error Handling
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchUserPosts(user.id);
const comments = await fetchPostComments(posts[0].id);
return comments;
} catch (error) {
logger.error('Failed to fetch user data:', error);
throw error;
}
}
// Best Practice: Parallel Operations with Error Handling
async function getUserProfile(userId) {
try {
const [user, posts, settings] = await Promise.all([
fetchUser(userId),
fetchUserPosts(userId),
fetchUserSettings(userId)
]);
return {
...user,
posts,
settings
};
} catch (error) {
logger.error('Failed to fetch user profile:', error);
throw error;
}
}
Prevención de fugas de memoria:
- Elimine los escuchadores de eventos y temporizadores no utilizados: Limpia después de ti mismo para evitar la retención de memoria involuntaria
- Evite variables globales innecesarias: Mantenga las variables en el ámbito en el que son necesarias
- Gestione las referencias con cuidado: Tenga en cuenta cómo los cierres y las retrollamadas se aferran a las variables
- Realice pruebas y perfiles con regularidad: Utilice herramientas para detectar y corregir las fugas de memoria antes de que se conviertan en un problema
Malas prácticas:
// Bad: Not removing event listeners when elements are removed
function attachListener() {
const button = document.getElementById('myButton');
button.addEventListener('click', handleClick);
}
function handleClick() {
// Handle click event
}
// Later, the button is removed from the DOM
document.getElementById('myButton').remove();
// Good: Properly removing event listeners and managing references
function attachListener() {
const button = document.getElementById('myButton');
const handleClick = () => {
// Handle click event
console.log('Button clicked!');
};
button.addEventListener('click', handleClick);
// Add a cleanup function to remove the listener when the button is removed
const cleanup = () => {
button.removeEventListener('click', handleClick);
button.remove();
};
return cleanup;
}
// Example usage
const cleanupFunction = attachListener();
// Later, when the button is no longer needed
cleanupFunction();
Buenas prácticas de seguridad
La entrada de datos del usuario es una de las vulnerabilidades más importantes de cualquier aplicación. Sin una limpieza adecuada, su aplicación se convierte en un terreno de juego para los atacantes que buscan explotar las debilidades a través de Cross-Site Scripting (XSS) y ataques de inyección. Es imprescindible proteger los datos durante la transmisión y el almacenamiento y comprobar regularmente las dependencias.
Saneamiento de la entrada del usuario:
- Utilice bibliotecas de confianza como DOMPurify para desinfectar el HTML y evitar ataques XSS
- Validar la entrada del lado del servidor: La validación del lado del cliente puede eludirse; valídela siempre en el servidor
- Permitir la entrada de datos: Acepte sólo los datos esperados; defina patrones aceptables y rechace todo lo demás
- Escape adecuado de la salida: Utilice distintos métodos de escape para HTML, URL, JavaScript y CSS
// Using 3rd-party library DOMPurify as it is more secure and robust than manually sanitizing the input using string manipulation or regular expressions
import DOMPurify from 'dompurify';
function sanitizeUserInput(unsafeInput) {
// Sanitize HTML input to prevent XSS attacks
const sanitizedInput = DOMPurify.sanitize(unsafeInput, {
ALLOWED_TAGS: ['a', 'b', 'i', 'em', 'strong'],
ALLOWED_ATTR: ['href'],
});
return sanitizedInput;
}
// Example usage
const userInput = 'Click me';
const cleanInput = sanitizeUserInput(userInput);
console.log(cleanInput);
// Output: Click me
Uso de HTTPS y cookies seguras:
- Establecer atributos de cookies seguras: Secure Flag (asegura que las cookies sólo se envían a través de HTTPS), HttpOnly Flag (evita que JavaScript acceda a las cookies, mitigando ataques XSS), SameSite Attribute (protege contra ataques CSRF).
- Actualice periódicamente los certificados: Supervise la validez de los certificados y renuévelos antes de que caduquen
- Desactive los protocolos y cifrados débiles: Utilizar estándares de cifrado fuertes como TLS 1.3+
Gestión de dependencias:
- Bloquee las versiones de las dependencias: Utilice package-lock.json o yarn.lock para garantizar instalaciones coherentes en distintos entornos
- Actualice regularmente las dependencias: Programar el tiempo para actualizar los paquetes y comprobar la compatibilidad
- Utilice los comandos npm audit o yarn audit regularmente para comprobar las vulnerabilidades de los paquetes
- Elimine las dependencias no utilizadas: Un código base esbelto reduce la superficie potencial de ataque y mejora el rendimiento
- Prefiera fuentes oficiales: Compruebe dos veces los nombres de los paquetes para evitar la instalación de paquetes maliciosos con nombres similares
- Automatice las comprobaciones: Integre herramientas que analicen las dependencias durante los procesos de compilación
Pruebas exhaustivas
Las pruebas exhaustivas abarcan varios niveles, desde la comprobación de unidades individuales de código hasta la verificación del buen funcionamiento conjunto de sistemas completos. Las pruebas continuas garantizan la creación de una base sólida para el crecimiento y el éxito futuros de la aplicación. Las pruebas unitarias, las pruebas de integración y el logro de una alta cobertura de pruebas pueden elevar notablemente la calidad de sus aplicaciones JavaScript.
Pruebas unitarias:
- Pruebe una sola responsabilidad: Cada prueba debe centrarse en un aspecto del comportamiento de la función
- Utilice nombres de prueba descriptivos: Indique claramente lo que la prueba está verificando
- Simule dependencias externas: Aísle la unidad de código simulando API, bases de datos u otros servicios
- Pruebas deterministas: Las pruebas deben producir los mismos resultados cada vez que se ejecuten
Mala práctica:
// Function to be tested
function add(a, b) {
return a + b;
}
// Test suite
describe('add function', () => {
it('should add two numbers', () => {
assert.equal(add(2, 3), 5);
});
});
// Function to be tested
function add(a, b) {
return a + b;
}
// Test suite
describe('add function', () => {
it('should add two positive numbers', () => {
assert.equal(add(2, 3), 5);
});
it('should add two negative numbers', () => {
assert.equal(add(-2, -3), -5);
});
it('should handle a positive and a negative number', () => {
assert.equal(add(2, -3), -1);
});
it('should handle zero as an argument', () => {
assert.equal(add(2, 0), 2);
assert.equal(add(0, 3), 3);
});
});
Pruebas de integración y de extremo a extremo:
- Mantenga un entorno de pruebas: Utilice un entorno independiente que refleje la configuración de producción
- Utilice datos de prueba: Rellenar las bases de datos con datos conocidos para obtener resultados coherentes
- Automatice las pruebas: Integre las pruebas en su proceso CI/CD para que se ejecuten en cada compilación o despliegue
- Priorice las rutas críticas: Céntrese en los flujos de usuario y puntos de integración más importantes
Cobertura de las pruebas:
- Aspire a un alto porcentaje de cobertura (por ejemplo, 80-90%), pero comprenda que una cobertura del 100% no garantiza un código sin errores
- Compruebe periódicamente qué partes de su código no están cubiertas y evalúe si necesitan pruebas
- Excluya de los informes de cobertura el código autogenerado o no crítico (por ejemplo, los archivos de configuración)
- Céntrese en pruebas significativas que validen el comportamiento: Escribir pruebas superficiales para aumentar los porcentajes de cobertura es una práctica contraproducente
Uso adecuado del control de versiones
El control de versiones permite a varios desarrolladores trabajar en la misma base de código sin pisarse los unos a los otros. El uso adecuado del control de versiones mejora la colaboración, garantiza la calidad del código y apoya el proceso de desarrollo ágil. Adoptando estrategias de bifurcación eficaces, escribiendo mensajes de confirmación significativos y realizando revisiones exhaustivas del código, preparará a su equipo para el éxito.
Bifurcación:
- Las ramas deben durar poco: Fusiona los cambios con regularidad para evitar bases de código divergentes
- Utilice nombres de rama descriptivos: Incluya el tipo y el propósito, por ejemplo, feature/add-payment-method o bugfix/fix-login-error
- Reglas de protección de ramas: Evite el envío directo a la rama principal o maestra y exija revisiones de las versiones preliminares
Mensajes de confirmación:
- Utilice el modo imperativo: Utilice verbos como «Añadir», «Corregir», «Actualizar»
- Los asuntos deben ser concisos: Resuma el cambio en 50 caracteres o menos
- Proporcione contexto: Ayude a los demás a entender los motivos de los cambios
- Separe los cambios adecuadamente: Un cambio lógico por confirmación
Mala práctica:
git commit -m "Fix stuff"
git commit -m "Fix authentication error when user logs in with Google"
Pull Requests:
- Al abrir un PR, proporcione un título y una descripción descriptivos
- Mencione cualquier dependencia o PR relacionado
- Asigne un miembro específico del equipo como revisor
- Utilice estrategias de fusión apropiadas: Merge Commit (conserva todos los commits y el historial), Squash and Merge (combina los commits en uno para un historial más limpio); Rebase and Merge (aplica sus commits sobre la rama principal para un historial lineal)
Gestión de dependencias
Las dependencias pueden afectar significativamente a la estabilidad, el rendimiento y la seguridad de tu aplicación. La gestión eficaz de las dependencias es un acto de equilibrio entre el aprovechamiento del código de terceros y el mantenimiento del control sobre la integridad y el rendimiento del proyecto. Aquí tienes algunas buenas prácticas que te ayudarán a evitar el llamado «infierno de las dependencias».
Uso de archivos de bloqueo:
- Confirme siempre los archivos de bloqueo: Manténgalos bajo control de versiones para mantener la coherencia
- No edite manualmente los archivos de bloqueo: Deje que el gestor de paquetes se encargue de ellos para evitar la corrupción
- Actualice los archivos de bloqueo intencionadamente: Ejecute npm install o yarn install sin cambiar las versiones a menos que tenga intención de actualizar.
- Manejar archivos de bloqueo en aplicaciones: Confirme los archivos de bloqueo para garantizar la coherencia de la implementación
- Gestión de archivos de bloqueo en bibliotecas/paquetes: Considere no confirmar los archivos de bloqueo, permitiendo que la aplicación final controle las versiones de las dependencias
Evitar el infierno de las dependencias:
- Comprender cómo los operadores Caret (^) y Tilde (~) afectan a los rangos de versiones
- Caret (^1.2.3): Actualiza a la última versión menor (pero no mayor)
- Tilde (~1.2.3): Actualiza a la última versión del parche (pero no a la menor ni a la mayor)
- Revise los registros de cambios antes de actualizar: Comprueba si hay cambios de última hora, especialmente al actualizar versiones mayores
- Automatiza la gestión de dependencias: Utiliza herramientas como Dependabot o Renovate para automatizar las actualizaciones y crear pull requests para ellas
- Realice pruebas exhaustivas tras las actualizaciones: Ejecute su conjunto de pruebas para detectar cualquier problema introducido por las dependencias actualizadas
Dependencias modulares:
- Importe sólo las partes necesarias: Los paquetes más pequeños se cargan más rápido, mejorando la experiencia del usuario
- Utiliza bundlers como Webpack o Rollup que soporten tree-shaking para eliminar código no utilizado
- Asegúrese de que utiliza módulos ES6 (importación/exportación) analizables estáticamente
Consideraciones adicionales:
- Importa módulos dinámicamente cuando sea necesario para mejorar los tiempos de carga iniciales
- Asegúrate de que no se incluyen múltiples versiones de la misma librería; utiliza herramientas como npm dedupe o yarn-deduplicate
Consideraciones sobre accesibilidad
El propósito de la accesibilidad es hacer que tu aplicación sea utilizable por el mayor número de personas posible, incluidas aquellas con discapacidades. Al incorporar las mejores prácticas de accesibilidad, no sólo cumples con los requisitos legales en algunas regiones, sino que también proporcionas una mejor experiencia de usuario para todos.
HTML semántico:
- No abuses de <div> y <span> cuando un elemento semántico sea más apropiado.
- No ponga <div> dentro de
- Asegúrese de que los niveles de encabezado se utilizan en orden (por ejemplo, <h2> sigue a <h1>)
- Utilice una jerarquía estricta de <h1> a <h6> para definir los encabezados y subencabezados
- Utilice <nav> para encerrar enlaces de navegación
- Utilice <main> para envolver el contenido principal de la página
- Utilice <section> y <article> para las distintas partes del contenido
- Utilice controles de formulario adecuados como <label>, <input>, <select> y <textarea>
Funciones y atributos ARIA:
- Utilice la especificación WAI-ARIA del W3C para aplicar las mejores recomendaciones de accesibilidad
- Utilice ARIA como último recurso: Prefiera elementos HTML nativos antes de añadir roles ARIA
- Garantice una interacción adecuada con el teclado: Los widgets personalizados deben permitir la navegación y activación mediante el teclado
- Mantenga actualizados los atributos ARIA: Actualice dinámicamente los estados ARIA para reflejar los cambios
- Definir el tipo de widget asignando el rol apropiado correspondiente a su lugar en la jerarquía de roles (role=«botón», role=«diálogo»)
- Describir el estado actual de los elementos roles específicos (aria-checked, aria-expanded)
- Proporcionar nombres accesibles (aria-label, aria-labelledby)
Mala práctica:
// Bad: Adding ARIA roles to non-semantic elements unnecessarily
<div role="button" onclick="toggleMenu()">Menu</div>
Buenas prácticas:
// Good: Use a button and enhance with ARIA if needed
<button aria-expanded="false" aria-controls="menu" onclick="toggleMenu(this)">Menu</button>
<ul id="menu" hidden>
<!-- Menu items -->
</ul>
Keyboard Navigation:
- Use standard controls: Native HTML elements like <button>, <a>, <input> are keyboard-friendly by default
- Manage focus order: Arrange focusable elements in a logical order
- Use tabindex=«0» to make custom elements focusable, but use sparingly
- Ensure that focused elements have a visible outline or highlight
- For custom components, listen to keyboard events like keydown and respond appropriately
- Test keyboard accessibility: Use the Tab key to move focus forward and Shift + Tab to move backward
- Use accessibility testing tools like Axe or Lighthouse
Su socio JS: Servicios de desarrollo y auditoría
Hemos estado en las trincheras del desarrollo web desde 2005, asociándonos con startups y scaleups para dar vida a sus visiones. Desde la creación de aplicaciones front-end interactivas con React y Vue.js hasta el desarrollo de servicios back-end escalables con Node.js, hemos abordado una amplia gama de desafíos que presentan los proyectos de JavaScript.
Nuestro equipo de experimentados desarrolladores, diseñadores y gestores de proyectos puede reforzar su proyecto y ayudarle a completar tareas importantes más rápidamente sin sentirse abrumado. He aquí un vistazo a los servicios específicos que ofrecemos y cómo nuestros clientes capitalizan nuestra experiencia y recursos para alcanzar sus hitos:
- Desarrollo desde cero. Estamos especializados en la creación de soluciones de software personalizadas desde cero. Vea cómo ayudamos a Kooky a desarrollar una plataforma web para el seguimiento de tazas de café reutilizables y convertirse en la startup de tecnología verde número 1 en Suiza.
- Refactorización de código. Podemos ayudarle a refactorizar su aplicación y adaptarla a los estándares modernos. Cakemail, una startup de marketing por correo electrónico con sede en Montreal, contrató a Redwerk para ayudarles a refactorizar un módulo de formulario de suscripción. Entregamos el código refactorizado en menos de 90 días, permitiendo a Cakemail lanzar una actualización esencial a tiempo.
- Ampliación de funcionalidades. Aumente el atractivo de su producto añadiendo nuevas funcionalidades. Orderstep, una plataforma líder de ventas y CRM, aprovechó nuestra experiencia en desarrollo front-end y React para ampliar su solución con un módulo de tienda web para usuarios Premium, aumentando así los ingresos de la empresa.
- Revisión de código: Obtenga una visión imparcial de la calidad de su aplicación con una revisión exhaustiva del código realizada por Redwerk. Hemos visto de primera mano cómo un par de ojos frescos pueden descubrir problemas ocultos u oportunidades de optimización que podrían pasarse por alto cuando su equipo está inmerso en la rutina diaria de desarrollo.
Nuestra experiencia práctica significa que entendemos las presiones de los plazos ajustados y la necesidad de una iteración rápida sin comprometer la calidad del código. Tanto si busca una auditoría exhaustiva del código, orientación sobre las mejores prácticas o un desarrollo completo, contamos con un equipo de expertos en tecnología para ayudarle. No dude en ponerse en contacto con nosotros: nos encantaría charlar, conocer sus necesidades empresariales y ayudarle a empezar a abordarlas.