paint-brush
Las máquinas de estado pueden ayudarlo a resolver problemas de programación complejospor@pragativerma
9,226 lecturas
9,226 lecturas

Las máquinas de estado pueden ayudarlo a resolver problemas de programación complejos

por Pragati Verma18m2022/09/26
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

'Estado' es un término de programación común que experimentan todos los desarrolladores a medida que avanzan desde el nivel inicial hasta el nivel intermedio de programación. En informática, el estado de un programa se define como su posición en referencia a las entradas previamente almacenadas. Una variable de control, como la que se usa en un ciclo, por ejemplo, cambia el estado del programa en cada iteración. El examen del estado actual de un programa se puede utilizar para probar o analizar el código base. Agregar otro estado es difícil porque requiere reescribir el código de muchas clases diferentes.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Las máquinas de estado pueden ayudarlo a resolver problemas de programación complejos
Pragati Verma HackerNoon profile picture
0-item


'Estado' es un término de programación común que experimentan todos los desarrolladores a medida que avanzan desde la programación inicial hasta la programación de nivel intermedio. Entonces, ¿qué significa exactamente el término "Estado"?

En general, el estado de un objeto es simplemente la instantánea actual del objeto o una parte de él. Mientras tanto, en informática, el estado de un programa se define como su posición con respecto a las entradas previamente almacenadas. En este contexto, el término "estado" se usa de la misma manera que en la ciencia: el estado de un objeto, como un gas, líquido o sólido, representa su naturaleza física actual, y el estado de un programa de computadora refleja sus valores o contenidos actuales.

Las entradas almacenadas se conservan como variables o constantes en un programa de computadora. Al evaluar el estado de un programa, los desarrolladores pueden examinar los valores contenidos en estas entradas. El estado del programa puede cambiar mientras se ejecuta: las variables pueden cambiar y los valores de la memoria pueden cambiar. Una variable de control, como la que se usa en un ciclo, por ejemplo, cambia el estado del programa en cada iteración. El examen del estado actual de un programa se puede utilizar para probar o analizar el código base.


En sistemas más simples, la administración de estado se maneja con frecuencia con sentencias if-else, if-then-else, try-catch o banderas booleanas; sin embargo, esto es inútil cuando hay demasiados estados imaginables en un programa. Pueden conducir a un código complicado y torpe que es difícil de entender, mantener y depurar.


Una desventaja de las cláusulas if-else o booleanos es que pueden volverse bastante extensos, y agregar otro estado es difícil porque requiere reescribir el código de muchas clases diferentes. Suponga que desea crear un juego que tenga el menú principal, un bucle de juego y una pantalla de finalización.


Construyamos un reproductor de video, por ejemplo:


 class Video: def __init__(self, source): self.source = source self.is_playing = False self.is_paused = False self.is_stopped = True # A video can only be played when paused or stopped def play(self): if not self.is_playing or self.is_paused: # Make the call to play the video self.is_playing = True self.is_paused = False else: raise Exception( 'Cannot play a video that is already playing.' ) # A video can only be paused when it is playing def pause(self): if self.is_playing: # Make the call to pause the video self.is_playing = False self.is_paused = True else: raise Exception( 'Cannot pause a video that is not playing' ) # A video can only be stopped when it is playing or paused def stop(self): if self.is_playing or self.is_paused: # Make the call to stop the video self.is_playing = False self.is_paused = False else: raise Exception( 'Cannot stop a video that is not playing or paused' )


El fragmento de código anterior es una implementación if-else de una aplicación de reproductor de video simple, donde los tres estados básicos son: reproducción, pausa y detención. Sin embargo, si tratamos de agregar más estados, el código rápidamente se volverá complejo, inflado, repetitivo y difícil de entender y probar. Veamos cómo se ve el código al agregar otro estado 'rebobinar':


 class Video: def __init__(self, source): self.source = source self.is_playing = False self.is_paused = False self.is_rewinding = False self.is_stopped = True # A video can only be played when it is paused or stopped or rewinding def play(self): if self.is_paused or self.is_stopped or self.is_rewinding: # Make the call to play the video self.is_playing = True self.is_paused = False self.is_stopped = False self.is_rewinding = False else: raise Exception( 'Cannot play a video that is already playing.' ) # A video can only be paused when it is playing or rewinding def pause(self): if self.is_playing or self.is_rewinding: # Make the call to pause the video self.is_playing = False self.is_paused = True self.is_rewinding = False self.is_stopped = False else: raise Exception( 'Cannot pause a video that is not playing or rewinding' ) # A video can only be stopped when it is playing or paused or rewinding def stop(self): if self.is_playing or self.is_paused or self.is_rewinding: # Make the call to stop the video self.is_playing = False self.is_paused = False self.is_stopped = True self.is_rewinding = False else: raise Exception( 'Cannot stop a video that is not playing or paused or rewinding' ) # 4. A video can only be rewinded when it is playing or paused. def rewind(self): if self.is_playing or self.is_paused: # Make the call to rewind the video self.is_playing = False self.is_paused = False self.is_stopped = False self.is_rewinding = True else: raise Exception( 'Cannot rewind a video that is not playing or paused' )


Sin el patrón de estado, tendría que examinar el estado actual del programa en todo el código, incluidos los métodos de actualización y dibujo. Si desea agregar un cuarto estado, como una pantalla de configuración, deberá actualizar el código de muchas clases distintas, lo cual es un inconveniente. Aquí es donde la idea de las máquinas de estado resulta útil.


¿Qué es una máquina de estado?

Las máquinas de estado no son un concepto novedoso en informática; son uno de los patrones de diseño básicos utilizados en el negocio del software. Está más orientado al sistema que a la codificación y se utiliza para modelar casos de uso.

Veamos un ejemplo simple de la vida real de contratar un taxi a través de Uber:

  1. Cuando inicia inicialmente el programa, lo lleva a la pantalla de inicio, donde escribe su destino en el área de búsqueda.
  2. Una vez que se ha identificado la ubicación correcta, Uber muestra las opciones de viaje recomendadas, como Pool, Premier, UberGo, Uber XL y otras, junto con una estimación de precios.
  3. El viaje se confirma y se asigna un conductor una vez que selecciona la opción de pago y presiona el botón 'Confirmar' con el tiempo de viaje especificado si es necesario.
  4. Uber ahora muestra un mapa en el que puede ubicar a su conductor.


La pantalla 1 es la primera pantalla que ven todos los usuarios en este caso de uso y es independiente. La Pantalla 2 depende de la Pantalla 1, y no podrá ir a la Pantalla 2 hasta que proporcione datos precisos en la Pantalla 1. Del mismo modo, la Pantalla 3 depende de la Pantalla 2, mientras que la Pantalla 4 depende de la Pantalla 3. Si ninguno de los dos ni su conductor cancela su viaje, pasará a la pantalla 4, donde no podrá planificar otro viaje hasta que finalice el actual.


Digamos que está lloviendo severamente y ningún conductor acepta su viaje o no se encuentra ningún conductor disponible en su región para terminar su viaje; aparece una notificación de error advirtiéndole de la falta de disponibilidad del controlador, y permanece en la pantalla 3. Todavía puede volver a la pantalla 2, a la pantalla 1 e incluso a la primera pantalla.

Está en un paso diferente del proceso de reserva de taxis y solo puede pasar al siguiente nivel si una acción específica en la etapa actual tiene éxito. Por ejemplo, si ingresa una ubicación incorrecta en la pantalla 1, no podrá continuar con la pantalla 2 y no podrá continuar con la pantalla 3 a menos que elija una opción de viaje en la pantalla 2, pero es posible que vuelve siempre a la etapa anterior a menos que tu viaje ya esté reservado.


En el ejemplo anterior, hemos dividido el proceso de reserva de un taxi en varias actividades, cada una de las cuales puede o no estar autorizada para llamar a otra actividad según el estado de la reserva. Se utiliza una máquina de estado para modelar esto. En principio, cada una de estas etapas/estados debe ser autónomo, convocando uno al siguiente solo después de que el actual haya terminado, con éxito o no.


En palabras más técnicas, la máquina de estados nos permite dividir una gran acción complicada en una sucesión de actividades separadas más pequeñas, como la actividad de reserva de taxis en el ejemplo anterior.


Los eventos conectan tareas más pequeñas y el cambio de un estado a otro se conoce como transición. Normalmente llevamos a cabo algunas acciones después de cambiar de un estado a otro, como crear una reserva en el back-end, emitir una factura, guardar los datos analíticos del usuario, capturar los datos de la reserva en una base de datos, activar el pago una vez finalizado el viaje, etc. .


Por lo tanto, la fórmula general para una máquina de estados se puede dar como:


Estado actual + Alguna acción / Evento = Otro estado


Veamos cómo se vería una máquina de estado diseñada para una aplicación de reproductor de video simple:





Y podemos implementarlo en el código usando transiciones de la siguiente manera:


 from transitions import Machine class Video: # Define the states PLAYING = 'playing' PAUSED = 'paused' STOPPED = 'stopped' def __init__(self, source): self.source = source # Define the transitions transitions = [ # 1. A video can only be played when it is paused or stopped. {'trigger': 'play', 'source': self.PAUSED, 'dest': self.PLAYING}, {'trigger': 'play', 'source': self.STOPPED, 'dest': self.PLAYING}, # 2. A video can only be paused when it is playing. {'trigger': 'pause', 'source': self.PLAYING, 'dest': self.PAUSED}, # 3. A video can only be stopped when it is playing or paused. {'trigger': 'stop', 'source': self.PLAYING, 'dest': self.STOPPED}, {'trigger': 'stop', 'source': self.PAUSED, 'dest': self.STOPPED}, ] # Create the state machine self.machine = Machine{ model = self, transitions = transitions, initial = self.STOPPED } def play(self): pass def pause(self): pass def stop(self): pass


Ahora, en caso de que queramos agregar otro estado, digamos rebobinar, podemos hacerlo fácilmente de la siguiente manera:





 from transitions import Machine class Video: # Define the states PLAYING = 'playing' PAUSED = 'paused' STOPPED = 'stopped' REWINDING = 'rewinding' # new def __init__(self, source): self.source = source # Define the transitions transitions = [ # 1. A video can only be played when it is paused or stopped. {'trigger': 'play', 'source': self.PAUSED, 'dest': self.PLAYING}, {'trigger': 'play', 'source': self.STOPPED, 'dest': self.PLAYING}, {'trigger': 'play', 'source': self.REWINDING, 'dest': self.PLAYING}, # new # 2. A video can only be paused when it is playing. {'trigger': 'pause', 'source': self.PLAYING, 'dest': self.PAUSED}, {'trigger': 'pause', 'source': self.REWINDING, 'dest': self.PAUSED}, # new # 3. A video can only be stopped when it is playing or paused. {'trigger': 'stop', 'source': self.PLAYING, 'dest': self.STOPPED}, {'trigger': 'stop', 'source': self.PAUSED, 'dest': self.STOPPED}, {'trigger': 'stop', 'source': self.REWINDING, 'dest': self.STOPPED}, # new # 4. A video can only be rewinded when it is playing or paused. {'trigger': 'rewind', 'source': self.PLAYING, 'dest': self.REWINDING}, #new {'trigger': 'rewind', 'source': self.PAUSED, 'dest': self.REWINDING}, # new ] # Create the state machine self.machine = Machine{ model = self, transitions = transitions, initial = self.STOPPED } def play(self): pass def pause(self): pass def stop(self): pass def rewind(self): pass


Por lo tanto, podemos ver cómo las máquinas de estado pueden simplificar una implementación compleja y evitar que escribamos código incorrecto. Habiendo aprendido las capacidades de las máquinas de estado, ahora es importante comprender por qué y cuándo usar las máquinas de estado.


¿Por qué y cuándo usar máquinas de estado?

Las máquinas de estado se pueden utilizar en aplicaciones que tienen distintos estados. Cada etapa puede conducir a uno o más estados posteriores, así como finalizar el flujo del proceso. Una máquina de estado emplea la entrada del usuario o cálculos en el estado para elegir a qué estado ingresar a continuación.


Muchas aplicaciones requieren una etapa de "inicialización", seguida de un estado predeterminado que permite una amplia gama de acciones. Las entradas anteriores y presentes, así como los estados, pueden tener un impacto en las acciones que se ejecutan. Las medidas de limpieza se pueden llevar a cabo cuando el sistema está "apagado".


Una máquina de estado puede ayudarnos a conceptualizar y administrar esas unidades de manera más abstracta si podemos dividir un trabajo enormemente complejo en unidades independientes más pequeñas, donde simplemente necesitamos describir cuándo un estado puede hacer la transición a otro estado y qué sucede cuando ocurre la transición. No necesitamos preocuparnos por cómo ocurre la transición después de la instalación. Después de eso, solo tenemos que pensar en cuándo y qué, no en cómo.

Además, las máquinas de estado nos permiten ver todo el proceso de estado de una manera muy predecible; una vez que se establecen las transiciones, no tenemos que preocuparnos por la mala gestión o las transiciones de estado erróneas; la transición incorrecta puede ocurrir solo si la máquina de estado está configurada correctamente. Tenemos una vista integral de todos los estados y transiciones en una máquina de estado.


Si no usamos una máquina de estados, no podemos visualizar nuestros sistemas en varios estados posibles, o estamos acoplando nuestros componentes a sabiendas o no, o estamos escribiendo muchas condiciones if-else para simular transiciones de estado, que complica las pruebas unitarias y de integración porque debemos asegurarnos de que todos los casos de prueba estén escritos para validar la posibilidad de todas las condiciones y ramificaciones utilizadas.


Ventajas de las máquinas de estado

Las máquinas de estado, además de su capacidad para desarrollar algoritmos de toma de decisiones, son formas funcionales de planificación de aplicaciones. A medida que las aplicaciones se vuelven más complejas, crece la necesidad de un diseño eficaz.


Los diagramas de estado y los diagramas de flujo son útiles y, en ocasiones, esenciales durante todo el proceso de diseño. Las máquinas de estado son importantes no solo para la planificación de aplicaciones, sino que también son fáciles de crear.


Las siguientes son algunas de las principales ventajas de las máquinas de estado en la informática moderna:

  • Le ayuda a eliminar las condiciones de codificación difíciles. En su nombre, la máquina de estados abstrae toda la lógica relacionada con los estados y las transiciones.
  • Las máquinas de estado a menudo tienen un número finito de estados con transiciones definidas, lo que simplifica la identificación de qué transición/datos/evento activó el estado actual de una solicitud.
  • Después de establecer una máquina de estado, los desarrolladores pueden concentrarse en crear acciones y condiciones previas. Con suficiente validación y preacondicionamiento, las máquinas de estado restringen las operaciones fuera de orden. Como en el ejemplo de Uber, un conductor no puede ser recompensado hasta que finalice el viaje.
  • Las máquinas de estado pueden ser bastante fáciles de mantener. Las acciones realizadas durante cada transición son lógicamente independientes entre sí. Como resultado, el código correspondiente se puede aislar.
  • Las máquinas de estado son menos propensas a cambiar y son más estables. Se vuelve mucho más fácil mantener dichos sistemas si los casos de uso actuales y futuros son muy obvios.


Desventajas de las máquinas de estado

No todo lo relacionado con las máquinas de estado es bueno, a veces también pueden generar inconvenientes y desafíos. Estos son algunos de los problemas comunes con las máquinas de estado:

  • Las máquinas de estado suelen ser síncronas. Por lo tanto, si necesita llamadas de API/ejecución de trabajos asincrónicas en segundo plano, tendrá que sopesar los pros y los contras cuidadosamente antes de decidir cuál es la mejor opción.
  • El código puede mezclarse rápidamente. Debido a que las máquinas de estado están basadas en datos, su equipo de producto puede pedirle que ejecute diferentes transiciones desde el mismo estado en función de diferentes datos/parámetros de entrada. Como resultado, este tipo de demanda puede resultar en múltiples transiciones con una verificación de condiciones previas torpe. Depende completamente del producto y de la configuración actual de la máquina.
  • Si necesita equilibrar la carga de instancias de máquinas de estado, vaya con la que tiene habilitada la persistencia; de lo contrario, deberá implementar su capa de persistencia y las validaciones necesarias para asegurarse de que múltiples solicitudes disparadas en instancias de máquinas de estado separadas produzcan resultados consistentes.
  • Debido a que hay pocos recursos o comunidades dedicadas a distintas implementaciones de máquinas de estado, la asistencia puede ser limitada una vez que haya elegido una biblioteca.


Cosas a tener en cuenta al usar máquinas de estado

Al usar una máquina de estado, su sistema idealmente debería tener dos componentes lógicos:

  1. el propio sistema de máquina de estado/flujo de trabajo
  2. la lógica empresarial contenida en uno o más servicios.


La máquina de estado puede considerarse como la infraestructura que impulsa las transiciones de estado; verifica las transiciones de estado y ejecuta acciones configuradas antes, durante y después de una transición; sin embargo, no debe saber qué lógica de negocio se hace en esas acciones.


Por lo tanto, en general, es una buena idea aislar la máquina de estado de la lógica comercial principal mediante el uso de abstracciones correctas; de lo contrario, administrar el código sería una pesadilla.


Aquí hay algunos otros escenarios de la vida real en los que debemos emplear la lógica de la máquina de estado con precaución:

  • Una máquina de estado es más que solo estados, transiciones y acciones. También debería ser capaz de definir un borde alrededor de un cambio de estado. Una transición solo puede tener éxito en casos particulares si la activa un sistema o usuario confiable. Puede haber una variedad de situaciones similares. Como consecuencia, deberíamos poder desarrollar una lógica de protección de transición de estado adecuada.
  • Por lo general, terminamos con muchos procesos para la misma entidad comercial que pueden ejecutarse simultáneamente. En tales casos, un proceso no obstruye otros flujos de trabajo; pueden o no desencadenarse simultáneamente, pero pueden coexistir; el segundo flujo de trabajo puede comenzar desde una de las fases elegibles del primer flujo de trabajo, después de lo cual puede bifurcarse y funcionar de forma independiente. Este tipo de caso de uso lo establece la empresa; no todas las organizaciones lo tendrán.
  • En principio, los sistemas de flujo de trabajo son independientes del dominio comercial. Como consecuencia, en un mismo sistema de flujo de trabajo se pueden establecer muchos procesos sin vínculo con la misma organización empresarial. Pueden tener un punto de partida compartido o distinto, dependiendo de si el sistema de flujo de trabajo permite múltiples puntos de partida.
  • Cuando se forman numerosos flujos de trabajo separados en el mismo sistema de flujo de trabajo, obtiene una imagen global de todos los procesos comerciales que se ejecutan en varias entidades comerciales en su sistema. Dependiendo de los casos de uso comercial, los diferentes procesos también pueden tener ciertas etapas idénticas.


Casos de uso prácticos o de la vida real para máquinas de estado:

Las siguientes son algunas de las aplicaciones prácticas que se benefician del concepto de máquinas de estado en nuestra vida diaria:

  • Cuadros de diálogo de una sola página o con pestañas Una pestaña en el cuadro de conversación representa cada estado. Un usuario puede iniciar una transición de estado seleccionando una determinada pestaña. El estado de cada pestaña incluye las acciones que el usuario puede realizar.
  • Un cajero automático de autoservicio (ATM). En esta aplicación, son concebibles estados tales como esperar la entrada del usuario, confirmar la cantidad necesaria contra el saldo de la cuenta, distribuir el dinero, imprimir el recibo, etc.
  • Un software que toma una sola medida, la almacena en la memoria y luego espera a que el usuario realice otra acción. Los pasos de este programa incluyen esperar la entrada del usuario, realizar la medición, registrar los datos, mostrar los resultados, etc. Configurar trabajos ETL, por ejemplo.
  • Las máquinas de estado se utilizan comúnmente para diseñar interfaces de usuario. Al diseñar una interfaz de usuario, distintas acciones del usuario cambian la interfaz de usuario en segmentos de procesamiento separados. Cada uno de estos elementos funcionará como un estado en la Máquina de Estado. Estos segmentos pueden conducir a otro segmento para su posterior procesamiento o esperar otro evento de usuario. En este escenario, State Machine supervisa al usuario para determinar qué acción debe realizar a continuación.


Cuando compra algo en un sitio de comercio electrónico en línea, por ejemplo, pasa por varias fases, como pedido, empaquetado, enviado, cancelado, entregado, pagado, reembolsado, etc. La transición ocurre automáticamente a medida que las cosas se mueven a través de un almacén o centro logístico y se escanean en varias etapas, como cuando un usuario cancela o desea un reembolso.

  • La prueba de procesos es otra aplicación típica de las máquinas de estado. En este ejemplo, cada etapa del proceso está representada por un estado. Dependiendo de los resultados del examen de cada estado, se puede declarar un estado separado. Esto puede suceder con frecuencia si el proceso bajo examen se estudia a fondo.


Conclusión

La noción de máquinas de estado es extremadamente útil en programación. No solo agiliza el proceso de desarrollo de aplicaciones de casos de uso más complicados, sino que también reduce el trabajo de desarrollo necesario. Proporciona una comprensión más simple y elegante de los acontecimientos modernos y, cuando se aplica correctamente, puede hacer milagros.