miércoles, 29 de enero de 2020

Capítulo 29: arquitectura embebida límpia - parte 1 - Clean Arquitecture


Hace tiempo leí un artículo titulado “The Growing Importance of Sustaining Software for de Dod” de Doug Schmidt’s blog. Doug hice la siguiente afirmación:

“Aunque el software no se desgasta, el firmware y el hardware se convierte en obsoleto y por lo tanto requiere de modificaciones software.”

Este fue un momento un momento esclarecedor para mi. Doug mencionó dos términos que yo habría pensado que eran obvios pero no. Software es esa cosa que puede tener una vida útil pero firmware se convertirá en obsoleto a medida que evolucione el hardware. Si has gastado algo de tiempo en desarrollo de sistemas embebidos, sabes que el hardware está continuamente evolucionando y mejorando. Al mismo tiempo, las características que se añadieron al nuevo software y este crece continuamente en complejidad.

Me gustaría añadir a la frase de Doug:

“Aunque el software no se desgasta, este se puede destruir desde el interior por dependencias no administradas del firmware y el hardware.”

No es raro que al software embebido se le niegue una vida potencialmente larga debido a estar infectado con dependencias del hardware.

Me gusta la definición de Doug del firmware, pero hay que ver otras definiciones:


La definición de Doug me hace darme cuenta que estas definiciones de firmware están mal o al menos están obsoletas. Firmware no significa que el código vive en un ROM. No es firmware por donde esté almacenado, más que eso, es firmware porque de lo que depende y de cómo de difícil es de cambiar a medida de cómo evoluciona el hardware. El hardware evoluciona así que estructuraríamos nuestro código embebido con esa realidad en mente.

No tengo nada contra el firmware o lo ingenieros firmware pero lo que se necesita es menos firmware y más software. Actualmente estoy en desacuerdo de que los ingenieros de firmware escriban tanto firmware.

Los ingenieros no firmware también escriben firmware! Los desarrolladores no firmware escriben esencialmente firmware cuando entierran una consulta SQL en el código o cuando difunden dependencias de la plataforma por todo su código. Los desarrolladores de Android escriben firmware cuando ellos no separan su lógica de negocio de la API de Android.

He estado involucrado en mucho esfuerzo donde la línea entre el código del producto (el software) y el código que interactúa con el hardware del producto (el firmware) es confuso hasta el punto de no existir. Por ejemplo, al final de los 90 tuve la diversión de ayudar a rediseñar un subsistema de comunicaciones que estaba en transición de time division multiplexing (TDM) a voice over IP (VOIP). VOIP es como se hacen las cosas ahora pero TDM fue considera estado del arte desde los 50 hasta los 60 y se desarrolló ampliamente en los 80 y 90.

En cualquier momento que teníamos una pregunta para el ingenieros de sistemas sobre como una llamada debería reaccionar a una situación dada, él desaparecería y un poco más tarde vendría con una respuesta detallada. ¿De dónde sacó la respuesta? La respuesta del ingeniero fue: “del código.” O sea, que el código enredado ¡fue la especificación para el nuevo producto! La implementación existente no tenía separación entre TDM y la lógica de negocio de hacer llamadas. Todo el producto fue hardware/tecnológicamente dependiente de arriba a abajo y no se podía desenredar. Todo el producto se había convertido esencialmente en firmware.

Considera otro ejemplo: Los mensajes comando que llegan a este sistema vía puerto serie. Como era de esperar, hay un procesador de mensajes. El procesador de mensajes conoce el formato de los mensajes, es capaz de decodificarlos y, entonces, puede enviar el mensaje al código que puede tratar la respuesta. Nada de esto es sorprendente, excepto que el procesador de mensajes reside en el mismo fichero que el código que interactúa con el hardware UART. EL procesador de mensajes estaba contaminado con detalles de UART. El procesador de mensajes podría haber sido software con una potencialmente larga vida útil pero en vez de eso es firmware. Al procesador de mensajes se le denegó la oportunidad de convertirse en software y esto no es lo correcto.

Yo he conocido y entendido la necesidad de separar el software del hardware desde hace mucho tiempo pero las palabras de Doug me aclararon cómo usar los términos “software” y “firmware” en relación uno con el otro.

Para ingenieros y programadores, el mensaje es claro; para de escribir tanto firmware y dar la oportunidad al código de tener vida útil larga. Por supuesto, exigirlo no lo hará. Veamos como se puede mantener limpia una arquitectura de software incrustado para dar al software oportunidad de tener una vida larga y útil. 
Prueba APP-TITUDE
¿Por qué tanto potencial software embebido se convierte en firmware? Esto parece que la mayoría del énfasis es que el código embebido funcione y no tanto en cómo estructurarlo para que tenga una vida útil. Kent Beck describe tres actividades en la construcción software (el texto entre comillas son las palabras de Kent y entre comillas las mías):

  1. “Primero haz que funcione”. Estás fuera del negocio si no funciona.
  2. “Después hazlo bien”. Refactorizar el código de manera que tú y otros puedan entenderlo y evolucionarlo  a medida que las necesidades cambian y están mejor entendidas.
  3. “Entonces haz esto rápido.” Refactorizar para las necesidades de rendimiento.

Muchos de los sistemas software embebidos que veo parecen haber sido escritos con “Haz que funcione” en mente, y quizá también por la obsesión por el “hazlo rápido” logrado por añadir micro-optimizaciones en cada oportunidad. En el libro “The Mythical Man-Month”, Fred Brooks nos sugiere “Cómo planear uno”. Kent y Fred están dando, virtualmente, el mismo consejo: aprende cómo funciona, entonces haz una mejor solución.

El software embebido no es especial cuando se trata de estos problemas. La mayoría de las aplicaciones no embebidas se construyen simplemente para trabajar, con poca consideración de hacer un código correcto para una larga vida útil.

Hacer que una aplicación funcione es lo que yo llamo la prueba “App-titude” para un programador. Los programadores que sólo se preocupan por hacer que sus aplicaciones funcionen están perjudicando a sus productos y empleadores. La programación es mucho más que sólo hacer que una aplicación funcione.

Como un ejemplo de código producido mientras se pasa la prueba “App-titude”, verifica estas funciones localizadas en un archivo de un pequeño sistema embebido:

ISR(TIMER1_vect) { ... }
ISR(INT2_vect) { ... }
void btn_Handler(void) { ... }
float calc_RPM(void) { ... }
static char Read_RawData(void) { ... }
void Do_Average(void) { ... }
void Get_Next_Measurement(void) { ... }
void Zero_Sensor_1(void) { ... }
void Zero_Sensor_2(void) { ... }
void Dev_Control(char Activation) { ... }
char Load_FLASH_Setup(void) { ... }
void Save_FLASH_Setup(void) { ... }
void Store_DataSet(void) { ... }
float bytes2float(char bytes[4]) { ... }
void Recall_DataSet(void) { ... }
void Sensor_init(void) { ... }
void uC_Sleep(void) { ... }

Esta lista de funciones está en el orden que se encuentra en el archivo fuente. Ahora, voy  a separarlas agrupadas por conceptos.

Funciones que tienen lógica de dominio.
float calc_RPM(void) { ... }
void Do_Average(void) { ... }
void Get_Next_Measurement(void) { ... }
void Zero_Sensor_1(void) { ... }
void Zero_Sensor_2(void) { ... }

Funciones que tienen configuración de la plataforma de software.
ISR(TIMER1_vect) { ... }*
ISR(INT2_vect) { ... }
void uC_Sleep(void) { ... }
Functions that react to the on off button press
void btn_Handler(void) { ... }
void Dev_Control(char Activation) { ... }
A Function that can get A/D input readings from the hardware
static char Read_RawData(void) { ... }

Funciones que almacenan valores para el almacenamiento persistente.
char Load_FLASH_Setup(void) { ... }
void Save_FLASH_Setup(void) { ... }
void Store_DataSet(void) { ... }
float bytes2float(char bytes[4]) { ... }
void Recall_DataSet(void) { ... }

Funciones que no hacen lo que su nombre implica.
void Sensor_init(void) { ... }

Mirando en otros ficheros de esta aplicación encontré muchos impedimentos para entender el código. También encontré una estructura de fichero que implicó que la única manera de comprobar cualquier cosa de este código es en el objetivo embebido. Virtualmente cada bit de este código sabe que está en una arquitectura especial de microprocesador, usando construcciones de C extendidas “extended” que vinculan el código a una cadena de herramientas y microprocesador en particular.  No hay manera para este código tener una vída larga útil a menos que el producto nunca necesite moverse a un entorno de hardware diferente.

Esta aplicación trabaja: el ingeniero pasó los test de App-titude pero la aplicación no puede decir que tenga una arquitectura limpia embebida.
El cuello de botella del hardware
Hay muchos aspectos especiales que los desarrolladores embebidos tienen que tratar que los desarrolladores de no embebidos no. Por ejemplo, el espacio de memoria limitada, las restricciones de tiempo o las líneas límite, la limitación IO, interfaces de usuario no convencionales y sensores y conexiones al mundo real. La mayoría del tiempo el  hardware está desarrollado de forma concurrente con el hardware y el firmware. Como un ingeniero desarrollando código para este tipo de sistemas, tú puedes no tener un lugar donde ejecutar ese código. Si esto no es suficientemente malo, una vez que obtienes el hardware, hay probabilidades que ese hardware tenga sus propios defectos, haciendo el  progreso de desarrollo de software incluso más lento de lo habitual.

Sí, embebido es especial. Los ingenieros embebidos son especiales pero el desarrollo de embebido no es especial de manera que los principio de este libro se puedan aplicar a los sistemas embebidos.

Uno de los problemas especiales es el cuello de botella del hardware. Cuando el código embebido se estructura sin aplicar los principios y prácticas de arquitectura limpia, tendrás que afrontar el escenario en el cual tú puedes probar tu código sólo en el objetivo. Si el objetivo es el único lugar donde son posibles las pruebas, el cuello de botella hardware lo ralentizará.

No hay comentarios:

Publicar un comentario