Checklist para Revisión de Código de Redwerk

¿Alguna vez se ha preguntado si su proveedor de TI ha hecho un buen trabajo y ha creado una aplicación de alta calidad? Es fácil comprobarlo con una revisión independiente del código. Las revisiones del código realizadas por expertos externos aportan una perspectiva nueva e imparcial sobre la calidad de su producto. Analizan el código por sus propios méritos y descubren problemas que los equipos internos podrían pasar por alto.

En este artículo, compartiremos nuestra exhaustiva lista de comprobación para la revisión del código de un proyecto web/SaaS para que comprenda cada paso del proceso de revisión del código. Como empresa de desarrollo de software con más de 19 años de experiencia, hemos realizado innumerables revisiones de código como práctica diaria y como parte de auditorías de diligencia debida. Tenemos muchos conocimientos que compartir, ¡así que hagámoslo!

Revisión manual y automatizada del código

Una revisión exhaustiva del código debe constar de dos partes: la verificación manual y la automática. Una combinación de ambas ayuda a conseguir la máxima calidad y valor de las revisiones del código.

Durante la revisión manual, se comprueban los siguientes aspectos generales:

  • Arquitectura
  • Estilo y limpieza del código
  • Nombres de clases y variables
  • Pruebas unitarias
  • Documentación

Cuando se realiza una verificación manual, es necesario recurrir a las mejores prácticas de revisión de código.

La revisión automática utiliza herramientas que ayudan a comprobar el sistema en busca de vulnerabilidades, bibliotecas obsoletas y código no utilizado mediante el análisis de grandes cantidades de datos. Todas las métricas que se encuentren utilizando el programa deben describirse cuidadosamente.

Las siguientes herramientas pueden utilizarse para la verificación automática:

  • Analizadores estáticos de código para encontrar posibles errores y vulnerabilidades de seguridad en el código.
  • Herramientas de cobertura del código para medir cuánto código ejecuta un conjunto de pruebas.
  • Linters para detectar desviaciones de las normas de estilo de codificación
  • Herramientas de automatización de pruebas para ejecutarlas automáticamente

Recuerda, aunque las herramientas de automatización son cruciales para apoyar y mejorar las revisiones de código profesionales, no pueden sustituir el pensamiento crítico, el juicio y la experiencia en el campo que los revisores humanos aportan al proceso. Pasemos ahora a la plantilla de revisión de código propiamente dicha.

Pasos preliminares

Hay varios pasos preliminares que deberías dar antes de sumergirte en la revisión del código en sí. Ayudan a garantizar una experiencia de revisión del código más centrada, productiva y colaborativa para todos los implicados.

Requisitos y objetivos del proyecto:

  • Leer la documentación del proyecto, incluidos los requisitos, las especificaciones y los documentos de diseño.
  • Comprender los objetivos principales de la revisión del código: ¿identificar problemas de seguridad, mejorar la calidad del código, garantizar el cumplimiento de las normas de codificación o algo más?
  • Averiguar si hay áreas específicas de interés o preocupaciones para la revisión.

Alcance del proyecto:

  • Determinar si la revisión se centrará en módulos, características o funcionalidades específicas dentro de la base de código.
  • Identificar las áreas donde se han producido problemas en el pasado o donde se sabe que existe deuda técnica.
  • En el caso de proyectos en sectores regulados, asegúrese de que se revisan las secciones pertinentes del código para comprobar su cumplimiento.
  • En el caso de auditorías de seguridad, identifique los componentes que manejan datos confidenciales o son críticos para la seguridad del sistema.

Configuración del entorno:

  • Asegúrese de que tiene acceso al código base, a las herramientas de prueba y a la documentación pertinente.
  • Elija las herramientas adecuadas para su proceso de revisión, como linters de código, herramientas de análisis estático o entornos de desarrollo integrados (IDE).
  • Si procede, asegúrese de que puede ejecutar y probar el código de forma eficaz.

Revisión de la arquitectura

La revisión de la arquitectura es parte integrante de nuestra lista de comprobación de la revisión del código. Evalúa cómo se adhiere el código a los principios generales de diseño del sistema y su mantenimiento a largo plazo. La detección temprana de problemas de arquitectura evita costosas modificaciones en fases posteriores del proceso de desarrollo. También garantiza que el sistema pueda crecer y funcionar eficazmente con cargas variables.

Alineación con las decisiones arquitectónicas:

  • Comprobar si el código se adhiere a la arquitectura del sistema documentada en las especificaciones.
  • Comprobar si los patrones arquitectónicos elegidos son apropiados para el dominio del problema y los requisitos del sistema.

Separación de intereses y modularidad:

  • Compruebe si el código está dividido en varias capas, como la lógica empresarial, el acceso a los datos y la presentación, si es necesario.
  • Compruebe si el código está dividido en archivos respectivos, por ejemplo, para el frontend: HTML, JavaScript y CSS.
  • Examine si los módulos están bien definidos, cohesionados y poco acoplados.
  • Asegúrese de que no hay signos de código espagueti o componentes estrechamente acoplados.

Principios de análisis y diseño orientados a objetos:

  • Comprobar si se sigue el principio de responsabilidad única (SRP): no debe colocarse más de una responsabilidad en una sola clase o función.
  • Comprobar si el código respeta el principio abierto-cerrado (OCP): al añadir nuevas funcionalidades, éstas deben escribirse en nuevas clases y funciones; el código existente no debe modificarse.
  • Comprobar si se cumple el principio de sustitución de Liskov (LSP): la clase hija no debe cambiar el comportamiento (significado) de la clase padre; la clase hija puede utilizarse como sustituta de una clase base.
  • Examinar la segregación de interfaces: Las interfaces largas deben dividirse en otras más pequeñas basadas en la funcionalidad. La interfaz no debe contener dependencias (parámetros) que no sean necesarias para la funcionalidad esperada.
  • Examinar la inyección de dependencias: las dependencias no deben codificarse, sino inyectarse.

Ejemplo de violación del SRP:

public class CustomerManager
{
    public void AddCustomer(Customer customer) { /* Implementation */ }
    public void UpdateCustomer(Customer customer) { /* Implementation */ }
    public Customer GetCustomer(int id) { /* Implementation */ }
    public void SendWelcomeEmail(Customer customer) { /* Implementation */ }
    public void GenerateCustomerReport() { /* Implementation */ }
}

Gestión y almacenamiento de datos:

  • Revisar si las tablas están correctamente normalizadas para eliminar la redundancia de datos y considerar la desnormalización cuando el rendimiento sea crítico.
  • Comprobar la implementación de restricciones de base de datos, transacciones y estrategias de reversión para gestionar errores.
  • Revisar las estrategias de copia de seguridad y recuperación de datos para garantizar su durabilidad.
  • Evaluar si los campos más consultados están indexados para acelerar las operaciones de búsqueda.
  • Analizar cómo se gestionan los datos confidenciales, incluidos los mecanismos de cifrado y control de acceso.

Integración e interoperabilidad:

  • Evaluar el diseño, la documentación y la gestión de las API, garantizando que estén bien definidas y faciliten una comunicación segura y fiable.
  • Evalúe la integración con servicios externos y la gestión de dependencias externas
  • Cuando proceda, compruebe si el sistema admite formatos de datos estándar para la interoperabilidad, como JSON para servicios web.
  • Evalúe el uso de middleware o herramientas de integración para conectar sistemas dispares.

Escalabilidad y rendimiento:

  • Comprobar si la arquitectura puede gestionar el crecimiento futuro previsto en términos de usuarios, datos y transacciones.
  • Compruebe si la arquitectura puede escalarse tanto verticalmente (añadiendo más recursos a un único servidor) como horizontalmente (añadiendo más servidores).
  • Compruebe si las operaciones que consumen muchos recursos se trasladan a servicios independientes y se ejecutan mediante colas u otros mecanismos.
  • Comprobar si es posible mover servicios separados a diferentes instancias de servidor y si es posible ejecutar múltiples instancias de servicio
  • Revisar las estrategias de optimización del rendimiento, incluidos el almacenamiento en caché, el equilibrio de carga y el procesamiento asíncrono, para garantizar que el sistema pueda gestionar los picos de carga de forma eficiente.
  • Examinar los mecanismos de reserva y redundancia diseñados para mantener la disponibilidad y el rendimiento del sistema en condiciones adversas.

Seguridad:

  • Analizar el marco de seguridad de la aplicación, asegurándose de que incorpora una estrategia de defensa en capas para protegerse contra diversos tipos de amenazas.
  • Revisar los mecanismos de autenticación, autorización y auditoría para confirmar que son sólidos y cumplen las mejores prácticas de seguridad.
  • Inspeccionar los protocolos de cifrado y las medidas de seguridad aplicadas a los datos en tránsito y en reposo, verificando su idoneidad para salvaguardar la información sensible.

Revisión de la calidad del código

Lo siguiente en nuestra lista de comprobación de revisión de código es evaluar la calidad del código en cuanto a legibilidad, mantenimiento, reutilización, fiabilidad y extensibilidad. Un código claro y bien formateado es más fácil de entender y mantener, lo que se traduce en menos errores, un desarrollo más rápido y una reducción de la deuda técnica. Además, un estilo de codificación coherente fomenta una mejor colaboración entre los miembros del equipo, ya que todos pueden trabajar fácilmente con el código de los demás.

Métodos y funciones:

  • Comprueba si los nombres de las funciones tienen la forma “verbo expresivo + objeto” para que sea conciso y fácil de entender
  • Compruebe si los nombres de las funciones describen todas las acciones que realizan.
  • Evaluar si la función es demasiado grande y si debe ser refactorizada en funciones más pequeñas
  • Analizar si la función hace una sola cosa pero la hace bien (principio de responsabilidad única)

Variables:

  • Comprueba si cada variable tiene un único objetivo
  • Comprobar si los nombres de las variables se ajustan a la convención, como camelCase, kebab-case y snake_case
  • Asegúrese de que los nombres de las variables describen la entidad representada de forma completa y precisa
  • Comprobar si los nombres de las variables ofrecen suficiente información y son concisos.
  • Asegúrese de que los nombres de las variables facilitan la comprensión del tipo de datos que guardan.
  • Compruebe si hay números o cadenas mágicas

Ejemplo de cadenas mágicas:

if (message == "Insufficient funds")
{
    // Handle insufficient funds error
}
else if (message == "Invalid product ID")
{
    // Handle invalid product ID error
}
// ...more logic based on string comparisons

Expresiones condicionales:

  • Compruebe si las comprobaciones complejas se convierten en llamadas a funciones lógicas.
  • Compruebe si se comprueban primero los casos más probables. ¿Se tienen en cuenta todas las opciones?
  • Compruebe si hay funciones que contengan muchas expresiones condicionales. ¿Existe una buena razón para no rediseñarlas?

Recursividad:

  • Comprobar si la función recursiva contiene un punto de salida.
  • Compruebe si el caso base es fácil de entender y reconocer. Los casos base complejos pueden oscurecer la lógica general
  • Compruebe si la profundidad de la recursión se ajusta a las restricciones impuestas por el tamaño de la pila del programa

Mantenibilidad de clases/estructuras:

  • Comprobar si todas las estructuras de datos tienen un propósito central
  • Asegúrese de que todas las estructuras de datos están bien nombradas y de que sus nombres describen su propósito central.
  • Comprobar si todas las estructuras de datos son independientes de otras clases y si están débilmente acopladas
  • Comprobar si todas las estructuras de datos colaboran con otras clases sólo cuando es absolutamente necesario.
  • Compruebe si todas las estructuras de datos ocultan los detalles de su implementación a otras estructuras de datos tanto como sea necesario.

Reutilización:

  • Asegúrese de que el código sigue el principio “No te repitas” (DRY): cada conocimiento debe tener una representación única, inequívoca y autorizada dentro de un programa
  • Examinar si hay código duplicado que pueda refactorizarse en un método o clase común.
  • Compruebe si hay funciones o métodos que realicen tareas similares que puedan consolidarse.

Ejemplo de funciones que pueden consolidarse en un único método con un parámetro adicional:

public class CustomerService
{
    public Customer GetCustomerById(int id)
    {
        // Logic to retrieve customer from database by ID
        return customer;
    }

    public Customer GetCustomerByEmail(string email)
    {
        // Logic to retrieve customer from database by email
        return customer;
    }
}

Tratamiento de errores y registro:

  • Comprobar si la técnica de gestión de errores está definida, especificando el formato de los errores (mensajes, códigos de estado) y dónde enviar los errores (consola, archivo, AWS S3).
  • Comprueba si los errores se ignoran en algún lugar. Por ejemplo, un bloque “catch” vacío significa que el error se ignora, lo que dificulta la depuración.
  • Examine si el código de gestión de errores está centralizado en un único módulo que pueda sustituirse en caso necesario
  • Verificar si se devuelve demasiada información al usuario y no se expone información sensible como rutas del sistema, detalles de la base de datos o códigos de error internos.
  • Evaluar la implementación de los niveles de registro, como DEBUG, INFO, WARN, ERROR, FATAL.
  • Evaluar las políticas de rotación y archivo de registros para gestionar el tamaño de los archivos de registro y conservar los datos históricos.

Ejemplo de código que dificulta una depuración eficaz:

public decimal CalculateAverage(List numbers)
{
    try
    {
        int sum = 0;
        foreach (var number in numbers)
        {
            sum += number;
        }
        return sum / numbers.Count;
    }
    catch (Exception) 
    {
        // Log the error somewhere (omitted for brevity)
        return 0;
    }
}

Bibliotecas de terceros:

  • Analice si una biblioteca en particular es realmente necesaria
  • Compruebe si la biblioteca se distribuye bajo una licencia de código abierto
  • Inspeccione si hay alguna vulnerabilidad conocida en la biblioteca
  • Compruebe si se utilizan bibliotecas no conocidas, especialmente en lugar de bibliotecas populares y bien respaldadas por la comunidad.
  • Compruebe si los responsables de la biblioteca y la comunidad son activos. ¿Es fácil contactar con ellos en caso de problemas?
  • Examine si el proyecto utiliza diferentes bibliotecas frontend en diferentes secciones.

Comentarios y documentación:

  • Asegúrese de que los comentarios sean significativos, concisos y que sólo expliquen el código no obvio.
  • Compruebe que la documentación sea completa y clara, especialmente en las partes del código que sean complejas o fundamentales.
  • Confirme que todos los comentarios y la documentación reflejan la versión más actual del código base, teniendo en cuenta cualquier cambio o actualización reciente.

Simplificación y refactorización del código:

  • Detectar oportunidades de refactorización o simplificación del código para reducir la complejidad sin perder funcionalidad.
  • Promover la eliminación de segmentos de código superfluos u obsoletos, haciendo que la base de código sea más ligera y eficiente.
  • Apoyar el uso de patrones de diseño y marcos de trabajo reconocidos cuando sea conveniente para que los desarrolladores compartan un entendimiento común y se adhieran a las mejores prácticas del sector.

Cobertura y calidad de las pruebas

La cobertura de las pruebas garantiza que las pruebas cubran tantos aspectos de la aplicación como sea posible. Esto incluye la cobertura del código, los requisitos, la funcionalidad, la lógica empresarial, los escenarios de usuario, el rendimiento y la seguridad. El código sin cubrir podría ocultar errores críticos. Pero recuerda que una cobertura de código alta no equivale a una calidad alta. Fíjate en el diseño, la ejecución y la eficacia de las pruebas para conseguir la verdadera excelencia del software.

Cobertura de las pruebas:

  • Garantizar que el conjunto de pruebas cubre todos los requisitos funcionales especificados en la documentación.
  • Compruebe que las pruebas cubren las condiciones límite y los casos extremos
  • Evalúe el equilibrio entre pruebas unitarias y pruebas de integración: debe haber un número suficiente de pruebas unitarias para los componentes individuales y pruebas de integración para garantizar que estos componentes funcionan juntos.

Documentación y mantenimiento:

  • Compruebe si cada prueba tiene una descripción clara de lo que está probando y del resultado esperado.
  • Revisar la estructura y organización del código de prueba para facilitar el mantenimiento: el uso de dispositivos de prueba, métodos de configuración/descarga y pruebas basadas en datos.
  • Asegurarse de que toda la documentación asociada a las pruebas, como los planes de prueba o las descripciones de casos de prueba, está actualizada y se corresponde con las pruebas reales.

Fiabilidad y estabilidad de las pruebas:

  • Compruebe si las pruebas producen resultados coherentes y no presentan fallos
  • Examine cómo gestionan las pruebas los sistemas o dependencias externos, como bases de datos o API.
  • Compruebe si los fallos de las pruebas indican claramente el problema, devolviendo mensajes de error significativos y registros que señalen el origen del fallo.

Rendimiento y eficacia:

  • Revise el tiempo total que se tarda en ejecutar el conjunto de pruebas. Las pruebas de larga duración podrían optimizarse o ejecutarse en paralelo.
  • Evaluar si las pruebas son eficientes en términos de uso de memoria y CPU.
  • Asegurarse de que el conjunto de pruebas puede soportar cargas mayores, lo que es especialmente importante para las pruebas de rendimiento.

Seguridad

Imagina construir una casa sin comprobar si tiene defectos estructurales. Del mismo modo, un código inseguro expone su aplicación a ataques, poniendo en peligro los datos de los usuarios, la integridad del sistema y su reputación. Por eso nuestras directrices de revisión del código también incluyen una comprobación de la seguridad.

Autenticación y autorización:

  • Examine si las contraseñas se almacenan de forma segura (con hash y sal).
  • Asegúrese de que se aplican políticas de contraseñas seguras, incluidas la longitud y la complejidad de las contraseñas.
  • Si se requiere autenticación multifactor para el acceso privilegiado, compruebe si existe
  • Compruebe si existen comprobaciones de autorización para todas las acciones sensibles
  • Compruebe si se siguen los principios de mínimo privilegio (concediendo sólo los permisos necesarios)

Validación y desinfección de entradas:

  • Compruebe si se validan todas las entradas del usuario, como el tipo, la longitud y los caracteres permitidos
  • Examinar si se utilizan técnicas de saneamiento de datos, escape o codificación para evitar ataques de inyección.
  • Comprobar si se utilizan procedimientos almacenados para las interacciones con la base de datos a fin de evitar la construcción manual de SQL.

Seguridad de los datos:

  • Compruebe si los elementos de datos sensibles están cifrados en reposo y en tránsito utilizando algoritmos potentes como AES y TLS.
  • Asegúrese de que se siguen prácticas seguras de gestión de claves, como el uso de módulos de seguridad de hardware para el almacenamiento y la rotación periódica de claves.
  • Garantizar que el acceso a los datos está restringido a los usuarios y procesos autorizados.
  • Verifique los procedimientos de eliminación de datos, asegurándose de que los datos se sobrescriben para evitar su recuperación no autorizada.

Gestión de sesiones:

  • Analizar la seguridad de los identificadores de sesión para evitar el secuestro de sesiones. ¿Son aleatorios e imposibles de adivinar?
  • Asegúrese de que las sesiones se invalidan tras inactividad o cierre de sesión.
  • Compruebe si se han implementado tokens CSRF (Cross-Site Request Forgery) para evitar acciones no autorizadas.
  • Compruebe si se utilizan protocolos de comunicación seguros como HTTPS.

Tratamiento de errores y registro:

  • Compruebe si se proporcionan mensajes de error genéricos en lugar de trazas de pila detalladas que expongan detalles internos.
  • Asegúrese de que los registros se almacenan de forma segura y se supervisan en busca de actividades sospechosas, como intentos fallidos de inicio de sesión o patrones de acceso inusuales.
  • Compruebe si los registros evitan almacenar datos sensibles como contraseñas o números de tarjetas de crédito

Prácticas de codificación seguras:

  • Compruebe si se siguen las mejores prácticas de seguridad como las 10 principales de OWASP
  • Analice si las prácticas de gestión de memoria son seguras para evitar desbordamientos de búfer y otros ataques relacionados con la memoria.
  • Compruebe si se emplean generadores de números aleatorios criptográficamente seguros para tareas críticas como la generación de contraseñas y testigos de sesión.

Nuestra experiencia con las revisiones de código

Para Redwerk, las revisiones de código son algo muy natural. Revisamos el código que escribimos mientras desarrollamos soluciones de software para nuestros clientes. Pero también nos gusta revisar código externo y compartir nuestra experiencia para mejorar esos productos y ayudar a otros desarrolladores. He aquí un vistazo a nuestros últimos proyectos de revisión de código.

Project Science

Project Science es un software de gestión de presupuestos de TI desarrollado por Complete Network, proveedor líder de servicios gestionados de red y soporte de TI en Estados Unidos. Complete Network contrató a Redwerk para realizar una revisión del código de la API backend de Project Science.

Revisamos la arquitectura, la estructura de la base de datos y la calidad del código y detectamos 40 problemas críticos. Detectamos oportunidades fáciles de aumentar el rendimiento de la aplicación mediante el uso de la caché de Django, la caché de consultas de base de datos y las herramientas de aceleración de Python. Nuestros desarrolladores de Python informaron de 50 casos de código no utilizado, archivos vacíos, funciones no utilizadas, uso excesivo de palabras reservadas de Python y otros problemas de legibilidad y reutilización.

Con nuestra ayuda, Complete Network ha conseguido aumentar la capacidad de mantenimiento de su código en un 80% y ha adoptado prácticas de codificación saludables para evitar problemas similares en el futuro.

Site Compass

Site Compass es una aplicación de mapeo de redes desarrollada por Reivernet Group, un integrador de sistemas de redes con sede en EE.UU. que gestiona complejas redes de datos para los sectores de la hostelería, la educación y la administración pública. Está construida en C# utilizando Uno Platform.

Reivernet Group recurrió a Redwerk para recibir una auditoría imparcial de la aplicación Site Compass y prepararse para el lanzamiento oficial. El alcance de nuestro trabajo incluía la revisión de la arquitectura, la base de datos, la calidad del backend, la cobertura del código y la seguridad.

Informamos de ejemplos de acoplamiento estrecho, clases con una profundidad de herencia demasiado alta, métodos con demasiados parámetros, tipos demasiado grandes que conducen al fenómeno del objeto dios, y varios otros problemas. Gracias a nuestra auditoría, Reivernet Group pudo solucionar los problemas críticos antes de sacar el producto al mercado, lo que se tradujo en un 90% de legibilidad y mantenimiento del código.

¿Todavía tiene preguntas sobre la revisión de código? Cuéntenos sus necesidades y organizaremos una consulta gratuita para usted.