PHPでの契約プログラミング

契約プログラミング 実生活では、私たちはどこでもさまざまな契約に直面しています:仕事に応募するとき、仕事をするとき、相互の合意書などに署名するとき。 契約の法的強制力は、利益の保護を保証し、結果なしに違反を許可しません。これにより、契約に記載されている項目が満たされるという確信が得られます。 この自信は、時間の計画、費用の計画、および必要なリソースの計画に役立ちます。 しかし、プログラムコードが契約で記述されている場合はどうでしょうか? 面白い? それから、キャットへようこそ!



はじめに



オブジェクト指向プログラミング言語Eiffelを開発したときに、90年代にBertrand Meyerと契約プログラミングのアイデアが生まれました。 Bertrandのアイデアの本質は、コードの正式な検証と正式な仕様を記述するためのツールを持つことでした。 このようなツールは、具体的な答えを提供します。「メソッドは、呼び出しに必要な条件を満たせば、そのメソッドが自身の仕事をコミットします」。 また、契約はこの役割に最適でした。なぜなら、前提条件が満たされた場合(検証)にシステムから受信する内容(仕様)を説明できるためです。 それ以来、このプログラミング手法の多くの実装は、特定の言語のレベルと、外部コードを使用してコントラクトを指定して検証を実行できる個別のライブラリの両方の形で登場しました。 残念ながら、PHPでは、言語自体のレベルではコントラクトプログラミングがサポートされていないため、実装はサードパーティライブラリを使用してのみ実行できます。



コード契約



コントラクトプログラミングはオブジェクト指向言語向けに開発されたため、コントラクトの主要な作業要素がクラス、メソッド、プロパティであると推測することは難しくありません。



前提条件


最も単純な契約オプションは前提条件です。特定のアクションを実行する前に満たす必要がある要件です。 OOPのフレームワーク内では、すべてのアクションはクラス内のメソッドで記述されるため、メソッドに前提条件が適用され、メソッドの呼び出し時、ただしメソッド本体自体が実行される前に検証が行われます。 明らかな使用法は、メソッドに渡されたパラメーターの有効性、構造、および正確性を確認することです。 つまり、契約書に記載されている前提条件の助けを借りて、私たちが絶対に取り組まないすべてのものを説明します。 これはすごい!



根拠がないように、例を見てみましょう。



class BankAccount { protected $balance = 0.0; /** * Deposits fixed amount of money to the account * * @param float $amount */ public function deposit($amount) { if ($amount <= 0 || !is_numeric($amount)) { throw new \InvalidArgumentException("Invalid amount of money"); } $this->balance += $amount; } }
      
      







暗黙の形式で残高を補充する方法では、補充量の数値が必要であり、これも厳密にゼロより大きくなければなりません。そうでない場合、例外がスローされます。 これは、コードの典型的な前提条件です。 ただし、いくつかの欠点があります。目でこれらのチェックを探すことを余儀なくされ、別のクラスにいるため、そのようなチェックの有無をすばやく評価することはできません。 また、明示的な契約がない場合、クラスコードには入力引数の必要なチェックがあり、それらについて心配する必要はないことを覚えておく必要があります。 別の要因:これらのチェックは、アプリケーションの開発モードと戦闘モードの両方で常に実行されます。これにより、アプリケーションの速度がマイナス方向にわずかに影響されます。



前提条件の実装に関して、PHPには、クレームを確認するための特別な構成-assert()があります。 その大きな利点は、戦闘モードでチェックを無効にして、コマンドコード全体を単一のNOPに置き換えることができることです。 このコンストラクトを使用して前提条件を記述する方法を見てみましょう。



 class BankAccount { protected $balance = 0.0; /** * Deposits fixed amount of money to the account * * @param float $amount */ public function deposit($amount) { assert('$amount>0 && is_numeric($amount); /* Invalid amount of money /*'); $this->balance += $amount; } }
      
      





コントラクトのフレームワーク内の前提条件がプログラム操作ロジックのチェックに使用され、クライアントから送信されたパラメーターの有効性について責任を負わないという事実に注意を喚起したいと思います。 契約は、システム自体内での相互作用に対してのみ責任を負います。 したがって、クレームは無効にできるため、ユーザー入力は常にフィルターでフィルター処理する必要があります。



事後条件


契約の次のカテゴリは事後条件です。 名前が示すように、このタイプのチェックはメソッド本体が実行された後、コントロールが呼び出し元のコードに戻るまで実行されます。 例からのdeposit



方法の場合、次の事後条件を形成できます。メソッドを呼び出した後の口座残高は、前の残高値に補充値を加えたものと等しくなければなりません。 残っている唯一のものは、コード内のステートメントの形でこれらすべてを記述することです。 しかし、ここで最初の失望に直面しています:最初にメソッド本体のバランスを変更してから、古いバランス値が必要なステートメントをチェックしようとするため、コードでこの要件を定式化する方法。 コードを実行する前にオブジェクトを複製し、事後条件をチェックすると、ここで役立ちます。



 class BankAccount { protected $balance = 0.0; /** * Deposits fixed amount of money to the account * * @param float $amount */ public function deposit($amount) { $__old = clone $this; assert('$amount>0 && is_numeric($amount); /* Invalid amount of money /*'); $this->balance += $amount; assert('$this->balance == $__old->balance+$amount; /* Contract violation /*'); } }
      
      







別の失望は、値を返すメソッドの事後条件を説明するのを待っています:



 class BankAccount { protected $balance = 0.0; /** * Returns current balance */ public function getBalance() { return $this->balance; } }
      
      





メソッドが現在の残高を返す必要があるという契約条件をここで記述する方法は? メソッドの本体の後に事後条件が満たされるため、チェックが機能する前にreturn



時につまずきます。 したがって、メソッドコードを変更して結果を$__result



変数に保存し、それを$this->balance



と比較する必要があり$this->balance







 class BankAccount { protected $balance = 0.0; /** * Returns current balance */ public function getBalance() { $__result = $this->balance; assert('$__result == $this->balance; /* Contract violation /*'); return $__result; } }
      
      







そしてこれは、メソッドが大きく、いくつかの戻り点がある場合は言うまでもなく、単純なメソッドのためです。 ご想像のとおり、この段階では、PHPのプロジェクトでコントラクトプログラミングを使用することに関するアイデアは、言語が必要な制御構造をサポートしていないため、すぐに消滅します。 しかし、解決策があります! そして、それについて以下に書かれますが、少し忍耐を持っています。



不変量


もう1つの重要なタイプの契約である不変条件を検討する必要があります。 不変条件は、オブジェクトの積分状態を記述する特別な条件です。 不変式の重要な機能は、クラス内のパブリックメソッドを呼び出した後、およびコンストラクターを呼び出した後に常にチェックされることです。 コントラクトがオブジェクトの状態を決定し、パブリックメソッドが外部から状態を変更する唯一の方法であるため、オブジェクトの完全な仕様を取得します。 この例では、条件は良好な不変条件である可能性があります。つまり、口座残高がゼロ未満になることはありません。 ただし、PHPの不変条件では、事後条件よりも状況がさらに悪化します。クラスのすべてのパブリックメソッドにチェックを簡単に追加する方法がないため、パブリックメソッドを呼び出した後、不変条件で必要な条件をチェックできます。 また、以前の状態の$__old



オブジェクトと、結果の戻り値$__old



にアクセスする方法もありません。 不変条件がなければ、契約は存在しないため、長い間、この機能を実装するためのツールやテクニックはありませんでした。



新機能



PHPでのコントラクトプログラミングのための実験的なDbC フレームワークであるPhpDealをご覧ください。

Go!Frameworkが開発された後 PHPでのアスペクト指向プログラミングのAOPで 、私の心は、パラメーターの自動検証、条件のチェック、その他多くのことを考えていました。 PHP.Internalsに関する議論は、契約プログラミングのプロジェクトを作成するきっかけとなりました。 驚くべきことに、AOPの助けを借りて、問題はわずか数ステップで解決されました:コントラクトアノテーションでマークされたメソッドの実行をインターセプトし、メソッドが呼び出される前または後に必要なチェックを実行する側面を記述する必要がありました。



このフレームワークでコントラクトを使用する方法を見てみましょう。



 use PhpDeal\Annotation as Contract; /** * Simple trade account class * @Contract\Invariant("$this->balance > 0") */ class Account implements AccountContract { /** * Current balance * * @var float */ protected $balance = 0.0; /** * Deposits fixed amount of money to the account * * @param float $amount * * @Contract\Verify("$amount>0 && is_numeric($amount)") * @Contract\Ensure("$this->balance == $__old->balance+$amount") */ public function deposit($amount) { $this->balance += $amount; } /** * Returns current balance * * @Contract\Ensure("$__result == $this->balance") * @return float */ public function getBalance() { return $this->balance; } }
      
      







お気づきのとおり、すべてのコントラクトはドックブロック内の注釈として記述され、注釈自体の内部に必要な条件が含まれています。 クラスの元の実行可能コードを変更する必要はありません。契約なしのコードと同じようにクリーンなままです。



前提条件は、 Verify



アノテーションを使用して指定され、メソッドが呼び出されたとき、メソッド本体自体が実行される前に実行されるチェックを決定します。 前提条件は、クラスメソッドのスコープ内で機能するため、プライベートプロパティを含むすべてのプロパティにアクセスでき、メソッドパラメータにもアクセスできます。



事後条件は、コントラクトプログラミングの観点から標準的にEnsureという名前の注釈によって定義されます。 コードのスコープはメソッド自体と同様ですが、これに加えて、メソッド実行前のオブジェクトの状態を含む変数$__old



と、このメソッドから返された値を含む変数$__result



を使用できます。



AOPを使用することで、不変式さえも実装できるようになりました-それらはクラスドックのInvariant



注釈の形式でエレガントに記述され、すべてのメソッドに対して事後条件と同様に動作します。



コードを試しているうちに、PHPのインターフェースとのコントラクトの驚くべき類似性を発見しました。 標準インターフェイスがクラスと対話するための標準の要件を定義している場合、コントラクトにより、クラスインスタンスの状態の要件を記述することができます。 インターフェイスでコントラクトの説明を適用すると、オブジェクトとのやり取りとオブジェクトの状態の両方の要件を説明することができます。これはクラスに実装されます。



 use PhpDeal\Annotation as Contract; /** * Simple trade account contract */ interface AccountContract { /** * Deposits fixed amount of money to the account * * @param float $amount * * @Contract\Verify("$amount>0 && is_numeric($amount)") * @Contract\Ensure("$this->balance == $__old->balance+$amount") */ public function deposit($amount); /** * Returns current balance * * @Contract\Ensure("$__result == $this->balance") * * @return float */ public function getBalance(); }
      
      







最も興味深い部分が始まります。クラスを作成し、必要なメソッドを定義すると、最新のIDEはすべての注釈をインターフェースのメソッド記述からクラス自体に転送します。 そしてこれにより、PhpDealエンジンはそれらを見つけて、このインターフェースを実装する特定のクラスごとに契約の自動検証を提供できます。 自分の手ですべてを感じたい人のために-githubからプロジェクトをダウンロードし、コンポーザーを使用してすべての依存関係をインストールし、このフォルダーでローカルWebサーバーを構成してから、ブラウザーのデモフォルダーからコードを開くことができます



おわりに



PHPのコントラクトプログラミングは、要件および仕様の形式で定義された、コードの品質を改善し、コントラクトの可読性を確保するために、防御プログラミングに使用できるまったく新しいパラダイムです。 この実装の大きな利点は、クラスコードが読みやすく、注釈自体がドキュメントとして読み取られることです。また、バトルモードでは、チェックを完全に無効にすることができ、コード内の追加の不要なチェックにまったく時間がかかりません。 興味深い事実:フレームワーク自体には、いくつかの注釈と、これらの注釈を特定のロジックに関連付ける1つのアスペクトクラスのみが含まれています。



ご清聴ありがとうございました!



関連リンク:

  1. ウィキペディア-契約プログラミング
  2. PHPでのコントラクトプログラミングのためのPhpDealフレームワーク
  3. フレームワークGo! PHPでのアスペクト指向プログラミングのAOP



All Articles