Pull to refresh

Code contracts vs валидация входящих данных

Reading time4 min
Views13K
Original author: Vladimir Khorikov
Правила валидации входящих данных часто принимают за контракты в коде (как, собственно, и наоборот). Давайте разберемся в чем отличие между этими двумя понятиями и в каких случаях они применимы.

Code contracts: предусловия


Какую цель преследует использование контрактов в коде, а именно предусловий, постусловий и инвариантов? Идея тесно связана с Fail-fast принципом: чем быстрее вы заметите неожиданное поведение, тем быстрее вы его исправите.

В большинстве случаев более эффективно дать приложению упасть, нежели пытаться автоматически восстановиться после ошибки, т.к. вы никогда не знаете наверняка, что именно это за ошибка и как она повлияет на приложение. Продолжение работы софта после ошибки в нем может привести к неконсистентному состоянию данных в БД.

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

Это основное отличие, все остальные являются следствием из него. Давайте посмотрим на них подробнее.

Отличия между предусловиями контрактов и валидацией входящих данных


Предусловия контрактов можно представить в виде защитного экрана, помещенного внутри вашего вашего кода для того, чтобы убедиться, что все идет хорошо. С другой стороны, валидация входящих данных — это экран, размещенный для «обороны» от внешнего мира:

image


Красные сигналы обозначают неверные взаимодействия (или невалидные данные), идущие от пользователей или других приложений. Зеленые сигналы означают допустимые взаимодействия. Ваша цель как разработчика — убедиться, что невалидные дынные не могут проникнуть внутрь системы.

Если красный сигнал появляется внутри вашей системы, это означает, что либо вы не отфильтровали входящие данные, либо ваш собственный код генерирует неверные данные.

В любом случае, это является багом в системе, который нужно локализовать и устранить как можно быстрее. И в этом как раз нам помогают контракты. Они позволяют остановить распространение невалидных данных по приложению и быстро выявить причину возникновения багов:

image


Когда ваше приложение имеет хороший набор предусловий контрактов (и, еще лучше, постусловий и инвариантов), невалидные взаимодействия локализуются и их распространение прекращается сразу же как только они возникают, что очень сильно облегчает отладку приложения.

Валидация входящих данных — это механизм, направленный на защиту вашего приложения от проникновения неверных данных извне. Этот механизм не делает никаких предположений о данных, с которыми он работает. Это означает, что данные могут быть невалидными, и это само по себе — валидная ситуация.

В самом деле, если пользователь вводит «десять» в целочисленное поле, вы не хотите чтобы ваше приложение обрушилось. Вместо этого, вы вежливо указываете пользователю на ошибку.

В другой стороны, предусловия контрактов делают предположения о том, что данные внутри вашей системы находятся в валидном состоянии. Если это не так, в ваше приложение закралась ошибка.

Предусловия контрактов: best practices


Давайте посмотрим, что может рассматриваться как предусловие контракта. Контракт — это публичная оферта, предложенная классом-сервисом. В ней говорится, что если клиент следует некоторым правилам (предусловиям), то сервис гарантирует некоторые результаты, описанные в постусловиях.

Это приводит к следующим характеристикам, которым должно следовать каждое предусловие контракта:

  • Предусловия должны быть публичными. Это означает, что разработчик класса-клиента должен иметь возможность узнать о них перед тем как начнет писать код.
  • Они должны быть легко проверяемыми. Разработчик клиента не должен писать сложных алгоритмов для их проверки перед вызовом метода.
  • Предусловия не должны полагаться на непубличное состояние, т.к. это ограничивает возможности клиента по их предварительной проверке.
  • Они должны быть стабильны. Это означает, что результат выполнения проверки предусловия не должен зависеть от внешнего состояния.

Давайте рассмотрим эти пункты более подробно. Первый довольно прост, он означает, что все предусловия должны быть каким-то образом описаны, так что разработчик может ознакомиться с ними перед использованием класса. C# пока что не позволяет встраивать контракты прямо в сигнатуру методов, так что наилучшим на текущий момент способом является описание их в начале метода.

Второй пункт означает, что сервисный класс не должен заставлять разработчика класса-клиента выполнять сложные проверки для того, чтобы удовлетворить контракт. Если алгоритм проверки сложен, его следует вынести в отдельный метод, который клиенты смогут использовать для валидации предусловия:

public int Distribute(int amount)
{
    Contract.Requires(CanDistribute(amount));
    return DistributeCore(amount);
}

Следующий пункт следует из этого: вы не должны делать ваши предусловия зависимыми от непубличных методов или полей, иначе клиенты не смогут делать соответствующие проверки перед взаимодействием с классом.

Последний пункт говорит от повторяемости результата проверки. Если предусловия класса зависят от внешней среды — к примеру, существования файла на диске, — клиенты не смогут добиться успешного выполнения метода в 100% случаев:

public string ReadFile(string filePath)
{
    Contract.Requires(File.Exists(filePath));
    // Rest of the method
}

В этом примере файл может быть удален или стать недоступным в промежутке между вызовом метода ReadFile и началом проверки контракта, и клиент не может ничего сделать для того, чтобы избежать этого. Такая проверка не является предусловием контакта, т.к. она не говорит о наличии бага в клиентском коде, поэтому мы не можем вносить ее как предусловие.

Заключение


Предусловия контактов и валидация входящих данных имеют схожее, но тем не менее различное предназначение. В то время как валидация входящих данных позволяет удостовериться, что система защищены от внешнего мира, предусловия контрактов защищают ваш код внутри системы и позволяют быстро локализовать и устранить ошибки в ней.

Ссылка на оригинал статьи: C# code contracts vs input validation
Tags:
Hubs:
Total votes 13: ↑11 and ↓2+9
Comments16

Articles