Прошлая статья про сеттеры/геттеры как способ работы с сущностью (на примере Symfony в PHP) получила бурное обсуждение. В данной статье попробую выразить свои мысли отдельно по поводу геттеров: зачем и когда что-то получать, какую ответственность они решают и когда их уместно использовать, когда не уместно. Постарался собрать мысли в одном месте и формализовать их вместе с вами.
Изображение из блога Фаулера: TellDontAsk
Геттеры нужны для того, чтобы получить некоторое состояние текущего объекта. В ООП языках — это значение некоторой переменной класса, как правило приватной.
Само значение переменной класса может быть получено как угодно. Мы просто просим объект нам дать что есть, само название метода нам говорит «дай»: но не «сделай», не «отправь», не «создай», не «посчитай». Говорит дай — даем что есть. Это вполне нормально работает в разного рода дата-объектах, ответственность которых как раз давать/нести информацию.
В геттере не может быть никакой логики, тк семантика слова прямолинейна. «Мама, дай мне пирог» не содержит в себе «Мама, купи муку, пожарь пирог и наконеееец дай мне пирог». Может быть фраза «обеспечь меня пирогом» как-то и может инкапсулировать в себя все эти действия, но это точно не «дай».
Вы удивитесь, но такой способ именовать все методы встречался мне неоднократно. Сложность кода при этом довольно вырастает, тк в цепочках геттеров не всегда понятно, что конкретно происходит в слоях приложений и какие связи задействуются, как правило не все в порядке с дизайном и попустительство такого рода — источник ошибок.
Назначение дата-объекта понятно — это капрал из фильма 1917, который бежит куда-то, чтобы донести некоторое послание о том, что нужно отступать.
Но что делать с бизнес-объектами? Зачем сущности «Документ» кому-то «давать» список своих полей?
Если это бизнес-объект, то документ может быть проведен, отклонен, проверен (в том числе по этим полям), заполнен или подтвержден.
Если же появилась необходимость «дать» поля, то значит данный документ не часть бизнес-процесса. Для чего нужно кому-то дать поля? Может для опубликации? Или выписки, или архивации, или отправки копии по почте, или отчета? Не совсем понятно зачем и кому — вырисовывается отдельная ответственность «дать» как у старого доброго дата-объекта, явно видно использование в виде некоторого источника для чтения в контексте другого бизнес-процесса, не основного в понимании самого документа.
Вернемся к бизнес-сущностям. Если оставить геттеры в сущностях, то велик соблазн использовать ее геттеры ровно везде и как угодно. Мы завязываемся на состояние некого объекта и воротим абсолютно любую логику походу. Может даже казаться, что мы не нарушили инкапсуляцию. Но как же? Состояние в одном месте, поведение в другом — классическое нарушение инкапсуляции.
Например есть некоторая сущность:
Наверняка, при такой архитектуре статус вы запросите в добром десятке/сотне мест. Это могут быть разного рода сервисы, контроллеры, другие модули. Я видел только один раз, когда искусственно были созданы ограничения для распространения этого кода некоторым набором правил для разработчиков, не кодом… Инкапсуляция была обеспечена стандартами кодирования, а не проектированием кода :).
Если вам понадобилось сделать где-то что-то подобное:
То скорее всего сущность не содержит в себе машину состояний. Значит инварианты объекта изнутри никак не контролируются — нет верификации данных при каждой операции изнутри. Как следствие — высокая связанность, сложное функциональное тестирование, тк недостаточно проверить юнит-логику того кода, который меняет состояние сущности, нужно проверить, что состояние внешней для кода сущности допустимое. И как следствие: увеличенная сложность, вероятность багов, больше кода и сложные тесты.
Итог: Надеюсь мои и ваши коллег будут меньше использовать геттеры как любой метод, который делает любую работу с результатом, который вернет.
И надеюсь больше разработчиков обратит внимание на концепцию CQRS, где ответственности для чтения и бизнес-операций разделены.
Почитать:
М. Фаулер, TellDontAsk
М. Фаулер, AnemicDomainModel
Всем добра!
Изображение из блога Фаулера: TellDontAsk
getHumanFactory().getHuman().getDoneWork().getWorkTime()
Геттеры нужны для того, чтобы получить некоторое состояние текущего объекта. В ООП языках — это значение некоторой переменной класса, как правило приватной.
class Article {
private String name
public function getName(): String {
return this.name
}
}
Само значение переменной класса может быть получено как угодно. Мы просто просим объект нам дать что есть, само название метода нам говорит «дай»: но не «сделай», не «отправь», не «создай», не «посчитай». Говорит дай — даем что есть. Это вполне нормально работает в разного рода дата-объектах, ответственность которых как раз давать/нести информацию.
В геттере не может быть никакой логики, тк семантика слова прямолинейна. «Мама, дай мне пирог» не содержит в себе «Мама, купи муку, пожарь пирог и наконеееец дай мне пирог». Может быть фраза «обеспечь меня пирогом» как-то и может инкапсулировать в себя все эти действия, но это точно не «дай».
Вы удивитесь, но такой способ именовать все методы встречался мне неоднократно. Сложность кода при этом довольно вырастает, тк в цепочках геттеров не всегда понятно, что конкретно происходит в слоях приложений и какие связи задействуются, как правило не все в порядке с дизайном и попустительство такого рода — источник ошибок.
getValue() throw WhyDoYouNeedIt? {}
Назначение дата-объекта понятно — это капрал из фильма 1917, который бежит куда-то, чтобы донести некоторое послание о том, что нужно отступать.
Но что делать с бизнес-объектами? Зачем сущности «Документ» кому-то «давать» список своих полей?
Если это бизнес-объект, то документ может быть проведен, отклонен, проверен (в том числе по этим полям), заполнен или подтвержден.
Если же появилась необходимость «дать» поля, то значит данный документ не часть бизнес-процесса. Для чего нужно кому-то дать поля? Может для опубликации? Или выписки, или архивации, или отправки копии по почте, или отчета? Не совсем понятно зачем и кому — вырисовывается отдельная ответственность «дать» как у старого доброго дата-объекта, явно видно использование в виде некоторого источника для чтения в контексте другого бизнес-процесса, не основного в понимании самого документа.
doEverythingEverywhere(world.getGod())
Вернемся к бизнес-сущностям. Если оставить геттеры в сущностях, то велик соблазн использовать ее геттеры ровно везде и как угодно. Мы завязываемся на состояние некого объекта и воротим абсолютно любую логику походу. Может даже казаться, что мы не нарушили инкапсуляцию. Но как же? Состояние в одном месте, поведение в другом — классическое нарушение инкапсуляции.
Например есть некоторая сущность:
class Order
{
private Status status
private Boolean closed
public function getStatus(): Status {
return this.status
}
public function setClosed(Boolean closed): void {
this.closed = closed
}
}
Наверняка, при такой архитектуре статус вы запросите в добром десятке/сотне мест. Это могут быть разного рода сервисы, контроллеры, другие модули. Я видел только один раз, когда искусственно были созданы ограничения для распространения этого кода некоторым набором правил для разработчиков, не кодом… Инкапсуляция была обеспечена стандартами кодирования, а не проектированием кода :).
Если вам понадобилось сделать где-то что-то подобное:
if(order.getStatus() == input.getStatus()) {
order.setClosed(true)
}
То скорее всего сущность не содержит в себе машину состояний. Значит инварианты объекта изнутри никак не контролируются — нет верификации данных при каждой операции изнутри. Как следствие — высокая связанность, сложное функциональное тестирование, тк недостаточно проверить юнит-логику того кода, который меняет состояние сущности, нужно проверить, что состояние внешней для кода сущности допустимое. И как следствие: увеличенная сложность, вероятность багов, больше кода и сложные тесты.
Итог: Надеюсь мои и ваши коллег будут меньше использовать геттеры как любой метод, который делает любую работу с результатом, который вернет.
И надеюсь больше разработчиков обратит внимание на концепцию CQRS, где ответственности для чтения и бизнес-операций разделены.
Почитать:
М. Фаулер, TellDontAsk
М. Фаулер, AnemicDomainModel
Всем добра!