paint-brush
As máquinas de estado podem ajudá-lo a resolver problemas complexos de programaçãopor@pragativerma
9,231 leituras
9,231 leituras

As máquinas de estado podem ajudá-lo a resolver problemas complexos de programação

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

Muito longo; Para ler

'Estado' é um termo de programação comum que é experimentado por todos os desenvolvedores à medida que avançam da programação de nível inicial para intermediário. Na ciência da computação, o estado de um programa é definido como sua posição em referência a entradas armazenadas anteriormente. Uma variável de controle, como uma usada em um loop, por exemplo, altera o estado do programa a cada iteração. Examinar o estado atual de um programa pode ser usado para testar ou analisar a base de código. Adicionar outro estado é difícil porque requer reescrever o código de muitas classes diferentes.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - As máquinas de estado podem ajudá-lo a resolver problemas complexos de programação
Pragati Verma HackerNoon profile picture
0-item


'Estado' é um termo de programação comum que é experimentado por todos os desenvolvedores à medida que avançam do início para a programação de nível intermediário. Então, o que exatamente significa o termo "Estado"?

Em geral, o estado de um objeto é apenas o instantâneo atual do objeto ou uma parte dele. Enquanto isso, na ciência da computação, o estado de um programa é definido como sua posição em relação a entradas previamente armazenadas. Nesse contexto, o termo "estado" é usado da mesma maneira que na ciência: o estado de um objeto, como gás, líquido ou sólido, representa sua natureza física atual e o estado de um programa de computador reflete seus valores ou conteúdos atuais.

As entradas armazenadas são preservadas como variáveis ou constantes em um programa de computador. Ao avaliar o estado de um programa, os desenvolvedores podem examinar os valores contidos nessas entradas. O estado do programa pode mudar durante sua execução - as variáveis podem mudar e os valores da memória podem mudar. Uma variável de controle, como uma usada em um loop, por exemplo, altera o estado do programa a cada iteração. Examinar o estado atual de um programa pode ser usado para testar ou analisar a base de código.


Em sistemas mais simples, o gerenciamento de estado é frequentemente tratado com if-else, if-then-else, instruções try-catch ou sinalizadores booleanos; no entanto, isso é inútil quando há muitos estados imagináveis em um programa. Eles podem levar a um código desajeitado e complicado, difícil de entender, manter e depurar.


Uma desvantagem das cláusulas if-else ou booleanas é que elas podem se tornar bastante extensas, e adicionar outro estado é difícil porque requer reescrever o código de muitas classes diferentes. Suponha que você queira criar um jogo que tenha o menu principal, um loop de jogo e uma tela de conclusão.


Vamos construir um player de vídeo, por exemplo:


 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' )


O snippet de código acima é uma implementação if-else de um aplicativo de player de vídeo simples, onde os três estados básicos são - reproduzindo, pausado e parado. No entanto, se tentarmos adicionar mais estados, o código rapidamente se tornará complexo, inchado, repetitivo e difícil de entender e testar. Vamos ver como fica o código ao adicionar outro 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' )


Sem o padrão State, você teria que examinar o estado atual do programa em todo o código, incluindo os métodos update e draw. Se você quiser adicionar um quarto estado, como uma tela de configurações, terá que atualizar o código de muitas classes distintas, o que é inconveniente. É aqui que a ideia de máquinas de estado é útil.


O que é uma máquina de estado?

As máquinas de estado não são um conceito novo na ciência da computação; eles são um dos padrões de projeto básicos utilizados no negócio de software. É mais orientado ao sistema do que à codificação e é usado para modelar casos de uso.

Vejamos um exemplo simples da vida real de contratar um táxi pelo Uber:

  1. Quando você inicia o programa inicialmente, ele o leva para a tela inicial, onde você digita seu destino na área de pesquisa.
  2. Depois que o local correto é identificado, o Uber exibe as opções de viagem recomendadas, como Pool, Premier, UberGo, Uber XL e outras, juntamente com uma estimativa de preço.
  3. A viagem é confirmada e um motorista é atribuído assim que você selecionar a opção de pagamento e pressionar o botão 'Confirmar' com o tempo de viagem especificado, se necessário.
  4. O Uber agora exibe um mapa no qual você pode localizar seu motorista.


A Tela 1 é a primeira tela que todos os usuários neste caso de uso veem e é independente. A tela 2 depende da tela 1 e você não poderá ir para a tela 2 até fornecer dados precisos na tela 1. Da mesma forma, a tela 3 depende da tela 2, enquanto a tela 4 depende da tela 3. Se nenhum dos dois nem seu motorista cancelar sua viagem, você será movido para a tela 4, onde não poderá planejar outra viagem até que a atual termine.


Digamos que esteja chovendo muito e nenhum motorista aceite sua viagem ou não encontre nenhum motorista disponível em sua região para finalizar sua viagem; uma notificação de erro avisando sobre a indisponibilidade do driver é exibida e você permanece na tela 3. Você ainda pode retornar à tela 2, à tela 1 e até à primeira tela.

Você está em uma etapa diferente do processo de reserva de táxi e só poderá passar para o próximo nível se uma ação especificada no estágio atual for bem-sucedida. Por exemplo, se você inserir o local errado na tela 1, não poderá prosseguir para a tela 2 e não poderá prosseguir para a tela 3, a menos que escolha uma opção de viagem na tela 2, mas poderá volte sempre ao estágio anterior, a menos que sua viagem já esteja reservada.


No exemplo acima, dividimos o processo de reserva de táxi em várias atividades, cada uma das quais pode ou não ser autorizada a chamar outra atividade com base no status da reserva. Uma máquina de estado é usada para modelar isso. Em princípio, cada um desses estágios/estados deve ser autônomo, com um convocando o próximo somente após o término do atual, com sucesso ou não.


Em palavras mais técnicas, a máquina de estado nos permite dividir uma grande ação complicada em uma sucessão de atividades menores separadas, como a atividade de reserva de táxi no exemplo anterior.


Os eventos conectam tarefas menores e a mudança de um estado para outro é chamada de transição. Normalmente, realizamos algumas ações após a mudança de um estado para outro, como criar uma reserva no back-end, emitir uma fatura, salvar dados analíticos do usuário, capturar dados de reservas em um banco de dados, acionar o pagamento após o término da viagem e assim por diante .


Portanto, a fórmula geral para uma máquina de estado pode ser dada como:


Estado Atual + Alguma Ação/Evento= Outro Estado


Vejamos como seria uma máquina de estado projetada para um aplicativo de player de vídeo simples:





E podemos implementá-lo no código usando transições da seguinte forma:


 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


Agora, caso desejemos adicionar outro estado, digamos rebobinar, podemos fazer isso facilmente da seguinte maneira:





 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


Assim, podemos ver como as máquinas de estado podem simplificar uma implementação complexa e evitar que escrevamos códigos incorretos. Tendo aprendido os recursos das máquinas de estado, agora é importante entender por que e quando usar máquinas de estado.


Por que e quando usar máquinas de estado?

As State Machines podem ser utilizadas em aplicações que possuem estados distintos. Cada estágio pode levar a um ou mais estados subsequentes, bem como encerrar o fluxo do processo. Uma máquina de estado emprega entrada do usuário ou cálculos no estado para escolher qual estado entrar em seguida.


Muitos aplicativos precisam de um estágio de "inicialização", seguido de um estado padrão que permite uma ampla variedade de ações. As entradas anteriores e atuais, bem como os estados, podem ter um impacto nas ações que são executadas. As medidas de limpeza podem então ser executadas quando o sistema é "desligado".


Uma máquina de estado pode nos ajudar a conceituar e gerenciar essas unidades de forma mais abstrata se pudermos dividir um trabalho extremamente complexo em unidades menores e independentes, onde simplesmente precisamos descrever quando um estado pode fazer a transição para outro estado e o que acontece quando a transição ocorre. Não precisamos nos preocupar em como a transição ocorre após a configuração. Depois disso, só precisamos pensar em quando e o quê, não como.

Além disso, as máquinas de estado nos permitem ver todo o processo de estado de uma forma bastante previsível; uma vez definidas as transições, não precisamos nos preocupar com má administração ou transições de estado errôneas; a transição imprópria pode ocorrer apenas se a máquina de estado estiver configurada corretamente. Temos uma visão abrangente de todos os estados e transições em uma máquina de estado.


Se não usarmos uma máquina de estado, somos incapazes de visualizar nossos sistemas em vários estados possíveis, ou estamos conscientemente ou inconscientemente acoplando nossos componentes firmemente, ou estamos escrevendo muitas condições if-else para simular transições de estado, que complica o teste de unidade e integração porque devemos garantir que todos os casos de teste sejam escritos para validar a possibilidade de todas as condições e ramificações usadas.


Vantagens das Máquinas de Estado

As máquinas de estado, além de sua capacidade de desenvolver algoritmos de tomada de decisão, são formas funcionais de planejamento de aplicativos. À medida que os aplicativos ficam mais complexos, cresce a necessidade de um design eficaz.


Diagramas de estado e fluxogramas são úteis e ocasionalmente essenciais ao longo do processo de design. As State Machines são importantes não apenas para o planejamento de aplicativos, mas também são simples de criar.


A seguir estão algumas das principais vantagens das máquinas de estado na computação moderna:

  • Ele ajuda você a eliminar as condições de hard coding. Em seu nome, a máquina de estado abstrai toda a lógica relacionada a estados e transições.
  • As máquinas de estado geralmente têm um número finito de estados com transições definidas, simplificando a identificação de qual transição/dado/evento acionou o estado atual de uma solicitação.
  • Depois de estabelecer uma máquina de estado, os desenvolvedores podem se concentrar na criação de ações e pré-condições. Com validação e pré-condicionamento suficientes, as máquinas de estado restringem as operações fora de ordem. Como no exemplo do Uber, um motorista não pode ser recompensado até que a viagem seja concluída.
  • As máquinas de estado podem ser muito fáceis de manter. As ações tomadas durante cada transição são logicamente independentes umas das outras. Como resultado, o código correspondente pode ser isolado.
  • As máquinas de estado são menos propensas a mudanças e são mais estáveis. Torna-se muito mais fácil manter esses sistemas se os casos de uso atuais e futuros forem muito óbvios.


Desvantagens das Máquinas de Estado

Nem tudo nas máquinas de estado é bom, às vezes elas também podem levar a desvantagens e desafios. Aqui estão alguns dos problemas comuns com máquinas de estado:

  • As máquinas de estado geralmente são síncronas. Portanto, se você precisar de chamadas de API/execução de trabalho assíncronas em segundo plano, terá que pesar os prós e os contras cuidadosamente antes de decidir sobre a melhor opção.
  • O código pode ficar confuso rapidamente. Como as máquinas de estado são orientadas por dados, sua equipe de produto pode solicitar que você execute diferentes transições do mesmo estado com base em diferentes dados/parâmetros de entrada. Como resultado, esse tipo de demanda pode resultar em várias transições com uma verificação de pré-condição desajeitada. Depende inteiramente do produto e da configuração atual da máquina.
  • Se você precisar balancear a carga das instâncias da máquina de estado, vá com aquela que tem a persistência habilitada; caso contrário, você precisará implementar sua camada de persistência e as validações necessárias para garantir que várias solicitações disparadas em instâncias de máquina de estado separadas produzam resultados consistentes.
  • Como há poucos recursos ou comunidades dedicadas a implementações distintas de máquinas de estado, a assistência pode ser limitada depois que você escolher uma biblioteca.


Coisas a ter em mente ao usar máquinas de estado

Ao usar uma máquina de estado, seu sistema deve idealmente ter dois componentes lógicos:

  1. a própria máquina de estado/sistema de fluxo de trabalho
  2. a lógica de negócios contida em um ou mais serviços.


A máquina de estado pode ser considerada a infraestrutura que conduz as transições de estado; verifica transições de estado e executa ações configuradas antes, durante e depois de uma transição; no entanto, ele não deve saber qual lógica de negócios é executada nessas ações.


Portanto, em geral, é uma boa ideia isolar a máquina de estado da lógica de negócios principal usando abstrações corretas; caso contrário, gerenciar o código seria um pesadelo.


Aqui estão alguns outros cenários da vida real em que precisamos empregar a lógica da máquina de estado com cautela:

  • Uma máquina de estado é mais do que apenas estados, transições e ações. Também deve ser capaz de definir uma fronteira em torno de uma mudança de estado. Uma transição só pode ser bem-sucedida em casos particulares se for acionada por um sistema ou usuário confiável. Pode haver uma variedade de situações semelhantes. Como consequência, devemos ser capazes de desenvolver uma lógica de guarda de transição de estado apropriada.
  • Geralmente acabamos com muitos processos para a mesma entidade comercial que podem ser executados simultaneamente. Nesses casos, um processo não obstrui outros fluxos de trabalho; eles podem ou não ser acionados simultaneamente, mas podem coexistir; o segundo fluxo de trabalho pode começar a partir de uma das fases elegíveis do primeiro fluxo de trabalho, após o que pode se ramificar e trabalhar de forma independente. Esse tipo de caso de uso é estabelecido pelo negócio; nem toda organização terá.
  • Em princípio, os sistemas de fluxo de trabalho são independentes do domínio de negócios. Como consequência, em um mesmo sistema de fluxo de trabalho, podem ser estabelecidos muitos processos sem vínculo com a mesma organização empresarial. Eles podem ter um ponto de partida compartilhado ou distinto, dependendo se o sistema de fluxo de trabalho permite vários pontos de partida.
  • Quando vários fluxos de trabalho separados são formados no mesmo sistema de fluxo de trabalho, você obtém uma imagem global de todos os processos de negócios executados em várias entidades de negócios em seu sistema. Dependendo dos casos de uso de negócios, diferentes processos também podem ter certos estágios idênticos.


Casos de uso práticos ou reais para máquinas de estado:

A seguir estão algumas das aplicações práticas que se beneficiam do conceito de máquinas de estado em nossas vidas diárias:

  • Caixas de diálogo de página única ou com guias Uma guia na caixa de conversação representa cada estado. Um usuário pode iniciar uma transição de estado selecionando uma determinada guia. O status de cada guia inclui todas as ações que o usuário pode executar.
  • Uma máquina bancária de autoatendimento (ATM). Neste aplicativo, estados como aguardar a entrada do usuário, confirmar o valor necessário no saldo da conta, distribuir o dinheiro, imprimir o recibo e assim por diante são concebíveis.
  • Um software que faz uma única medição, armazena-a na memória e espera que o usuário execute outra ação. As etapas deste programa incluem aguardar a entrada do usuário, realizar a medição, registrar os dados, exibir os resultados e assim por diante. Configurar trabalhos ETL, por exemplo.
  • Máquinas de estado são comumente usadas para projetar interfaces de usuário. Ao projetar uma interface do usuário, ações distintas do usuário deslocam a interface do usuário em segmentos de processamento separados. Cada um desses elementos funcionará como um estado na State Machine. Esses segmentos podem levar a outro segmento para processamento posterior ou aguardar outro evento do usuário. Nesse cenário, a State Machine monitora o usuário para determinar qual ação ele deve executar a seguir.


Quando você compra algo em um site de comércio eletrônico online, por exemplo, ele passa por várias fases, como Pedido, Embalado, Enviado, Cancelado, Entregue, Pago, Reembolsado e assim por diante. A transição ocorre automaticamente à medida que as coisas passam por um depósito ou centro de logística e são digitalizadas em vários estágios, como quando um usuário cancela ou deseja um reembolso.

  • O teste de processo é outra aplicação típica para State Machines. Neste exemplo, cada estágio do processo é representado por um estado. Dependendo dos resultados do exame de cada estado, um estado separado pode ser declarado. Isso pode acontecer com frequência se o processo sob exame for estudado minuciosamente.


Conclusão

A noção de máquinas de estado é extremamente útil na programação. Ele não apenas agiliza o processo de desenvolvimento de aplicativos de caso de uso mais complicados, mas também reduz o trabalho de desenvolvimento necessário. Ele fornece uma compreensão mais simples e elegante dos eventos modernos e, quando aplicado corretamente, pode fazer milagres.