Объекты и структуры данных. Разбор книги "Чистый Код" Роберта Мартина #2
Привет, друзья. Это подкаст Организованное программирование. Я его ведущий Кирилл Макевнин. И сегодня у нас вторая часть разбора книги, а, Роберта Мартина Чистый код. И мы будем говорить про абстракции, про границы, про структуры данных и объекты. Это шестая глава в его книжке. А мы перескочили с функцией, потому что в предыдущий раз мы разбирали функции. Дальше там идёт комментарии, отступы и всякие такие вещи, которые, на самом деле, я думаю, вы и без меня разберётесь, как правильно с этим быть. А вот эта глава, она имеет довольно большое значение. Там очень много вещей, которые надо обсудить. И при этом она суперкороткая, то есть там буквально 10 страничек, но на эти 10 страничек, видимо, мы тут наговорим минимум на целый час, а то может быть и больше. И я думаю, что количество споров, комментариев на тему того, что надо по-другому или так правильно, так неправильно, будет, наверное, даже ещё больше, чем в предыдущем видео, а в котором тоже, в принципе, этого много было. Во время подготовки к этому видео в начале Мартин сразу даёт интересный пример, который мы будем сейчас разбирать, но я хочу сказать о том, что я сделал. Значит, я увидел этот пример, он показывал, какой интерфейс нужно сделать для точки. И я подумал, что это интересная штука м спросить у подписчиков моего канала на тему того, что они об этом думают. Соответственно, я показал тот интерфейс, который предлагает Мартин, сбросил туда и сказал, что вы думаете об этом. к чему у вас есть вопросы в плане того, написал ли он правильно, его пример, его предложение - это то, что нужно или нет. И мнения, конечно, очень разделились, но там был один очень интересный момент, связанный с побочными эффектами, который в реальности показывает о том, что, ну, с моей точки зрения, вот общее понимание того, какие вещи действительно важны, как это влияют на код, они требуют как бы пояснения. Сразу скажу, у меня есть некое представление о том, что важно, что неважно и куда нужно прилагать усилия, на что обращать внимание, что больше всего влияет на архитектуру. И к этому я пришёл довольно давно. Это не только моё мнение, да? То есть я там периодически, когда это всё показываю, привожу примеры. А, но тут нужно сказать следующее. Всё-таки учу я людей тоже довольно много. И я знаю, что понимать те вещи, о которых вот я рассказываю, даже вот в том видео или в этом я видео буду, немножко сложно и даже вызывает часто отторжение, когда люди привыкли к тому, о чём мы сегодня говорим. Ну, то есть начитавшись Мартина солида чистого кода, восприятие как бы реальности идёт вот через такие конструкции. Я всеми силами пытаюсь это немножко изменить. Я надеюсь, это хотя бы чуть-чуть получается, но прекрасно понимаю комментарии тех, кто пишет, что всё это неважно. Важно, какой там интерфейс. И смотрите, единая ответственность. У меня был такой период в жизни, когда я всем этим сильно увлекался. Ещё раз просто напомню, самый большой сдвиг и понимание, что по-настоящему важно, что влияет на код и как это вообще всё работает, оно пришло только после того, как я поработал с функциональными языками. То есть те вещи, о которых я рассказываю, к сожалению, вот так вот просто послушать меня, например, и сказать: "А, ну да, наверное, это так". Несмотря даже на то, что я сотрясаю воздух, крайне сложно. И моя большая рекомендация для тех, кто действительно хочет попробовать что-то новое, расширить свои горизонты, попробовать всё-таки попрограммировать на таких языках, потому что там это явно вынесено и проговорено. Вы сразу просто это почувствуете и дальше будет легче сравнивать и сопоставлять. Если желания такого нет, ну, продолжайте писать, как обычно, свои, так сказать, замечания, предположения и то, что по-настоящему важно с вашей точки зрения. Тут я ничего сделать не могу, но всегда за радостный срач в комментариях. Это это, ребята, с удовольствием. Итак, давайте, а, приступать к скринкасту. А значит, сегодня мы разбираем объекты, абстракции, границы и всё в этом духе. Вот такой вот чувак здесь изображён. Я подозреваю, что он даже с кого-то это рисовалось, кто, наверное, прочитал его книгу и так теперь смотрит на мир. Итак, шестая глава. Объекты и структуры данных. Существует везкая причина для ограничения доступа к переменнам в программах. Мы не хотим, чтобы другие программисты зависели от них. Мы хотим иметь возможность свободу менять тип и реализацию этих переменных так, как считаем нужным. Тогда почему многие программисты автоматически включают свои объекты, методы чтения записи, представляя другим приватным переменнам, так, словно они являются открытыми. Уже, в принципе, можно пообсуждать, но давайте чуть дальше пойдём, чтобы делать это на конкретном примере. Давайте сравним между собой листинги. А значит, один листинг и второй. И в обоих случаях код представляет собой точку недикартовой плоскости. Однако в одном случае реализация открыта, а в другом она полностью скрыта от внешнего пользователя. А значит, мы мотаем чуть ниже и видим тут два листинга. Первый - это конкретная реализация точки, э, где есть xy. И второе - это абстрактная реализация point, где есть xy с одной стороны, а с другой стороны, во-первых, это интерфейс. Во-вторых, а здесь нету имплементации в том смысле, что предоставляется некий интерфейс, который позволяет одновременно работать с точкой как в одной системе координат, так и в другой системе координат. Дальше написано: "Элегантность решения из листинга 62.2 заключается в том, что внешний пользователь не знает, какие координаты использованы в реализации: прямоугольные или полярные, а может ещё какие-нибудь. Тем не менее интерфейс, безусловно, напоминает структуру данных, однако представляет нечто большее, чем обычную структуру. Его метод устанавливать политику доступа. Пользователь может читать значение координат, независимо друг друга. Но присваивание должно выполняться одновременно в режиме атомарной операции. Он приводит пример, что у него есть два сета. Один, который устанавливает это через полярные координаты, другой через обычный XY, декартовую систему координат. А, и о чём он здесь говорит? Он показывает вот этот point и говорит, что как будто бы пользователь вынужден работать с этими координатами независимо. Он фактически просто даёт там некий удобный механизм работы с ним. При этом внутри это может быть вообще сохранено и упаковано в какой-то совершенно невообразимый формат, который мы может так вот без полулитра-то и не поймём. И да, в этом как бы сила абстракции тогда, когда нам нужны такие абстракции, тогда, когда мы об этом говорим. А, в принципе, на этом можно остановиться. Дальше он уже переходит к другим примерам. В принципе, здесь уже очень много чего хочется сказать. А значит, давайте по порядку. Любая абстракция, она призвана для того, чтобы решать какую-то реальную сложность, проблему, которая у нас возникла, начиная от там производительности, заканчивая действительно тем, что у нас много каких-то, может быть, элементов, и вокруг них мы строим что-то более общее для работы. Смотрите, он здесь пишет: "Вот есть полярные координаты, да? И это классно, потому что мы можем и так, и так, а есть точка". Каждый раз, когда разбираешь вот подобные примеры, люди, как правило, предъявляют претензию, что а в каком вообще мы говорим, о каком мы говорим вообще кейсе, а какие условия у задачи и так далее. Но дело в том, что если мы начнём предъявлять те же самые условия для Мартина, то возникнет вопросов ещё больше. Во-первых, почему мы в принципе задумались о полярных координатах? То есть он это, понимаете, он так подаёт это, что это типа лучше. Смотрите, у нас вот сразу есть так, сразу абстрактно, сразу мы получаем такую возможность. Ну, единственное ещё добавлю, конечно, в теории он мог бы сказать: "Смотрите, мы можем добавить такую, э, часть в интерфейс, и для этого нам не придётся менять, например, код по всей программе". И в этом есть определённый плюс, это правда, но он показывает это сразу. Он говорит: "Вот смотрите, вот классный интерфейс, вот какая сразу появилась возможность". То есть он уже в своём же тексте прямо говорит: "Это лучше, чем вот предыдущее. Не в конкретном сценарии, а вообще в принципе, потому что таким образом мы получаем именно абстракцию". И вот здесь возникает, собственно, проблема: а в какой реальной жизни кто-нибудь видел хоть раз, чтобы это вообще было нужно? То есть я честно вам скажу, что специально после того, как опубликовал тот пост на тему того, какой интерфейс лучше, я прямо пошёл, полез, э, во-первых, с чат GPT поговорил, спросил у неё, как обычно эти интерфейсы делаются, а что в них входит. Я открыл исходники некоторых приложений на Гитхабе, которых там довольно много. Элидро, например, есть с одним из разработчиков, который, кстати, у меня был даже подкаст однажды, а, я открыл всё это посмотреть. И знаете, что я там увидел? Ни в одном из этих мест нигде вообще никак не нужно а-э использовать полярные координаты. Их просто нигде нет. Я могу себе представить, что это в каких-то ситуациях действительно отдельно нужно, но вот чтобы просто люди это использовали в каких-то стандартных задачах для создания там, например, редакторов, а, ну, как будто бы этого вообще нету. Банально открываешь экскалидро. И что ты видишь? что точка - это просто тип массив из двух элементов и всё остальное накручено поверх него. И то же самое касается практически всего остального. Если я ошибаюсь, напишите об этом. Но абстрагирование для того, чтобы иметь возможность, а давайте сейчас мы тут поменяем декартовую систему на систему полярных координат. Ну, честно говоря, это настолько притянуто за уши, что я даже прямо не знаю. Мне это, знаете, что напоминает? Это мне напоминает, когда вот ОРМ сделали и история про то, что смотрите, мы теперь абстрагировали базу и можем поменять одну базу на другую, люди, которые реально с этим работали и которые пытались или примерно представляют, как устроено, они, конечно, сейчас нервно засмеялись, потому что понимают, что это сказки, которые к реальности не имеют никакого отношения. Я не говорю о том, что здесь, а, ситуация прямо та же самая. Понятно, точки явно проще, чем абстракция над базой данных и база данных вообще. Но даже в этом случае в как зачем нам в принципе может это понадобиться? То есть с точки зрения представления для пользователя может быть да, а то, что он тут начнёт задавать в полярных координатах, ну это очень маловероятный сценарий. То есть, ну, как минимум можно сказать, что подобная задача может возникнуть только в особых кейсах. То есть, её уже нельзя подавать как некую общую концепцию. Ну, а как максимум сказать, что, ну, это вообще с таким же успехом можно было ещё что-нибудь вообще совершеннолевое придумать и говорить, что как это классно. То есть с этой точки зрения это явно овенженеринг. Можно со мной не соглашаться, да, но для меня это овежиниринг. Это в первую очередь. Второе интересное, ну, вообще давайте так, сам интерфейс, то есть тут у меня ещё целая пачка претензий, но вот я хочу в целом про сам интерфейс поговорить. Он как бы подаёт это как вот прямо нужно нам абстракцию не для того, чтобы гетеры сетеры сделать, да? именно для того, чтобы абстрагироваться от структуры данных. Несмотря на то, что теоретически звучит так, что как будто да, но я мало себе могу представить, что pointт, во-первых, заменяется один на другой и что какая-то другая особо может быть имплементация, которая будет напрямую использоваться в коде. Смотрите, в чём дело. Дело в том, что он часто м создаёт дихотомии, которых не существует, потому что вот типа раз так можно сделать, значит мы так будем делать. Например, он говорит: "Вот смотрите, у нас есть pointт, в нём есть тупо два свойства". Это значит, что мы напрямую уходим в эти свойства. Вот просто при любом раскладе. Он так пишет, как будто просто по-другому не бывает. Что бывает в реальной жизни? Как в реальной жизни пишут код? Опять же, несмотря на то, что здесь написано на Джаве, он рассказывает про все языки. Это можно у него и в блоге посмотреть, это можно посмотреть во всех спорах, короче, везде можно посмотреть и понять, что, несмотря на то, что примерно на Джаве эта книга подаётся, и он это постоянно декларирует как штука для всех, что все это пишут, что это универсальные правила и так далее. Ну, за исключением некоторых, да, он рассказывает такую вещь, что типа вот, мол, смотрите, вот point, значит, всё, напрямую его используют, по-другому быть не может, и вот такие вот проблемы возникают. Но в реальности это не так, потому что если посмотреть на тот же я калидро буду больше пример приводить, потому что именно его код я больше всего смотрел, хотя и в других тоже, да, вы можете это посмотреть. М нет такого, что раз его определили тип как массив из двух элементов, что к нему прямо везде там напрямую обращаются. То есть у тебя если необходимо, например, написать какую-то функцию, которая делает преобразование, например, там складывает эти точки, какие-то перемещения делает, это всё делается просто на отдельных функциях, которые вокруг этого типа построены. И нет такого, что есть обязательно вот раз у нас нет абстракций, значит, мы напрямую с ними работаем. Это скорее история про восприятие как бы конкретных сущностей, ну, в рамках Джавы, потому что у нас абстракция - это как бы практически равно интерфейс, когда говорят про Java. В других языках это часто не так, и даже в Джаве это может быть не так. То есть можно создать класспоит, а при этом ходить и обращаться статическими методами к нему. А это, конечно, не диаматично, сразу скажут: "Фу-фу-фу, как так можно?" Но это не говорит о том, что, во-первых, этого нельзя сделать, а, во-вторых, что в других языках не бывает по-другому. Например, в том же Джаваскрипте или Тайpeскрипте, да, где вот всё-таки такое более как бы O, близкое к Джаве, ну нет там такого, что люди на каждый чих-класс создают или интерфейс, а там часто создают просто типы и вокруг них что-то строит. И никто с не прибегает и не говорит, что а кошмар, переписываем весь код. Если посмотрите вообще в принципе на код в этом смысле в тайпскрипте, то с позиции, которую рассказывает Мартин, ну там просто надо всё выкидывать и вообще весь код считать неправильным, потому что там как раз так разделено. Поэтому я здесь хочу как бы отделить зёрна от плёве. То есть концептуально про абстракции он всё правильно говорит. Он делает акцент на очень важные вещи. Другой вопрос, что это не обязательно конструкция языка. То есть это не значит, что у вас должен быть обязательный интерфейс, в который вы там всё это засунули, да? Вы можете просто, если у вас в языке принято, что существуют отдельные функции, они, собственно, и создают эту абстракцию. Вы просто к этому типу не обращаетесь напрямую, но при этом тип у вас есть. Вот это одна часть. Вторая часть с преобразованиями, как мы уже говорили, если у вас вы строите редактор, вам необходимость переключения по координатам просто банально не нужна. И сама идея того, что вот мы делаем какую-то абстракцию, которую мы совсем прямо это скрываем, она просто как будто бы тоже особо не нужна. Ну нет никакой проблемы с этим, если у вас просто это массив из двух элементов. Теоретически вы, конечно, можете захотить это отрефакторить, но вы отрефакторите всё равно очень небольшой объём методов, которые будут непосредственно их там извлекать и как-то преобразовывать. То есть нет такого, что напрямую в любом месте кода просто взяли такие начали оперировать ими. Понятно? Это вопрос уже того, как люди пишут код. Не можно, конечно, сказать, что, да, Java в этом плане, ну, не то, что Java, а такой подход немножко больше защищает, потому что если вы так всё построили, то как будто нельзя снаружи обратиться. Но там другая проблема есть. Это вот постоянное создание объектов, что может приводить к большим проблемам с производительностью. Поэтому там есть такая история, что подходы с выделением абстракции - это тоже не очень однозначная вещь, когда мы говорим про производительность. Есть даже большие видео, я думаю, что ссылку приложу в комментариях к этому видео, где ребята из игровой индустрии, кстати, довольно известные и довольно успешные в том, что они делают. То есть там создатели движков, в том числе и создатели языков есть. Они прямо разбирают мартиновские все эти выкладки с точки зрения производительности. У них прямо упорно это о том, что огромное количество правил, идей, которые он привносит, они практически противоречат э производительному коду. И в отличие от меня, они очень жёстко проходятся по этой штуке. Я в этом плане, э, наоборот, как бы историю с производительностью больше оставляю на самостоятельное, так сказать, обучение или разбор, потому что всё-таки это немножко не моя тема. Я не играми занимаюсь, не реалтаймом, а более классическая история с вебом. Поэтому там в этом плане акцент всё-таки немножко на другие вещи идёт, когда идёт сложная бизнес-логика. Поэтому в этом плане, наверное, я даже ближе к позиции Мартина, чем к позиции тех ребят. Но надо понимать, что когда мы говорим про точки, всё-таки речь идёт про, как правило, про редакторы. А редакторы - это всё-таки история, сильно отличающаяся в плане требований к тому, что происходит на холсте, когда у вас там сотни, тысячи точек, э, происходят их изменения, перемещения и так далее. Это накладывает очень много всяких разных ограничений, которые в обычном вебе просто неприменимы. Кстати, это ещё одна причина, почему с этой книжкой местами сложно, потому что, если вы посмотрите на веб, это очень сложно наложить. Там практически все примеры такие, как будто мы пишем какие-то, ну, прямо программы аля толстый клиент, типа редактор игры или ещё что-то. И именно для этих кейсов как раз-таки на Мартина больше всего и наезжает, что у него не очень всё хорошо получается в плане там производительности, да, и подходов, которые он даёт. А если мы говорим про веб, то в целом там всё это, честно говоря, большого значения не имеет. Там вообще очень по-разному можно делать. Там акцент немножко другой идёт. Хорошо, давайте поедем дальше. Я долго-долго вас к этому подводил. И, наверное, самая большая история, которая влияет больше всего на действительно код здесь, она связана не с тем, что используем мы интерфейсы или не используем, не с тем, нужны ли абстрактный класс или нет. Кстати, таких комментариев было очень много, хотя абстрактный класс - это очень очень прикладная конструкция, которая не решает никаких фундаментальных проблем. В общем, я что хотел сказать? Допустим, даже если вы используете интерфейс или не используете, ваша программа кардинально не поменяется. То есть, если мы не говорим про саботаж и специальных злодеев, которые там что-то фигарят внутри этих точек, то, по большому счёту это будет просто, может быть, не обязательно, но может быть чуть другой вызов в конкретном месте, но архитектура программы, структура программы будет абсолютно идентичная. И так или иначе вы всё равно, скорее всего, будете не напрямую к этому обращаться через какие-то там дополнительные, ну, у вас всё равно будут дополнительные какие-то методы для того, чтобы, не знаю, выполнять преобразование. То есть, очевидно, любой здравомыслящий разработчик, не джуниор там какой-нибудь, да, он будет всё равно видеть, что ага, это там дублирование, поэтому, например, вычисление чего-нибудь я вынесу в какую-то отдельную функцию. То есть у вас всё равно будет некий набор функций, который так или иначе это, а если мы имеем дело с типом, а не просто с с двумя точками, которые мы везде передаём, то у вас всё равно будет абстракция, хотите вы этого или нет. Ну, будет. И даже если вы этого слова не знаете и не применяете в своей жизни, потому что это логично просто функции писать для вычисления каких-то. И поэтому, когда мы говорим вот с этой точки зрения, мы такие: "Вот тут, значит, солид, вот этот подход не подходит, вот этот подход". Честно говоря, это всё мелочи. Несмотря на то, что основное обсуждение крутится вокруг этих вещей, и про это говорится как невероятно о чём-то важном, в реальности я считаю, что это практически не влияет на архитектуру вашей программы. И есть какие-то плюсы, минусы, там, типа, здесь чуть больше производительность, там чуть проще рефакторить, ну, допустим, да, может поменяться, но и то это теоретические такие вещи. То есть изменение внутреннего представления в данном случае - это нулевая вероятность. Просто если вы посмотрите опять же на существующее решение. А вот что здесь реально влияет и от чего у вас вообще совершенно будет построена по-другому вся логика работы программы, количество багов, которые вы будете получать, просто подходы к реализации разных фич вообще будут совершенно другими. Это, как ни странно, побочный эффект. И побочный эффект заключается в том, что у нас точка по сути является мутабельной. Это видно по наличию сеттеров. Сейчас я буду эту тему немножко размусоливать, а потому что она важна. И потому что, когда мы это начали обсуждать в комментариях в Телеграме, м очень большое количество людей, конечно же, написало: "Не понимаю, в чём здесь проблема. Всё нормально абсолютно, всё можно менять. И как бы вообще непонятно, что Кирилл придирается". А ведь это именно та причина, почему, в принципе, я занимаюсь тем, чем я занимаюсь. То есть я бы и не начал, а, наверное, вести свой канал, вообще обучать людей программирования, что-то рассказывать, если как раз с этой фундаментальной проблемой не было никаких вопросов. И я знаю, что я не переубежу большую часть этих людей, но надеюсь, что хотя бы до кого-то немножко это дойдёт. У меня даже, кстати, после этого поста появилась мысль о том, что было бы неплохо, а, придумать какой-то проект микроскопический тоже, где есть эти точки, вот эта вот вся фигня, чтобы мы смогли, а, написать это маленькое приложение и увидеть, как оно действительно работает именно с точки зрения мутабельности или иммутабельности. Давайте по порядку, потому что здесь много всего надо сказать. Начнём вот с чего. Это ещё проявилось после первого разбора. Значит, побочный эффект. Очень многие пытаются воспринимать побочный эффект как слово, ну, что-то не основное, что делает функция, допустим, или там программа. Несмотря на то, что логически с точки зрения здравого смысла мы можем так говорить, но всё-таки побочный эффект имеет очень конкретное значение в компьютерса. И это не я там придумал, да? То есть это можно посмотреть и с точки зрения систем типов определённых языков, определения функциональности, определений в разных спецификациях и так далее. То есть побочный эффект всё-таки это какое-то изменение внешнего окружения. То есть у вас функция, которая считает два числа - это просто посчитала и вывела, зависит только от входных аргументов, да? Она называется чистая, она детерминированная и не обладает побочными эффектами. Вот. А если у вас функция называется saveфайл, с точки зрения здравого смысла сохранение прямо файл - это не побочный эффект этой функции, да, потому что она так и называется, она это делает. Но с точки зрения понятия sideй-эffectкт, которое принято в компьютерса - это побочный эффект именно потому, что мы делаем что-то за рамками функции. Ну и, соответственно, появляется проблема. У нас появляется внешнее состояние, у нас появляется, а, проблемы в конкурентном доступе, у нас появляются проблемы при тестировании, появляется возможность возникновения ошибок, которые в других ситуациях просто не могут возникнуть и так далее. И, собственно, из-за этого возникает много-много много всяких сложностей и проблем. И побочный эффект, он может быть совершенно разным. То есть, с одной стороны, чаще всего это i какое-то, да, то есть это вот вывод чтения, сеть, диск, ещё что-то, но и изменение внутреннего состояния. а-а, тоже в этом смысле является, а, побочным эффектом, потому что повторные вызовы функций могут приводить к разным всяким ситуациям, да, если у нас пропадает детерминированность. Но помимо побочный эффект - это, в том числе, изменение внутреннего состояния. То есть всё, что касается изменения вне локальной области функции или метода, которого мы вызываем. А на самом деле методы, если кто знает, как это внутри устроено, что это на самом деле такое, это на самом деле обычная функция, в которой передаётся объект, структура, в зависимости от того, как мы это воспринимаем, для которого она является методом. В некоторых языках это явно, кстати, видно, например, в том же Пайthне, когда мы видим там self. Поэтому получается, мы изменяем что-то за рамками тех вычислений, которые просто делаются в рамках этой функции. Вот поэтому это тоже является побочным эффектом. Так вот, к чему это приводит и в чём у нас возникает проблема. Давайте предположим, есть такая фича, да, откат назад. Эта фича, как правило, хранит ссылки на простейшем случае, если у нас, например, точка является неизменяемой, просто вот точка один раз создали и всё, то есть нет никакого сета, то откат - это как раз добавление в стек ссылок на объекты, с которыми мы работаем. Ну и, соответственно, при откате мы просто это легко оттуда достаём и, соответственно, выполняем необходимую операцию обратную. Как только вы начинаете менять, у вас тут же всё это начинает ехать, потому что то, что было актуально сейчас, через какое-то время становится неактуальной. Вы не можете просто общаться с ссылками. У вас получается ситуация, при которой равенство точек может определяться двумя параметрами. То есть, с одной стороны равенство точек - это вроде как бы параметры одинаковые внутри них, ну, значения там XY, но вообще-то есть ещё и идентификатор некой внутренней ссылки. Одно тоже или не одно и то же? Во-первых, сама вот эта концепция, она резко усложняет понимание того, а чем вы сейчас работаете. То есть вот точка, с которой я сейчас взаимодействую, эта точка относится только к этой штуки или на самом деле ещё к чему-то. Насколько вообще её безопасно менять, если посмотреть вообще какие, в принципе, механизмы тут же поломаются при попытке их реализовывать стандартным подходом. А что такое стандартный? Ну, допустим, если бы у вас была система, не знаю, просто вычисления уравнений, да, вы просто с числами работали, ни для кого не является удивительным, что числа являются неизменяемые. ни для кого не является удивительным, что строки являются неизменяемой, кроме ранних версий Руби, в которых они всё-таки были изменяемые. Сейчас это уже не совсем так. А все базовые вещи, они всегда в этом плане неизменяемые. И даже если вы, кстати, представите, что мы оперируем деньгами, а есть ещё такой паттерн, да, деньги в программировании, что у вас была купюра 100 долларов и вы такие типа её изменили, сделали 50, вы просто, если хорошо об этом подумаете, поймёте, что это какой-то бред. И это очень сильно может влиять на происходящее, когда вы понимаете, а вам надо новый объект создавать или вы можете менять текущий. Короче, у вас возникает очень много неоднозначностей, появляется очень много проблемных мест, для которых стандартные решения перестают работать. Теряется возможность, например, кэширования. Возникает вообще ещё такая вещь, которая неоднозначная, знаете, в каком-то плане. Ну, допустим, вы такие понимаете: "О'кей, у меня в одном и том же месте две разные точки". Ну, разные, опять же, видите, надо объяснять разные в каком смысле? это одна и та же точка с точки зрения места на координатной плоскости, но мы создаём два разных объекта с одинаковыми иксом иком. И вам надо будет именно каждый раз об этом думать, что вот так надо делать или типа сет сделать. И проблема здесь ещё дополнительная в ментальной нагрузке, потому что вы очень легко можете просто банально перепутать и переиспользовать одну точку. И получаются такие ссылки, которые никто не ожидал. То есть эта штука, она позволяет, точней, мутации не отслеживаются в системы типов, и мы можем, и, точнее, будем 100% случайно делать постоянные ошибки, добавляя, э, не добавляя новые точки там, где это надо, а мутируя старые. Ну и, соответственно, а как мы будем проверять, это одно и то же или не одно и то же? Вот. Более того, я когда специально ещё дополнительно про эту проблему спрашивал чат GPT, поскольку всё-таки я не специалист в редакторах, да, и не знаю всех фич, которые там реализованы, я говорю: "Расжи, пожалуйста, мне чат GPT, дорогой ты мой, а какие потенциально возможны ошибки, если у вас объекты, которые находятся на плоскости, они мутабельные?" И он назвал что-то, какое-то гигантское количество причин, я не помню, там штук 20. Причём очень многие фичи. Я просто опять же из-за того, что не специалист, я не знаю, что там есть такая функциональность. и там это мо может вылазить наружу. В общем, а я даже все эти скрины просто приложил в комментариях для того, чтобы люди могли почитать, ознакомиться. Так вот, вы понимаете, да, о чём речь идёт. А мы ещё, кстати, не поговорили про поток и безопасность. То есть как только у вас появляется мутабельность, привет. Пока. Если, например, у вас есть отдельно вычислительный слой, отдельно есть UI слой, в которых происходят какие-то действия, то вы получаете ещё дополнительную проблему с тем, чтобы а как это всё совместить. Если посмотреть на всё это вместе, то можно, наверное, понять, ну, мне хотелось бы в это верить, что это видно, о том, что мутабельность настолько сильно влияет на всё, ну, что это перекрывает всё остальное. То есть проблема из серии ой, давайте делать абстракцию данных или не делать, в данном случае, ну, просто ничто по сравнению с той проблемой, которую мы описали, потому что она так не повлияет. Хотя, кстати, тоже вы, наверное, могли бы сказать мне: "Кирил, ну как же? Смотри, вот конкретная реализация поинта, которую показал Мартин, она же тоже мутабельная, по сути. Но дело в том, что он про это даже не говорит, это не подразумевает. То есть он подразумевает, что как будто мы раз мы создали класс, где можно напрямой это делать, мы обязательно это будем делать. Вот то же самое экскалидро, про которое я смотрел, я специально посмотрел. Там массив из двух элементов. Если в библиотеке, а там отдельно сделана библиотека для работы с поинтами, есть ли там изменения собственно этого поинта? Нет. Все методы, точнее, все функции, которые там написаны для работы с этим поинтом, всегда создают новый поинт. Вот и всё. При том, что это просто структура, массив из двух элементов, которые можно просто пойти и менять. Они этого просто не делают. Вот и всё. Опять же, можно, конечно, отдельно говорить: "Ну как же это там защита, если программист случайно что-то сделает". Я, кстати, всегда к этому отношусь так: по факту это защита от дурака. Если очень сильно хотеть, человек это всегда обойдёт. Ну, то есть технически обойти почти всегда такие вещи можно. Но по большому счёту private protected и вот подобные вещи, они не особо отчёта спасают. То есть это скорее история, что мы намерение своё говорим трогать это не надо, но это не значит, что без этого жить невозможно. Опять же, возьмите языки, в которых этого нет, типа пайна или джавоскрипта, вполне себе живут и никаких проблем не возникает, несмотря на то, что там есть типа попытки как-то там скрыть, закрыть и спрятать. Но обычно этим никто не пользуется. И понятно, что люди, которые работают над каким-то проектом, там что-то понимают, что они делают. И есть, ну, и просто ломаться банально всё начнёт, если бы все так творили такую дичь. Вот. Не говорю, что проблема совсем не существует, но просто говорю, что её значение переоценено, потому что на практике, если вы посмотрите реальные проекты, нету такого, что всё там всё там перепрятано, спрятано тыся раз. Тем более вы в той же Джаве почти всегда берёте какую-нибудь вот связь, особенно которая коллекцию, например, возвращает или вообще объект какой-то из сущности. у вас сразу появляется возможность эту сущность напрямую менять. Поэтому всё это, в принципе, очень легко обходится, если очень сильно хочется. То есть вот такую полную защиту построить нельзя. Просто таким бы языком и такой программой бы пользоваться было бы невозможно и писать код, если бы она вот так пыталась всё там защитить от и до. А речь не о том, что инкапсуляция нужна или не нужна. Просто с этой точки зрения я к тому, что от того, что мы сделали сущность, которую можно в теории поменять, это не означает, что мы её будем менять. Вот это немножко другим определяется. То есть, например, слоем функции, которая поверх неё написана, а дальше мы понимаем, что вот мы ими просто пользуемся, они там работают в иммутабельном режиме. Вот в конечном итоге, если брать этот пример, который вот так быстро разобрал Мартин и побежал дальше, как будто, ну, ничего не произошло, всё нормально, смотрите, какая прикольная штука, я считаю, что он здесь намного больше сделал зла, чем добра. Меня очень сильно удивило в комментариях. Я когда запостил это, я думал сейчас люди мне сразу скажут: "Смотри, Кирилл, побочный эффект, все дела". Комментариев, наверное, 30 было все, которые только про то, что надо вас в трактный класс, интерфейс, не интерфейс. И никто про это не сказал. И только один человек там в какой-то момент сказал: "Хм, может быть, речь про иммутабельность?" И я очень сильно удивился. То есть я думал, что сейчас это сразу определят и в принципе все скажут: "Ну всё, поехали дальше. Типа мы понятно, в чём здесь причина". Но нет. И более того, когда потом я начал расписывать, как раз вот пошли все эти споры, что типа, ну нет, там Кирилл, типа, мутабельность - это вообще непонятная проблема. Ну, мы в своей жизни с этим не сталкиваемся, мы вообще всё мутируем, всё хорошо. А вот здесь важнее гораздо там интерфейс или не интерфейс. Я надеюсь, что моя хотя бы попытка немножко это разобрать и объяснить вам хоть как-то помогла. Мне очень хочется показать это на реальных примерах, но я думаю, всё равно понятно, когда речь идёт про тот же анду, к чему это приведёт, если мы делаем эти замены или там в многопоточном режиме и так далее, и так далее. И этот кейс настолько важный и настолько невероятно серьёзный, что как будто, в принципе, всё видео можно было только ему посвятить и вообще про эти побочные эффекты, про всё это рассказывать. Такие дела. Что? Поехали дальше. Ну, единственное, наверное, что всё равно надо сказать, то, что я сейчас объяснял, это не совсем относится к абстракции в плане чего. То есть нет никакого отрицания мной абстракции, нет никакого отрицания мной сеттеров. Но конкретно, когда речь идёт про поинты, потому что всё-таки он это не рассказывает, что конкретно в моём редакторе, конкретно в моём кейсе, он это говорит абстрактно, типа вот в целом, смотрите, есть плохой интерфейс, а есть хороший интерфейс. Он создаёт именно проблему, выдавая это за истину в последнией инстанции. И те поинты, которые я поинты для обсуждения поинтов, которые я обозначил здесь, это штука, которую достаточно легко загуглить, увидеть, почитать в исходниках. Поэтому прежде чем что-то писать, попробуйте тоже проведите этот анализ. И, кстати, вот последнее, наверное, что я хочу сказать, и уже пойдём дальше. Я хотел бы здесь подчеркнуть, вот если вы посмотрите библиотеки, которые реализуют поинты в разных языках, тоже я специально с чат GPT поговорил, говорю: "Давай покажи мне стандартные, значит, библиотеки, которые это делают, как там всё устроено". И чтобы вы понимали, вот они, например, представляют пои, они представляют, как правило, эти библиотечки два поинта. И эти два поинта один в полярных координатах, а другой в декартовых координатах. Нет, один из них мутабельный, а другой иммутабельный. И, как правило, базовый, который является основным, является иммутабельным. И при необходимости можно пользоваться мутабельным. Это должно как бы вам говорить о том, что сама важность мутабельности настолько большая, что у вас, в принципе, существование в библиотеке поинтов, разделённых именно по этому признаку, ну, это очень-очень важная вещь. и дефолтная и мутабельная опять же в современных либах. То есть, если мы старые берём, там, конечно, может быть по-другому, но современные либы и если увидеть, посмотреть на прогресс, на то, как это меняется, то видно, что современные как раз приходит к иммутабельным имплементациям с необходимостью, если что, там по где-то менять, когда речь идёт про то, что мы действительно должны по производительности очень сильно выигрывать и нам надо прямо всё очень точечно ручками менять. Но надо понимать, что как только вы встаёте на эту дорожку, все те проблемы, которые мы обсуждали, вам придётся решать самим. То есть любой человек, которого есть большой опыт работы вот с в конкурентной среде, особенно с мутабельными штуками, а все знают, что самая главная проблема в таких ситуациях - это sharй. То есть это как раз изменяемое общее состояние, а это всё связано с побочными эффектами в первую очередь. Они все знают, что лучше с этим не связываться, потому что у вас сложность программирования, количество возможных сложно обнаружимых любых багов, оно просто растёт в геометрической и чуть ли не квадратичной прогрессии. Такие дела. Поехали дальше. Мотаем немножко ниже. Он резко переключается на машины и говорит: "В первом случае для получения информации о запасе топлива используются конкретные физические показатели, а во втором абстрактные проценты". Ну и приводит в пример два листинга, слава богу, очень короткие, чтобы можно было их обсудить. Значит, в одном interface vehicle и там get field and capacity in gallance и второй get gallons of gasoline. Здесь мы просто пытаемся там capacity, да, то есть это объём посмотреть нашего бака. И он говорит, что это типа, смотрите, с одной стороны, у нас вроде как бы есть методы, у нас есть интерфейс, но он говорит, что, смотрите, это не абстракция, потому что мы фактически говорим о структуре, о том, в чём это хранится внутри. И вот, значит, абстрактная реализация, где, соответственно, есть vehicle и интерфейс get percent F remaining, то есть типа мы возвращаем процент оставшегося топлива внутри. И он говорит: "В обоих примерах вторая реализация является предпочтительной". Подчеркну, он опять это начинает говорить вот без относительно кейсов. Просто вот что есть правильный подход, есть правильно, есть неправильно. Дальше мы не хотим раскрывать подробности страстни данных. Вместо этого желательно использовать представлены данные на абстрактном уровне. Задача не решается простым использованием интерфейсов. Чтобы найти лучший способ представления в объекте, необходимо серьёзно поразмыслить. Ну и понятно, что говорит: "Не надо делать объекты, в которых есть только методы чтения записей". Он потом будет использовать ещё термин ДТО, который мы тоже с вами обсудим. И вообще разницу между сущностью и обжектом мы тоже чуть попозже обсудим. Но конкретно здесь у меня тоже есть к нему претензия, потому что он опять нас запутывает. Почему? Потому что, смотрите, он приводит пример. Первый пример, который он, как говорит, плохой - это посмотреть капасити в галонах и посмотреть количество галонов оставшегося топлива. Кстати, почему в галонах он американец? Американцы вот галонами мыслят. И, честно говоря, я тоже уже голонами мыслю, потому что у тебя на заправке галоны и расход галоны, поэтому ты как бы всегда в галонах думаешь. Так что для меня это уже привы. Я и молоко в галонах покупаю, поэтому я знаю, сколько это и гораздо лучше это чувствую. Так вот, первая проблема, он берёт и тупо показывает два вообще нерелевантных примера, потому что в первом случае он показывает одни методы, которые вообще-то одну задачу решают, а во втором случае он показывает другие методы, которые решают другую задачу. То есть вернуть процент оставшегося топлива - это вообще не та же самая задача, что посмотреть вообще объём моего бака. И я не понимаю, почему он вообще это в принципе привёл. Это это прямо дурилка какая-то, потому что человек, который читает, должен сразу задаться вопросом: "О'кей, ну это вообще разные штуки. А хорошо, а если мы, допустим, хотим всё равно узнать объём нашего бензобака, как он себ это представляет в абстрактных единицах?" То есть он в чём это должен вернуть? То есть то, что он придумал какой-то другой метод и перевёл его в проценты, но проценты - это тоже очень конкретная единица. Это проценты, да, это не голоны, но, блин, проценты - это тоже единица. И в итоге получается, что он нас увёл как бы от этих методов, показал вообще другой пример и сказал, что смотрите, это правильно. Ну, вообще говоря, нет. Почему? Потому что если вам тупо нужно узнать каким-то образом объём бензобака, вам нужно его, а, во-первых, узнать. У вас для этого должен быть какой-то способ это сделать. А второе, у вас должен быть какая-то конкретная единица. вы не можете возвращать его в абстрактных единицах. Да, конечно, там может говорить о том, что иногда бывает такая ситуация, вот, например, с деньгами, особенно когда вы возвращаете, реально у вас есть некая абстракция внутри, это value object обычно, например, деньги, и вы возвращаете понятие деньги. И потом, если вы хотите уже конкретно, и вы можете с этими деньгами работать. То есть, например, даже в одной сущности деньги могут в одной валюте храниться, в другой в другой. Но за счёт того, что это абстракция деньги и над ними определены определённые операции, и когда вы пытаетесь это делать, оно внутри там само конверти. Это звучит очень классно, это реально может работать на определённых кейсах. Но, во-первых, в конце концов, вы всё равно, если захотите это вывести или понять что-то, вам надо сказать: "Сконвертируйся в конкретное представление там в виде чего-то". А, во-вторых, если просто это применять ко всему, абсолютно везде в коде, это превратится в реальности в ват, потому что у вас будет на любую, по сути, значение числовое на, ну, может быть, не числовое, но, допустим, числовое, у вас будет, по сути, абстракция, которая значительно будет усложнять свой код. То есть, например, мы действительно могли бы в таком случае, ну, я бы понял его пример, если бы он именно это приводил, он сказал: "Смотрите, надо не в галоннах возвращать, а нужно вернуть абстрактную сущность". Ну, это в данном случае уже не сущность получается. Опять же, это value object, значение, которое является объёмом. И дальше мы можем объём разных машин сравнивать, потому что у этой сущности есть объект значения object. У него, значит, есть возможность сравнивать, выполнять какие-то операции и так далее. И только в конце, когда нам надо, например, на выводе где-нибудь, мы говорим там to что-нибудь там two gonce. И он в галонах это показывает. Мм, звучит классно, реально применяется. Есть для этого кейсы и а есть прямо паттерны, опять же, паттерн мани, но это не значит, что мы это втыкаем под каждую штуку, потому что в одном месте это упростит, а в другом месте это очень сильно усложнит. И вот вот в данном случае это очень сильно усложнит. То есть если мы как бы работаем в какой-то программе, ну, скорее всего, у нас будет всё в галонах. А если опять же это тот самый момент, когда мы говорим про кейсы, да, например, мы строим софт, который действительно должен работать и в таком, и в таком режиме. Ну, даже в этом случае у нас есть два подхода. Первый подход - это, например, внутри, в самой программе полностью всё строится на американской системе, то есть галоны и так далее. И мы просто при выводе можем любую штуку при выводе прогонять через специальные методы, там, функции, которые форматируют вывод. И они всегда учитывают, ну, допустим, локаль или там ещё какой-то параметр, настройку, например, в программе они проверяют, если настройка такая, то выводим как есть. Вот галонах было, в галонах и выводим. Если нет, производим конвертацию. Это допустимый способ. Вполне допустимый. Многие так и делают. Ничего страшного не произошло. А зато внутри всё очень легко, понятно, однозначно и глазами видно. То есть нет такого, что у вас там абстракции на абстракции и хрен победишь, что происходит. С другой стороны, вы можете пойти путём, о'кей, у нас есть вообще миллиард способов, ещё может там всё это добавиться, все дела. Причём, что, кстати, важно, это чаще проявляется в том случае, когда у вас внутреннее представление может меняться не потому, что весь интерфейс как бы поменялся. То есть, допустим, мы говорим: "Вот сейчас мы работаем на американский рынок, и поэтому всё, что происходит внутри, должно быть под американскую систему". А, например, у вас такая система, которая должна уметь одновременно работать и с американской системой Imperial System, как по-русски это говорится, я не помню. И, собственно, метрическая система мер, мм, европейская. Ну да, весь остальной мир. Не знаю, ребят, почему мне так сложно всё это говорить. У меня в голове эти при том, что если об этом не говорить, всё понятно, когда начинаешь говорить, почему-то мозг начинает всё это путать. Все эти понятия. Короче, если у вас в программе, независимо от того, где вы находитесь, может получиться так, что одновременно встречается и то, и то в рамках даже вот выбранной одной системы отчётов. Ну, с деньгами, кстати, такое часто бывает, что у вас, например, какой-то товар куплен в каких-то единицах, при каких-то условиях, в какой-то там стране. И при этом всё это одновременно внутри в базе есть и одновременно используется, то да, потому что, например, если мы захотим выполнять какие-то общие операции над ними, нам придётся везде производить какие-то преобразования. И поэтому тут есть какой вариант: либо внутри всё постоянно превращать то есть получается, мы не только вывод меняем, но у нас получается ещё всё, что входит, мы постоянно производим эти конвертации, но в конечном итоге храним в одном формате. Либо мы используем тот же самый паттерн маoney, который фактически создаёт нам, когда мы говорим про деньги, это выглядит, может быть, так, что у нас есть мании, в которой при создании объекта передаётся валюта и currency, да, то есть, собственно, размер денег, там 100 и доллары, например, или там рубли. И вот дальше мы уже не паримся, мы это вот как есть используем, как есть сохраняем, преобразованиями вообще не занимаемся. Дальше просто, понятно, в базе это легко сохранить. Это обычно два поля, то есть число иcy. А вот когда это из базы, допустим, достаётся и мы этим оперируем, то у нас просто вот есть эти два объекта, в которых может быть действительно в одном одно, в другом другое. То есть мы не преобразовывали, мы как есть сохранили. Но при всех операциях очень важно делать это только через этот интерфейс. То есть мы напрямую больше числами не имеем права оперировать сами. То есть эти операции должны быть реализованы там, либо как-то их надо уметь и расширять эти объекты. То есть там тоже целая история на самом деле появляется по с такими библиотеками. Ну и в конечном итоге на выводе мы, конечно, говорим, что надо вывести, например, в такой-то валюте. Вот. Если, кстати, кто с этим не знаком, обязательно посмотрите. Вообще с деньгами это очень полезно. Patternтер маoney очень прикольная штука, которая позволяет многие вещи такие действительно улучшить, автоматизировать. Но повторюсь, опять же, не всегда это имеет смысл, даже если у вас разные валюты и разные бабки. А всё-таки внедрение таких систем прямо меняет подход работы в коде и добавляет а сложности, честно говоря. Их, знаете, кстати, ещё такая вот всегда фигня с этими штуками бывает, когда добавляется вот подобная сложность, вы как бы в процессе работы над конкретными фичами можете не чувствовать, что вы уделяете много времени вот этой фигне, которую внедрили, новой абстракции. То есть будет казаться, что, ну да, я просто с ней работаю. А в реальности, допустим, бывает такое, что если бы её убрали вообще, то скорость работы и эффективность была бы выше, и даже количество ошибок было бы меньше. Ну, потому что меньше там абстракций, меньше слоёв, меньше всего. Но отрефлексировать это крайне сложно в таких ситуациях. Поэтому здесь обычно, конечно, опыт и ребята рядом, и кто-то, кто периодически смотрит на систему сверху, в этом плане помогает. Иначе каждая такая абстракция кажется хорошей идеей. А в конечном итоге мы работаем в рамках монстра, в котором вообще сделать там простую операцию, докопаться до чего угодно, это прямо мега-мега проблема. Ну а что можно сказать по поводу векл и по поводу его идей с тем, что мы здесь прячем, не прячем? Вообще невозможно сказать, честно говоря, без конкретной задачи, конкретной проблемы. Потому что, если опять же нам нужен объём бака, ну, будет метод, который достаёт объём бака, даже если этот объём туда зашит просто как конкретное значение. Вот и всё. Ну, кстати, тут можно, знаете, ещё о чём было бы поговорить. Сейчас не имеет смысл. Это как раз о том, в какой момент где какие преобразования делать. Но в целом стандартный подход, наверное, самый - это просто унифицировать как минимум всё на запись. То есть у вас всё внутри должно храниться в единой э системе координат, имеется в виду всё, что угодно, там деньги, валюты и так далее, а на выводе и, соответственно, операции все простые, поэтому, потому что вы понимаете, с чем вы внутри работаете. А вот на выходе при показывании уже выводе, ну, можно делать соответствующее форматирование. Но обычный самый простой вариант. Дальше два предыдущих примера показывают, чем объекты отличаются от структур данных. Объекты скрывают свои данные из абстракциями, представляют функции, работающие этими данными структуры данных, раскрывают свои данные и не имеют осмысленных функций. А теперь ещё раз перечитайте эти определения. Это опять мартиновский этот заход. Надавить, проманипулировать. Обратите внимание то, как они друг друга добавляют, фактически являясь противоположностями. Различия могут казаться тривиальными, но приводят далеко идущие последствия. Опять он приводит эту дихотомию, что у вас как будто абстракция возможна только в том случае, если вы используете классы, в которых есть методы. Не так, мы это в первый раз говорили, есть огромное количество языков, в которых структура - это просто отдельное описание. И более того, даже вам часто не нужно специальные конструкции языка, как в Тайпскрипте часто делают, там создают типы, вокруг него просто набор функций, которые м нужно, грубо говоря, сказать, такие типа рукопашные методы. Но это не сложно, это не приводит ника каким проблемам. Разница, единственная разница вот по-настоящая, которая здесь проявляется, классы по дефолту предоставляют полиморфизм. А если вы вы просто пишете обычной функции, полиморфизма нет. Но дело в том, что поливарфизм нужен крайне редко, если посмотреть глобальные любые проекты. И обычные функции в этом плане будут тупо быстрее, потому что там от создания новых сущностей, заканчивая тем, что функция - это функция, вот она есть, вот вы её вызвали, там никакой динамической диспетчеризации на фоне нет. И поэтому в некоторых языках это вообще очень красиво решено. Наверное, акамол здесь ярчайший пример, где в обычной жизни у вас действительно так и есть. Вы просто создаёте функции сколько хотите, над чем хотите, создавая любые абстракции. И всё это с типами, всё это классно, там, никаких проблем. Но если вам прямо нужно полиморфное поведение, когда у вас, а, функция, на самом деле, метод, который вы вызываете, в реальности зависит от типа, с которым мы работаем, то это там решается действительно прямо вот таким классическим оп, когда вы прямо создаёте классы такие: "О, ни фига себе, я в акаamле функциональном языке, а работаю так, как будто я в джаве". Но только это просто такой небольшой дополнительный механизм, чтобы реализовать эту функциональность, а не основной способ написания, который заставляет всё дело через это, раздувая очень сильно код, создавая вот эти вот абстракции. И опять же, как в Джаве, в том числе благодаря Мартину, рассказывают о том, что вот это и есть только правильный способ, да? Нет, ребят, но посмотрите, просто прикиньте любой проект большой, да, посмотрите на него и подумайте, как много из тысяч классов, которые там есть, нуждаются реально в полиморфном поведении. Ну, почти никакие. Хотя, согласен, наверное, есть какие-то кейсы, в которых это чаще встречаются, но мы в первую очередь про обычный веб говорим, там это редко бывает. Ну давайте так, в библиотеках гораздо чаще, вот это точно. А там и абстрактные фабрики встречаются, и всё на свете. Но ребята, которые пишут библиотеки - это немножко другой уровень, чем просто писать прикладной код. Вот. А опять же, это не означает, что он они там везде нужны. Вот поэтому, когда он постоянно рассказывает про то, что есть типа структура данных, и это ни фига не абстракция, я отказываюсь следовать этому нарративу, потому что считаю, что он специально создаёт дихотомию, которая не является истинной ни в какой инстанции. Если у вас нету классов, это не означает, что у вас нет абстракций. Вот. Вот и всё. И более того, часто это ещё и типами поддерживается. То есть там к структурам вообще может быть запрещён тупо доступ без функции там в каком-нибуд Го или хаскеле, да, и всё. нет никакого прямого доступа и возможности с этим работать. Вот, вот вам и другие подходы к реализации этих механизмов. Он ещё любит слово процедурный говорить. Возьмём процедурный пример. Класс Geometry работает с тремя классами геометрических фигур. А класс фигур представляют собой простые структуры данных, лишённые какого-либо поведения. Всё поведение сосредоточено в классе Geometry. А, и он показывает square, rectangle и circle. Значит, у них, ну, какие-то там внутри точки, ещё что-то есть. И дальше он показывает отдельный класс, в котором типа он вычисляет площадь. И дальше он внутри мало того, что принимает на ход object, то есть он фактически кастует типы к общему обжекту, дальше внутри проверяет instance офом типы и типа выполняет вычисление. Что я хочу по этому поводу сказать? Он, конечно, пишет: "Объектно ориентированный программист недовольно поморчестве пожалуваться на процедуру природу реализации, будет прав. Но, возможно, его презрительная усмешка не обоснована, и он дальше там начинает обсуждать. Вот даже здесь стоит остановиться. Почему? Потому что, ну, по мне, это какая-то жёсткая манипуляция. Он показывает пример кода, как вообще никто не пишет. Ну, то есть это надо прямо специально говнокодить, чтобы такое написать. То есть в этом случае прямо кастовать, а, и делать общий метод для вычисления, ну, это какое-то безумие. То есть это не связано с объектно ориентированным программированием. То есть, кстати, когда он это пишет, он, по сути, ведь ставит знак равенство между полиморфным поведением и объектноориентированным программированием, потому что он называет это объектноориентированным программистом. То есть это программист, который рассматривает все вещи через полиморфное поведение с точки зрения именно сабтайпинга, о чём, кстати, я тоже часто говорю, что в ООП сабтайпинг - это самая главная вещь. Вот именно, потому что она определяет, э, то, как, собственно, программа выглядит и как она работает и на что больше всего влияет вот именно объектно ориентированность, да, то есть не всякие абстрактные классы, ни наследование, хотя оно для полиморфизма в том числе используется, но не обязательно. А то та же самая инкапсуляция в виде как бы и объединения методов с функциями, и приватность protected, которые, кстати, меньше всего влияют на архитектуру, это всё равно всё в конечном итоге так или иначе приводит вот к этой полиморфной истории. Значит, он показывает пример того, как код вообще не пишут с фактически с кастингом типов постоянным. То есть это просто дело не в объектнотивном программисте. Это нормальный программист так не напишет код. То есть вот надо прямо постараться. И он это как бы пишет, что вот если я значит процедур, то есть если я не объектно ориентированный программист, если по его мнению я процедурный программист, то я буду писать код вот именно так. Не буду, потому что, например, если бы я был в кложе, я бы мультиметоды для этого использовал, да? Это как раз механизм для реализации там полиморфного поведения вообще. Откуда у него появилась вот именно такая имплементация? О, понимаете, он ещё очень хитёр в том, что он не проговаривает явно. А, а почему в принципе имплементация такая? Вот он делает единый метод, в котором делает cast в object и дальше cast уже в конкретные типы. А для чего так сделано? Почему бы не сделать три метода area? То есть, э, это же наоборот очевидное решение. То есть, если я делаю шейп, допустим, я настолько туп или, не знаю, у меня в голове непонятно что, и я такой типа в классе на джаве не пишу метод, хотя это надо, конечно, ещё постараться. Я такой решил: "Нет, я хочу метод написать отдельно. У меня есть класс Square, допустим, и я рядом делаю класс, который называется Square Methods, допустим, и я там пишу вот этот area. То же самое для ректангла, то же самое для сёркла. То есть я беру, делаю отдельные классы, и у меня получается не один метод эри, а три. И каждый из них принимает на входжный тип. Каждый из них как бы не делает никаких кастов, преобразований, просто выполняет нужную операцию и всё. Это гораздо более естественный способ решения задачи, чем то, что он предлагает. И в большинстве языков плюс-минус так бы было сделано, если у вас нету, ну, допустим, без классов. Да, конечно, речь не идёт о том, что так бы всё называлось. И плюс есть везде свои механизмы там пакетов, того, как методы раскладываются, как функции раскладываются. Но в любом случае по дефолту человек бы и понимал, что если у него есть тип square, то он под него, собственно, area и пишет, и никакого проверки типов там бы, ну, просто бы не было никак. А почему он, собственно, так написал? А он здесь об этом не говорит. То есть правильно бы это звучало так, что, допустим, нам не просто надо вычислять э, а нам нужно сделать так, чтобы у нас как бы мы одновременно работаем с разными типами объектов. То есть мы никогда не знаем в программе вот в данной конкретном месте вот эта переменная - это square или rectangle. При этом нам надо вычислять его площадь. И тогда получается мы прямо явные задачи понимаем, ага, вот конкретно в данном месте нам нужно полиморфное поведение. И я не хочу называть это объектноориентированным программистом, потому что для меня это просто всего лишь один из элементов программирования, которых вообще миллион. И это очень прикладной такой специфический, который нужен там в каких-то конкретных кейсах. И мы понимаем, что ага, если нам нужен эрия полиморфная, то, ну, очевидным образом она будет добавлена в каждый из этих классов как отдельный метод. То есть в конечном итоге мы получим ту же самую реализацию, о которой он говорил, а, и которую он ниже приводит. То есть вот, если посмотреть, он, собственно, это приводит. Вот я перемотал, он пока говорит, что типа geometry лишний, давайте функции разнесём. Абсолютно всё правильно с точки зрения решения. Это то, как плюс-минус должно быть решено в Джаве, особенно исходя из тех вводных, которые он дал. Но это не объектноориентированный программист. Это не единственный способмента, короче, он все предпосылки, которые здесь описывает, даёт, ну, не совсем корректно и из-за этого запутывает. Возникает ощущение, что это вообще и есть нормальный способ. Нет, это способ связанный. Там, например, помимо того, что мы уже поняли, да, полиморфное поведение нужно, то, что именно в Джаве оно так реализуется, потому что если вы придёте в кложу и, например, вот тех концепции, которые я описываю сейчас, не знаете, а вы такие просто, ну вот начитали с Мартина вы такие так, ну где же должен же быть где-то здесь класс, а если его нет, то значит это невозможно и значит это плохой язык. Необъектно ориентированный, объектно ориентированный программист морщится по его словам, и говорит, что здесь всё плохо. А в реальности мышление должно быть немножко другим. То есть мы понимаем, что нам нужен сабтайпинг. Мы такие: "О'кей, а как сабтайпинг реализуется в кложе?" Читаем про мультидисpatch, понимаем, что это механизм создания функций, которые как раз умеют работать с разными типами. И берём просто это, имплементим. Задача решена. Выглядит это так же? Нет, это не выглядит так же. Это будет выглядеть по-другому. Более того, если вы это попробуете, вероятнее всего мысль, с которой вы окажетесь в конце, блин, а этот механизм, во-первых, а более компактный, более гибкий и прикольней. Вот. Так что попробуйте. Я ещё не видел ни одного человека, который бы мультиметоды в кложе попробовал и после этого не сказал, что ни хрена себе, как оказывается можно. И нас всё это время обманывали, что диспетчиться можно только по типу и вот так вот ограничено. Это особенно начинает проявляться, когда иерархии становятся хитрыми, когда у вас как бы с одной стороны, например, вы там делите, ну, допустим, вы говорите, что по полу делим, у нас есть мальчики и девочки, для них разное поведение, а потом появляется ещё до 18 лет и после восемнадцати вот такие фактически комбинаторика появляется. То есть нужно делать что-то для девочек старше восемнацати, младше восемнацати, то же самое для мальчиков. А с точки зрения наследования вы это не выразите. у вас просто получится гигантское количество, там комбинаторика такая, что будет расти очень сильно объём классов и управляться этим просто станет невозможно. А если у вас есть механизмы типа мультиметодов, то же самое достигается вообще линейным уровнем сложности. И вы такие понимаете: "О, а тут вообще класса не надо делать". Просто вы комбинируете эти признаки и на базе этого получаете нужное вам поведение. И это гораздо более правильная имплементация, гораздо более простая и понятная. И не создаётся гигантское количество сущностей. А из-за вот этой вот сложности, что иерархия, она как бы всегда только в одну сторону, в таких языках, как Java, существует огромное количество вот этих вот книжек, правил, того, как надо делать признаки, чтобы вот у вас там множественное наследований нельзя, но тогда у вас появлялись такие проблемы, поэтому у вас там куча отдельных интерфейсов, а, соответственно, тут дублируется имплементация и так и пошли, поехали, там у вас вырастает целая идеология вокруг языка, с которой вам приходится взаимодействовать. И многие такие: "Ну да, это сложно, это надо знать". и гробят годы лучшей жизни своей на то, чтобы со всем этим разобраться. А потом приходят в некоторые другие языки, видит, что ой, оказывается, это сложность не была необходимай, она была случайной. И я не говорю, кстати, что это значит, что неправильно набросать. Речь просто идёт про то, что когда вы понимаете лучше и шире, чем просто язык, вы в самом языке можете понимать, вот то, что я сейчас пишу и делаю - это реальная сложность и так надо, и это является объективной какой-то реальностью, или это связано с тем, что тут есть какие-то проблемы, можно по-другому, можно не делать. Ну и так далее. Или я это делаю и при этом понимаю трейдофы этого решения. Вот и всё. Потому что, например, если, чтобы уж было понятно моё к этому отношение, я, например, Java люблю, мне Java нравится, и современная Java, именно современное, там и с выводом типов, и со всякими фишечками, мне очень приятно в плане написания кода. При этом я прекрасно понимаю, там где она более вербозная. Почему, э, например, когда я в течение дня пишу на тайпскрипте, я ни одного класса не делаю, а если я пишу на Джаве, у меня 10 классов из-под пера выходит минимум. А просто потому, что вот так устроена система, но это не объективная реальность, не объектно ориентированное программирование, это особенность Java. Вот. Так что он как бы приводит пример, про который не говорит, почему он такой, и показывает типа правильное решение. Опять же, это только касается кейса необходимости полиморфного поведения. Вот и всё. Ну и и опять экспрем проблем возник. Здесь мне, кстати, уже писали про то, что имеет смысл его отдельно разобрать. Я как минимум пост напишу. Я не думаю, что прямо, наверное, я сделаю это как видео. Хотя, знаете, есть мысль сделать как видео, но именно скорее в кодинге. То есть просто рассказывать про это не будет классно в кодинге. Причём с примером на разных языках, так чтобы вы ещё и увидели, как эта проблема может быть решена вообще другим способом. Вот. Но в целом то, что он тут рассказывает, где проще добавить методы, где функции, честно говоря, по большому счёту не актуально. То есть по дефолту языки, которые позволяют писать мультипарадигменно, там обычно всё сводится к чему? Обычный код вы пишете без всяких вот этих вот абстракций. Я не говорю, что без абстракций вообще. То есть понятно, что есть типы наружу имплементацию никто обычно не суёт в большинстве ситуаций, но там нету необходимости прямо городить классы с интерфейсами, чтобы вот прямо фигарить вот таким способом. Только в тех местах, где вы точно понимаете, что ага, мне полиморфное поведение нужно, там уже начинают такие вот механизмы включаться, но не везде, не в каждом углу. Вот. А, кстати, тут ещё знаете, что можно сказать? В чём он не прав? Вот он дальше приводит пример, что смотрите, как классно мы придумали шейп. Смотрите, мы все его имплементим. И в шейпе есть, значит, вычисление площади. Да, он, кстати, почему забавно, тут почему-то нету примера этого шейпа, но в любом случае он говорит, смотрите, и как раз вот пошло это. Он начинает говорить про полиморфные фигуры, про то, что вот смотрите, что это даёт, добавление новых функций, там, как легко всё это происходит и, значит, соответствующая имплементация. могу ему предъявить с его же позиции. Он делает, значит, интерфейс shape и кладёт туда. И это вот типа вычисление площади. Не кажется ли вам, что это нарушает один из принципов солида отдельный интерфейс? То есть, по сути, он такой типа делает свалку, куда а ну все вещи, которые связаны с шейпом, давайте туда запихнём дальше. Смотрите, что получается. Допустим, у вас появляется какая-то другая фигура, которой, ну, просто не нужно вычислять область, потому что она не используется в этих кейсах. Мм, что у вас произойдёт? Вы будете обязаны это сделать. Но я, конечно, немножко ёрничаю, просто ответ здесь в том, что стиль Мартина был бы в том, чтобы написать про это и сказать, что вот так неправильно, так правильно безотносительно контекста. То есть в целом мой поинт в том, что вот эта вот идея Ага, надо отделить этот интерфейс, это супер неочевидно. И никакие подходы из серии одна ответственность и так далее здесь не работает. Это всегда вопрос конкретных кейсов в вашей программе, потому что кейсы могут быть так, лечи могут так. То есть вот если у вас есть, например, какая-то штука, которая вот вот отдельно может встречаться сама по себе, но вы в какой-то момент просто поймёте, что либо я это делаю отдельным интерфейсом, этот метод независимо имплементирую там, где надо, либо просто те места, где это не надо, ну, вы сразу поймёте, что вот я реализую фигуру, допустим, из этого кейса, да, и она заставляет меня реализовать э, а мне она не нужна. Вот в данном случае, а какой из этого вывод ровно один? Однозначно правильный, что в таком случае должно было быть реализовано как отдельный интерфейс. Соответственно, вы это из шейпа выносите areable, например, и дальше implement shape, там запятая area. Ну, areable, ну, допустим, вот так это всегда делается в коде. Поэтому этот подход использовать, когда его просто показывают и приводят в пример, обычно это, ну, такая спекуляция. Мы, мол, сразу решили, что это правильно. И вот в этом кейсе, смотрите, это работает. Ну, блин, в других кейсах это не сработает. В большинстве кейсов это будет не очевидно, что так надо делать. Поэтому чаще всего либо у вас уже есть этот опыт, и вы знаете, что это надо так делать. Бывает такое. Ну, например, countable, там часто очень знают, что люди с опытом, они такие: "О, вот то, что считается, оно должно быть отдельно, потому что вот часто это такая независимая история". Но в большинстве случаев это всегда приходит просто с конкретной имплементацией. Вы это делаете такие понимаете: "О, набор методов не соответствует тому, что мне надо". Ну, а дальше, честно говоря, очень часто, когда там нет времени, человек понимает, что ему надо решить сейчас задачу, афакторить - это вообще не его политические какие-то причины, не знаю, какие KPI, и он такой: "Ну нахер, просто заимплеменчу, выброшу исключение на этом всё". Вот так вот это всё очень часто и заканчивается. Кстати, напишите в комментариях, если в ваших проектах так и происходит. Всё, мы это проговорили. Давайте опять его слова, я немножко с ними поспорю. Процедурный код, код, который использует структуру данных, позволяет легко добавлять новые функции без изменения существующих структур данных. Объектно ориентированный код, напротив, упрощает добавление новых классов без изменения существующих функций. Слово процедурной тут не нужно. Читаем expression problem. Нет там никаких понятий процедурный, непроцедурный. Это вопрос организации кода. Мы либо группируем действия вокруг типа, в рамках типа, либо мы группируем по действиям для разных типов в одном месте. Вот. И это не значит, что только так всё существует и что, грубо говоря, у нас только такая дихотомия существует. Кстати, в этом плане здесь ещё в чём прикол? Дело в том, что Java там же без класса ничего сделать нельзя, поэтому он автоматически делает так, что код, который там пишется идиоматический, он может писаться только в режиме всё вокруг типа, а что иногда действительно содаёт проблемы. И если пишется по-другому, то это выглядит, ну, неестественным для языка и, как правило, воспринимается как что-то плохое. Поэтому мне в этом плане всё-таки при том, что мне на Джаве нравится писать вот те кейсы, в которых я с этим встречаюсь, я немножко жалею, что это не Typeesрипt, потому что там как бы тебя никто не заставляет. То есть он вполне, а, с одной стороны не относится к таким, ну, он относится к широко распространённым языкам, и при этом он не навязывает вот этот стиль, когда ты только так можешь видеть любую программу, и тебе приходится её как бы любую проблему рассматривать как тип. Задумайтесь над этой мыслью. Если она вам не до конца понятна, я обязательно сделаю пост на эту тему, потому что она довольно важная. Это то, как устройство самого языка заставляет ваш мир превращать в определённую структуру, с которой вы потом работаете у себя в коде. И в этом плане довольно жёсткая, потому что она всё превращает в типы. Кстати, это даёт определённые плюсы, даже чтобы я уж не говорил, что только минусы есть, да, что вас там заставляют писать в определённом стиле. Автогенерацию очень легко и хорошо делать, когда вы таким образом всю программу превращаете. Кода становится немеренно, но зато автогенерация. Поехали дальше. Таким образом, что о просто в процедурном программировании сложно. Повторяться больше не буду. Кстати, забавно, но он даже в конце немножко сдался, сказал опытный. То есть он с одной стороны там говорит, что типа оп программист будет плеваться и вообще всё плохо. А с другой стороны, опытные программисты хорошо знают представление о том, что все данные должны представляться в виде объектов МИФ, иногда предпочтительные простые структуры данных и процедуры, работающие с ними. Здесь опять история про то, что компоновка может быть другая, но мне не нравится то, что он всё равно это превращает, как будто у тебя обязательно простые структуры данных и процедуры. Да, это не обязательно так. Могут быть классные механики для абстракции именно в таком же стиле. Дихотомия другая в данном случае. Закон Диметры. Значит, хорошо. Известное ивристическое правило, называемое законом Диметри, гласит, что модуль не должен знать внутреннее устройство тех объектов, с которыми он работает. Как мы видели в предыдущем разделе, эти объекты скрывают свои данные, представляют операции для работы с ними. Это означает, что объект не должен раскрывать свою внутреннюю структуру через методы доступа, потому что внутреннюю структуру следует скрывать. Ну, он тут даёт более точную формулировку. Там идея в чём? Если у вас есть объект, внутри которого вложен ещё один объект, а там ещё один объект, то вот если вы как бы в своём коде вот такие вот цепочки строите, то как бы то место, в котором вы это вызываете, нарушается, значит, абстракции. Вы слишком много знаете о соседних элементах, соответственно, там их менять будет сложней, бла-бла-бла и так далее. И вот это всё. И, соответственно, что предлагается? Если у вас там цепочка вызовов идёт там больше двух, допустим, ну, это очень упрощённо, то давайте, значит, внутри сделаем ещё один метод, который внутри себя эту цепочку сделает, а код, который снаружи, не будет знать, в общем, что там можно это вызвать. Но тут мы опять, видите, что получаем? То, что я в начале говорил. Во-первых, у вас эти методы всё равно останутся. Вы всё равно можете это напрямую вызвать, и кто-то это обязательно сделает. Во-вторых, полное следование принципу диметры, честно говоря, такое себе, если это делать. У вас просто каждый класс в конечном итоге, потому что чем сложнее, тем больше всяких взаимодействий, превратится в какой-то гигантский набор методов, проксирующих внутрь. И самое главное, это не поможет избежать взаимодействий, потому что в конечном итоге, ну, бывает такое, что нужно к чему-то обратиться и не просто достать какое-то значение, а поработать с этой штукой тоже. Если у вас в рамках конкретного кейса, конкретной функции, конкретного сервиса, чего угодно, действительно есть обращение к глубокой вложности на постоянной основе в большом количестве, тогда действительно над этим стоит задуматься. И не столько, и скорее не столько с точки зрения того, чтобы сделать какую-то обёртку, а с точки зрения понимания того, а что у вас вообще здесь является корневой сущностью, с чем вы вообще работаете. Возможно, тут модель данных неправильная и с вообще переосознать всё надо, а не с симптоматикой бороться. Но, по большому счёту, закон Диметрии - это очень-очень такая рекомендательная вещь, которая глобального не оказывает влияние на происходящее. Опять же, я специально пошёл, поговорил тоже с чатом GPT на эту тему. Я говорю, как вообще сейчас в современном мире люди относятся к закону Диметры? Но он тоже об этом пишет, что всё-таки сейчас большинство склоняется к тому, что, ну, такая типа полезно знать, но никакой фундаментальной истории в этом нет. И это правда. Ну, можно, конечно, ему не верить, но я думаю, он нормально суммирует здесь, потому что я достаточно неплохо тоже смотрел эти вещи раньше, читал, использовал, практиковал и про них рассказывал. Но, конечно, в какой-то момент подостыл, потому что я понимаю, насколько это мелочь по сравнению с реальными проблемами, которые могут быть вследствие неправильного там проектирования, того, где хранится логика, как взаимодействуют объекты, мутабельности, связь с побочными эффектами и всем остальным. Вот поэтому, ну, если много цепочек, повторяющихся к объектам, в каком-то месте одном собралось, это точно повод задуматься, но не о том, чтобы в первую очередь херануть какой-то метод, который всё это скрывает, потому что это может быть просто следствием, а не причиной, а понять, а что у вас вообще с моделью данных? Сущности, агрегаты, в общем, вот такие вот понятия тут начинают возникать. Поэтому, в принципе, тут глубоко обсуждать не надо. Хотя забавно, что про это он написал столько же примерно, сколько и про точку. и не написал промутабельность в самом начале этого блока. То есть сопоставление важных вещей с неважными не соотносится с объёмом текста. Ну да, здесь он приводит ещё пример, что иногда всё это появляется вообще глобально вся эта фигня приводит гибридных структур данных, что у вас наполовину объекты, наполовину структуры данных, где-то вы данные можете получить, где-то методы чтения, бла-бла-бла. Ну, наверное, да, в каком-то смысле, когда итеративно естественным образом развивается любой проект. Ну, всякое там может быть, но тут, наверное, стоит сказать, что идеальных систем в этом плане не бывает. Оно, как правило, происходит, ну, как-то происходит, происходит, происходит. Там нужно пересматривать саму модель, с которой вы работаете, сущности их связи. Периодически это нужно перетряхивать. Вот. А так вы не сможете сделать систему, при которой мы такие каждый раз, когда что-то добавляем, а так чётко, вот тут надо положить вот так, чтобы было правильно. Ну, так это просто не работает. Вот поэтому, когда он говорит: "Не используйте гибриды", они являются признаком сумбурного проектирования. Ну, есть история про то, над какими проектами работал сам Мартин, но он как бы не работает в таких местах. Ну ладно, он прекрасно знает, о чём он говорит. Опять же, если вы почитаете его твиттеры и срачи, там всё видно, на самом деле. А так вот есть реальная жизнь, хочется просто успокоить. Люди, которые читают такой текст, они просто могут воспринимать, что, блин, надо всё время проектировать, всё должно быть идеально, добавляем только правильно. Вообще не так. Как правило, везде всё через жопу. Заходишь, там страшные legсиcy, гад обджекты, понапихано хер знает что. А ребята, у которых вот всё, как говорит Мартин, зашито, закрыто, абстракции, тра-та-та-та, я готов поспорить, что эти проекты либо не вышли в продакшн, либо уже закрылись. Скрытие структуры. Ладно, я тут не буду читать. Давайте я немножко сразу перемотаю ниже и расскажу про вещь, которую он рассказывает, про которую стоит в двух словах сказать. Он говорит про объекты передачи данных. Значит, понятие ДТО очень распространено, его все знают в Javaмире. В других языках тоже иногда называют ДТО где-то другими словами, а в некоторых языках вообще такого слова особо не используют. Объясню, о чём идёт речь. Дело в том, что когда мы работаем с сущностями, я очень не хочу, кстати, привязываться классовым языкам. Я очень не хочу говорить словообъекты. Почему? Потому что это всё касается, то есть это фундаментальная вещь, которая не касается того, объектно ориентированный у вас язык или нет. Потому что опять же абстракция - это не всегда объект с классом. Значит, есть две вещи. Действительно, есть сущности, есть объекты значения. В чём они отличаются? По-моему, подробно об этом написано у Фаулера. Я читал ещё в каких-то местах, но вот сейчас вот не упомню. Ну, в общем, идея в чём? У вас есть сущность, это штука, которая обладает идентификацией и лайфтаймом. И объект значения, у которого этого нет. Что имеется в виду? Ну, давайте возьмём пользователя, да? У него есть идентификация, то есть вы его по айдишником отличаете разных сущностей. То есть вообще неважно, там объекты, вот один там ссылки, всё остальное. У вас просто два разных идентификаторы, два разных юзера. Одинаковый идентификатор - это один и тот же юзер. Кстати, в базе в коде это может быть представлено как одним, так и двумя объектами в зависимости там, особенно если вы с Ураймом каким-то работаете, да, может быть, по-разному. И совершенно нормально частая ситуация, что у вас юзер просто в нескольких экземплярах, хотя это один и тот же юзер, просто в зависимости от того, кто где с ним работает. В вебе такое встречается. Так вот, э, сущность, у него есть идентификация, есть время жизни, да, он появился в какой-то момент и исчез. Причём оно может быть по отдельности как раз наоборот. То есть если у вас есть что-то одно из этого, то оно не будет сущностью. Значит, приведу пример. Например, у вас есть 100 долларов в базе и в сущности, да? Вот, например, есть юзер, у него есть 100 долларов и вот у другого есть 100 долларов. Для нас 100 долларов - это просто 100 долларов. То есть наши 100 долларов и его 100 долларов - это одно и то же, потому что мы их можем поменять. Он даст нам свою бумажку, мы ему свою дадим. То есть в этом случае нету никакого время жизни, нет никакой идентификации. То есть нет 100 долларов вот моих вот пронумерованных и его 100 долларов, которые как-то друг от друга отличаются. Не потому, что этого нет вообще, а потому, что нет с точки зрения нашей системы. Почему? Потому что, например, если мы пишем программу для людей, которые выпускают деньги, для них одни 100 долларов отличаются, потому что у них есть идентификация. Там, если вы помните, на деньгах номер проставлен, и они их таким образом друг от друга отличают. Но они деньги не воспринимают как просто деньги. они воспринимают их как независимые сущности, за которыми, например, там можно нужно следить и так далее. Это довольно важно, потому что это сильно влияет на то, как это устроено в коде и для чего используется та или иная история. Поэтому, например, говорят value object. Value object - это вот те же самые деньги, например, чаще всего. Но это не только деньги, те же самые поинты, в данном случае тоже, по сути, value обжекты. А опять же, это не всегда обжекты. Ну, в каком-то смысле можно сказать, если мы взяли число, начали его использовать как рост, вот тоже вам в каком-то смысле value object. Ну, обжектом он станет только в том случае, если мы, конечно, хоть какие-то там методы накрутим. Это не совсем про ДТО, но это связано с ними, потому что дтошки в этом плане часто связаны с обжектами. Теперь это первая история. Она говорит о том, что в нашем коде чаще всего является сущностью, нет и какие операции позволены, где хранится логика, там связи и так далее. Наверное, такой пример. Вот у нас есть пользователь, да, и у него, допустим, есть, ну, давайте сейчас не деньги, приведу адрес, но адрес встроен прямо вот в базе в табличку и там, например, юзер, и у него несколько полей в табличке, которые обозначают адрес. И у вас есть несколько вариантов. Например, вы можете напрямую работать с этими полями. Вот когда вы юзера достали из базы, да, вы с этими полями работаете просто напрямую, там, обращаясь к ним, соединяя и так далее. И в какой-то момент вы просто обнаружите, что у вас очень много повторяющейся логики в разных местах, когда вы из юзера это достаёте и пытаетесь что-то из этого скомпоновать. Здесь есть такая концепция, так называемый embeded object, то есть когда мы понимаем, что адрес, вот этот набор полей, вообще-то можно объединить как раз в некий объект, который даст, собственно, тот интерфейс для работы, чтобы не повторяться. Мы, собственно, в самом начале про это говорили, да, когда мани приводили в пример. И получается, грубо говоря, у вас есть сущность юзер, у которой внутри есть набор разных параметров, адресов, но вы дополнительно создаёте внутри него метод, возвращающий объект-адрес, который получается сбором вот этих полей внутрь. И тогда получается, что как бы в разных местах программы, когда вам надо с адресом выполнять какие-то манипуляции, преобразовывать, что-то вычислять и так далее, вы уже не просто оперируете обычными полями, а у вас есть уже некая абстракция, с которой можно работать. Она там может обладать методами для преобразования, для форматирования, выдачи, чего угодно. И у вас появляется некий отдельный класс, который как бы внедрён в сущность и в каком-то месте этой сущности, где-то там в объекте, он собирается, собственно, в этот объект и может переиспользовать. Ну, его можно как бы отдельно использовать, а можно вот в MBD-режиме. Это показывает разницу между value обжектами и сущностями, в том числе. То есть вот так вот они соединяются друг с другом. Поэтому, например, города часто являются обжектами, потому что мы сравниваем не по идентификатору, а просто, ну, там Ульяновск - Ульяновск, а Москва - это Москва, то есть нет Москва- один, Москва-два. Ну давайте так, я знаю, что сейчас есть разные вещи вы мне хотите сказать, и я понимаю, что мир сложнее, и я бы сам ещё сюда накинул, но это невозможно в рамках э таких вот видосов делать. Это можно обсуждать потом отдельно в комментариях или какие-то я посты периодически буду писать на те темы, которые больше всего горят. Хорошо. Что такое ДТО? Для чего они используются? Где они тут вообще? Дело в том, что мы, когда работаем с внешними системами, то есть мы куда-то отдаём или принимаем данные, то мы не можем просто взять и упаковать их в какую-то сущность внутри в статических языках. У вас обычно это две разные истории, потому что данные могут приходить совершенно по-разному. То есть вы хотите сначала их вообще получить, а потом, если надо, преобразовать в нужные внутренние формат, потому что формат не вы можете определять снаружи. И для этого как раз используют ДТО, потому что опять же, если бы это был какой-то динамический язык, вы просто берёте данные и у вас там, не знаю, это получается в рубе это хэш, в Пайтоне - это словарь, где-то это объект в Джейсе, например, да, и и вы делаете с ним всё, что угодно. Кстати, именно поэтому в этих языках, особенно в динамических, слово ДТО редко используются, потому что не всегда понимают люди, о чём идёт речь. Они такие: "Ну, у меня вот просто есть объект, я просто вот получил данные или вот у меня есть данные, я их в JSON просто сконвертировал и всё". В статических языках так не работает. Нужно всегда иметь какую-то структурку, в которую мы это упаковываем. Причём именно структуру. То есть у неё нету никакой логики вообще. И эта штука нужна только исключительно для того, чтобы вот те данные получить, например, откуда-то туда упаковать и, наоборот, их оттуда отправить наружу. Эта вещь обеспечивает несколько вещей. Во-первых, это типизация. Во-вторых, ну, в принципе, так работает э мапинг базовый, чтобы потом дальше это преобразовывать. Но есть ещё, на самом деле, третья вещь, которая сейчас особенно актуальна стала в современном мире - это, конечно, превращение этого добра, например, в фронтэндовые типы. То есть, например, когда у вас есть взаимодействие Бэка с фронтом, хрен вы вот так вот сущность превратите в тип, например, в тайпскрипте. Потому что, во-первых, это не одно и то же. Там внутренние сущности - это опять же это абстракция с точки зрения внешнего пользователя. Поэтому как наружу это представляется другая история. Поэтому дтошки в этом плане играют вот эту важную роль, что помимо того, что они через них как бы идёт получение и выдача данных наружу, многие системы позволяют либо эти ДТО генерить, собственно, из, например, там Open API спеки, либо на базе этих дшек генерировать готовые, соответственно, типы для чего угодно, включая, например, Typpeesриpt. И тогда вам ручками этого не надо будет делать. И, соответственно, вы благодаря этому можете удобно работать. И поэтому, как часто бывает, у вас например сущность одна, а дтошек много, вплоть до того, что одна дшка вообще может внутри себя объединять две сущности. Ну, потому что в конкретном данном месте так нужно. Скорее я здесь, видите, не говорю о том, что он что-то правильно, неправильно объясняет. Скорее просто в целом хочу объяснить эту концепцию, потому что её понимать надо. И это абсолютно нормально, что у вас в разных местах разные используются дтошки, потому что в разных местах одна и та же сущность по-разному представляется. Или вообще это не одна сущность может быть, а связанная. Кстати, из-за этого иногда это странно выглядит для людей, которые работают в динамических языках, они смотрят, не понимают, почему там есть папочка в духе, которая называется ДТО, и там просто сотни файлов, а то и тысячи, когда у вас там одна и та же сущность в пятидесяти разных вариантах, да, так это работает. Вот поэтому ДТО - это важная концепция, она в любом случае есть при любом раскладе. И да, это чистая структурка для того, чтобы данные туда-сюда перегнать и всё. Она не участвует в каких-то бизнес-историях внутри кода. Ну, не должна участвовать. Какая-то базовая валидация там может быть, но не более того. Но он показывает, что вот типа ДТОшка, там ещё и методы какие-то навешали. Ну, ничего тут особо не скажу. И фактически последний блок. На этом всё. Дальше это будет в отдельных видео. Он говорит про Active Record. И тут, честно, я вообще его не понял. Знаете, почему? Потому что, ну, вот представьте, вот такой вот абзац. Сколько тут? Три абзаца, да? Он вообще не объясняет, что это такое. Это, чёрт побери, сложнее. Ну, как бы не простой, это большой паттерн, если на имплементации особенно посмотреть с кучей вообще всего в РМах для того, чтобы взаимодействовать с базой. И Active Record вообще ни разу не ДТО. То есть, ну, просто это бесконечно далеко с точки зрения того, как используете Active Record, что туда добавляют, что там напихано, сколько там всякой логики, а что вообще в большинстве случаев сущность. Короче, для меня это выглядит как какая-то дичь, потому что он привёл сюда вообще абсолютно другую область. Ну, то есть Ормки по сути, да, со всеми этими вытекающими историями. И так как бы между делом типа а, да, это просто разновидность ДТО и что-то есть в духе. Ну, обычно в них присутствуют навигационные методы типа save find. К сожалению, такие разработчики часто пытаются напритировать структуры данных как объекты, включают в них методы. Вообще не к сожалению, он так и задуман этот паттерн, вообще-то, и все и все так делают, потому что он, собственно, такой вот. Ну и дальше ла-ла-ла, активные записи интерпретируют как структуру данных программа создаются отдельные объекты, которые содержат бизнес-лойку. Никто так не делает. То, что он рассказывает, это вообще не то для чего. То есть те люди, которые, ну, пользуются этим паттерном, они прекрасно понимают плюсы и минусы и как это работает. Опять же, от языка ещё зависит. Но самое главное даже не это. Нельзя просто вводить целую концепцию, которая вообще должна быть понята отдельно как история про связь базы с сущностями. Вот так вот мимолётом, рассказывая буквально двумя словами, как будто вот а все всё поняли, о чём идёт речь. Это зло, и как бы пошли дальше. Вот по сути вот он сейчас так сделал. Честно, у меня как бы я когда прочитал, я протовно же книжку читал перед тем, как вот стал перечитывать, я такой, что это вообще происходит. Лучше, конечно, он бы сюда вообще про это не писал. Про это так нельзя разговаривать. И мой поинт очередной вот на тему того, почему эта книжка всё-таки опасно и почему я разбираю вот именно из-за таких вот вбросов, потому что я не могу это ничем назвать, кроме как взбросом, потому что, ну, нельзя как бы вот так вот проектив record рассказать и сделать вывод, что это зло, так не делают и пошли дальше. При том, что он привёл пример того, как никто не делает, что во внутренние данные там превращают, всё это просто ДТО. Вот, вот на такой вот ноте я хочу закончить сегодняшнее обсуждение. Особо не рассказывая проектив Record, потому что это просто отдельная история. Он даёт какое-то заключение, все дела. А, в общем, представьте, мы сколько потратили время на обсуждение буквально семи страниц из книги, которой там сотни страниц, да, и сколько вопросиков возникает и к чему это может приводить. Значит, я хотел такую историю рассказать. Когда я написал пост и мне в комментариях начали писать, там многие заметили, точнее, стали пытаться говорить такую вещь, типа, ну, Кирилл, что ты докапываешься до чувака, который писал книжку в 2008 году? Ну, тогда жизнь была другая, времена были другие, подходы были другие. Я вообще не согласен, ребята. Я вот, ну, во-первых, я программировал в то время и могу сказать следующее. Очень многие концепции, которые я и сегодня в прошлый раз обсуждал, были придуманы вообще не вчера и даже не 10 лет назад и не 20. Хаскель появился там в девяностые или там в конце восьмидесятых, РЛН появился в восьмидесятые, лиспы появились в шестидесятые, появился в восьмидесятом. Огромное количество понимания того, как вообще делать, что является злом, не злом, подходы и так далее, было придумано чёрт знает сколько лет назад. И только за последние вот буквально 10 лет это было адаптировано в мейнстримовые языки. Очень многие вещи. Кстати, по этой причине действительновые языки очень сильно выросли. И многие старые языки или те вот инструменты, в которых это появилось, стали из-за этого неактуальны. Но не было такого, что в 2008 году кто-то не понимал историю с побочными эффектами. Я имею в виду с точки зрения тех, кто реально занимается либо наукой, либо понимает вообще программирование. А это всё гораздо раньше появилось. Это как раз появилось примерно в восьмидесятые, когда появились эти понятия, их начали вводить и начали очень сильно ими пользоваться и на этом строить и системы типов и внедрять как бы в повседневное, так сказать, программирование а функциональные языки, там многопоточность, понятное дело, разные подходы к тому, как вообще конкурентные вещи решать, потому что те же самые экторы ирланга с отсутствием шаренного состояния, короче, очень много вещей вообще не сегодня появилось. И когда мне говорят про то, что, ну, Кирилл, он там не мог этого знать, тогда такого не было. Это был шит. Всё было, всё знали, всё было всем известно и понятно. И книжек на тот момент было полно классных, офигенных. И написаны они были гораздо раньше. Если вы берёте Сикп, он был написан в восемьдесят пятом году. А есть люди, которые часто вот когда про это говоришь, они говорят: "Ну, сикп - это несерьёзно, а все дела". Каждый раз, когда начинаешь разговаривать об этом подробнее, выясняешь, что человек его не читал. Вот поэтому говорить о том, что на тот момент не было книг, и слава богу, что он хоть что-то написал, и без этого нам мы не могли программировать, это вообще не так. Была куча книжек, были очень много фундаментальных книжек, было очень много того, что действительно а показывает, объясняет, как надо быть, делать и так далее. Был и программист-прагматик, был и совершенный код, и много чего ещё. Да, понятно, что книг сейчас ещё больше. Другой вопрос, читают ли их или нет, это тоже открытый вопрос. А, короче, все эти претензии я не принимаю. А, единственное, конечно, какие-то были моменты по производительности, это правда, но те вещи, которые мы обсуждаем, они этого особо не касаются. А так, если глобально посмотреть. Вот поэтому я по-прежнему считаю, что эта книга опасная в плане того, что она создаёт иллюзию понимания, но не настоящее понимание. При этом я не могу про неё не говорить, потому что все очень многие люди сводят принципы программирования, опять же, не в Джаве, а во всём программировании именно к этой книге. Её выдают как эталон и постоянно про это говорят. У меня нету задачи типа идти с хейтом. Я, кстати, не очень понимаю людей, которые воспринимают это как хейт, потому что если вы посмотрите на то, что я в других местах пишу, например, есть книжки Мартина, которые я наоборот рассказываю и говорю, что они классные, и они мне очень нравятся. А поэтому здесь речь идёт про то, что мир не чёрно-белый. Есть вещи, за которые мне что-то может не нравиться, есть вещи, за которые что-то может нравиться. И, скажем, если бы никто не говорил про чистый код, мы бы его не разбирали. Но поскольку каждый раз, когда мы скатываемся к обсуждению каких-то штук, начинают кидаться цитатами выз книги, говорить, что, а, вот он говорил, вот там написано, конечно, это игнорировать нельзя. И поэтому такие видео будут выходить и будут выходить, кстати, на другие книжки тоже. А в обязательном порядке я, наверное, пройдусь по всему, если этот формат будет заходить. Мне показалось, что сегодня было немножко суб сумбурно, а, возможно, потому что я не, наверное, глубже копал на маленьком объёме, да, и здесь, может быть, мне чуть сложнее с этим. Опять же, я знаю, что вы очень многие моете посуду. А, напишите, что вы об этом думаете. Если вам понравилось видео, поставьте обязательно лайк. Если понравилось, не понравилось, поставьте дизлайк. Обязательно пишите злой комментарий. Я люблю злые комментарии. Меня они вообще не трогают. И они показывают, конечно, очень классно вообще восприятие мира у других людей и помогают мне лучше дальше перестраиваться, э, понимать, адаптироваться, что лучше писать, о чём лучше не писать. Я надеюсь, что вам понравился этот выпуск. Э, я надеюсь, что, точнее, не надеюсь, я знаю, что мы дойдём до конца и сделаем, пройдёмся по всей книжке, пройдёмся по его другой книжке, пройдёмся по всем остальным книжкам. Думаю, что в какой-то момент я войду в режим, где-то в месяц буду выпускать по одному подобному ролику, и это станет постоянной рубрикой. А количество подкастов с живыми людьми, их будет, ну, три в месяц. Потом, может быть, сократим до двух, потому что в конечном итоге, честно говоря, я бы хотел ещё и делать реальное программирование, ещё какие-то вещи. Поэтому мне кажется, что должно быть два видео, которые идут в не общения, и два видео, которые, соответственно, это встречи с другими людьми. Всем спасибо. Пока, до новых встреч.
[музыка]
