Ejecutar Charts de Helm con helmfile
Guía para un Plan de Recuperación de Desastres en un entorno Kubernetes

Código Espagueti, el «anti patrón» de programación

Codigo espagueti

El código espagueti no es realmente un patrón de programación, más bien un «anti patrón», por eso se debe tener en cuenta por lo menos algunos patrones avanzados de programación. Muchos estudiantes o graduados de electrónica que trabajan con sistemas embebidos desconocen los patrones o paradigmas de programación avanzados. Aunque algunos están familiarizados con el patrón Plano Secundario/Plano Principal. Todavía hay muchos que escriben «Código Espagueti» y desconocen los patrones más sofisticados como las Máquinas de Estado Finito, los Planificadores de Tareas y los Sistemas Operativos de Tiempo Real.

Lista de antipatrones de diseño

Para escribir un código más profesional y de calidad, un desarrollador de software para sistemas embebidos debe conocer y dominar gradualmente los diferentes patrones o métodos de programación más utilizados. A continuación, explicaremos brevemente cada uno de ellos. Es la inauguración de la sección de programación y es teoría pura.

Código Espagueti

El código espagueti es un término peyorativo para los programas de computación que tienen una estructura de control de flujo compleja e incomprensible. Su nombre deriva del hecho que este tipo de código parece asemejarse a un plato de espaguetis, es decir, un montón de hilos intrincados y anudados.

Tradicionalmente suele asociarse este estilo de programación con lenguajes básicos y antiguos, donde el flujo se controlaba mediante sentencias de control muy primitivas como goto y utilizando números de línea. Se caracteriza por el uso excesivo de sentencias condicionales anidadas y falta de estructura y modularidad. Esto dificulta la comprensión, el mantenimiento y la expansión del código. Además, puede llevar a errores y problemas de programación.

Ejemplo:

/* Ejemplo de Código Espagueti*/
// Controlar que la puerta esté cerrada
main() {
     while(TRUE) {
         while (!(puerta_cerrada()))
              ;
         // Empezar con el llenado de agua
         while (!(NIVEL_AGUA_OK)) {
             if (puerta_cerrada()) {
                 valvula_agua(ABRIR);
                 compartimento_detergente(ABRIR);
             }
             else
                 valvula_agua(CERRAR);
        }

         //Calentar el agua
         if (SELECCION_AGUA_CALIENTE) {
             while (tiempo <= TIEMPO_CALENTAMIENTO) {
                 if (puerta_cerrada())
                     calentador(ON);
                 else
                     calentador(OFF);
                 ++tiempo;
             }
         }

         // Empezar ciclo de lavado
         tiempo = 0;
         while (tiempo <= TIEMPO_LAVADO) {
             if (puerta_cerrada())
             ciclo_lavado(ON);
             else
                 ciclo_lavado(OFF);
             ++tiempo;
        }
     }
}

El análisis del código revela lo siguiente:

  1. Uso recurrente de sentencias condicionales anidadas (if – then – else, while, etc.), conocido como «Código Convolucionado», lo cual dificulta la comprensión del algoritmo y aumenta la posibilidad de errores de programación.
  2. Importancia de verificar si la puerta de la lavadora está cerrada en cada sección principal del programa, lo cual requiere repetir código de comprobación y aumenta la complejidad del programa. Añadir más funciones complicaría aún más el seguimiento del programa.
  3. Presencia de «Código de Bloqueo» en las tres funciones implementadas, donde una instrucción condicional bloquea al procesador hasta que se cumpla una condición. Esto puede afectar la ejecución de tareas críticas y dificultar la adición de funciones paralelas. Existe el riesgo de quedar atrapado en un ciclo permanente si ocurre una falla en un sensor, a menos que se implementen medidas adicionales, como una función de temporización o un «watchdog timer».
  4. Falta de estructura y modularidad en el Código Espagueti, lo que dificulta la expansión, comprensión, mantenimiento, depuración y mejora del sistema, tanto para el autor original como para otros programadores que puedan hacerse cargo en el futuro.

El código analizado presenta problemas de complejidad, repetición de código, bloqueo del procesador y falta de estructura, lo cual dificulta su comprensión, mantenimiento y expansión. Es necesario aplicar técnicas de programación más organizadas y modulares para mejorar la calidad del código.

Patrones de diseño

Los patrones de diseño son técnicas para resolver problemas comunes en el desarrollo de software y otros ámbitos de diseño. Surgieron a partir de la arquitectura y se popularizaron en la informática con el libro «Design Patterns» del grupo Gang of Four. Como se ha mencionado, los patrones de diseño tienen como objetivo proporcionar soluciones reutilizables, estandarizar el diseño y facilitar el aprendizaje.

Se clasifican en patrones de arquitectura, patrones de diseño y dialectos. Los antipatrones de diseño se deben tener en cuenta también, ya que previenen errores comunes. La descripción de un patrón sigue una plantilla estándar que incluye su nombre, clasificación, intención, estructura, participantes, consecuencias, implementación y ejemplos de uso.

Algunos patrones por categorías:

También se puede categorizar según la escala o nivel de abstracción:

  • Patrones de arquitectura: Aquellos que expresan un esquema organizativo estructural fundamental para sistemas de software.
  • Patrones de diseño: Aquellos que expresan esquemas para definir estructuras de diseño (o sus relaciones) con las que construir sistemas de software.
  • Dialectos: Patrones de bajo nivel específicos para un lenguaje de programación o entorno concreto.

Patrón «Super Lazo» (Super Loop)

El patrón Super Lazo consiste en un gran lazo cerrado que se repite indefinidamente y contiene muchas instrucciones. Este patrón puede ser válido para aplicaciones simples, pero requiere una buena estructura y claridad para evitar problemas. Una forma de mejorar la estructura del Super Lazo es agrupar el código en funciones que encapsulen el comportamiento del sistema.

Ejemplo:

/* Ejemplo de un Super Loop */
main() {
     while (TRUE) {
         // Muchas y muchas instrucciones aquí
         // ..........
     }
}

El ejemplo anteriormente mencionado como «Código Espagueti» en realidad también es un Super Lazo. No hay nada inherentemente malo en el uso del Super Lazo, ya que si se le proporciona cierta estructura, el código puede volverse más claro. Para mejorar la estructura y claridad del Super Lazo, se puede comenzar agrupando el código en funciones que encapsulen adecuadamente el comportamiento del mismo:

/* Ejemplo de un Super Loop estructurado */
main() {
     while (TRUE) {
         inicialización();
         cargar_agua();
         calentar_agua();
         ciclo_lavado_1();
         ciclo_lavado_2();
         centrifugado();
     }
}

A pesar de que en el ejemplo anterior el código parece estar mejor estructurado, todavía se requiere la presencia de código para verificar si la puerta está cerrada y retardos de tiempo en la mayoría, si no en todas, las funciones que lo necesiten.

Codigo spagueti VS super lazo

Patrón Plano Secundario/Plano Principal (Background/Foreground)

Este patrón se basa en el Super Lazo pero incorpora el uso de interrupciones. El código se divide en dos secciones principales: el Lazo Principal (Background) y la Sección de Rutina/s de Interrupción (Foreground). El Lazo Principal contiene operaciones que no requieren un tratamiento en tiempo real, mientras que las Rutinas de Interrupción se encargan de operaciones que necesitan una respuesta inmediata.

Ejemplo:

/* Ejemplo de Patrón Background/Foreground */

/* Sección Lazo Principal (Background) - En esta sección se ubican
 todas las operaciones del sistema que no necesitan tratamiento 
 crítico inmediato, que duran mucho tiempo y son generalmente
 sincrónicas */
main() {
  while (TRUE) {
   // Funciones principales sin código de comprobación
   // y retardos de tiempo
   inicialización();
   cargar_agua();
   calentar_agua();
   ciclo_lavado_1();
   ciclo_lavado_2();
   centrifugado();
  }
}

/* Sección de Rutina(s) de Interrupción - En esta sección se ubican
 las operaciones en el sistema que requieren tratamiento crítico inmediato, son de comparativamente corta duración y generalmente
 asíncronas (pueden suceder de manera impredecible) */
void interrupt isr(void) {
  /* Código que se ejecuta si la puerta de la lavadora
  se abre por algún motivo */
  if (pin_puerta_lavadora == OPEN)
   // Bandera para comunicar a todas las funciones que
   // la puerta está abierta
   puerta_lavadora = OPEN;
 
 /* Código único para generar retardos de tiempo a ser
 usados por las distintas funciones del sistema */
  if (timer_interrupt_flag)
   // Bandera para comunicar a la función en turno
   // que el retardo de tiempo ha concluido
   timer_over = TRUE;
}

Máquinas de Estado Finito (Finite State Machines)

Las Máquinas de Estado Finito (MEFs) son una forma de programación que describe un sistema a través de una abstracción con características específicas:

  • El sistema tiene un número finito de estados o condiciones de existencia.
  • Cada estado realiza acciones definidas o muestra un comportamiento específico.
  • Se conocen los eventos que causan transiciones entre estados según reglas específicas.

Ejemplo MEF en lenguaje C es mediante bloques switch – case:

 
/* Ejemplo de Patrón con Máquina de Estado Finito */
enum eEstados {
     INICIALIZACION,
     CARGAR_AGUA,
     CALENTAR_AGUA,
     CICLO_LAVADO_1,
     CICLO_LAVADO_2,
     CENTRIFUGADO } estado;
 
main() {
    estado = INICIALIZACION;
    while (TRUE) {
        switch (estado) {
             case INICIALIZACION:{
                if (inicialización())
                    estado = CARGAR_AGUA;
             } break;
             case CARGAR_AGUA:{
                 if (cargar_agua())
                     estado = CALENTAR_AGUA;
             } break;
             case CALENTAR_AGUA:{
                 if (calentar_agua())
                     estado = CICLO_LAVADO_1;
             } break;
             case CICLO_LAVADO_1:{
                 if (ciclo_lavado_1())
                     estado = CICLO_LAVADO_2;
             } break; 
             case CICLO_LAVADO_2:{
                 if (ciclo_lavado_2())
                     estado = CENTRIFUGADO;
             } break;
             case CENTRIFUGADO:{
                 if (centrifugado())
                     estado = INICIALIZACION;
             } break;
         }
     }
}

Las MEFs definen el comportamiento del sistema. En la implementación en lenguaje C, se utilizan bloques switch-case. Algunas ventajas del uso de MEFs incluyen:

  • Proporcionan abstracción y estructura al sistema, lo que facilita la comprensión del código y ayuda a evitar el código espagueti.
  • Cada estado guarda información sobre eventos y estados pasados, lo que ayuda a reducir el uso de variables globales y banderas en la aplicación.

Si bien las MEFs no son adecuadas para todas las aplicaciones, se recomienda su uso en aquellas que se ajusten a este modelo.

Autómata Finito

Patrón con Planificadores de Tarea (Task Schedulers)

Un planificador de tareas decide qué tarea se ejecutará a continuación en un sistema operativo multitarea. Utiliza el estado actual de la tarea, las prioridades y un algoritmo específico para tomar esta decisión. Aunque solo se ejecuta una tarea a la vez en un procesador, el planificador permite simular la ejecución simultánea al alternar rápidamente entre las tareas. Su objetivo es optimizar el uso de recursos y garantizar una distribución equitativa del tiempo de procesamiento.

Los algoritmos de planificación más populares son:

Planificador Equitativo (Round-Robin Scheduler)

El Planificador Equitativo asigna a cada tarea un tiempo constante llamado «rebanada» o «time slice». Este algoritmo distribuye de manera equitativa el tiempo del procesador entre todas las tareas.

Round-Robin Scheduler

Planificador Cooperativo (Cooperative Scheduler)

El Planificador Cooperativo asigna un orden de ejecución y se basa en el principio de «Correr Hasta Terminar». Las tareas cooperan y ceden el control al planificador cuando han terminado, evitando bloqueos prolongados.

Cooperative Scheduler

Planificador Preemptivo (Preemptive Scheduler)

Los Planificadores de Tarea son sistemas operativos de tiempo real simplificados, dependiendo de la complejidad de sus algoritmos y los servicios adicionales que ofrecen. También se les conoce como Sistemas Operativos «A Medida».

Preemptive Scheduler

Los planificadores de tareas pueden ser vistos como sistemas operativos de tiempo real simplificados. Su complejidad de implementación y los servicios adicionales que ofrecen varían. También se les conoce como sistemas operativos «a medida».

Conclusiones

En resumen, el «Código Espagueti» es un estilo de programación desorganizado y difícil de mantener, que carece de estructura y modularidad. A medida que los desarrolladores de software para sistemas embebidos se adentran en patrones de programación más avanzados, como el Super Lazo, el Patrón Plano Secundario / Plano Principal y los Sistemas Conducidos Por Eventos, pueden mejorar la calidad, seguridad y confiabilidad del código.

Estos patrones permiten una mejor organización y claridad del código, facilitan la expansión y modificación del sistema, y permiten manejar eventos en tiempo real de manera eficiente. En particular, los Sistemas de Tiempo Real y los Sistemas Conducidos Por Eventos son especialmente relevantes en aplicaciones críticas y reactivas.

Conocer y dominar estos patrones y paradigmas de programación avanzados es fundamental para los desarrolladores de software que trabajan en sistemas embebidos, ya que les permite escribir código más profesional y acorde con los estándares de calidad y seguridad requeridos en estas aplicaciones.

Más apuntes

Invítame a un café con bitcoins:
1QESjZDPxWtZ9sj3v5tvgfFn3ks13AxWVZ

Bitcoins para café
También puedes invitarme a algo para mojar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Rellena este campo
Rellena este campo
Por favor, introduce una dirección de correo electrónico válida.
Tienes que aprobar los términos para continuar