Хабрахабр

Что не так с валидацией данных и при чем тут принцип подстановки Лисков?

Формально это можно описать следующим образом: пусть мы получаем некоторую структуру данных, проверяем ее значение на соответствие некоторой области допустимых значений (ОДЗ) и передаем ее дальше. Если вы иногда задаете себе вопрос: «а всё ли хорошо мне в этот метод приходит?» и выбираете между «а вдруг пронесет» и «лучше на всякий случай проверить», то добро пожаловать под кат…
При разработке часто возникает потребность проверки валидности данных для некоторого алгоритма. В случае неизменяемости структуры, повторная проверка ее валидности – очевидно лишнее действие. Впоследствии эта же структура данных может быть подвергнута такой же проверке.

Гораздо неприятнее лишняя ответственность. Хотя валидация может действительно быть долгой, проблема тут не только в производительности. Кроме лишней проверки, можно наоборот допустить отсутствие всякой проверки, неверно предполагая, что структура была проверена ранее. У разработчика нет уверенности нужно ли проверять структуру на валидность еще раз.

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

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

class Parent
class Child : Parent { ... } ... void processValidObject(Parent parent) { if (parent is Child) { // process } else { // error }
}

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

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

Идея состоит в том, чтобы разделить типы на допускающие значение null и не допускающие. В Swift'е, на уровне синтаксиса, решается проблема проверки на null. При объявлении типа переменной ClassName гарантируется, что в переменной ненулевое значение, а при объявлении ClassName? При этом сделано это в виде сахара таким образом, что программисту не требуется объявлять новый тип. При этом между типами существует коваринтность, то есть в методы, принимающие ClassName?, можно передать и объект типа ClassName. переменная допускает значение null.

Снабжение объектов метаданными, содержащими ОДЗ, хранящимися в типе, устранит описанные выше проблемы. Эту идею можно расширить до задаваемых пользователем ОДЗ. Хорошо бы получить поддержку такого средства в языке, но такое поведение реализуемо и в «обычных» ОО-языках, таких как Java или C# с помощью наследования и фабрики.

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

UPD: Как правильно подметили в комментариях, подтипы создавать стоит только в том случае, если мы получим дополнительную надежность и уменьшим количество одинаковых валидаций.

Пусть на вход к нам поступают некоторые пути файлов. Так же в статье не хватает примера. Далее мы хотим передать их в разные подсистемы, которые так же работают как с доступными, так и с недоступными файлами. Наша система в некоторых случаях работает со всеми файлами, а в некоторых случаях только с фалами, к которым мы имеем доступ. Таким образом во всяком сомнительном месте появится проверка доступа или может напротив забудется. Далее эти подсистемы передают файлы еще дальше, где опять не понятно файл доступен или нет. А проверки эти грузят диск и вообще тяжелые. Из-за этого система усложнится в силу повсеместной неоднозначности и проверок. Я предлагаю ответственность проверки переложить с разработчика на компилятор. Можно эту проверку кешировать в булевом поле, но это нас не избавит от самого факта необходимости проверки.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть