Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Контрактное программирование в PHP

Anna | 29.05.2014 | нет комментариев

В реальной жизни мы повсеместно сталкиваемся с разными контрактами: при устройстве на работу, при выполнении работ, при подписании взаимных соглашений и многими другими. Юридическая сила контрактов гарантирует нам охрану интересов и не допускает их нарушения без последствий, что дает нам убежденность в том, что те пункты, которые описаны в контракте — будут исполнены. Эта убежденность помогает нам планировать время, планировать расходы, а также планировать нужные источники. А что если и программный код будет описываться контрактами? Увлекательно? Тогда добросердечно пожаловать под кат!

Вступление

Сама идея контрактного программирования появилась в 90-х годах у Бертрана Мейера при разработке объектно-ориентированного языка программирования Eiffel. Суть идеи Бертрана была в том, что необходимо было иметь инструмент для изложения формальной верификации и формальной спецификации кода. Такой инструмент давал бы определенные результаты: «способ обязуется сделать свою работу, если вы исполните данные, нужные для его вызова». И контракты как невозможно отменнее подходили для данной роли, потому что разрешали описать что будет получено от системы (спецификация) в случае соблюдения предусловий (верификация). С тех пор возникло уйма реализаций данной методологии программирования как на ярусе определенного языка, так и в виде отдельных библиотек, дозволяющих задавать контракты и проводить их верификацию с поддержкой внешнего кода. К сожалению, в PHP нет поддержки контрактного программирования на ярусе самого языка, следственно реализация может быть исполнена только с поддержкой сторонних библиотек.

Контракты в коде

Так как контрактное программирование было разработано для объектно-ориентированного языка, то не трудно додуматься, что основными рабочими элементами для контрактов являются классы, способы и свойства.

Предусловия

Самым простым вариантом контракта являются предусловия — требования, которые обязаны быть исполнены перед определенным действием. В рамках ООП все действия описываются способами в классах, следственно предусловия используются к способами, а их проверка происходит в момент вызова способа, но до выполнения самого тела способа. ?вственное применение — проверка валидности переданных параметров в способ, их конструкции и корректности. То есть с поддержкой предусловий мы описываем в контракте все то, с чем мы верно трудиться не будем. Это же здорово!

Дабы не быть голословным, давайте разглядим пример:

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:

class BankAccount
{
    protected $balance = 0.0;

    /**
     * Returns current balance
     */
    public function getBalance()
    {
        $__result = $this->balance;
        assert('$__result == $this->balance; /* Contract violation /*');

        return $__result;
    }
}

И это для простого способа, не говоря теснее о том случае, когда способ огромный и в нем несколько точек возврата. Как вы теснее додумались, на этом этапе идеи об применении контрактного программирования в плане на PHP стремительно гибнут, так как язык не поддерживает нужных руководящих конструкций. Но есть решение! И о нем будет написано ниже, наберитесь немножко терпения.

Инварианты

Нам осталось разглядеть еще один значимый тип контрактов: инварианты. Инварианты — это особые данные, которые описывают целостное состояние объекта. Главной спецификой инвариантов является то, что они проверяются неизменно позже вызова всякого публичного способа в классе и позже вызова конструктора. Так как контракт определяет состояние объекта, а публичные способы — исключительная вероятность изменить состояние извне, то мы получаем полную спецификацию объекта. Для нашего примера отличным инвариантом может быть условие: равновесие счета никогда не должен быть поменьше нуля. Впрочем, с инвариантами в PHP дело обстоит еще дрянней чем с постусловиями: нет никакой вероятности легко добавить проверку во все публичныеспособы класса, Дабы позже вызова всякого публичного способа дозволено было проверить нужное условие в инварианте. Также нет вероятности обращаться к предыдущему состоянию объекта $__old и возвращаемому итогу $__result. Без инвариантов нет контрактов, следственно длинное время не было никаких средств и методологий для реализации данного функционала.

Новые вероятности

Встречайте, PhpDeal — экспериментальный DbC-фреймворк для контрактного программирования в PHP.
Позже того, как был разработан фреймворк Go! AOP для аспектно-ориентированного программирования в PHP, у меня в голове вертелись мысли насчет механической валидации параметров, проверки условий и много-много иного. Триггером к созданию плана для контрактного программирования послужило обсуждение наPHP.Internals . чудесно, но с поддержкой АОП задача решалась каждого в пару действий: необходимо было описать аспект, тот, что будет перехватывать выполнение способов, помеченных с поддержкой контрактных аннотаций, и исполнять надобные проверки до либо позже вызова способа.

Давайте посмотрим на то, как дозволено применять контракты с поддержкой этого фреймворка:

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, содержащая в себе то значение, которое было возвращено из данного способа.

Вследствие применению АОП стало допустимым реализовать даже инварианты — они изящно описываются в виде аннотаций 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();

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB