Зачем (не)нужны геттеры?
Прошлая статья про сеттеры/геттеры как способ работы с сущностью (на примере 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-программисты на разных языках. Данная статья для того, чтобы осознать семантику обычного геттера, чтобы понимать — зачем он нужен в принципе.