15

コントラクトによるプログラミングは.NETの最近の傾向ですが、PHPのコードコントラクトのライブラリ/フレームワークはどうでしょうか。このパラダイムのPHPへの適用性についてどう思いますか?

「コードコントラクトphp」をグーグルで検索しても何も得られませんでした。

注:「契約によるコード」とは、契約による設計を意味するため、.NETまたはPHPインターフェースとは何の関係もありません。

4

4 に答える 4

28

好奇心で同じものを探していたところ、この質問を見つけたので、答えてみます。

まず、PHPは、設計上、実際にはコード契約ではありません。必要に応じて、メソッド内のパラメーターのコアタイプ¹を強制することさえできないため、コードコントラクトがいつかPHPに存在するとはほとんど信じられません。

カスタムのサードパーティライブラリ/フレームワークの実装を行うとどうなるか見てみましょう。

1.前提条件

引数の不正な値からメソッドを保護することは通常のプログラミングと比較して難しいため、少なくとも前提条件では、コードコントラクト(またはコードコントラクトにほぼ類似したもの)をメソッドに渡す自由が非常に価値があります。言語。言語自体を介して型を適用できます。

次のように書く方が便利です。

public function AddProduct($productId, $name, $price, $isCurrentlyInStock)
{
    Contracts::Require(__FILE__, __LINE__, is_int($productId), 'The product ID must be an integer.');
    Contracts::Require(__FILE__, __LINE__, is_string($name), 'The product name must be a string.');
    Contracts::Require(__FILE__, __LINE__, is_int($price), 'The price must be an integer.');
    Contracts::Require(__FILE__, __LINE__, is_bool($isCurrentlyInStock), 'The product availability must be an boolean.');

    Contracts::Require(__FILE__, __LINE__, $productId > 0 && $productId <= 5873, 'The product ID is out of range.');
    Contracts::Require(__FILE__, __LINE__, $price > 0, 'The product price cannot be negative.');

    // Business code goes here.
}

それ以外の:

public function AddProduct($productId, $name, $price, $isCurrentlyInStock)
{
    if (!is_int($productId))
    {
        throw new ArgumentException(__FILE__, __LINE__, 'The product ID must be an integer.');
    }

    if (!is_int($name))
    {
        throw new ArgumentException(__FILE__, __LINE__, 'The product name must be a string.');
    }

    // Continue with four other checks.

    // Business code goes here.
}

2.事後条件:大きな問題

前提条件で簡単にできることは、事後条件では不可能なままです。もちろん、あなたは次のようなものを想像することができます:

public function FindLastProduct()
{
    $lastProduct = ...

    // Business code goes here.

    Contracts::Ensure($lastProduct instanceof Product, 'The method was about to return a non-product, when an instance of a Product class was expected.');
    return $lastProduct;
}

唯一の問題は、このアプローチは、実装レベル(前提条件の例のように)でもコードレベル(事後条件はコードとメソッドの戻りの間ではなく実際のビジネスコードの前にあるため)でも、コードコントラクトとは関係がないことです。

また、メソッドまたは、に複数のリターンがある場合、 before everyまたは(メンテナンスの悪夢!)throwを含めない限り、事後条件はチェックされないことも意味します。$this->Ensure()returnthrow

3.不変条件:可能ですか?

セッターを使用すると、プロパティである種のコードコントラクトをエミュレートすることができます。ただし、セッターはPHPでの実装が非常に悪いため、多くの問題が発生し、フィールドの代わりにセッターを使用するとオートコンプリートが機能しません。

4.実装

最後に、PHPはコード契約の最適な候補ではありません。また、PHPの設計は非常に貧弱であるため、言語設計に大幅な変更がない限り、おそらくコード契約はありません。

現在、擬似コード契約²は、事後条件または不変条件に関してはかなり価値がありません。一方、一部の疑似前提条件はPHPで簡単に記述できるため、引数のチェックがはるかにエレガントで短くなります。

このような実装の簡単な例を次に示します。

class ArgumentException extends Exception
{
    // Code here.
}

class CodeContracts
{
    public static function Require($file, $line, $precondition, $failureMessage)
    {
        Contracts::Require(__FILE__, __LINE__, is_string($file), 'The source file name must be a string.');
        Contracts::Require(__FILE__, __LINE__, is_int($line), 'The source file line must be an integer.');
        Contracts::Require(__FILE__, __LINE__, is_string($precondition), 'The precondition must evaluate to a boolean.');
        Contracts::Require(__FILE__, __LINE__, is_int($failureMessage), 'The failure message must be a string.');

        Contracts::Require(__FILE__, __LINE__, $file != '', 'The source file name cannot be an empty string.');
        Contracts::Require(__FILE__, __LINE__, $line >= 0, 'The source file line cannot be negative.');

        if (!$precondition)
        {
            throw new ContractException('The code contract was violated in ' . $file . ':' . $line . ': ' . $failureMessage);
        }
    }
}

もちろん、例外は、ログアンドコンティニュー/ログアンドストップアプローチ、エラーページなどに置き換えることができます。

5。結論

事前契約の実施を見ると、全体のアイデアは無価値に思えます。通常のプログラミング言語のコードコントラクトとは実際には非常に異なる擬似コードコントラクトに悩まされるのはなぜですか?それは私たちに何をもたらしますか?実際のコードコントラクトを使用しているかのようにチェックを記述できるという事実を除いて、ほとんど何もありません。そして、私たちができるという理由だけでこれを行う理由はありません。

なぜコードコントラクトが通常の言語で存在するのですか?2つの理由:

  • これらは、コードのブロックが開始または終了するときに一致する必要がある条件を適用する簡単な方法を提供するため、
  • コードコントラクトを使用する.NETFrameworkライブラリを使用すると、ソースコードにアクセスしなくても、IDE内でメソッドに必要なもの、メソッドに期待されるもの、そしてこれを簡単に知ることができます³。

私が見たところ、PHPでの擬似コード契約の実装では、最初の理由は非常に限られており、2番目の理由は存在せず、おそらく存在しないでしょう。

これは、特にPHPが配列でうまく機能するため、実際には、引数の単純なチェックが適切な代替手段であることを意味します。これは、古い個人プロジェクトからのコピーペーストです。

class ArgumentException extends Exception
{
    private $argumentName = null;

    public function __construct($message = '', $code = 0, $argumentName = '')
    {
        if (!is_string($message)) throw new ArgumentException('Wrong parameter for ArgumentException constructor. String value expected.', 0, 'message');
        if (!is_long($code)) throw new ArgumentException('Wrong parameter for ArgumentException constructor. Integer value expected.', 0, 'code');
        if (!is_string($argumentName)) throw new ArgumentException('Wrong parameter for ArgumentException constructor. String value expected.', 0, 'argumentName');
        parent::__construct($message, $code);
        $this->argumentName = $argumentName;
    }

    public function __toString()
    {
        return 'exception \'' . get_class($this) . '\' ' . ((!$this->argumentName) ? '' : 'on argument \'' . $this->argumentName . '\' ') . 'with message \'' . parent::getMessage() . '\' in ' . parent::getFile() . ':' . parent::getLine() . '
Stack trace:
' . parent::getTraceAsString();
    }
}

class Component
{
    public static function CheckArguments($file, $line, $args)
    {
        foreach ($args as $argName => $argAttributes)
        {
            if (isset($argAttributes['type']) && (!VarTypes::MatchType($argAttributes['value'], $argAttributes['type'])))
            {
                throw new ArgumentException(String::Format('Invalid type for argument \'{0}\' in {1}:{2}. Expected type: {3}.', $argName, $file, $line, $argAttributes['type']), 0, $argName);
            }
            if (isset($argAttributes['length']))
            {
                settype($argAttributes['length'], 'integer');
                if (is_string($argAttributes['value']))
                {
                    if (strlen($argAttributes['value']) != $argAttributes['length'])
                    {
                        throw new ArgumentException(String::Format('Invalid length for argument \'{0}\' in {1}:{2}. Expected length: {3}. Current length: {4}.', $argName, $file, $line, $argAttributes['length'], strlen($argAttributes['value'])), 0, $argName);
                    }
                }
                else
                {
                    throw new ArgumentException(String::Format('Invalid attributes for argument \'{0}\' in {1}:{2}. Either remove length attribute or pass a string.', $argName, $file, $line), 0, $argName);
                }
            }
        }
    }
}

使用例:

/// <summary>
/// Determines whether the ending of the string matches the specified string.
/// </summary>
public static function EndsWith($string, $end, $case = true)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'string' => array('value' => $string, 'type' => VTYPE_STRING),
        'end' => array('value' => $end, 'type' => VTYPE_STRING),
        'case' => array('value' => $case, 'type' => VTYPE_BOOL)
    ));

    $stringLength = strlen($string);
    $endLength = strlen($end);
    if ($endLength > $stringLength) return false;
    if ($endLength == $stringLength && $string != $end) return false;

    return (($case) ? substr_compare($string, $end, $stringLength - $endLength) : substr_compare($string, $end, $stringLength - $endLength, $stringLength, true)) == 0;
}

引数だけに依存しない前提条件をチェックしたい場合(たとえば、前提条件のプロパティの値をチェックする場合)は十分ではありません。しかし、ほとんどの場合、必要なのは引数をチェックすることだけであり、PHPの擬似コードコントラクトはそれを行うための最良の方法ではありません。

言い換えれば、あなたの唯一の目的が引数をチェックすることであるならば、擬似コード契約はやり過ぎです。オブジェクトのプロパティに依存する前提条件など、さらに何かが必要な場合に可能になる場合があります。しかし、この最後のケースでは、おそらくもっとPHPyの方法で物事を行うことができるので、コードコントラクトを使用する唯一の理由は残ります


¹引数はクラスのインスタンスでなければならないことを指定できます。不思議なことに、引数が整数または文字列でなければならないことを指定する方法はありません。

²擬似コードコントラクトとは、上記の実装が.NETFrameworkのコードコントラクトの実装とは大きく異なることを意味します。実際の実装は、言語自体を変更することによってのみ可能になります。

³契約参照アセンブリが構築されている場合、またはさらに良いことに、契約がXMLファイルで指定されている場合。

⁴単純なif - throw方法でうまくいくことができます。

于 2011-01-23T00:29:41.260 に答える
2

PHP-Contractを作成しました。

PHP用のC#コントラクトの軽量で用途の広い実装。これらのコントラクトは、多くの点でC#の機能を上回っています。私のGithubプロジェクトをチェックして、コピーを入手し、wikiを見てください。

https://github.com/axiom82/PHP-契約


基本的な例を次に示します。

class Model {

public function getFoos($barId, $includeBaz = false, $limit = 0, $offset = 0){

    $contract = new Contract();
    $contract->term('barId')->id()->end()
             ->term('includeBaz')->boolean()->end()
             ->term('limit')->natural()->end()
             ->term('offset')->natural()->end()
             ->metOrThrow();

    /* Continue with peace of mind ... */

}

}

ドキュメントについては、wikiにアクセスしてください。

于 2014-03-10T01:03:00.927 に答える
1

インターフェースは連絡先ではなく(実際、Laravelの定義は間違っています)、契約による設計(DbC)はソフトウェアの正当性の方法論です。前提条件と事後条件を使用して、プログラムの一部によって引き起こされた状態の変化を文書化(またはプログラムでアサート)します。私はここで良いphpアプローチを見つけます

于 2018-10-11T20:49:04.923 に答える
0

ウィキペディアがコンポーネント指向のソフトウェア方法論に言及していると思います。このような方法では、メソッドはコンポーネントのパブリックインターフェイスまたはコントラクトと呼ばれます。

契約は、サービスのプロバイダーとクライアントの間の「一種の合意」です。システムがさまざまな作成者/ベンダーによるコンポーネントで構成されているコンポーネント環境では、契約の「構築」が非常に重要です。

このような環境では、コンポーネントを、他の人が作成した他のコンポーネントと効率的に共存およびコラボレーションできるブラックボックスと考えてください。これにより、より大きなシステムまたはより大きなシステムのサブシステムなどが形成されます。

詳細については、コンポーネント指向プログラミングに関連するすべてのものについて、「コンポーネントソフトウェア-コンポーネント指向プログラミングを超えて」の本をグーグルで検索することをお勧めします。

于 2010-10-26T00:26:17.347 に答える