Search
Write a publication
Pull to refresh
10
0
Андрей @customlabs

Backend разработчик

Send message

Либо опечатка, либо за новость выдают старую информацию :)

Судя по всему это запись симуляции из фрейворка ROS. В статье написано, что


В течение лета в МТИ сконструируют первого такого робота. Его представят на Humanoids 2020 в июле этого года.
И да, далеко не всегда попадаются внешние библиотеки, где интересный нам функционал, который нам надо модифицировать, вынесен под интерфейс.

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


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

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

Мне жаль если вы не понимаете разницу.


Если вы посмотрите на принципы SOLID, то они все про ограничения, а ограничения вводятся не от хорошей жизни. И если используемый подход обеспечивает соблюдение этих ограничений «из коробки», то ничего плохого в этом уж точно нет.

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


Вы читали мою мысль? Давайте на примере, библиотеки с http клиентом и набором классов (сервисов)


final class SomeHttpClient {
    // some implementation

   public function request(/* Some arguments */) {
       // some implementation
   }
}

final class SomeService {
   public function __construct(SomeHttpClient $httpClient) {
      // ...
   }

   public function getComment(int $authorId) {
   }

   // etc ...
}

Так, вот я говорю, что таким кодом нельзя пользоваться. Так как он полностью закрывает возможность его расширения, в том числе в рамках SOLID.


В коде есть метод который в аргументе принимает final класс. То есть закрыта даже композиция.


Допустим мы подключили эту библиотеку в проект. А через некоторое время приходит бизнес требование. К примеру: журналировать все внешние обращения, мерить время при внешних обращениях и т.д.


Самое простое и правильное решить это композицией над SomeHttpClient


public function ProfiledSomeHttpClient {
   private SomeHttpClient $httpClient;

   public function __constuct(SomeHttpClient $httpClient) {
       $this->httpClient = $httpClient;
   }

   public function  request(/* Some arguments */) {
       // Фиксируем время старта
       // ...

      $result = $this->httpClient->request(...$arguments);

      // Замеряем итоговое время и что то с ним делаем
       // ...
      return $result;
   }
}

И в коде где конфигурируется SomeService инжектим туда нашу обертку.


$profiledHttpClient = new ProfiledSomeHttpClient(
    new SomeHttpClient()
);

$service = new SomeService($profiledHttpClient);

Все задача решена в лучших традициях SOLID.


НО!!! Нет, у нас же требуется передать в SomeService именно финальный HttpClient. То есть жестко фиксированная реализация. И вместо подобного решения, все, что нам остается, это во всех местах в проекте, где вызывается методы из someService — там мерить время. Это полный ужас.


Именно поэтому я написал, да можно финалить классы, НО, если этот финальный класс где-то ожидается как аргумент метода иного класса, ВЫ обязаны закрыть его контрактом и ожидать именно контракт!


В итоге должно быть:


interface SomeHttpClientInterface {
    public function request(/* Some arguments */) 
}

final class SomeHttpClient implements SomeHttpClientInterface {
    // some implementation

   public function request(/* Some arguments */) {
       // some implementation
   }
}

final class SomeService {
   public function __construct(SomeHttpClientInterface $httpClient) {
      // ...
   }

   public function getComment(int $authorId) {
   }

   // etc ...
}

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

Он также применим и к интерфейсам.
В случае описываемом в статье, если у нас финальный класс, наследовать нельзя, интерфейса нет. То и принцип не применим. То есть из SOLID можно вычеркнуть одну букву.

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

Пример с Logger я привел, как самый очевидный, никто не будет спорить, что он имеет множество реализаций.

Но даже DTO объекты в рамках какой-либо vendor библиотеки, не могут быть финальными без контракта, если хотя бы один публичный метод любого класса этой библиотеки принимает его в качестве аргумента. Принцип подстановки Барбары Лисков. Вы сами говорили о SOLID, но при этом этот принцип полностью вычеркиваете. Финальный класс без контракта требуемый в качестве аргумента, это запрещение использования данного принципа. Это запрещение разработчику который хочет воспользоваться вашим кодом, расширения его частей (даже через композицию), для его задач в рамках его программной инфрастуктуры.

Пример из жизни, это многострадальный usb разъем и то, что было до него. А был зоопарк разъемов. Этот зоопарк разъемов — это ваши финальные классы без контракта. Производитель выпускал оборудование с фиксированной реализацией иных аксессуаров к нему, только под своей маркой.

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

Очень длинная статья. Но в итоговом виде, порядок я бы поменял, так как в случае публичного api не имеет смысла вообще выпускать final классы, без интерфейсов. Поэтому, если хочешь написать final — будь добр одновременно с этим описать и контракт в виде интерфейса.

Очень часто встречаю код, коллег, где они видимо прочитали про принцип закрытости и низкого зацепления, и все свои классы всегда объявляют финальными (забывая выделять контракт). И получаем в коде вот такое:

final class Logger { /* какая-то реализация */ }


final class SomeService { 
    public function setLogger(Logger $logger);
}


Специально взял пример с логером у которого может быть множество реализаций, но контракт един (см. psr/logger)

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

Поэтому повторюсь, финальные классы без контрактов — это больше антипатерн, аля Singletone. Финальные классы нужно использовать всегда совместно с контрактом. Тогда код будет легко переиспользовать, он будет менее сцеплен, и более гибок.

Также стоит добавить и ложку дегтя про final. Не все библиотеки и фрейморки умеют корректно работать с таким классами, хорошим примером является symfony lazy сервис, symfony не сможет сделать сервис lazy если класс сервиса финальный.
  • Косов Андрей Сергеевич архитектор и ведущий backend разработчик
Эм… я в основном код смотрел.
Таких падений, может быть множество, при преодолении долины отчаяния.
Соглашусь, что сейчас у детей море и телега развлечений, особенно игр на планшетах и т.д. Их в принципе на учебу (любую) мотивировать сложно.

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

К программированию, я уже в старших классах пришел, когда у родителей на работе и в школе добрался до компа. По сравнению с радиоэлектроникой это был творческий рай. Электроника, в те годы 90е, это месяцы поиска нужных компонентов, схем и т.д. и когда собираешь и включаешь — а оно не работает, это недели поиска причин и новых компонентов. А тут, написал код, запустил — увидел, что не работает, и подсказку компилятора, поправил и запустил снова, сокращение времени итерации на порядок, твори не хочу. Это, сделало моё будуще.

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

Тем не менее читать было интересно. Я часто задумываюсь, как «показать» мир кодинга, своему ребенку, или супруге. Учитель из меня плохой, ввиду того, что примеры мне приходят более сложные, чем то, с чего стоит начинать и проекты или объяснения.

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

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

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

Успехов, ему.
Я вернул долг, вроде как 0,5 в итоге задолжал
1

Information

Rating
Does not participate
Registered
Activity