Pull to refresh

Comments 23

Автор даже не удосужился копнуть глубже. Свойству типа int не нужен атрибут [Required], так как EF поймёт всё правильно и отразит его в поле NOT NULL. Атрибут [Required] требуется, например, для типа String, где нельзя по-другому. Если автор решил, что все String надо оборачивать в идеологически верные доменные структуры, то пошёл он куда по-дальше. Может и красиво, но кашу не сваришь. Надо соблюдать баланс, идеальными бывают только сферические кони в вакууме.
А, простите, причём здесь EF? Автор же не о нём совсем.
«Атрибут [Required] примитивный и ядовитый хак. Это главная причина того, почему я никогда не буду использовать Entity Framework.» (с) автор

Атрибут [Required] — явное указание, что значение должно быть определено. Для значимых типов, таких как int, атрибут не требуется, так как существует определённая разница между int и int?, которую можно распознать без всяких атрибутов. Для String, который может принимать значение null без подобного атрибута не обойтись. Автор похоже в жизни не писал программного кода, иначе бы не плёл такой бред.
Вы сейчас описываете нечто, что должно называться NotNull, а не атрибут Required. Автор поста именно к этому заблуждению и отсылает.

Давайте всё-таки удосужимся копнуть глубже. Вы пишете, что Required — указание, что значение должно быть определено и для значимых типов оно не требуется. Как вы собираетесь трактовать 0, который там будет по-умолчанию (для чисел)? :)
Вы сейчас описываете нечто, что должно называться NotNull, а не атрибут Required. Автор поста именно к этому заблуждению и отсылает.

Не надо фантазировать, в EF нет атрибута [NotNull]
Если мы хотим гарантировать инициализацию int-поля, и сделать в базе not-null column, надо написать так:

public class Smell
{
    [Required]
    public int? Id { get; set; }
}

Печально, что автор не разобрался в вопросе и филосовствует
Вот один из комментариев самого автора к данному посту в его же блоге:

Я согласен с тем, что когда речь заходит о валидации входных данных от пользователя, использование атрибута на одном или более свойств с очень рыхлой поддержкой инвариантов это лучшее решение. В целом, на границах приложения, всегда лучше предполагать, что ввод может быть некорректен и введён как угодно. Это также справедливо как для UI-ввода, так и автоматического ввода (XML, CSV, JSON и т.д.). Таким образом, любой «объект», моделирующий структуру данных на границе приложения не является инкапсулированным.
Однако, было бы непростительной ошибкой, если бы мы позволили пользовательскому интерфейсу направлять проектирование наших доменных моделей. Таким образом, как только мы знаем, что у нас случай с корректными входными данными, мы должны транслировать его в правильно инкапсулированный объект. С этой точки зрения (включая дизайн схемы БД), атрибут оказывается абсолютно излишним.
Кстати, да, я не вчитался в подробности того твита. Возможно, я выбрал неудачный пример. Я не ставил себе целью разносить Entity Framework. Всё, что я до сих пор слышал о NHibernate говорит мне о том, что он имеет те же проблемы относительно инкапсуляции.
Как я отписал ниже, есть разные сценарии использования объектов, разные слои в приложении.

Есть слой, программист которого — царь и бог в доменной модели. Здесь объекты просты, а логика в сервисах над ними. Опечатка может уничтожить все данные в базе, но в обмен даётся полная свобода программировать нетривиальные операции и сложнейшие, но оптимальные запросы к базе.

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

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

Ошибка автора в том, что он от слоя первного уровня требует свойства слоя второго уровня. Но так не бывает. Не бывает safe mode + god mode одновременно. И без слоя god mode тоже не бывает — кто-то должен делать грязную работу.
Не надо фантазировать, мы (и автор) не обсуждаем EF.
Но если мы обсуждаем публичное АПИ вообще, то entity-классы плохой материал для примеров.
Я уже не понимаю, что мы обсуждаем. Автор не имел ввиду EF. И RequiredAttribute — не является его частью! Но похоже что т.к. он там используется, люди накинулись критиковать идеи автора, которые с колокольни ORM и схемы БД действительно могут выглядеть перегибом.
При отказе от конструкторов, как и при любом отходе от инструкций на производстве возникают и проблемы, которые приходится решать уже не вам. Как говорил один мой знакомый программист: «Какая тебе разница, не тебе же поддерживать этот сайт?! Говнокодь по-быстрому». Пример проблемы:
— блин, почему у меня здесь падает?
— А блин, пришёл объект у которого почему-то поле сумма или дата пустые.
— А почему они пустые?
— Потому что в БД пустые.
— А может быть сущность «платёж» в бизнесе пустым? Думаю что нет.
— конечно не может
— а как так случилось? А при создании платежа и его сохранении мы забыли проверить дату (или где-то в логике изменили) и там null (или что-то ещё неприемлемое)
и на выяснение всего этого могут уйти сутки, а вот на кодинг через конструктор на 30 секунд больше, чем через параметры

несмотря на то, что да, EF не умеет использовать параметризованный конструктор (надеюсь пока) и использует виртуальные паля во время создания абстрактной сущности на основе данных БД это не является правильным подходом согласно той концепции для которой и нужно абстрагирование. Сущность должна быть создана с помощью фабрики (агрегат) или фабричного метода (сущность или value object). Именно так гарантируется то, что сущность будет создана лишь в том случае, когда все необходимые ей поля и свойства, а главное, сочетания свойств были иницализированы одновременно и правильно. Это можно проверить лишь в конструкторе. Если же создавать объекты через свойства, то нет гарантии, что два одновременно необходимых поля (например сумма и дата) были в обязательно порядке иницализированы предыдущим разработчиком ранее в коде или некой частью системы. Тем более откладывать эту задачу на базу данных в корне не верно, потому что ОРМ есть механизм отсоединения от конкретного источника данных и разные источники могут возвращать разные результаты.
Присвоение свойств это крайней степени удобный и популярный, но всё же говнокод, который увеличивает скорость разработки (незначительно) и сильно уменьшает надёжность. Особенно, когда приходят в команду новые члены, которые не знают, что сумма и даты должны быть инициализированны непременно вместе или сущность не должна быть создана.
Даже сами гуру проектирования типа фаулера крайне осторожно относятся к пратике POCO\POJO объектов так как это типичный антипаттерн. И На мой взгляд его можно использовать исключительно для DTO объектов, но точно не сущностней уровня доменной модели. Об этом говорит Domain Driven Design.
Итого:
1) EF и другие ОРМ не позволяет использовать конструктор, заменяя всё виртуальными свойствам и POCO объектами, что есть неверно.
2) Доменный объект должен быть создан только через параметризированный конструктор, где будут произведены необходимые проверки и гарантируется целостность объекта (сущности или агрегата)
3) POCO объекты допустимы только для транспортных объектов (DataContract в сервисах, Model и ViewModel в MVC.).
4) Использование anemic model (POCO ) для доменных сущностей является антипаттерном для DDD да и вообще Фаулер его таковым считает и не любит. А наличие ORM уже говорит, что вы пытаетесь абстрагировать слой доменной модели от слоя данных фактические используете DDD и скорее всего будете реализовывать репозитории.
5) POCO подход крайне сильно распространён и в итоге только мешает. Для говнокодерства «по 5 тасков за день» отлично подходит. Проблемы начинаются через 3-4 месяца разработки, при тестировании, при поддержке, смене источника данных (например на сервисы) и т.д. подход DDD, а не Transaction Script в котором anemic model чувствует себя как рыба в воде.

Давайте не будем пересаливать мысль убойными трёхбуквенными сокращениями, потому что суть теряется. Что вы хотели сказать лично мне не понятно. Вынес только одно. Есть некие неугодные вам «говнокодеры», а есть DDD, DTO, POCO/POJO, ORM, MVC и «истинно правильное программирование».

Теория автора разбивается о банальный пример. День Рождения. Давайте создадим такой тип, ибо вполне уместно. День Рождения не должен быть ранее, допустим 100 лет от текущей даты и позже текущей даты. И День Рождения не может быть пустым, ибо не бывает таких людей, у которых нет Дня Рождения. Посему должен быть обязательный конструктор в который надо передать Дату, и если что-то с ней не так, выплюнуть исключение. И всё вроде хорошо, и ладушки. Но вот проходит 10 лет со дня выпуска программы. Программа загружает из базы данных Людей их Дни Рождения. 10 лет назад в базу данных была добавлена Старушка, которой было 99 лет. И вот сегодня, программа падает с исключением, так как не бывает таки Дней Рождения, которые датируются ранее 100 годов. Что делать? Завести второй конструктор? Один для актуальных ДР (от сегодняшнего дня), другой для загружаемых? Или проще. Пусть будет один конструктор, в который будет запихиваться ДР и ещё одна дата отсчёта? Класс. И так с каждым доменным типом. Видимое удобство влечёт кучу неочевидных побочных эффектов.

Но и это ещё не все.

Вместо того, чтобы пользоваться атрибутами и прочими «расширяющими дыру» инструментами, будем творить домен на каждый чих. Лично я в таком аду сразу откажусь что-то делать. Надо добавить День Рождения? Таааа-аа-ак, как же он там называется? BirthDay? BirthDate? DateOfBirth?..

Всё должно быть в меру с умом. Нельзя вот так взять и сказать «Атрибуты — зло». Очевидно, что автор этих слов бездумный фанатик, не написавший в своей жизни ни строчки кода для продакшена. Одни только разговоры разговаривать. И сливать этот поток бреда на чьи-нибудь уши на каких-нибудь конфах.
Нет, не разбивается.
Согласен со всем написанным вами, но хочу высказать два замечания.
1) Вы неправильно используете термин «POCO-объекты». Poco по аналогии с pojo означает «чистые» объекты, не унаследованные от громоздких базовых классов и интерфейсов. Например если вы строите свою доменную модель, то не нужно наследоваться от классов из пространства имен ComponentModel, напишите свои, простые и понятные классы. При этом в poco вполне могут быть как данные, так и методы по работе с ними, это номальные классы rich-модели. То, о чем пишете вы, называется просто анемичная модель.
2) Если вы используете NHibernate, то можете указать два конструктора — один private без параметров только для ORM, второй нормальный public для классов бизнес логики, это решает проблемы контрактов класса.
Кроме этого этот атрибут можно использовать для клиентской валидации, кроме этого при сохранении пустого значения ORM упадет с ошибкой. Атрибут Required — просто отличный, автор просто не умеет его готовить.
Для DTO — да. Для объектов, обладающих логикой — нет. Здесь нужны другие механизмы декларирования обязательных к заполнению свойств. Атрибут в данном случае не сработает, вообще.
С помощью в том числе этого атрибута EF сам делает для меня миграции. И разобрать дерево выражений на предмет наличия атрибута и поставить атрибут, вместо if == null throw гораздо проще и нагляднее. Докурутите туда post# и получите проверку на этапе компиляции. Поставить проверку в свойство вам тоже никто не мешает. Атрибут — это метаинформация. Не нужно придумывать сложности там, где их нет
Вы просто не поняли мысль автора и не более того.
Мне кажется, не объекты должны обладать логикой, а сервисы над ними.
В таком случае можно легко писать другую логику над теми же объектами (другой сервис), не ломая существующий код.

Да и взаимодействие проще строить.
Как правильно: asteroid.collide(spaceship), spaceship.collide(asteroid) или physEngine.collide(asteroid, spaceship)?
Если в объектах оставить одни лишь данные, то в чём тогда соль ООП? :)

Про пример с астероидом. Всё ведь зависит от контекста. Взаимодействие нескольких объектов действительно зачастую логично вынести во внешний объект. Но облетать астероид логичнее так spaceship.flyRound(asteroid).

Вообще, мы здесь обсуждаем атрибут Required. И мысль автора оригинального поста не изменится от того, будет ли логика жить в самом объекте или вынесена во внешний сервис.
Видимо, надо различать данные, которыми оперирует программа, и закрытые объекты, созданные для передачи им команд через интерфейс (например, файл, открытый в TextReader).

В первом случае — полностью открытая структура. Это всякие бизнес-сущности, которыми программист свободно оперирует.

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

Критерий выбора между этими случаями — должен ли код, использующий объект, определять его поведение и логику. Если да, нужен открытый объект без методов.
Sign up to leave a comment.

Articles