пятница, января 30, 2009

The validation problem

Сложно найти программиста, который бы никогда не сталкивался с задачей проверки пользовательских данных. Буквально на первой же лабе в институте, преподаватель "ломает" программу некорректным вводом. Почти все через это прошли и признают, что относится к входным данным нужно со здоровой паранойей. Вопрос лишь в том, где именно проверять входные данные. Возбужденные сторонники ООП поднимают на щит инкапсуляцию и провозглашают немедленную валидацию на уровне классов бизнес-сущностей, бородатые DBA ратуют за проверки на уровне БД, очкастые архитекторы стоят на вынесении валидаторов в отдельную сущность, аппелируя к SRP. Разумеется, все они правы по-своему.

Есть такая штука, которая называется инвариантом системы. Это такой набор утверждений, истинных в любом ее состоянии. Хороший пример такого инварианта -- бухгалтерский баланс. Сумма активов равна сумме пассивов. Если это не так, значит где-то произошла серьезная ошибка. Для отдельных компонентов могут быть свои инварианты, попроще. Например, для любого счета всегда известна валюта или метод никогда не возвращает null. В первую очередь инварианты нужны внутри самой системы. Если мы знаем, что для всех счетов всегда известна валюта, мы можем обращаться к ней без проверки не рискуя получить NullPointerException. Кто же ответсвенен за консистентное состояние системы? Очевидно, сама система. Технически это можно сделать разными способами. Годятся простые проверки, вынос обязательных полей в конструкторы, констрейнты на базе, ассерты, современные средства контрактного программирования и все прочее, что придет в голову. Однако у инварианта есть side effect в том, что каждое дополнительное утверждение снижает гибкость системы. Если какое-то состояние системы нарушает инвариант, оно невозможно, независимо от желаний программиста. Избыточность в инварианте системы приводит к тому, что вместо простых действий устраиваются танцы вприсядку с целью обойти инвариант.

Чтобы избежать этой опасности, инвариант следует тщательно оберегать от распухания. Для того утверждения не влияющие на целостность системы целесообразно выделить в отдельный слой. Например, в системе никому не требуется номер паспорта клиента, но с точки зрения бизнеса это поле относится к обязательным, потому что по закону положено. Более того, часто возникают ситуации, когда в одних случаях поле является обязательным, а в других опциональным. Например, паспортные данные требуется от клиента клиент собирается получить деньги и небязательны если он принес деньги отдать.

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

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

Комментариев нет: