Закон Деметры. Нужен ли?

На Хабре в статьях неоднократно упоминается закон Деметры — правило хорошего тона при разрабботке архитектуры. Пусть некая доля истины в этом правиле есть, но я категорически не понимаю ограничения вызова только на 1 уровень.

Как пример, возьмем типичный код, который работает с доктриновской моделью данных:

$user->getPosts()->getLast()->getComments();



Чем плох данный код? Ведь под обьектом $user я понимаю не только конкретную сущность, а и все реляции этой сущности.
И было бы крайне странно бить меня по пальцам и не пускать к этим реляциям, когда доктрина как раз предполагает, использовать доступ к ним через коллекции по геттеру.

Чем плох данный код?
Надо ли его переписать согласно закону деметры?

* тестирование как аргумент не приводить
* ссылаться на то что это правильно в Java, а значит — истина, не обязательно.
  • Вопрос задан
  • 9849 просмотров
Пригласить эксперта
Ответы на вопрос 8
everzet
@everzet
Допустим вы хотите купить молоко:

дом->лестница->машина_Opel->магазин->кассир_Люба->купить_молоко();

Так как вы уважающий себя software developer который не видит смысла в законе Деметры, вы это скорее всего напишете в 10 разных местах системы.

2 недели назад вы продали свой Opel и купили BMW. Вы теперь должны в 10 разных местах поменять код на:

дом->лестница->машина_BMW->магазин->кассир_Люба->купить_молоко();

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

дом->лестница->велосипед->магазин->кассир_Люба->купить_молоко();

Через пару дней Любу уволили и на работу взяли нового кассира Клаву? Меняем в 10 разных местах код на:

дом->лестница->велосипед->магазин->кассир_Клава->купить_молоко();

Через другую пару дней в вашем доме поставили лифт и вы не хотите бегать по лестнице за молоком? Меняем в 10 разных местах код на:

дом->лифт->велосипед->магазин->кассир_Клава->купить_молоко();

Мораль: этих всех замен можно мыло бы избежать, если бы для покупки молока вы использовали абстракцию:

магазин->купить_молоко();
Ответ написан
@StepEv
А ваш пример закон не нарушает.

В $user->getPosts()->getLast()->getComments(); каждый объект обращается только к одному знакомому ему объекту:

posts = user.getPosts();
lastPost = posts.getLast();
comments = lastPost.getComments();

Так что не переживайте, всё в порядке. Выше понаписали какой-то ереси, честно говоря.

Нарушением было бы что-то типа $user.posts.last->getComments();

Здесь получается что user знает о том, как устроен posts, а это не хорошо.

Вообще-то по вашей же ссылке на википедии это всё хорошо расписано, даже с примерами.
Ответ написан
NightTiger
@NightTiger
Попробую и я ответить. Закон применять надо всегда, если есть возможность его применить.

Немного лирики: та тонна бизнес-кода 20-30 разработчиков, которая прошла через меня позволила познать тот самый дзен Clean Code, не могу сказать, что у нас все везде хорошо, но за последние несколько лет мы так улучшили его общее состояние, что с ним становится приятно работать — и это передается от разработчика к разработчику, вселяя уверенность, понимание и осознание каждого участка кода.

При чем здесь правило Деметры? Оно и дает эти возможности. Как вы думаете, почему так популярен IoC и DIC Symfony2 на его основе? Своей популярностью он как раз обязан тем, что использует неявно правило Деметры для всех создаваемых сервисов: каждый сервис в контейнере получает только свои зависимости и не лезет через них к другим, более того, сам контейнер зависимостей — отражение правила Деметры, потому что он пользуется исключительно своими методами для создания инстансов сервисов, обращению к параметрам.

Идем дальше, слои приложения. Если кто не знает — учите мат.часть. Но если кратко, то слой данных не должен содержать бизнес-логики и взаимодействует с уровнем сервисов. Уровень сервисов содержит необходимую логику и предоставляет API наружу для контроллеров, веб-сервисов и других, но не отвечает за поддержку различных форматов запросов/ответов. Уровень контроллеров позволяет распаковывать запросы/упаковывать ответы, работая только с уровнем сервисов, не делая никаких предположений об источнике данных. Где здесь правило Деметры? Ответ очевиден: везде. Если мы соблюдаем это правило, то мы можем независимо менять реализацию кажого из слоев, не волнуясь о том, что сломается что-то в слое, который мы не трогаем сейчас.

Следующий пункт: уровень детализации участка кода. Если вы проводили ревью кода, то как часто вам приходилось думать о том, что делает конкретный участок кода с этой цепью вызовов внутри? Есть простой факт, что мы можем держать в голове эффективно около 7 объектов, когда идет ревью кода, то каждый уровень цепочки обязует загрузить себе в память лишний класс, потому что только так вы поймете что происходит в конечном счете. Проведем легкую арифметику: если у нас есть класс, он же сервис, в который инжектятся три других сервиса, то у нас пока в памяти 4 сущности, которые мы легко понимаем. Дальше идет вызов метода с аргументами, если этот метод принимает три параметра и работает только с прямыми зависимостями на один уровень, то все хорошо: 7 объектов, но все еще понятно, как оно работает. Но как только начинаются запросы вида $this->service->createResult($request)->translate($mapping); приходится материться, так как такой код не помещается со всеми зависимостями в наше ОЗУ — мозг эффективно. Отсюда вывод — каждый участок кода отвечает за свой уровень детализации, позволяя более низким сервисам раскрывать детали. Если мне понадобятся детали — я дойду до них, но они не должны мне мешать понимать текущий код уровнем выше, а для этого надо вызывать только свои соседей, делегируя детали все ниже и ниже.

Надеюсь, мои мысли подтолкнут вас к добрым намерениями )
Ответ написан
ConConovalofff
@ConConovalofff
Сегодня прочитал об этом законе и сделал для себя следующие выводы:

Закон не нацелен на улучшение читаемости или эстетичности кода. Наоборот, красота кода теряется, а структура класса обрастает проксирующими функциями-пустышками, портя читаемость класса.

Основная цель использования закона, это избавить команду программистов от страха изменять существующий код.

К примеру, представим 2 разные ситуации:
1. В проекте игнорируется закон Деметры. Класс b включен в классы a, c и d. Каждый класс инстанцируется в разных участках кода по 10 раз каждый и применяется a.b.Method(), c.b.Method() и d.b.Method()
2. В проекте применяется закон Деметры. Класс b включен в классы a, c и d. Каждый класс инстанцируется в разных участках кода по 10 раз каждый и применяется a.Method(), c.Method() и d.Method()

При замене класса 'b' на класс 'z':
В 1-ом случае нам придется изменить код в 30 местах где используется a.b.Method().
Во 2-ом случае нам потребуется изменить код в 3-ех методах классов a, c и d.

Следуя этому правилу, я думаю будет логичен следующий код:
$user = new User('Paul')
$comments = $user->getCommentsOfLastPost()


В классе User:
function getCommentsOfLastPost()
{
    $posts = new Posts()
    $posts->getCommentsOfLastPostUser($this)
}

и т.д.

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

Я уловил суть?
Ответ написан
SLY_G
@SLY_G
журналист, переводчик, программист, стартапщик
Так этот закон — не юридический. Это скорее rule of thumb, как говорят у них. То бишь, на заметку взять. Чем меньше связанность, тем проще менять код. А с кодом надо сразу иметь в виду, что он будет меняться.
Ответ написан
Комментировать
EugeneOZ
@EugeneOZ
Так всё ж просто — возвращая объект, вы даёте внешнему коду юзать этот объект. И если раньше этот внешний код зависел только от API вашего объекта, то теперь будет зависеть ещё и от тех объектов, которые вы вернули. Так вы теряете контроль над API и возможность его безболезненно менять.

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

Есть случаи-исключения, когда не нарушить этот закон нельзя (фабрики), но в целом — это один из самых больно бьющих законов, если его нарушать.
Ответ написан
ZmeeeD
@ZmeeeD
я не считаю что это плохой код. Он хорош.

Просто его реализация скрытая внутри этого всего может быть ужасна… например если не забрать эти реляции сразу, или забрать и не использовать. (под забрать я понимаю SQL запрос который делается ORM) Уже не помню про доктрину. Но вот AR в yii точно поддерживает вытягивание реляций по параметрам.
Ответ написан
Комментировать
@jimpanzer
У любого правила/закона есть свои достоинства/недостатки. В частности в той же википедии описаны оные про Закон Деметры. Все сводится к конкретной ситуации.
Если существует потребность в
1. Тестировании
2. Максимальном реюзанье кода
3. Дальнейших модификациях при минимальном вмешательстве
И можно мериться с немного запутанным кодом, то почему бы и не следавать данному принципу.

Просто одно дело думать про себя: «Я знаю как это написано, как это работает и др.» А другое дело, когда сторонний разработчик пытается вникнуть в ваш код: первым делом он ищет известные ему паттерны/законы/приемы/методики.

Повторюсь, все зависит от конкретной ситуации и выполняемых задач. И только ВАМ решать стоит ли использовать какой-либо закон/паттерн в собственных проектах.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы