Сколько бессонных ночей мы провели, подбирая идеальное наименование для переменных, функций и файлов – увы, не всегда успешно. Сколько тысяч вариантов перебрали, сколько нервов потратили! А все ради чего? Чтобы наш код выглядел логичным и единообразным.
Чтобы облегчить муки подбора, великие умы придумали соглашения о наименованиях. Но теперь появилась новая проблема – какому соглашению следовать?
Чтобы понять, что действительно хорошо, пойдем от противного и попробуем разобраться, что плохо, вдохновляясь Doctrine Coding Standard и демо-проектом Proophessor Do.
Антипаттерн #1. Тавтология интерфейсов
Начнем с классической тавтологии – добавлении префикса/суффикса Interface
к – чему бы вы думали! – названиям интерфейсов.
Тавтология – это структура речи, в которой одна и та же идея выражается с помощью очень похожих морфем, слов или фраз. Иначе говоря, повторяется дважды без значимого обоснования.
В повседневном языке тавтологии обычно считаются недостатком стиля. "Круглый круг", "сухая пустыня", "новые инновации" – все это не самые изящные словосочетания. Та же проблема постоянно встречается при использовании аббревиатур, например, "система GPS" или "номер ISBN".
А теперь прочтите вслух определение этого PHP-класса:
interface TodoRepositoryInterface
{
}
Снова тавтология!
Не все разработчики обращают внимание на подобные вещи. Однако если задуматься, это соглашение об именовании кажется довольно глупым. Вы ведь не используете суффикс Class
для имен ваших классов: TodoClass
вместо простого Todo
. Чем интерфейсы хуже?
В определении интерфейсов уже присутствует ключевое слово Interface
. Нет никакой необходимости добавлять к их именам суффикс Interface
или префикс I
. Это тавтология, которая не несет никакого дополнительного значения, а только размывает основную цель структуры и нарушает принцип DRY.
UML-диаграммы классов предоставляют достаточные возможности для представления и различения интерфейсов и абстрактных классов. Перед именем первых ставится ключевое слово interface
, а вторые выделяются курсивом.
Современные IDE также прекрасно отличают интерфейсы:
Те же самые аргументы можно применить к абстрактным классам и трейтам с соответствующими префиксами/суффиксами Abstract
и Trait
. Однако это может быть приемлемым исключением из правила, ведь абстрактные классы не должны быть частью какого-либо публичного интерфейса. Вы также можете использовать альтернативный префикс, например, Base
, но не стоит злоупотреблять этим подходом. Практически в любой ситуации можно придумать хорошее имя, просто описав цель и область действия абстрактного класса. К примеру, можно поместить логику, общую для всех PDO-репозиториев в абстрактный класс PdoRepository
, а MySqlTodoRepository
станет конкретной реализацией.
Еще одна тавтология, распространенная в мире программирования, но, к счастью, почти обошедшая стороной PHP – это практика использования суффикса Impl
для конкретных реализаций. Так как обычно все, что не является интерфейсом, и есть конкретная реализация, это создает очень много лишнего шума и даже выглядит абсурдно.
Очевидно, что подобные префиксы и суффиксы не приносят в ваш код ничего полезного, а только захламляют его.
Антипаттерн #2. Тавтология доменных классов
Перейдем теперь к доменным классам, содержащим основную бизнес-логику приложения: сущностям, объектам-значениям, исключениям, событиям и т. д.
С сущностями и Value Objects проблем обычно не возникает, ведь мы обычно тщательно выбираем для них хорошие имена: User
, EmailAddress
, Todo
, TodoText
. Но дальше начинаются сложности:
namespace My\Todo\Exception;
final class CannotReopenTodoException extends \Exception
{
}
Суффикс Exception
особенно не бросается в глаза, как это было с интерфейсами. Но давайте посмотрим на FQCN \My\Todo\Exception\CannotReopenTodoException
.
Снова тавтология, ведь Exception
уже встречается в пространстве имен.
Кроме того, формулировка и контекст использования класса CannotReopenTodoException
(throw
, catch
, имя переменной $ex
) однозначно указывают на то, что мы имеем дело с исключением:
try {
throw CannotReopenTodoException::notDone($todo);
} catch (CannotReopenTodoException $ex) {
}
Очевидно, что суффикс Exception
тут лишний.
Еще одна неотъемлемая часть архитектуры – это события, которые часто именуются по такому же принципу. Действительно, в имени FooEvent
суффикс необходим, иначе мы не поймем, что речь идет о событии. Однако было бы лучше использовать точное описание действия в прошедшем времени – TodoWasMarkedAsDone
.
В этом паттерне большое значение имеет понятность имен. Если название класса достаточно хорошо описывает его смысл, суффикс можно (и нужно) удалить. Плохое имя чаще всего – дефект дизайна. Если вы не можете изменить его, значит, что-то в вашем коде пошло не так.
Антипаттерн #3. Тавтология гет-методов
Этот паттерн не так однозначно плох, как первые два. Возможно даже, вы абсолютно не согласитесь с утверждением об избыточности префикса get
, но давайте хотя бы обсудим эту идею.
Геттеры и сеттеры – основная форма взаимодействия со свойствами объекта через публичный интерфейс. Учитывая, что объекты-значения иммутабельны, а сущности поощряют инкапсуляцию, у них не должно быть классических сеттеров. В такой ситуации геттеры берут на себя роль простых методов доступа к свойствам.
final class Todo
{
public function getId(): TodoId
{
}
public function getDescription(): string
{
}
public function getStatus(): Status
{
}
}
И здесь префикс get
становится лишним. Он не несет никакой полезной информации – разве что группирует все методы доступа.
Альтернативное "strip naked" соглашение об именах для методов доступа к свойствам – это важный шаг к общему стандарту оформления сущностей и объектов-значений. В PHP 7.4 стали доступны типизированные свойства и, может быть, скоро появятся readonly свойства, так что в ближайшем будущем мы, возможно, сможем писать наши классы вот так:
final class Todo
{
public TodoId readonly $id;
public string readonly $description;
public Status readonly $status;
}
Нет тавтологии!
Множество существующих PHP-проектов страдают от тавтологического синдрома и большая часть из них никогда не будет вылечена. Но в наших силах создать новое поколение здоровых и красивых приложений!
Комментарии
Согласен со всеми комментаторами и добавлю, что принцип DRY совсем о другом. Лучше удалите этот бред
Статью писал махровый джун, с опытом менее года, без малейшего понимания сколь-нибудь крупных систем. В мусорку.
Почему вы так думаете?
Потому что название должно отражать суть. Когда я вижу в коде
$object instanceof Foo
я хочу точнее понимать, что такоеFoo
класс или интерфейс, поэтому когда$object instanceof FooInterface
нет никаких непоняток, всё четко и определенно. Это касается и эвентов, и эксепшенов, и трейтов. В том же golang принято, что просто структура называется напримерpost
, а интерфейс к нейposter
. Правила PHP-FIG PSR конечно не обязательны, но следование им признак хорошего тона и их пишут не просто, чтобы они были, а чтобы им все старались следовать. А автор сего опуса PHP-FIG точно не читал, а всего лишь следовал своему субъективному пониманию стиля кодирования.Это перевод статьи. Перейдите в оригинал и посмотрите, какие там отношения между автором и комментаторами. Они как минимум принимают точку зрения автора, а как максимум: пытаются понять, а не прав ли он. И я не предлагаю принимать, я предлагаю обсудить это, а не писать "автор долбоеб, автор джун, автор сдохни".
Извиняюсь, но добавление интерфейса к интерфейсу имеет другую цель. Чтобы когда твой редактор сверху во вкладках показывает имя - было видно - что это. Не важно какой префикс. Я видел, ДотНетчики делают iClass, это быстрее проще писать. Но системы разработки добавят интерфейс слово сами, так что пофигу.
та же беда. Папка Exceptions (папку Exception помоему вообще нельзя в неймспейс добавить, потому что Эксепшен - зарезервированное слово - может с Эксепшеном и прокатывает но Interface в единственном числе точно нельзя). Для полноты чтения и поддержки всеми редакторами папка Exceptions и в имени файла тоже Exception.
хорошо что ты прочитал про то, чего еще нет - версию 7.4 и уже дал рекомендацию, автор, но методы getProperty() нужны чтобы первый глагол рассказывал что именно делается над проперти. А метод "description()" например оставить для очень важных вещей - например чтобы предоставить к нему доступ верстальщикам, которые врядли понимают ООП но как отрисовать $form->description() - понимают вполне. Еще их успешно использовали в jquery использовали, чтобы синтаксис был короче, передав в них два действия с одним условием - если параметров больше - тото, если меньше - это.
И вот если ты на коммент не ответишь, что я всё понял и про этот сайт и про тебя.
Вы PHP-FIG PSR-2, PSR-12 читали? $object instanceof Foo сходу можно понять, что это класс или интерфейс? А если так $object instanceof FooInterface ? get и set так приятнее разграничивать геттеры/сеттеры от обычных методов и вызываются они из __set()/__get(). Короче, туфту какую-то прогнали.
Сеттеры не нужны. Во-первых, они не описывают реальное действие, осуществляемое объектом. Возьмем пример:
Вы создали объект поста, установили ему заголовок. Позже вы хотите отредактировать заголовок, и что, сеттер будет описывать это действие? Сет - установить, отредактировать - это изменить. Думайте объектами. По нормальному сеттером будет метод
edit
:Такой метод может не только изменить заголовок, но и инкапсулировать внутри себя изменение слага и время редактирования поста.
Во-вторых, у любой сущности или объекта может быть больше 20 полей, которые в разное время могут принимать null. Положим, при авторизации через одну соц сеть вы получаете один набор данных, через другую - другой. Используя сеттеры, вы не только рискуете установить не те поля, но и забыть вызвать какие-то из них, отчего ваша сущность перестанет быть валидной. Откройте для себя именованные конструкторы:
Теперь у нас есть явный интерфейс с обязательными аргументами, которые мы никогда не забудем заполнить.
А в 7.4 не нужны будут и геттеры.
@franzkafkiansky Возьмем пример:
edit(Что?)
а если у тебя пропертей 12 штук в классе? плохой класс, пиши 12 классов? ну блин...
@franzkafkiansky Такой метод может не только изменить заголовок, но и инкапсулировать внутри себя изменение слага и время редактирования поста.
Именно стремление к тому, чтобы каждый метод выполнял ОДНУ (единственную) задачу - стремление к Солиду - говорит что метод должен делать так. Не нужно инкапсулировать в один метод 7 действий, потому что когда ты его наследуешь или обернешь, то ты будешь матюгаться, почему ты не можешь заменить половину этой штуки, а только целиком.
@franzkafkiansky Откройте для себя именованные конструкторы
и познакомьтесь с тем, что конструктор это единственное место, где можно нарушать принцип Барбары Лисков, и при наследовании обалдейте что вы не можете спереди добавить зависимости, а только сзади. И когда в конструктор у вас распаковка ...$arguments, то вы приехали только что
То. Поле присваивается через конструктор, а все последующие действия с ним - это изменение, изменение - это edit. Не умеете ничего, кроме сеттеров, пишите сеттеры. Не умеет думать в рамках объектов, клепайте классы, которые любой "программист" может вызвать, дернуть один сеттер и добиться невалидного стейта. Внедрение зависимостей через сеттеры из той же оперы.
Дался тебе этот стейт, ты чтоли в программе на ПХП сагу делаешь, где проблемы со сборкой логов?
Тогда пропертей быть не должно и ПХП 7.4 с его именованными пропертями - это бесполезная трата времени израильских коллег.
Умный шопипец. Иди давай, противно разговаривать! Я ожидал уточнений, вопросов, связаться, обговорить, но ты!!! ты утверждаешь, ты доказываешь свою правоту! Иди доказывай айтишникам во дворе.
Не все поля присваиваются через конструктор, бывает, что есть поля с дефолтными значениями, которые могут или должны устанавливаться по ходу кода и это не будет редактированием.
Программист, да и вообще любой человек, должен прежде думать, а только потом что-то дергать. Собственно, что мешает "программеру" дернуть ваши статические методы с невалидными данными и получить невалидный стейт? Ничего, поэтому ваши рассуждения тут на этот счёт невалидны.
Программист, да и вообще любой человек, должен прежде думать, а только потом что-то дергать.
Вы слишком хорошего мнения о программистах.
Собственно, что мешает "программеру" дернуть ваши статические методы с невалидными данными и получить невалидный стейт?
Ему помешает мой конструктор, где, во-первых, могут быть не примитивы, а VO, а во-вторых, никто не отменял эксепшены. Не имея доступ к пропертям напрямую через сеттер, программист будет использовать конструктор. Через сеттер у меня нет контроля над программой и ошибку он поймает уже на этапе сохранения в базу, например. В случае с конструктором он дальше создания объекта не уедет.
А какая у вас проблема в том, чтобы проверять значение в сеттере? А ничего, что вам придётся говнокодить копи-паст, т.к. придётся проверять все значения и в конструкторе и при редактировании, вместо одного раза в сеттере?
И какая проблема в сеттере выкинуть исключение?
Проблема в сеттере в том, что его можно НЕ вызвать. Проблема не в проверке. У конструктора такой проблемы нет.
Когда я пишу $object->title = "Hello", то сеттер вызывается всегда, его нельзя не вызвать и нельзя забыть вызвать, оно автоматически вызывается.
Речь не про одно поле. Речь про то, когда и что вызывать. Стоит задача сохранения данных из одного источника, откуда приходят только 5 значений, в то время как у класса их 10. Что вы начинаете делать:
Вы уверены, что вызвали все сеттеры или что сеттеры, которые вы вызвали - те самые?
Да хоть 50
всегда вызываются те сеттеры, которые надо - 100%, а то что не пришло, остаётся со значениями по-дефолту ибо нет проблем в классе объявить такое значение
Да речь не про то, что делает программа, а про то, что ВЫ или ваш КОЛЛЕГА забудет или не вызовет сеттер, физически не вызовет, в программе не сделает
$post->title
, а не то, что там пыха сделает. Речь о человеческом факторе.Например, при публикации поста надо еще статус ему поставить, что, мол, пост у нас теперь
PUBLISHED
. И вот забыл человек в программе проставить статус через сеттер (то есть сделал$post->setTitle()...
, аsetStatus()
забыл дернуть), и в итоге поста нет в ленте, потому что вы выводите посты со статусомPUBLISHED
. И дефолтные значения тут не помогут, потому что пост не может сразу принять статусPUBLISHED
, если он в черновике. А если иметь методpublish()
, куда передавать окончательный вариант заголовка, менять слаг и устанавливать статус, то все будет ок.Я забыл вызвать метод
publish()
и пост остался не опубликованным, чем эта забывчивость отличается от забывчивость написать$post->status = PUBLISHED;
? Вы не понимаете сути сеттеров. Сеттер делает только одну единственную вещь - устанавливает соответствующее ему полю указанное значение. Если вы публикуете пост и это подразумевает несколько разных действий, то конечно же это выносится в отдельный метод$post->publish();
который вызываете и уже в нём делаете окончательный вариант зага, слага и пишете$this->status = PUBLISHED;
кто и чего в этом случает забудет? Никто и ничего, и при том, можно ещё в сеттере проверить, а точно ли криворукий коллега передал валидное значение для проперти, и выкинуть исключение, типа "вы тут мне передали статус 10, а я знаю только два значение 1 и 0 - опубликовано/не опубликовано соответственно".Вы не забудете вызвать
publish()
, потому что он держит в себе весь пул действий. У вас вообще нет сеттеров, есть только сущность иpublish()
, куда вы передаете заголовок, статью, а методpublish()
уже их устанавливает и внутри еще статус меняет сDRAFT
наPUBLISHED
. Иначе говоря, у вас есть юзкейс, где сохраняется пост, и там вы дергаете толькоpublish()
. Нет сеттеров, толькоpublish()
. Суть в том, что необязательно модели делать анемичным.Почему это я забуду про
$post->status = PUBLISHED
и не забуду про$post->publish()
? Ну вот писал, писал и забыл, всяко бывает. Собственно, не обязательно сам пост должен себя публиковать, может какой адаптер к фейсбуку захочет опубликовать его в ленте и тогда значение статуса уже может менять не сам объект, а непосредственно адаптер, а чёрт его знает какое он там значение сунет в полеstatus
.Сеттер не содержит никак пулов действий, а если и содержит, то весь этот пул связан исключительно с полем класса, к которому привязан и не должен выполнять никаких действий, типа, сохраниться в базу, проверить аутентификацию пользователя и т.п., для этого есть обычные методы класса.
Потому что
publish()
- это одно действие, аset
- это много разных действий. И обновление - это неset
.Опять вы всё напутали.
publish()
- это куча всяких действий, которые должны в конечном итоге опубликовать пост, аset
- это всего лишьустановка
- одно действие, которое что-то устанавливает, сеттер делает только одно действие - устанавливает значение для конкретного свойства объекта, публикация - это бизнес-логика состоящая из множества действий.Одно действие для программиста, а не одно действие внутри себя. Вам не нужно думать, что нужно еще засеттить, вам нужно только дернуть publish, сигнатура которого покажет, что требует этот метод, вот и все.
Например, мне попался ваш код, в гитхабе, открываю его и вижу строку
$post->publish()
, какая тут сигнатура и как мне понять, что делает этот метод? Когда я вижу$post->state = PUBLISHED
я понимаю, вполне чётко и определенно, что эта сточка кода делает, но когда я вижу$post->publish()
я думаю, что обычный метод, который что-то там делает, а что - да хрен его знает.Тут все понятно, не передергивайте.
Ничего не понятно, видно только, что принимает два строковых параметра и ничего более, неявный вызов сеттера может замечательно скрываться внутри этого метода, только лишь потому, вы не понимаете сути сеттера. Тем более, необязательно, что пост должен сам себя публиковать, его может опубликовать какой-нибудь адаптер к фейсбуку или вконтакту, и который понятия не имеет про ваш publish().
Сеттер - тупое как пробка действие, что в нем может быть непонятного? Я говорю, что он не отражает действие. Объект не постепенно принимает свою форму или размер, он сразу становится объектом. Как и при смене чего-либо - это НЕ установление с нуля, это изменение, а изменение - это чейнджер, а не сеттер. Учитесь правильно называть вещи своими именами.
А какая вам разница что отражает
$user->changeName($name)
и$user->name = $name;
точнее что лучше и понятнее отражает суть установки имени пользователя? У меня есть ваш класс, я не хочу разбираться какие у вас там методы существуют для каждого поля класса, я хочу поменять только значение определенного поля, для этого я пишу$user->name = $name
, а вы мне говорите, не чувак иди лесом и никто не догадается, что для этого есть некий методchangeName()
Объект может менять и пополнять своё состояние в рантайме и не обязательно, что изменение состояния будет делать сам объект внутри себя, и это вы знаете про какой-то там метод
changeName()
, любой другой после вас афигеет, от того, что не сможет написать банальное$user->name = $name
Потому что имя изменяется, а не устанавливается. И если для вас
changeName()
менее красноречиво, чем сеттер, то у нас разное восприятие слов, увы. В целом, можем не продолжать, останемся при своем. Спасибо за разговор.Когда вы создаете объект, поля уже и так содержат
null
и любое присвоение является изменениемnull
на другое значение, хоть в конструкторе, хоть где. Всё есть изменение, зачем вам оператор "="? Откажитесь, правда я не знаю как вы вchangeName()
сможете поменять полеname
без этого оператора.Уфф, ни я ни коллега не забудет вызывать сеттер, потому что если он есть, то вызывается автоматически во время присвоения значения. Если у вас есть модель, и для неё пришли новые данные, то вы создаёте новый объект, вместо того, чтобы просто поменять старый? И только ради того, чтобы ваш коллега не забыл поменять старое значение title на новое?
Вы не поняли меня. Речь не про метод сеттер, а про действие установить . Дело не в том, что присваивая данные проперти, вы автоматически дергаете сеттер, а в том, что вы вообще забудете при создании объекта присвоить какое-то из значений или присвоите не тому, что потому что источников данных может быть много.
Если у вас есть модель, и для неё пришли новые данные, то вы создаёте новый объект, вместо того, чтобы просто поменять старый?
Мы, как и вы, знаем, что и в каком виде откуда придет, поэтому есть определенные методы вроде
changeName()
,publish()
,reject()
,record()
и так далее.Все эти методы выполняют достаточно большую логику и функции, сеттер выполняет всего лишь одну, в общем случае. Установка значения проперти - это всего лишь установка значения проперти, а не: залогиниться, проверить права доступа, найти посты пользователя и под конец не забыть поменять значение для проперти, что пользователь авторизовался в такое-то время. Разницу чувствуете?
Эти методы не проверяют ничего, что стоит выше, они просто изменяют состояние модели, они не дергают сервисы, не работают с сессией и так далее. Они работают с данными, вот и все. Это общий вид сеттеров, которые собраны в один конкретный юзкейс. Когда бизнес говорит вам, что пост нужно опубликовать, он понимает именно "Опубликовать", а не "установи сначала заголовок, потом статью, потом поменяй слаг, обнови время и статус".
Состояние - это как раз и есть совокупность значений свойств объекта. Изменение состояния - это изменение значения и не более. "Опубликовать" - это действие, которое помимо смены состояния совершает действия, как-то сохранение в базу. Когда бизнес говорит "опубликовать" это именно и значит: установи заголовок, статью, поменяй слаг, поменяй статус и сохрани в базу. Грубо говоря - состояние меняют конкретные сеттеры, а бизнес делают общие методы.
У меня
publish()
не сохраняет в базу, он просто сеттит поля. В базу сохраняет репозиторий.Вот и получается, что хрен поймёшь, что делает ваш метод
publish()
, зато любому дураку понятно, что делает$post->state = PUBLISHED
Ни хрен поймешь, у него более чем говорящий интерфейс: опубликовать. Для публикации дай мне то, это и это. Если оно все валидно, я опубликую пост. А в случае с сеттерами ВАМ надо думать, что нужно, чтобы пост можно было опубликовать.
Зачем думать? Сеттер не предназначен для того, чтобы что-то публиковать, сеттер изменяет состояние объекта, сама процедура публикации - это бизнес-логика и с сеттерами никак не связана.
Вот
publish()
- это и есть бизнес-логика. А то, что предлагаете вы, это постепенное собирание объекта из сеттеров. Вы вспомните, как изначально придумывался ООП и что вообще такое объекты. Объекты, как и реальные предметы из внешнего мира, не создаются без размеров и форм или не собираются постепенно, они сразу появляются (__construct()
) со всеми свойствами в валидном состоянии. Я говорю, что валидность легко контролируется через конструктор. Если какой-то сервис требует контейнер, о чем вы не знаете, и если этот контейнер задается черезsetContainer()
, какой есть шанс его не вызвать? Такой же шанс у вас или у вашего коллеги или у тех, кто будет использовать ваш класс, не засетить какое-то обязательное свойство. Ну и еще раз, изменить что-либо .- это не сет. Люди в жизни даже так не говорят. Все, что заменяется другим, изменяется, а не устанавливается.publish()
не отменяет использование сеттеров, он как раз и является тем методом, который собирает объект, а сеттеры в процессе этого неявно вызываются, когда вы делаете процедуру присвоения через оператор "=" внутриpublish()
. У меня есть модель пользователя, загруженная из базы и есть запрос на изменение имени пользователя с новым значением, какой шанс забыть в экшене написатьТакой шанс - 0.0% Вы в курсе, что оператор "=" оператор присвоения, а операция называется присвоение/установка/применение/изменение. У полей класса всегда есть значение по-умолчанию -
null
и когда вы устанавливаете новое значение вы в любом случаеизменяете
то что былоnull
на другое новое значение. Таким образом вы сами всегда и везде должны перестать использовать оператор "=", потому что он так или иначе изменяет начальное значение чего либо. Вы сами себе противоречите и вдаетесь в дебри, где на самом деле всё просто и понятно.Мы о разном. Я про восприятие, а вы мне про то, что равно - это равно. Я и без вас это знаю. Мы ни к чему не придем, давайте закончим. Спасибо.
Я о программировании, а вы о субъективном восприятии. Я говорю, что
а вы мне говорите - это не установка, это изменение, поэтому нужно
Я говорю нафига, ведь $value =+ 2 более выразительно, вы мне, ну я так воспринимаю, а вдруг кто-нибудь забудет написать
$value =+ 2;
и всё упадёт.Ясно.
Или что насчет зависимостей между свойствами? Есть кейсы, когда свойство не может принять какое-либо значение или диапазон значений, если другое свойство класса имеет определенное значение. Или же при установке одно значения, нужно менять обязательно другое. Как пользователь вашего класса обо всем это будет знать? Идея в том, чтобы предоставлять более явный интерфейс по работе с классом, а не обычные сеттеры, из которых ничего непонятно.
Вы путаете сеттер с конструктором. Сеттер всего лишь устанавливает значение (как довесок, проверять его на валидность), всю остальную логику выносят в другие места: конструкторы, отдельные методы, например, edit(...$values), в котором все эти зависимости проверяются и только потом устанавливаются нужные значения. Инкапсуляция, вам не нужно знать, как оно там работает, вам достаточно знать, что полю с таким-то именем нужно присвоить какое-то значение, а что там подкатом делается никого не интересует. Вас же не интересует, что делает Exception::getTrace() у себя внутрях, вам достаточно того, что он возвращает массив определенной структуры.
Я не путаю. Я приверженец конструкторов как наиболее надежного способа создать объект в валидном состоянии и ничего не забыть. Плюс сеттер не описывает, что реально происходит. Ваш возраст с каждым годом устанавливается заново или меняется? Примерно так же надо поступать и с объектами.
Ещё как путаете. Т.е. вы, чтобы поменять значение у проперти объекта создаете новый объект? Вам кровь из носу важно знать что происходит при
$post->title = "Title"
? Именно меняется, но в ваших словахЯ приверженец конструкторов как наиболее надежного способа создать объект в валидном состоянии и ничего не забыть
, утверждается, что я должен рождаться каждый год с новым значением возраста, т.к. только рождение - конструктор способен ничего не забыть и надежно родить меня таким, каким я должен быть.Создать - конструктор, изменить - чейнджеры. Что тут непонятного? Причем здесь "каждый раз заново создавать объект"? Объект создается один раз и заполняется через конструктор/именованный конструктор. Дальше объект меняется, а не устанавливается.
Идея в том, чтобы использовать не сеттеры, а нормальное именование методов; чтобы использовать модель как модель, а не как анемичную структуру данных, где только сеттишь, и все.
А какая разница
$user->name = $name;
, нафига мне знать, что помимо свойстваname
есть ещё специальный методchangeName()
? Собственно вашchangeName()
по сути является сеттером, потому что делает тоже самое$this->name = $name;
только вызываете его явно, вместо обычного$user->name = $name;
И откуда мне знать, что этот ваш метод меняет именно нужное полеname
, а не какое-то другое? И я точно не знаю, что ещё делает методchangeName()
кроме того, что меняет значение свойства, может оно ещё пытается сохранить всё это в базу, а мне оно не нужно. Где тут контроль и надежность? Нет, зато можно быть на все 100% уверенным, что сеттер$user->name = $name;
изменит только значение свойства и ничего более, потому что это явно следует из написанного кода.Потому что
changeName()
отражает то, что вы делаете, а сеттер - нет? Контроль и надежность в том, что теперь у вас модель, а не тупая структура данных, в которой легко можно похерить изменение какого-то свойства, если он должно меняться при смене другого. И нет, это не AR и сущность сама себя в базу не запишет. В общем, давайте заканчивать. Вы предпочитаете анемичные модели, я - жирные.Потому что changeName() отражает то, что вы делаете, а сеттер
Это как, т.е.$user->name = $name;
для вас не отражает ничего конкретного и надежного? Хм, а по мне ваш методchangeName()
не отражает того, что я ожидаю. Ну есть методchangeName()
он что-то там типа меняет, а что он ещё делает я ручаться не могу, может он ещё пытается залезть в базу и сохранить объект с новым именем. Ну не вызвал я вашchangeName()
для объекта, ну похерил изменения свойстваname
, чем это отличается от того, что я забыл написать$user->name = $name;
и тоже похерил изменения?Я предпочитаю то, что гибко, читаемо, красиво и самое главное - легко поддерживается, а чтобы ваш монолит понять, а уж не дай бог что-то в нём поменять, чтобы не дай бог ничего не сломалось, нужно не одно поле бамбука выкурить.
и при наследовании обалдейте что вы не можете спереди добавить зависимости
Если для вас единственный способ расширить класс - это наследование, то да, у вас будут проблемы.
А, ну конечно, оберни его в декоратор и нарушай сразу ВСЕ принципы солида. Меняй местами параметры. Меняй их количество. Выпускай говнопакеты. А нет, я забыл. В тренде - "не выпускай пакеты, юзай готовое".
А готовое пишут те, кто имел в виду все советы. Вот так отцы пользуются тем, что делают дети и умничают
А что у нас все поля объекта обязательно должны устанавливаться внутри класса? А с какого рожна класс пользователя, в данном случае, должен что-то знать о гитхабе и гугле?
А изменение дефолтного значения поля - это установка или редактирование? Собственно, ну я понимаю редактирование, например, модели, но обычно значение в поле устанавливается или присваивается, что делается оператором "=", т.е. "сеттер" как раз и занимается тем, что присваивает/устанавливает значение в указанное поле объекта, как бы редактированием тут мало пахнет. Не понимаю, чем вам сеттеры с геттерами не угодили, не нравится, объявляйте тогда всё в паблик.
А что там будет в 7.4 на счёт геттеров и сеттеров, как-то пропустил этот момент?
https://wiki.php.net/rfc/typed_properties_v2
Ну, типизированные поля и сплошной паблик, что там с геттерами, почему они будут не нужны? Собственно, ваш отсыл к 7.4 говорит, что любой "программер" может дернуть что-то не то или установить что-то не то и получить невалидный стейт, пАтАмуШтА там сплошной паблик, делай что хочешь.
Что вам мешает использовать вместо примитивов (string, int) классы (Bio, User), которые уже заполнять через конструктор?
А зачем мне примитивы string, int оборачивать в классы? На кой ляд, например, при загрузке из базы все эти примитивы разворачивать в классы, а потом при сохранении схлопывать обратно в примитивы? Это ж сколько памяти и ресурсов/времени будет занимать.
Затем, что какие-то данные могут всегда меняться вместе. Это не панацея, это один из выходов. Короче, идея простая: контроль доступа и именование методов так, чтобы они показывали, что они делают. Сеттеры не показывают.
Сеттер - это инкапсуляция, он не обязан ничего вам показывать, собственно это не ваше дело, а сеттера и программиста, который записал в сеттер какую-то логику, ваше дело только присваивать значения для проперти объектов.
Хорошо
Первый вариант
Второй вариант
В каком варианте есть шанс забыть установить время или юзера? И что будет, когда вы это сделать забудете, если в базе стоит
not null
на поле с юзером и временем?Вот, почитайте, если интересно. https://williamdurand.fr/2013/06/03/object-calisthenics/
там всё. забей
пиши мне лучше, он поймёт. или уже понял, что никогда не поймёт
Да как-то печально просто, а потом все на пых гонят, что мол г**но, а не язык.
Причем здесь пых говно, печаль? С вами разговаривают, а не травлю устраивают.
Когда разговаривают, звучат вопросы "что вы имеете в виду", "вы уверены?", "почему вы так считаете".
У вас звучат изречения "так надо потому что вы лохи", только вы не произносите слово лох. Признак "мы не переходили на ты" это не речевой оборот, он вас выдал. Уважения к себе требуют те, кто считает себя выше всех.
С Вами разговор невозможен пока вы ведёте себя так, выражая свои унижения в "невиноватой" манере - типа "я ничего такого не говорил, это вы сами обиделись". Вы могли подумать о том, что я, услышав это, могу обидется, и сказать иначе. Но вы думаете что есть некое "быдло" для которого незачем стараться. И валите как есть, давите, унижаете, выступаете.
Заинтересуйтесь желанием помочь и кому-нибудь написать, обговорить, поделится. Хотя я напомню - вам то это незачем. Вы уже итак официально пиздат, как думает Ваш мозг.
Первое. Про тавтологию интерфейсов. Что по поводу PSR Naming Conventions? https://www.php-fig.org/bylaws/psr-naming-conventions/
Второе. Как же принцип, что имя функции должно быть глаголом? id() что? create, generate, get, set
PSR - это рекомендации, а не обязательства. Многие из правил абсолютно дурацкие, как этот пример с добавлением суффиксов. У вас проблемы с именованием, а не суффиксами
Когда то был стандарт СССР. За несоблюдение рекомендаций сажали, расстреливали и увозили. Знаешь почему? Потому что нужна была унификация. Чтобы в Сибири и в Москве делали одинаковые стулья/станки/патроны/программы с взаимозаменяемыми модулями.
Прошу, не обижайся на меня, я не желаю тебе зла и не обсираю твой труд. Я говорю что перед тем как писать статью, попробуй найти пару десятков разработчиков в телеграме и спросить, что они думают.
А не сначала писать статью, чтобы все о тебе узнали, а потом "вы все не правы и идиоты". Я так говорю потому что сам так делал и тоже обжегся.
Во-первых, мы не переходили на ты. Во-вторых, статью писал не я, откройте глаза.
Ах ты к себе уважения требуешь, равенство значит не твой конёк. Ты такой здесь пришел миссия. Иди своей дорогой, дольше проживешь.
Вы на вы - это не равенство? Что вы несете. Я вам выкал, вы мне тыкаете, где тут равенство?
Это означает забить на титулы в принципе. Не позволять вешать на вас ярлык типа "а это тот странный типок". Вы считаете "ты" ярлыком? Блин, почему?