Пожаловаться

Интерфейсный интерфейс, или три плохих совета для именования

26 сентября 2019 в 10:40
41159
Пожаловаться
Во все времена самым сложным делом в программировании было наименование. Подбирать имена для классов за вас мы, конечно, не будем, но кое-что посоветуем.

Сколько бессонных ночей мы провели, подбирая идеальное наименование для переменных, функций и файлов – увы, не всегда успешно. Сколько тысяч вариантов перебрали, сколько нервов потратили! А все ради чего? Чтобы наш код выглядел логичным и единообразным.

Чтобы облегчить муки подбора, великие умы придумали соглашения о наименованиях. Но теперь появилась новая проблема – какому соглашению следовать?

Чтобы понять, что действительно хорошо, пойдем от противного и попробуем разобраться, что плохо, вдохновляясь 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-проектов страдают от тавтологического синдрома и большая часть из них никогда не будет вылечена. Но в наших силах создать новое поколение здоровых и красивых приложений! 

Шпаргалка на память
41159

Комментарии

ответил на комментарий оставил(а) комментарий 26 сентября в 16:01
Пожаловаться

Статью писал махровый джун, с опытом менее года, без малейшего понимания сколь-нибудь крупных систем. В мусорку.

изменено 26 сентября в 04:09
Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 17:36
Пожаловаться

Почему вы так думаете?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 21:27
Пожаловаться

Потому что название должно отражать суть. Когда я вижу в коде $object instanceof Foo я хочу точнее понимать, что такое Foo класс или интерфейс, поэтому когда $object instanceof FooInterface нет никаких непоняток, всё четко и определенно. Это касается и эвентов, и эксепшенов, и трейтов. В том же golang принято, что просто структура называется например post, а интерфейс к ней poster. Правила PHP-FIG PSR конечно не обязательны, но следование им признак хорошего тона и их пишут не просто, чтобы они были, а чтобы им все старались следовать. А автор сего опуса PHP-FIG точно не читал, а всего лишь следовал своему субъективному пониманию стиля кодирования.

изменено 26 сентября в 09:09
Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 22:33
Пожаловаться

Это перевод статьи. Перейдите в оригинал и посмотрите, какие там отношения между автором и комментаторами. Они как минимум принимают точку зрения автора, а как максимум: пытаются понять, а не прав ли он. И я не предлагаю принимать, я предлагаю обсудить это, а не писать "автор долбоеб, автор джун, автор сдохни".

изменено 26 сентября в 10:09
Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:30
Пожаловаться
  1. Извиняюсь, но добавление интерфейса к интерфейсу имеет другую цель. Чтобы когда твой редактор сверху во вкладках показывает имя - было видно - что это. Не важно какой префикс. Я видел, ДотНетчики делают iClass, это быстрее проще писать. Но системы разработки добавят интерфейс слово сами, так что пофигу.

  2. та же беда. Папка Exceptions (папку Exception помоему вообще нельзя в неймспейс добавить, потому что Эксепшен - зарезервированное слово - может с Эксепшеном и прокатывает но Interface в единственном числе точно нельзя). Для полноты чтения и поддержки всеми редакторами папка Exceptions и в имени файла тоже Exception.

  3. хорошо что ты прочитал про то, чего еще нет - версию 7.4 и уже дал рекомендацию, автор, но методы getProperty() нужны чтобы первый глагол рассказывал что именно делается над проперти. А метод "description()" например оставить для очень важных вещей - например чтобы предоставить к нему доступ верстальщикам, которые врядли понимают ООП но как отрисовать $form->description() - понимают вполне. Еще их успешно использовали в jquery использовали, чтобы синтаксис был короче, передав в них два действия с одним условием - если параметров больше - тото, если меньше - это.

И вот если ты на коммент не ответишь, что я всё понял и про этот сайт и про тебя.

изменено 26 сентября в 01:09
Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 12:03
Пожаловаться

Вы PHP-FIG PSR-2, PSR-12 читали? $object instanceof Foo сходу можно понять, что это класс или интерфейс? А если так $object instanceof FooInterface ? get и set так приятнее разграничивать геттеры/сеттеры от обычных методов и вызываются они из __set()/__get(). Короче, туфту какую-то прогнали.

изменено 26 сентября в 12:09
Ответить
2 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 12:18
Пожаловаться

Сеттеры не нужны. Во-первых, они не описывают реальное действие, осуществляемое объектом. Возьмем пример:

class Post
{
    ...

    private $title;

    public function setTitle(string $title)
    {
          $this->title = $title;
    }
}

Вы создали объект поста, установили ему заголовок. Позже вы хотите отредактировать заголовок, и что, сеттер будет описывать это действие? Сет - установить, отредактировать - это изменить. Думайте объектами. По нормальному сеттером будет метод edit:

public function edit(string $title)
{
     $this->title = $title;
}

Такой метод может не только изменить заголовок, но и инкапсулировать внутри себя изменение слага и время редактирования поста.

Во-вторых, у любой сущности или объекта может быть больше 20 полей, которые в разное время могут принимать null. Положим, при авторизации через одну соц сеть вы получаете один набор данных, через другую - другой. Используя сеттеры, вы не только рискуете установить не те поля, но и забыть вызвать какие-то из них, отчего ваша сущность перестанет быть валидной. Откройте для себя именованные конструкторы:

private function __construct(Id $id)
{
      $this->id = $id;
      $this->date = new \DateTimeImmutable('now');
}

public static function fromGoogle(Id $id, string $clientId, string $email)
{
      $user = new self($id);
      $user->clientId = $clientId;
      $user->email = $email;

      return $user;
}

public static function fromGithub(Id $id, string $clientId, string $email, string $repo)
{
      $user = new self($id);
      $user->clientId = $clientId;
      $user->email = $email;
      $user->repo = $repo;

      return $user;
}

Теперь у нас есть явный интерфейс с обязательными аргументами, которые мы никогда не забудем заполнить.

А в 7.4 не нужны будут и геттеры.

изменено 26 сентября в 12:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:33
Пожаловаться

@franzkafkiansky Возьмем пример:

edit(Что?)

а если у тебя пропертей 12 штук в классе? плохой класс, пиши 12 классов? ну блин...

@franzkafkiansky Такой метод может не только изменить заголовок, но и инкапсулировать внутри себя изменение слага и время редактирования поста.

Именно стремление к тому, чтобы каждый метод выполнял ОДНУ (единственную) задачу - стремление к Солиду - говорит что метод должен делать так. Не нужно инкапсулировать в один метод 7 действий, потому что когда ты его наследуешь или обернешь, то ты будешь матюгаться, почему ты не можешь заменить половину этой штуки, а только целиком.

@franzkafkiansky Откройте для себя именованные конструкторы

и познакомьтесь с тем, что конструктор это единственное место, где можно нарушать принцип Барбары Лисков, и при наследовании обалдейте что вы не можете спереди добавить зависимости, а только сзади. И когда в конструктор у вас распаковка ...$arguments, то вы приехали только что

изменено 26 сентября в 01:09
Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:39
Пожаловаться

То. Поле присваивается через конструктор, а все последующие действия с ним - это изменение, изменение - это edit. Не умеете ничего, кроме сеттеров, пишите сеттеры. Не умеет думать в рамках объектов, клепайте классы, которые любой "программист" может вызвать, дернуть один сеттер и добиться невалидного стейта. Внедрение зависимостей через сеттеры из той же оперы.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:43
Пожаловаться

Дался тебе этот стейт, ты чтоли в программе на ПХП сагу делаешь, где проблемы со сборкой логов?

Тогда пропертей быть не должно и ПХП 7.4 с его именованными пропертями - это бесполезная трата времени израильских коллег.

Умный шопипец. Иди давай, противно разговаривать! Я ожидал уточнений, вопросов, связаться, обговорить, но ты!!! ты утверждаешь, ты доказываешь свою правоту! Иди доказывай айтишникам во дворе.

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:50
Пожаловаться

Не все поля присваиваются через конструктор, бывает, что есть поля с дефолтными значениями, которые могут или должны устанавливаться по ходу кода и это не будет редактированием.

Программист, да и вообще любой человек, должен прежде думать, а только потом что-то дергать. Собственно, что мешает "программеру" дернуть ваши статические методы с невалидными данными и получить невалидный стейт? Ничего, поэтому ваши рассуждения тут на этот счёт невалидны.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:54
Пожаловаться

Программист, да и вообще любой человек, должен прежде думать, а только потом что-то дергать.

Вы слишком хорошего мнения о программистах.

Собственно, что мешает "программеру" дернуть ваши статические методы с невалидными данными и получить невалидный стейт?

Ему помешает мой конструктор, где, во-первых, могут быть не примитивы, а VO, а во-вторых, никто не отменял эксепшены. Не имея доступ к пропертям напрямую через сеттер, программист будет использовать конструктор. Через сеттер у меня нет контроля над программой и ошибку он поймает уже на этапе сохранения в базу, например. В случае с конструктором он дальше создания объекта не уедет.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:02
Пожаловаться

А какая у вас проблема в том, чтобы проверять значение в сеттере? А ничего, что вам придётся говнокодить копи-паст, т.к. придётся проверять все значения и в конструкторе и при редактировании, вместо одного раза в сеттере?

И какая проблема в сеттере выкинуть исключение?

изменено 26 сентября в 02:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:04
Пожаловаться

Проблема в сеттере в том, что его можно НЕ вызвать. Проблема не в проверке. У конструктора такой проблемы нет.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:11
Пожаловаться

Когда я пишу $object->title = "Hello", то сеттер вызывается всегда, его нельзя не вызвать и нельзя забыть вызвать, оно автоматически вызывается.

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:14
Пожаловаться

Речь не про одно поле. Речь про то, когда и что вызывать. Стоит задача сохранения данных из одного источника, откуда приходят только 5 значений, в то время как у класса их 10. Что вы начинаете делать:

$post
    ->setTitle($title)
    ->setBody($body)
    ->setMeta($meta);
...

Вы уверены, что вызвали все сеттеры или что сеттеры, которые вы вызвали - те самые?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:20
Пожаловаться

Да хоть 50

$post->title = $title; 
$post->body = $body; 
$post->meta = $meta; 
...

всегда вызываются те сеттеры, которые надо - 100%, а то что не пришло, остаётся со значениями по-дефолту ибо нет проблем в классе объявить такое значение

class Post 
{ 
    private $_title = 'Default title'; 
    ... 
}
изменено 26 сентября в 02:09
Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:21
Пожаловаться

Да речь не про то, что делает программа, а про то, что ВЫ или ваш КОЛЛЕГА забудет или не вызовет сеттер, физически не вызовет, в программе не сделает $post->title, а не то, что там пыха сделает. Речь о человеческом факторе.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:25
Пожаловаться

Например, при публикации поста надо еще статус ему поставить, что, мол, пост у нас теперь PUBLISHED. И вот забыл человек в программе проставить статус через сеттер (то есть сделал $post->setTitle()..., а setStatus() забыл дернуть), и в итоге поста нет в ленте, потому что вы выводите посты со статусом PUBLISHED. И дефолтные значения тут не помогут, потому что пост не может сразу принять статус PUBLISHED, если он в черновике. А если иметь метод publish(), куда передавать окончательный вариант заголовка, менять слаг и устанавливать статус, то все будет ок.

изменено 26 сентября в 02:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:50
Пожаловаться

Я забыл вызвать метод publish() и пост остался не опубликованным, чем эта забывчивость отличается от забывчивость написать $post->status = PUBLISHED;? Вы не понимаете сути сеттеров. Сеттер делает только одну единственную вещь - устанавливает соответствующее ему полю указанное значение. Если вы публикуете пост и это подразумевает несколько разных действий, то конечно же это выносится в отдельный метод $post->publish(); который вызываете и уже в нём делаете окончательный вариант зага, слага и пишете $this->status = PUBLISHED; кто и чего в этом случает забудет? Никто и ничего, и при том, можно ещё в сеттере проверить, а точно ли криворукий коллега передал валидное значение для проперти, и выкинуть исключение, типа "вы тут мне передали статус 10, а я знаю только два значение 1 и 0 - опубликовано/не опубликовано соответственно".

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:55
Пожаловаться

Вы не забудете вызвать publish(), потому что он держит в себе весь пул действий. У вас вообще нет сеттеров, есть только сущность и publish(), куда вы передаете заголовок, статью, а метод publish() уже их устанавливает и внутри еще статус меняет с DRAFT на PUBLISHED. Иначе говоря, у вас есть юзкейс, где сохраняется пост, и там вы дергаете только publish(). Нет сеттеров, только publish(). Суть в том, что необязательно модели делать анемичным.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:06
Пожаловаться

Почему это я забуду про $post->status = PUBLISHED и не забуду про $post->publish()? Ну вот писал, писал и забыл, всяко бывает. Собственно, не обязательно сам пост должен себя публиковать, может какой адаптер к фейсбуку захочет опубликовать его в ленте и тогда значение статуса уже может менять не сам объект, а непосредственно адаптер, а чёрт его знает какое он там значение сунет в поле status.

Сеттер не содержит никак пулов действий, а если и содержит, то весь этот пул связан исключительно с полем класса, к которому привязан и не должен выполнять никаких действий, типа, сохраниться в базу, проверить аутентификацию пользователя и т.п., для этого есть обычные методы класса.

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:10
Пожаловаться

Потому что publish() - это одно действие, а set - это много разных действий. И обновление - это не set.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:45
Пожаловаться

Опять вы всё напутали. publish() - это куча всяких действий, которые должны в конечном итоге опубликовать пост, а set - это всего лишь установка - одно действие, которое что-то устанавливает, сеттер делает только одно действие - устанавливает значение для конкретного свойства объекта, публикация - это бизнес-логика состоящая из множества действий.

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:46
Пожаловаться

Одно действие для программиста, а не одно действие внутри себя. Вам не нужно думать, что нужно еще засеттить, вам нужно только дернуть publish, сигнатура которого покажет, что требует этот метод, вот и все.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 16:01
Пожаловаться

Например, мне попался ваш код, в гитхабе, открываю его и вижу строку $post->publish(), какая тут сигнатура и как мне понять, что делает этот метод? Когда я вижу $post->state = PUBLISHED я понимаю, вполне чётко и определенно, что эта сточка кода делает, но когда я вижу $post->publish() я думаю, что обычный метод, который что-то там делает, а что - да хрен его знает.

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 16:03
Пожаловаться
$post->publish('Заголовок', 'Текст')

Тут все понятно, не передергивайте.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 18:20
Пожаловаться

Ничего не понятно, видно только, что принимает два строковых параметра и ничего более, неявный вызов сеттера может замечательно скрываться внутри этого метода, только лишь потому, вы не понимаете сути сеттера. Тем более, необязательно, что пост должен сам себя публиковать, его может опубликовать какой-нибудь адаптер к фейсбуку или вконтакту, и который понятия не имеет про ваш publish().

изменено 26 сентября в 06:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 18:45
Пожаловаться

вы не понимаете сути сеттера.

Сеттер - тупое как пробка действие, что в нем может быть непонятного? Я говорю, что он не отражает действие. Объект не постепенно принимает свою форму или размер, он сразу становится объектом. Как и при смене чего-либо - это НЕ установление с нуля, это изменение, а изменение - это чейнджер, а не сеттер. Учитесь правильно называть вещи своими именами.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 19:08
Пожаловаться

А какая вам разница что отражает $user->changeName($name) и $user->name = $name; точнее что лучше и понятнее отражает суть установки имени пользователя? У меня есть ваш класс, я не хочу разбираться какие у вас там методы существуют для каждого поля класса, я хочу поменять только значение определенного поля, для этого я пишу $user->name = $name, а вы мне говорите, не чувак иди лесом и никто не догадается, что для этого есть некий метод changeName()

Объект может менять и пополнять своё состояние в рантайме и не обязательно, что изменение состояния будет делать сам объект внутри себя, и это вы знаете про какой-то там метод changeName(), любой другой после вас афигеет, от того, что не сможет написать банальное $user->name = $name

изменено 26 сентября в 07:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 19:11
Пожаловаться

Потому что имя изменяется, а не устанавливается. И если для вас changeName() менее красноречиво, чем сеттер, то у нас разное восприятие слов, увы. В целом, можем не продолжать, останемся при своем. Спасибо за разговор.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 19:34
Пожаловаться

Когда вы создаете объект, поля уже и так содержат null и любое присвоение является изменением null на другое значение, хоть в конструкторе, хоть где. Всё есть изменение, зачем вам оператор "="? Откажитесь, правда я не знаю как вы в changeName() сможете поменять поле name без этого оператора.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:28
Пожаловаться

Уфф, ни я ни коллега не забудет вызывать сеттер, потому что если он есть, то вызывается автоматически во время присвоения значения. Если у вас есть модель, и для неё пришли новые данные, то вы создаёте новый объект, вместо того, чтобы просто поменять старый? И только ради того, чтобы ваш коллега не забыл поменять старое значение title на новое?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:32
Пожаловаться

Вы не поняли меня. Речь не про метод сеттер, а про действие установить . Дело не в том, что присваивая данные проперти, вы автоматически дергаете сеттер, а в том, что вы вообще забудете при создании объекта присвоить какое-то из значений или присвоите не тому, что потому что источников данных может быть много.

Если у вас есть модель, и для неё пришли новые данные, то вы создаёте новый объект, вместо того, чтобы просто поменять старый?

Мы, как и вы, знаем, что и в каком виде откуда придет, поэтому есть определенные методы вроде changeName(), publish(), reject(), record() и так далее.

изменено 26 сентября в 02:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:54
Пожаловаться

Все эти методы выполняют достаточно большую логику и функции, сеттер выполняет всего лишь одну, в общем случае. Установка значения проперти - это всего лишь установка значения проперти, а не: залогиниться, проверить права доступа, найти посты пользователя и под конец не забыть поменять значение для проперти, что пользователь авторизовался в такое-то время. Разницу чувствуете?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:58
Пожаловаться

Эти методы не проверяют ничего, что стоит выше, они просто изменяют состояние модели, они не дергают сервисы, не работают с сессией и так далее. Они работают с данными, вот и все. Это общий вид сеттеров, которые собраны в один конкретный юзкейс. Когда бизнес говорит вам, что пост нужно опубликовать, он понимает именно "Опубликовать", а не "установи сначала заголовок, потом статью, потом поменяй слаг, обнови время и статус".

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:25
Пожаловаться

Состояние - это как раз и есть совокупность значений свойств объекта. Изменение состояния - это изменение значения и не более. "Опубликовать" - это действие, которое помимо смены состояния совершает действия, как-то сохранение в базу. Когда бизнес говорит "опубликовать" это именно и значит: установи заголовок, статью, поменяй слаг, поменяй статус и сохрани в базу. Грубо говоря - состояние меняют конкретные сеттеры, а бизнес делают общие методы.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:26
Пожаловаться

У меня publish() не сохраняет в базу, он просто сеттит поля. В базу сохраняет репозиторий.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:57
Пожаловаться

Вот и получается, что хрен поймёшь, что делает ваш метод publish(), зато любому дураку понятно, что делает $post->state = PUBLISHED

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 16:01
Пожаловаться

Ни хрен поймешь, у него более чем говорящий интерфейс: опубликовать. Для публикации дай мне то, это и это. Если оно все валидно, я опубликую пост. А в случае с сеттерами ВАМ надо думать, что нужно, чтобы пост можно было опубликовать.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 18:24
Пожаловаться

Зачем думать? Сеттер не предназначен для того, чтобы что-то публиковать, сеттер изменяет состояние объекта, сама процедура публикации - это бизнес-логика и с сеттерами никак не связана.

изменено 26 сентября в 06:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 18:55
Пожаловаться

Вот publish() - это и есть бизнес-логика. А то, что предлагаете вы, это постепенное собирание объекта из сеттеров. Вы вспомните, как изначально придумывался ООП и что вообще такое объекты. Объекты, как и реальные предметы из внешнего мира, не создаются без размеров и форм или не собираются постепенно, они сразу появляются (__construct()) со всеми свойствами в валидном состоянии. Я говорю, что валидность легко контролируется через конструктор. Если какой-то сервис требует контейнер, о чем вы не знаете, и если этот контейнер задается через setContainer(), какой есть шанс его не вызвать? Такой же шанс у вас или у вашего коллеги или у тех, кто будет использовать ваш класс, не засетить какое-то обязательное свойство. Ну и еще раз, изменить что-либо .- это не сет. Люди в жизни даже так не говорят. Все, что заменяется другим, изменяется, а не устанавливается.

изменено 26 сентября в 06:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 19:28
Пожаловаться

publish() не отменяет использование сеттеров, он как раз и является тем методом, который собирает объект, а сеттеры в процессе этого неявно вызываются, когда вы делаете процедуру присвоения через оператор "=" внутри publish(). У меня есть модель пользователя, загруженная из базы и есть запрос на изменение имени пользователя с новым значением, какой шанс забыть в экшене написать

$user->name = $newName;
$user->update();

Такой шанс - 0.0% Вы в курсе, что оператор "=" оператор присвоения, а операция называется присвоение/установка/применение/изменение. У полей класса всегда есть значение по-умолчанию - null и когда вы устанавливаете новое значение вы в любом случае изменяете то что было null на другое новое значение. Таким образом вы сами всегда и везде должны перестать использовать оператор "=", потому что он так или иначе изменяет начальное значение чего либо. Вы сами себе противоречите и вдаетесь в дебри, где на самом деле всё просто и понятно.

изменено 26 сентября в 07:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 19:34
Пожаловаться

Мы о разном. Я про восприятие, а вы мне про то, что равно - это равно. Я и без вас это знаю. Мы ни к чему не придем, давайте закончим. Спасибо.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 19:41
Пожаловаться

Я о программировании, а вы о субъективном восприятии. Я говорю, что

$value = 1;
$value =+ 2;

а вы мне говорите - это не установка, это изменение, поэтому нужно

$value = 1;
add($value, 2);

Я говорю нафига, ведь $value =+ 2 более выразительно, вы мне, ну я так воспринимаю, а вдруг кто-нибудь забудет написать $value =+ 2; и всё упадёт.

изменено 26 сентября в 07:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:10
Пожаловаться

Или что насчет зависимостей между свойствами? Есть кейсы, когда свойство не может принять какое-либо значение или диапазон значений, если другое свойство класса имеет определенное значение. Или же при установке одно значения, нужно менять обязательно другое. Как пользователь вашего класса обо всем это будет знать? Идея в том, чтобы предоставлять более явный интерфейс по работе с классом, а не обычные сеттеры, из которых ничего непонятно.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:36
Пожаловаться

Вы путаете сеттер с конструктором. Сеттер всего лишь устанавливает значение (как довесок, проверять его на валидность), всю остальную логику выносят в другие места: конструкторы, отдельные методы, например, edit(...$values), в котором все эти зависимости проверяются и только потом устанавливаются нужные значения. Инкапсуляция, вам не нужно знать, как оно там работает, вам достаточно знать, что полю с таким-то именем нужно присвоить какое-то значение, а что там подкатом делается никого не интересует. Вас же не интересует, что делает Exception::getTrace() у себя внутрях, вам достаточно того, что он возвращает массив определенной структуры.

изменено 26 сентября в 02:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:39
Пожаловаться

Я не путаю. Я приверженец конструкторов как наиболее надежного способа создать объект в валидном состоянии и ничего не забыть. Плюс сеттер не описывает, что реально происходит. Ваш возраст с каждым годом устанавливается заново или меняется? Примерно так же надо поступать и с объектами.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:15
Пожаловаться

Ещё как путаете. Т.е. вы, чтобы поменять значение у проперти объекта создаете новый объект? Вам кровь из носу важно знать что происходит при $post->title = "Title"? Именно меняется, но в ваших словах Я приверженец конструкторов как наиболее надежного способа создать объект в валидном состоянии и ничего не забыть, утверждается, что я должен рождаться каждый год с новым значением возраста, т.к. только рождение - конструктор способен ничего не забыть и надежно родить меня таким, каким я должен быть.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:23
Пожаловаться

Создать - конструктор, изменить - чейнджеры. Что тут непонятного? Причем здесь "каждый раз заново создавать объект"? Объект создается один раз и заполняется через конструктор/именованный конструктор. Дальше объект меняется, а не устанавливается.

  1. Создание
$user = User::fromGoogle($clientId, $email);
  1. Изменение
$user->changeName($name);

...

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

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:37
Пожаловаться

А какая разница $user->name = $name;, нафига мне знать, что помимо свойства name есть ещё специальный метод changeName()? Собственно ваш changeName() по сути является сеттером, потому что делает тоже самое $this->name = $name; только вызываете его явно, вместо обычного $user->name = $name; И откуда мне знать, что этот ваш метод меняет именно нужное поле name, а не какое-то другое? И я точно не знаю, что ещё делает метод changeName() кроме того, что меняет значение свойства, может оно ещё пытается сохранить всё это в базу, а мне оно не нужно. Где тут контроль и надежность? Нет, зато можно быть на все 100% уверенным, что сеттер $user->name = $name; изменит только значение свойства и ничего более, потому что это явно следует из написанного кода.

изменено 26 сентября в 03:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:43
Пожаловаться

Потому что changeName() отражает то, что вы делаете, а сеттер - нет? Контроль и надежность в том, что теперь у вас модель, а не тупая структура данных, в которой легко можно похерить изменение какого-то свойства, если он должно меняться при смене другого. И нет, это не AR и сущность сама себя в базу не запишет. В общем, давайте заканчивать. Вы предпочитаете анемичные модели, я - жирные.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:55
Пожаловаться

Потому что changeName() отражает то, что вы делаете, а сеттер Это как, т.е. $user->name = $name; для вас не отражает ничего конкретного и надежного? Хм, а по мне ваш метод changeName() не отражает того, что я ожидаю. Ну есть метод changeName() он что-то там типа меняет, а что он ещё делает я ручаться не могу, может он ещё пытается залезть в базу и сохранить объект с новым именем. Ну не вызвал я ваш changeName() для объекта, ну похерил изменения свойства name, чем это отличается от того, что я забыл написать $user->name = $name; и тоже похерил изменения?

Я предпочитаю то, что гибко, читаемо, красиво и самое главное - легко поддерживается, а чтобы ваш монолит понять, а уж не дай бог что-то в нём поменять, чтобы не дай бог ничего не сломалось, нужно не одно поле бамбука выкурить.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:46
Пожаловаться

и при наследовании обалдейте что вы не можете спереди добавить зависимости

Если для вас единственный способ расширить класс - это наследование, то да, у вас будут проблемы.

изменено 26 сентября в 01:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:49
Пожаловаться

А, ну конечно, оберни его в декоратор и нарушай сразу ВСЕ принципы солида. Меняй местами параметры. Меняй их количество. Выпускай говнопакеты. А нет, я забыл. В тренде - "не выпускай пакеты, юзай готовое".

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

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:40
Пожаловаться

А что у нас все поля объекта обязательно должны устанавливаться внутри класса? А с какого рожна класс пользователя, в данном случае, должен что-то знать о гитхабе и гугле?

А изменение дефолтного значения поля - это установка или редактирование? Собственно, ну я понимаю редактирование, например, модели, но обычно значение в поле устанавливается или присваивается, что делается оператором "=", т.е. "сеттер" как раз и занимается тем, что присваивает/устанавливает значение в указанное поле объекта, как бы редактированием тут мало пахнет. Не понимаю, чем вам сеттеры с геттерами не угодили, не нравится, объявляйте тогда всё в паблик.

А что там будет в 7.4 на счёт геттеров и сеттеров, как-то пропустил этот момент?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:43
Пожаловаться
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:55
Пожаловаться

Ну, типизированные поля и сплошной паблик, что там с геттерами, почему они будут не нужны? Собственно, ваш отсыл к 7.4 говорит, что любой "программер" может дернуть что-то не то или установить что-то не то и получить невалидный стейт, пАтАмуШтА там сплошной паблик, делай что хочешь.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:02
Пожаловаться

Что вам мешает использовать вместо примитивов (string, int) классы (Bio, User), которые уже заполнять через конструктор?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:14
Пожаловаться

А зачем мне примитивы string, int оборачивать в классы? На кой ляд, например, при загрузке из базы все эти примитивы разворачивать в классы, а потом при сохранении схлопывать обратно в примитивы? Это ж сколько памяти и ресурсов/времени будет занимать.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:18
Пожаловаться

Затем, что какие-то данные могут всегда меняться вместе. Это не панацея, это один из выходов. Короче, идея простая: контроль доступа и именование методов так, чтобы они показывали, что они делают. Сеттеры не показывают.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:56
Пожаловаться

Сеттер - это инкапсуляция, он не обязан ничего вам показывать, собственно это не ваше дело, а сеттера и программиста, который записал в сеттер какую-то логику, ваше дело только присваивать значения для проперти объектов.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 15:01
Пожаловаться

Хорошо

Первый вариант

$post
   ->setTitle($title)
   ->setBody($body)
   ->setSlug($slug)
   ->setUser($user)
   ->setCreateDate($date)
...

Второй вариант

$post = Post::fromDraft($title, $body, $slug, $user, new \DateTimeImmutable)

В каком варианте есть шанс забыть установить время или юзера? И что будет, когда вы это сделать забудете, если в базе стоит not null на поле с юзером и временем?

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:45
Пожаловаться

там всё. забей

пиши мне лучше, он поймёт. или уже понял, что никогда не поймёт

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:57
Пожаловаться

Да как-то печально просто, а потом все на пых гонят, что мол г**но, а не язык.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:03
Пожаловаться

Причем здесь пых говно, печаль? С вами разговаривают, а не травлю устраивают.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:09
Пожаловаться

Когда разговаривают, звучат вопросы "что вы имеете в виду", "вы уверены?", "почему вы так считаете".

У вас звучат изречения "так надо потому что вы лохи", только вы не произносите слово лох. Признак "мы не переходили на ты" это не речевой оборот, он вас выдал. Уважения к себе требуют те, кто считает себя выше всех.

С Вами разговор невозможен пока вы ведёте себя так, выражая свои унижения в "невиноватой" манере - типа "я ничего такого не говорил, это вы сами обиделись". Вы могли подумать о том, что я, услышав это, могу обидется, и сказать иначе. Но вы думаете что есть некое "быдло" для которого незачем стараться. И валите как есть, давите, унижаете, выступаете.

Заинтересуйтесь желанием помочь и кому-нибудь написать, обговорить, поделится. Хотя я напомню - вам то это незачем. Вы уже итак официально пиздат, как думает Ваш мозг.

изменено 26 сентября в 02:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 11:43
Пожаловаться

Первое. Про тавтологию интерфейсов. Что по поводу PSR Naming Conventions? https://www.php-fig.org/bylaws/psr-naming-conventions/

Второе. Как же принцип, что имя функции должно быть глаголом? id() что? create, generate, get, set

Ответить
2 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 12:27
Пожаловаться

PSR - это рекомендации, а не обязательства. Многие из правил абсолютно дурацкие, как этот пример с добавлением суффиксов. У вас проблемы с именованием, а не суффиксами.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:35
Пожаловаться

Когда то был стандарт СССР. За несоблюдение рекомендаций сажали, расстреливали и увозили. Знаешь почему? Потому что нужна была унификация. Чтобы в Сибири и в Москве делали одинаковые стулья/станки/патроны/программы с взаимозаменяемыми модулями.

Прошу, не обижайся на меня, я не желаю тебе зла и не обсираю твой труд. Я говорю что перед тем как писать статью, попробуй найти пару десятков разработчиков в телеграме и спросить, что они думают.

А не сначала писать статью, чтобы все о тебе узнали, а потом "вы все не правы и идиоты". Я так говорю потому что сам так делал и тоже обжегся.

изменено 26 сентября в 01:09
Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:36
Пожаловаться

Во-первых, мы не переходили на ты. Во-вторых, статью писал не я, откройте глаза.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:41
Пожаловаться

Ах ты к себе уважения требуешь, равенство значит не твой конёк. Ты такой здесь пришел миссия. Иди своей дорогой, дольше проживешь.

Ответить
0 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 13:43
Пожаловаться

Вы на вы - это не равенство? Что вы несете. Я вам выкал, вы мне тыкаете, где тут равенство?

Ответить
1 0
Показать ветку
ответил на комментарий оставил(а) комментарий 26 сентября в 14:10
Пожаловаться

Это означает забить на титулы в принципе. Не позволять вешать на вас ярлык типа "а это тот странный типок". Вы считаете "ты" ярлыком? Блин, почему?

изменено 26 сентября в 02:09
Ответить
0 0
Показать ветку

Рекомендуем