Концепция Design by Contract
Смысл этой методологии в том, чтобы заранее определять предусловия для корректного выполнения кода, постусловия по корректному результату этого выполнения а также инварианты. Всё это пишется в самом коде, потому его контрактные обязательства очевидны.
Например для некой функции задан аргумент. В предусловии мы проверяем тип этого аргумента, скажем число, и допустим некий диапазон. В постусловии мы проверяем что возвращаемое значение, скажем непустая строка.
Что предлагает PHP для DbC?
Оказывается, ещё со времён PHP4 существует специальная встроенная функция assert, которая берёт на себя эту задачу. А именно: выдать ошибку если результат выполнения кода из аргумента не равен true.
Зачем использовать assert если есть if/throw
Разница между ними принципиальная: assert определяет обязательства, вне которых наш код не будет исполнятся. Либо все контрактные обязательства соблюдаются либо код не выполняется. Условие же и исключения нужны для допустимых ситуаций и соответственно обрабатываются.
Почему полезно использовать inline код?
Функция assert в качестве аргумента может принимать строку с PHP кодом, которая затем компилируется (eval).
assert(‘is_int($var) && $var > 10’);
Смысл такой записи в том, чтобы парсер не тратил ресурсы на анализ контрактных проверок, для него это всего лишь строка. По этому при деактивации
assert_options(ASSERT_ACTIVE, false);
замедления быстродейтвия практически не будет. Имейте ввиду что DbC это методика во время разработки и попав на продакшн, проверки теряют смысл.
DbC vs TDD
Одно другому совершенно не помеха. Тесты запускают код, вылавливают возможные дефекты и не дают коду деградировать при рефакторинге. Контракты «рубят на корню» потенциальные баги и гарантируют определённую экосистему: либо контракт соблюдён, либо код не исполнится.
Простой пример использования DbC
Есть somefile.php, который подключается снаружи:
assert(‘isset($config[\’a\’]’);
// …
do_something($config[‘a’]);
// …
В данном примере assert проверяет предусловие: установленная переменная $config с ключом. Таким образом кто-бы не подключал этот файл, он сразу будет обязан определить эту переменную.
У меня eval() всегда вызывал антипатию. Как минимум это не самый безопасный подход.
Да, я тоже к eval отношусь без особого энтузиазма, однако в данной ситуации это экономит ресурсы. На счёт безопасности — это безопасно, данные приходят не извне.
Ещё интересный момент: DbC в ООП это Принцип подстановки Барбары Лисков (LSP): в двух словах он означает что классы наследники должны сохранять поведение родителя так, чтобы их использование не отличалось от использования родительского класса. Аналогично исключения должны быть унаследованы от тех, которые кидаются в базовом классе.