paint-brush
ステート マシンは、複雑なプログラミングの問題を解決するのに役立ちます@pragativerma
8,873 測定値
8,873 測定値

ステート マシンは、複雑なプログラミングの問題を解決するのに役立ちます

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

長すぎる; 読むには

「状態」は、初級レベルから中級レベルのプログラミングに進むすべての開発者が経験する一般的なプログラミング用語です。コンピューター サイエンスでは、プログラムの状態は、以前に保存された入力に対する位置として定義されます。たとえば、ループで使用されるような制御変数は、反復ごとにプログラムの状態を変更します。プログラムの現在の状態を調べることは、コードベースをテストまたは分析するために使用できます。別の状態を追加することは、多くの異なるクラスのコードを書き直す必要があるため困難です。

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - ステート マシンは、複雑なプログラミングの問題を解決するのに役立ちます
Pragati Verma HackerNoon profile picture
0-item


「状態」は、初級レベルから中級レベルのプログラミングに進むすべての開発者が経験する一般的なプログラミング用語です。では、「州」という用語は正確には何を意味するのでしょうか。

一般に、オブジェクトの状態は、オブジェクトまたはその一部の現在のスナップショットにすぎません。一方、コンピュータ サイエンスでは、プログラムの状態は、以前に保存された入力に関する位置として定義されます。このコンテキストでは、「状態」という用語は、科学と同じように使用されます。気体、液体、固体などのオブジェクトの状態は、現在の物理的性質とコンピューター プログラムの状態を表します。現在の値または内容を反映します。

保存された入力は、変数または定数としてコンピュータ プログラムに保存されます。プログラムの状態を評価する際に、開発者はこれらの入力に含まれる値を調べることがあります。プログラムの状態は、実行中に変化する可能性があります - 変数が変化する可能性があり、メモリ値が変化する可能性があります。たとえば、ループで使用されるような制御変数は、反復ごとにプログラムの状態を変更します。プログラムの現在の状態を調べることは、コードベースをテストまたは分析するために使用できます。


より単純なシステムでは、状態管理は if-else、if-then-else、try-catch ステートメント、またはブール フラグで処理されることがよくあります。ただし、プログラムで考えられる状態が多すぎる場合、これは役に立ちません。それらは、理解、保守、およびデバッグが困難な、不格好で複雑なコードにつながる可能性があります。


if-else-clauses または boolean の欠点の 1 つは、それらがかなり広範囲になる可能性があることです。また、多くの異なるクラスのコードを書き直す必要があるため、別の状態を追加することは困難です。メイン メニュー、ゲーム ループ、および完了画面を含むゲームを作成するとします。


たとえば、ビデオ プレーヤーを作成してみましょう。


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


上記のコード スニペットは、単純なビデオ プレーヤー アプリケーションの if-else 実装であり、再生中、一時停止中、停止中の 3 つの基本的な状態があります。ただし、さらに状態を追加しようとすると、コードは急速に複雑になり、肥大化し、反復的になり、理解とテストが難しくなります。別の状態「巻き戻し」を追加したときのコードを見てみましょう。


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


State-pattern がなければ、update メソッドや draw メソッドを含め、コード全体でプログラムの現在の状態を調べる必要があります。設定画面など、4 つ目の状態を追加する場合は、多数の個別のクラスのコードを更新する必要があり、不便です。ここで、ステート マシンのアイデアが役に立ちます。


ステートマシンとは?

ステート マシンは、コンピューター サイエンスにおいて目新しい概念ではありません。これらは、ソフトウェア ビジネスで使用される基本的な設計パターンの 1 つです。コーディング指向というよりもシステム指向であり、ユースケースをモデル化するために使用されます。

Uber でタクシーを雇う簡単な実際の例を見てみましょう。

  1. プログラムを最初に起動すると、ホーム画面が表示され、検索領域に目的地を入力します。
  2. 適切な場所が特定されると、Uber はプール、プレミア、UberGo、Uber XL などの推奨される旅行オプションと料金の見積もりを表示します。
  3. 支払いオプションを選択し、必要に応じて指定された移動時間で [確認] ボタンを押すと、旅行が確認され、ドライバーが割り当てられます。
  4. Uber は、ドライバーを見つけることができる地図を表示するようになりました。


画面 1 は、このユース ケースのすべてのユーザーが最初に見る画面であり、自己完結型です。画面 2 は画面 1 に依存しており、画面 1 に正確なデータを与えるまで画面 2 に進むことはできません。同様に、画面 3 は画面 2 に依存していますが、画面 4 は画面 3 に依存しています。ドライバーが旅行をキャンセルしない場合は、画面 4 に移動し、現在の旅行が終了するまで別の旅行を計画することはできません。


激しい雨が降っていて、あなたの旅行を受け入れるドライバーがいないか、あなたの地域であなたの旅行を終えることができるドライバーが見つからないとしましょう。ドライバーが利用できないことを警告するエラー通知が表示され、画面 3 にとどまります。画面 2、画面 1、さらには最初の画面に戻ることもあります。

あなたはタクシー予約プロセスの別の段階にいます。現在の段階で指定されたアクションが成功した場合にのみ、次のレベルに進むことができます。たとえば、画面 1 で間違った場所を入力すると、画面 2 に進むことができず、画面 2 で旅行オプションを選択しない限り画面 3 に進むことができませんが、旅行がすでに予約されていない限り、常に前の段階に戻ります。


上記の例では、タクシーの予約プロセスをいくつかのアクティビティに分割しました。それぞれのアクティビティは、予約のステータスに基づいて別のアクティビティを呼び出すことが許可されている場合と許可されていない場合があります。これをモデル化するためにステート マシンが使用されます。原則として、これらの各段階/状態は自律的である必要があり、現在の段階/状態が正常に終了したかどうかにかかわらず、次の段階を呼び出す必要があります。


より専門的な言い方をすれば、ステート マシンを使用すると、前の例のタクシー予約アクティビティのように、大規模で複雑なアクションを一連の個別の小さなアクティビティに分割できます。


イベントは小さなタスクを接続し、ある状態から別の状態に移行することを遷移と呼びます。バックエンドでの予約の作成、請求書の発行、ユーザー分析データの保存、データベースへの予約データのキャプチャ、旅行終了後の支払いのトリガーなど、通常、ある状態から別の状態に変化した後にいくつかのアクションを実行します。 .


したがって、ステート マシンの一般式は次のようになります。


現在の状態 + 何らかのアクション / イベント = 別の状態


単純なビデオ プレーヤー アプリケーション用に設計されたステート マシンがどのようになるかを見てみましょう。





そして、次のようにトランジションを使用してコードで実装できます。


 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


ここで、巻き戻しなどの別の状態を追加したい場合に備えて、次のように簡単に行うことができます。





 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


このように、ステート マシンが複雑な実装を簡素化し、間違ったコードを書かないようにする方法を理解できます。ステート マシンの機能を学んだので、次に、ステート マシンを使用する理由とタイミングを理解することが重要です。


ステート マシンを使用する理由とタイミング

ステート マシンは、個別の状態を持つアプリケーションで利用できます。各ステージは、プロセス フローを終了するだけでなく、1 つ以上の後続の状態につながる可能性があります。ステート マシンは、ユーザー入力またはインステート計算を使用して、次に入るステートを選択します。


多くのアプリケーションでは、「初期化」段階が必要であり、その後にさまざまなアクションを可能にするデフォルト状態が続きます。以前と現在の入力、および状態はすべて、実行されるアクションに影響を与える可能性があります。その後、システムが「シャットダウン」されたときに、クリーンアップ措置を実行できます。


ステート マシンは、非常に複雑なジョブをより小さな独立した単位に分解できれば、これらの単位をより抽象的に概念化し、管理するのに役立ちます。状態が別の状態に遷移できるタイミングと、遷移が発生したときに何が起こるかを記述するだけで済みます。セットアップ後に遷移がどのように発生するかを気にする必要はありません。その後は、どのようにではなく、いつ、何を考えればよいのです。

さらに、ステート マシンを使用すると、非常に予測可能な方法でステート プロセス全体を確認できます。遷移が設定されると、管理ミスや誤った状態遷移について心配する必要がなくなります。状態マシンが適切に構成されている場合にのみ、不適切な遷移が発生する可能性があります。ステート マシンのすべての状態と遷移を包括的に把握できます。


ステート マシンを使用しないと、考えられるさまざまな状態でシステムを視覚化できないか、コンポーネントを故意または無意識に緊密に結合しているか、状態遷移をシミュレートするために多くの if-else 条件を記述しています。すべての条件と分岐の可能性を検証するためにすべてのテスト ケースを作成する必要があるため、単体テストと統合テストが複雑になります。


ステート マシンの利点

ステート マシンは、意思決定アルゴリズムを開発する能力に加えて、アプリケーション計画の機能的な形態です。アプリケーションが複雑になるにつれて、効果的な設計の必要性が高まります。


状態図とフローチャートは有用であり、設計プロセス全体で必要になる場合があります。ステート マシンは、アプリケーションの計画だけでなく、作成も簡単です。


以下は、現代のコンピューティングにおけるステート マシンの主な利点の一部です。

  • ハードコーディング条件を排除するのに役立ちます。ユーザーに代わって、ステート マシンは状態と遷移に関連するすべてのロジックを抽象化します。
  • ステート マシンには多くの場合、明確な遷移を伴う有限数の状態があり、どの遷移/データ/イベントが要求の現在の状態をトリガーしたかを簡単に識別できます。
  • ステート マシンを確立した後、開発者はアクションと前提条件の構築に専念できます。十分な検証と事前調整により、ステート マシンは順不同の操作を制限します。 Uber の例のように、航海が完了するまでドライバーに報酬を与えることはできません。
  • ステート マシンの保守は非常に簡単です。各遷移中に実行されるアクションは、互いに論理的に独立しています。その結果、対応するコードを分離することができます。
  • ステート マシンは変化しにくく、より安定しています。現在および将来のユースケースが非常に明白な場合、そのようなシステムを維持することははるかに簡単になります。


ステート マシンの欠点

ステート マシンのすべてが良いわけではなく、欠点や課題につながることもあります。ステート マシンに関する一般的な問題の一部を次に示します。

  • ステート マシンは通常、同期しています。そのため、非同期のバックグラウンド API 呼び出し/ジョブ実行が必要な場合は、最適なオプションを決定する前に、長所と短所を慎重に検討する必要があります。
  • コードはすぐにごちゃごちゃになります。ステート マシンはデータ駆動型であるため、製品チームは、異なるデータ/入力パラメーターに基づいて同じ状態から異なる遷移を実行するよう依頼する場合があります。結果として、このタイプの要求は、不器用な前提条件チェックを伴う複数の遷移をもたらす可能性があります。製品とマシンの現在の構成に完全に依存します。
  • ステート マシン インスタンスの負荷を分散する必要がある場合は、永続性が有効になっているものを使用してください。それ以外の場合は、パーシスタンス レイヤーと必要な検証を実装して、別々のステート マシン インスタンスで発行された複数のリクエストが一貫した結果を生成するようにする必要があります。
  • 個別のステート マシンの実装に特化したリソースやコミュニティはほとんどないため、ライブラリを選択すると、サポートが制限される場合があります。


ステート マシンを使用する際の注意事項

ステート マシンを使用する場合、システムには次の 2 つの論理コンポーネントが理想的です。

  1. ステート マシン/ワークフロー システム自体
  2. 1 つ以上のサービスに含まれるビジネス ロジック。


ステート マシンは、状態遷移を駆動するインフラストラクチャと考えることができます。状態遷移を検証し、遷移前、遷移中、遷移後に構成されたアクションを実行します。ただし、これらのアクションでどのようなビジネス ロジックが実行されるかを認識しないようにする必要があります。


したがって、一般的には、正しい抽象化を使用して、ステート マシンをコア ビジネス ロジックから分離することをお勧めします。そうしないと、コードの管理が困難になります。


ステート マシン ロジックを慎重に使用する必要があるその他の実際のシナリオを次に示します。

  • ステート マシンは、単なる状態、遷移、およびアクションではありません。また、状態変化の境界を定義できる必要があります。移行は、信頼できるシステムまたはユーザーによってトリガーされた場合にのみ、特定のケースで成功する可能性があります。似たような状況がいろいろあるかもしれません。結果として、適切な状態遷移の保護ロジックを開発できるはずです。
  • 通常、同じビジネス エンティティに対して、同時に実行できる多くのプロセスが発生します。このような場合、1 つのプロセスが他のワークフローを妨げることはありません。それらは同時にトリガーされる場合とされない場合がありますが、共存する場合があります。 2 番目のワークフローは、最初のワークフローの適格なフェーズの 1 つから開始することができ、その後、分岐して独立して作業することができます。この種の使用例は、ビジネスによって確立されます。すべての組織がそれを持っているわけではありません。
  • 原則として、ワークフロー システムはビジネス ドメインから独立しています。その結果、同一のワークフローシステム内に、同一の業務組織に紐づけられていない多くのプロセスが成立する可能性があります。ワークフロー システムが複数の開始点を有効にするかどうかに応じて、共有または個別の開始点を持つことができます。
  • 同じワークフロー システムで多数の個別のワークフローが形成されると、システム内のさまざまなビジネス エンティティで実行されているすべてのビジネス プロセスの全体像が得られます。ビジネス ユース ケースによっては、異なるプロセスに同じステージが含まれる場合もあります。


ステート マシンの実用的または実際のユース ケース:

以下は、私たちの日常生活におけるステート マシンの概念から恩恵を受ける実用的なアプリケーションの一部です。

  • 単一ページまたはタブ付きのダイアログ ボックス 会話ボックスのタブは、各状態を表します。ユーザーは、特定のタブを選択することで状態遷移を開始できます。各タブのステータスには、ユーザーが実行できるすべてのアクションが含まれます。
  • セルフサービス バンキング マシン (ATM)。このアプリケーションでは、ユーザーの入力を待っている状態、口座残高に対して必要な金額を確認している状態、お金を分配している状態、領収書を印刷している状態などが考えられます。
  • 単一の測定値を取得してメモリに保存し、ユーザーが別のアクションを実行するのを待つソフトウェア。このプログラムのステップには、ユーザー入力の待機、測定の実行、データの記録、結果の表示などが含まれます。たとえば、ETL ジョブの設定。
  • ステート マシンは、ユーザー インターフェイスの設計によく使用されます。ユーザー インターフェイスを設計している間、個別のユーザー アクションは、ユーザー インターフェイスを個別の処理セグメントにシフトします。これらの各要素は、ステート マシンの状態として機能します。これらのセグメントは、さらに処理するために別のセグメントにつながるか、別のユーザー イベントを待つことができます。このシナリオでは、ステート マシンはユーザーを監視して、ユーザーが次に何をすべきかを判断します。


たとえば、オンラインの e コマース サイトから商品を購入すると、注文、梱包、発送、キャンセル、配送、支払い、払い戻しなど、さまざまな段階を経ます。移行は、物が倉庫または物流センターを通過するときに自動的に発生し、ユーザーがキャンセルしたり払い戻しを希望したりするときなど、さまざまな段階でスキャンされます。

  • プロセス テストは、ステート マシンのもう 1 つの典型的なアプリケーションです。この例では、プロセスの各段階が状態で表されています。各州の試験の結果に応じて、別の州が宣言される場合があります。検査中のプロセスが徹底的に研究されている場合、これは頻繁に発生する可能性があります。


結論

ステート マシンの概念は、プログラミングに非常に役立ちます。より複雑なユース ケース アプリの開発プロセスを合理化するだけでなく、必要な開発作業を削減します。現代の出来事をよりシンプルかつエレガントに把握することができ、正しく適用すると奇跡が起こる可能性があります.