Зачем (не)нужны геттеры?

?v=1

Прошлая статья про сеттеры/геттеры как способ работы с сущностью (на примере Symfony в PHP) получила бурное обсуждение. В данной статье попробую выразить свои мысли отдельно по поводу геттеров: зачем и когда что-то получать, какую ответственность они решают и когда их уместно использовать, когда не уместно. Постарался собрать мысли в одном месте и формализовать их вместе с вами.

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, где ответственности для чтения и бизнес-операций разделены.

Всем добра!

PS: Минусящим. Статья говорит о довольно простой теме, о которой не задумываются даже довольно опытные regular-программисты на разных языках. Данная статья для того, чтобы осознать семантику обычного геттера, чтобы понимать — зачем он нужен в принципе.

© Habrahabr.ru