Комментарии 380
Если не брать то, что примеры немного утрированы, и некоторые упомянутые вещи (например SOLID это далеко не всегда про ООП, скорей про общую организацию кода), в целом статья великолепна. Я недавно сменил место работы и тут DI завезен по полной и все перекрыто иерархиями в микросервисе из одной ручки, нахожусь в перманентном удивлении и пока еще так и не понял, что происходит.
Это называется: Архитектора на кол и как чучело на масленицу сжечь…
По факту DI если и нужен то меня вот уже давно мучает вопрос, зачем строить фабрики из всего чего можно чтобы в DI мы могли засунуть сущность, когда просто на протоколах/интерфейсах все делается и так. Зачем мне контейнер, а многие не понимают что все выше описанное в 99% случаях не копию объекта в фабрике отдаст, а сам объект, а потом стди ломай моз где этот кадр сломал все..
например SOLID это далеко не всегда про ООП
Я вообще за то, чтобы сократить SOLID до фразы "делайте нормальные интерфейсы, б***!".
"иерархия в микросервисе из одной ручки" - смотря на сколько это паттерн. В принципе микросервис из одной ручки уже звучит странно. Но если таких немного, то в целом может быть и нормально. И иерархия может быть в таких сервисах также оправдана.
Например у нас: есть стандартый шаблон, разворачивающийся за секунду. В нем стандартная иерархия: презентация, прикладной, доменный и инфра. То, что в 2-3 сервисах из 100, задача на столько проста, что можно не заморачиваться насчет моделек, еще не говорит что нужно городить огород и делать в этих сервисах по разному. Нужно задавать вопрос "на сколько это проблемно завести прикладной сервис, дернуть его из слоя перезентации и отдать данные?".
шаблоны и стандартные решения дают выигрышь в поддержке и разработке, при условии что они применяются правильно (т.е. не превращаются в антишаблоны)
С Func as service я знаком. Не знаю имел ли в виду автор именно их.
Не, с точки зрения нагрузки не логично или не факт что логично следовать шаблону "сервис имени ручки". Тут нужно смотреть на конретику, а то может совместить несколько сценариев (ручек) в одном даже выгоднее будет (общая бд, кэширование, отсутствие походов по сети, отсутствие репликации и т.д.).
Слепое фанатичное следование чему угодно - даже самой Лучшей, Доброй и Гениальной концепции - порождает только проблемы, дичь, трэш и угар.
На мой взгляд, главное свойство зрелого человека - умение осознанно и интуитивно определять оптимальный баланс различных качеств, и не вестись на тренды и хайпы.
У каждой медали есть обратная сторона и это надо бы учитывать.
Какой-либо паттерн предназначен для решения определённой проблемы. Решаемая проблема является частью самого паттерна. Без неё он не может существовать.
В примерах кода я не увидел ни одного применения паттерна.
А так это выглядит как мартышка и очки. "Очки не всегда полезны, потому что их применимость ограничена и многие их зря фанатично применяют".
То есть, треш и угар порождается не при слепом следовании какой-либо лучшей, доброй и гениальной концепции, а при следовании искажённому представлению о концепции в голове. Обратная сторона или же применимость является частью концепции, а не находится где-то снаружи.
UPD: поэтому мой голос за то, что автор не понимает ООП и не знает паттернов проектирования.
А ничего что паттерны это лишь советы а не план для действия?
Ни то, ни то. "Паттерны" - это набор именований для часто используемых решений в SE. Значительная часть святого пула паттернов изобретается самостоятельно при первом столкновении с определённой проблемой.
Вот, кстати, да. Либо потому что сталкивался с ним в библиотеках или другом коде, который изучал. И когда появляется похожая проблема решение обычно очевидно, это вообще как-то интуитивно всё. А вот если специально их "изучать" на каких-то обычно очень искуственных примерах, потом пытаться применять везде где не надо получается фигня.
Имена паттернам дают не для того, чтобы их изучать, а для того, чтобы были готовые названия для объяснения коллегам. И вот тут без стандартизации не обойтись.
То есть, треш и угар порождается не при слепом следовании какой-либо лучшей, доброй и гениальной концепции, а при следовании искажённому представлению о концепции в голове.
Как отличить одно от другого? Если человек имеет искажённое представление о концепции, из-за чего не видит границ её применимости или намеренно игнорирует их - то это и выглядит как фанатизм и слепое следование.
В результате человек начинает, например, воевать против синглтонов как таковых. Или против исключений как таковых.
После ваших слов захотелось пересмотреть фильм "Догма".
Любая концепция, доведенная до абсурда, является совершенством с точки зрения этой концепции.
ООП, ФП с монадами, SOLID и прочие практики нужны для того, чтобы в первую очередь управлять сложностью. И они не приносят пользы при отсутствии сложности. Поэтому нет смысла приводить примеры, где нет сложности - они будут абсурдными. Что и получилось в этой статье.
Проблемы 3:
Это не управление сложностью, а перекладывание ее по разным ящикам, ибо сложность - это всегда константа, это понимаешь с опытом - т.е. что-то дает возможность просто сделать одно, в тоже время дает сложность сделать что-то другое - утрированный пример - сделал все по всем лучшим принципам - хорошо расширяется, тестируется, мокится - но вместо 1 файла получил 15 с хитрой связкой компонент и вложенности их друг в друга, и новый дев вникает в это месяц, а сам ты, вернувшись в свой код через год - два :)
Попытка использовать везде одни и теже инструменты и подходы. Типа: "ООП и СОЛИД хорошо - везде про это написано, давайте и тут запилим" - а там одна функция из 10 строк во временном скрипте, который один раз в жизни должен выполнится для получения временых данных, которые будут использованы один раз - зато круто и расширяемо и по бест практис :)
Большинcтво компонентов софта не живет больше 1-2 лет, за исключением энтерпрайза и некоторых других исключений, не надо думать и закладывать туда все возможное и делать максимально универсально, нужно всегда делать балансируя между достаточной универсальностью и простотой - и именно это и есть золотая грань, которую да - очень сложно держать. Не надо бояться переписывать и рефакторить - иногда проще и быстрее переписать компонент с нуля на современном фрейморке и в современной парадигме, учтя опыт его текущего использования и контекста, чем пытаться патчить/расширять существующий.
Ну и, всегда стоит помнить статью "Вы - не Гугл" :)
Большинcтво компонентов софта не живет больше 1-2 лет
эт конечно вы загнули) БОЛЬШИНСТВО компонентов живет 5-10-15 лет, к сожалению...
Не надо бояться переписывать
пока по башке не получили от продакта или собственника бизнеса
Про 1-2 года.... я даже услышал как WordPad/notepad/calc икнули)
Огромное количество кода живет и поддерживается более 10 лет. И паттерны как раз в этом случае очень полезны. Если кода много и это не однодневка.
Вообще борьба с паттернами связана с ростом популярности Javascript, в котором нет нормальных полноценных классов с множественным наследованием, нет полноценных метаклассов.
Борьба идет не с паттернами, борьба идет с применением паттернов там, где оно и нафиг не надо, от чего страдает читаемость кода. В старых проектах это особенно актуально, когда одна команда накручивает зубодробительную архитектуру, а спустя N лет другая пытается ее распутать и свести концы с концами, которые просто так не сводятся из-за over9000 оберток и универсальных молотков, которые так и не были использованы.
Причем, от старой команды уже никого не осталось, а в лучшем случае есть пара человек, которые однажды длинной палкой тыкали в этого мамонта.
ЗЫ. А множественное наследование классов - это вообще злейшее зло, особенно в "умелых" руках.
Я плюсанул в целом, но наезда на множественное наследование не понял. Если не делать ромбов и прочих странностей, то бывают такие случаи неоднократно и при непересечении функциональности двух баз воспринимается и работает совершенно нормально.
Так я как раз про очумелые ручки и писал. Сначала разделят так, что функционал пересекается, потом накрутят поверх разрешение этих проблем, а спустя несколько лет, а то и десяток, все дружно грызут этот кактус, пытаясь разобраться как же оно все таки работает, потому что оно уже далеко не так очевидно. А предпосылки и особенности уже почти никто не знает, потому что разработчики сменились естественной текучкой.
За продобные призывы Ктулху уже не один десяток лет очень не любят множественное наследование и по рукам бьют, когда его бездумно применяют в ущерб KISS.
Можно купить молоток и успешно забивать гвозди. Можно также бить по пальцам или (теоретически) совершать противоправные действия. Но это не повод хейта в сторону молотка как инструмента.
Надеюсь, аналогия понятна.
В данном случае ближе будет аналогия с бейсбольной битой на постсоветском пространстве. Да, есть отдельные владельцы, которые играют в бейсбол, но большинство покупают для использования совсем не по назначению. ))
Аналогично и с множественным наследованием классов, да есть определенный процент тех, кто действительно понимает как и зачем его использует. Проблема в остальных, которые уже 4 десятка лет используют его в основном для выстрелов в ногу.
Причем, эта жесткая нелюбовь к множественному наследованию передается уже много поколений плюсовиков, т.к. зачастую само множественное наследование происходит из-за изначально переусложненной архитектуры, слишком сильно перегруженной абстракциями ради абстракций.
Вообще борьба с паттернами связана с ростом популярности Javascript, в котором нет нормальных полноценных классов с множественным наследованием, нет полноценных метаклассов.
Применение паттернов ООП в JS возможно, особенно при использовании TypeScript. Просто джаваскрипте ООП это часто не самый удобный способ решения проблем, поэтому рождаются альтернативные паттерны.
И паттерны как раз в этом случае очень полезны.
Полезны адекватные архитектурные решения. Если они укладываются в конкретное название, которое было дано кем-то типа GoF - хорошо, если нет - увы, селяви, но лучше делать то, что полезно, а не то, что кто-то назвал.
У GoF, например, описан "строитель". Но это внешний строитель. Я его видел в разы реже, чем внутреннего строителя или хранилища параметров для строителя. Верю, моя специфика.
Чего это "не поверю"? Для меня и GoF не авторитет (почему именно эти 23 или сколько там из сотен?), и выбор 5 букв SOLID ради красивой аббревиатуры не аргумент. О том и говорю.
Но ещё они хороши как учебные примеры. Как сортировки: дай бог чтобы один из 50 вообще применил их в явном виде, но учить алгоритмическому мышлению - они до сих пор один из лучших дидактических комплектов.
В этот же перечень можно добавить COBOL, Fortran, которым далеко за 60, и которые до сих пор являются основополагающими во многих областях. COBOL никак не могут заменить в банковской сфере, а на FORTRAN-е написано огромное количество научных программ, переписать которые тоже проблематично. И вот эти пенсионеры не используют ни классы, ни паттерны, ни SOLID и никакие другие аналогичные вещи. И разобраться в коде COBOL-а, Fortran-а ой как проблематично. То, что выдается за хороший код и называется хорошим, это больше вопрос привычки. Если человек привык работать с определенными операторами и они у него хорошо отложились в голове, то для него и код хороший. И попробуйте в этот код добавить, всеволишь, несколько незнакомых операторов. Крику, возгласов, эмоций будет выше нормы, мягко говоря. И, пожалуйста, не надо привычку - работать с определенными операторами и по определенным правилам - выдавать за хороший код. Все эти новомодные тенденции, такие как классы, паттерны и прочее, так сильно нагружают систему, что говорить о качестве кода не приходиться. Хороший код не может быть простым.
И вот эти пенсионеры не используют ни классы, ни паттерны, ни SOLID и никакие другие аналогичные вещи. И разобраться в коде COBOL-а, Fortran-а ой как проблематично.
Может, тут есть какая-то связь?
То, что выдается за хороший код и называется хорошим, это больше вопрос привычки.
Да не, ерунда какая-то.
Может, тут есть какая-то связь?
Вообще-то нет. Чтобы понять что-то, написанное на том же COBOL, нужно, во-первых, знать сам COBOL (не "читать со словарем", но уверенно на нем писать), во-вторых, понимать логику бизнес-процесса - что этот код делает и почему именно так, ну и в-третьих, еще понимать особенности платформы на которой все это работает.
Все остальное ничуть не сложнее чем найти ошибку в одном из 100500 базовых классов (для чего нужно перелопать соответствующее количество исходников). А потом ее исправить так, чтобы везде, где этот класс используется, условные котики не превратились в условных рептилоидов.
Так что вопрос знаний, пониманий и привычки на самом деле.
Скорее так: слишком простой код вряд ли будет полезным.
Хороший код не может быть простым.
Лучший код написан так, что кажется, что написать такой код проще простого. Это можно сказать про любой продукт.
Я не очень люблю PHP (много лет писал на нём), но я восхищаюсь тем, как написан код и документация Laravel. Это полноценный веб-фреймворк, но читается и понимается он легко.
COBOL никак не могут заменить в банковской сфере
IBM давно запустила инициативу перегонки с COBOL на Java и подталкивает к этому тех, кто не хочет / не может тратить средства на постоянное поддержание "лора" такой инфраструктуры.
Насколько я знаю, 90+% практической специфики COBOL не в алгоритмах или особых возможностях языка, а в заточке на конкретный тип операций - десятичная арифметика с фиксированной точкой. Для них нужна библиотека и внимательный перевод со всеми тонкостями. Если где не могут его заменить, то не потому, что это типа невозможно или значительно что-то удорожает, а потому, что разовые CapEx на перевод и страх за результатсчитаются худшим вариантом, чем постоянные OpEx на поддержание текущего, даже если представитель IBM давно поселился в конторе.
И вот эти пенсионеры не используют ни классы, ни паттерны, ни SOLID и никакие другие аналогичные вещи. И разобраться в коде COBOL-а, Fortran-а ой как проблематично. То, что выдается за хороший код и называется хорошим, это больше вопрос привычки.
Им обычно и не нужен ООП и всё, что за ним тянется. "Паттерны" обычного понимания это артефакты одновременно сложности задачи не в виде "как для Лабрадора считается налог с продажи лыж для собачьих упряжек для некоммерческой женской организации америндов", а множества типов объектов и разнородных связей между ними, и ООП, который призван это решать и задаёт собой определённые подходы к решению. Поэтому именно таких проблем там просто нет. (Конечно, можно и на этих двух языках пытаться написать веб-сервер форума, к примеру. Но обычно этого таки не делают.)
Хороший код не может быть простым.
Запросто может. Почти весь хороший код простой. Исключения - ужаты в специализированные места.
Ох, доводилось мне видеть компоненты, которые писали джуны "на один раз", которые (джуны в смысле) сбегали через годика полтора-два. Собственно, если ты раз в два года меняешь работу в очередной стартап, то не просто успеваешь застать долгоживующие компоненты.
Ну 5 лет нигде не видел, видел больше пока работает старый принцип: «Работает - не трогай»
А переписывается ибо всегда есть эффективный менеджер который приложение с формочками предлагает переписать на Uniti3D - модно, молодежно, кроссплатформенно. А софт у нас CRM… такое видел и плакал доказывать бред таких решений. Плакал от смеха над дибилизмом.
Ну 5 лет нигде не видел, видел больше пока работает старый принцип: «Работает - не трогай»
Естественно. Потому что любое изменение кода тащит за собой регресс-тесты. И бом больше система, тем больше объем этих тестов. В результате вы тратите час на минорные изменения небольшого участка кода, а потом две недели уходит на ретест (компоненты-бизнес-нагрузка-интеграция-прелайв...) потому что этот модуль mission critical и используется из 100500 других mission critical модулей...
Отдельный вопрос, что часть паттернов и архитектур буквально направлено на то, чтобы не переписывать.
В том смысле, что если тебе нужно добавить что-то, то ты пишешь новый код, а не переписываешь старый, и это нормально встраивается.
Как раз раскладываение кода по разным ящикам - один из способов управления сложностью.
Это не управление сложностью, а перекладывание ее по разным ящикам
Так ведь divide et impera
Вам вероятно не попадались проекты такого размера, что он не может уместиться целиком в голове одного человека (ну во всяком случае новичек в команде не сможет преисполниться за вменяемое время)
Большинcтво компонентов софта не живет больше 1-2 лет, за исключением энтерпрайза и некоторых других исключений, не надо думать и закладывать туда все возможное и делать максимально универсально.
Тут что-то на фронтэндерском, не получается прочитать.
Про "сложность - это всегда константа", честно говоря не понял, что это значит?
Значит что как не дроби проект сложность всегда одна, но при дроблении его части становятся меньше, вопрос взамодействия лишь.
Если я правильно вас понял, та сложность, про которую вы говорите, не существует объективно. Но вот мы декомпозировали это на много мелких кусков, решили каждый, соединили и в итоге решили поставленную задачу. Применив некую методологию мы снизили субъективную сложность задачи для конкретного мозга, который эту задачу решает.
Ну в том и заключается процесс - разбить большую нереализуемую задачу на много мелких реализуемых :-)
... а потом не запутаться в их композировании обратно
А вот это уже вопрос правильного разбиения и минимизации количества зависимостей. И это уже не зависит от того, ООП там или не ООП.
Я бы тут уточнил, наверное, что это уже вопросы к конкретной методологии, которая призвана снизить сложность, будь то ООП или что-то другое
Минимизироваться должна связка {количество компонентов, сложность каждого компонента, количество связей конкретного компонента, общее количество связей, длина пути между функционально связанными компонентами, наверняка что-то ещё}.
Архитектуру можно считать относительно успешной, если интегральный рост этих метрик медленнее, чем функциональность проекта.
Дальше может быть дискуссия как это всё считать, но я бы не хотел открывать этот ящик Пандоры.
Ну мы работаем в модели, близкой к модели акторов. Т.е. логическая функция - это отдельный бинарник.
Стек позволяет - тут процедура (функция) на уровне языка может быть где угодно - в том же коде, в сервисной программе (аналог динамической библиотеки) или в виде отдельной программы. Нужно просто правильно указать в прототипе - extproc для сервисной программы (с подключением нужной binding directory - аналог библиотеки импорта) или extpgm с указанием имени программы если это отдельная программа.
Это позволяет
Локализовать всю логику и внутренние данные внутри отдельного бинарника, оставив снаружи только интерфейс с входными и выходными параметрами (контракт)
На этапе тестирования делать мок для потребителей, тестировать логику модуля без привязки к потребителю
При необходимости изменения логики (или оптимизации) делать это только в рамках одного модуля, не затрагивая (совсем) потребителей (при условии сохранения контракта), которых могут быть десятки или сотни.
Работать с компактными модулями, не более 1000 строк в среднем (бывают и больше, но не часто).
Но это, опять, не про ООП, а про общую архитектуру.
Не только субъективную, но и объективную в первую очередь. Меньше состояний и переходов в КА - проще. Ну и не всегда, но как правило, субъективная сложность очень сильно коррелируется с объективной.
Что-то созвучное с анекдотом "Разум на Земле - величина постоянная. А население растёт."
Сложность не константа. Для того кто написал и помнит карту кода, контекст проблемы это не сложно. Когда он уйдет другим будет сложно потому что с уходом создателя код превращается в тыкву легаси.
Откуда у вас цифра, что сложность всегда константа? Что такое сложность тогда?
Сразу вас направлю к теории автоматов. И к иследованиям про сложность (например, what makes rules complex)
Сделайте любой запрос по сети с использованием какойто библиотеки (нр request в питоне)? Сложно? А теперь представьте сколько деталей от вас скрыли, в том числе благодаря ооп, чтобы реально сделать такой запрос. Сможете перечислить сколько всего происходит при вызове?
>Большинcтво компонентов софта не живет больше 1-2 лет
Я тут я вспоминаю систему на Visual Basic 6 которая по информации от бывших коллег живет до сих пор, все собираются переписать, да все денег на это нет. Там же система на Access. От DOS там вроде избавились, но подозреваю кое-где и он до сих пор живет. Про COBOL вроде уже написали.
С ООП головного мозга сложность - вообще не проблема, что и показал автор.
Неужели вы не видели примеров, когда нужно создать фабрику через билдер, в фабрику передать аргументы каждый своим билдером, а результатом будет новый билдер, который надо передать...
В общем-то слепое следование догматам никогда ни к чему хорошему не приведёт
Здесь скорее приведены примеры того, как не нужно добавлять сложностей там, где их не должно быть.
Я сам отнюдь не против ООП (и пользуюсь), но там, где оно помогает "упрощать сложности". Но бывают и обратные ситуации, когда ООП "не ложится на логику процесса" и вместо упрощения приведет к неоправданному усложнению. Вот такие ситуации надо понимать и не зацикливаться только на ООП, но выбирать иные, более подходящие, парадигмы.
Проблема в том, что слепое следование принципам само по себе создаёт сложность. В результате для управления этой сложностью приходится добавлять ещё больше сложности. И так до победного конца
Не столько управление сложностью, сколько изоляция одного куска кода от другого, а одного программиста от другого. И вот изоляция в ООП очень неудобна, тяжеловесна. А если границы абстракций надо перенести превращается в ужас.
ООП хорошо только когда объект моделирует что-то в реальном мире. Когда появляется руководитель отдела сусликов, который уточняет задания и ставит им задачи, то это фабрика. Но обычно в реальном мире такой фигнёй не маются.
ООП хорошо только когда объект моделирует что-то в реальном мире
Вот! Тоже хотел про это написать. Именно с таким подходом сталкивался в реалии когда делали систему мониторинга инженерного оборудования зданий. Там была аппаратная часть (сеть контроллеров, к ним подключены устройства, устройства расположены на объектах...) и программная часть, работающая на компе в диспетчерской.
Так вот когда реализовали иерархию классов (устройство, контроллер, объект и т.п.) со всеми свойствами и методами, вся архитектура ПО стала очень прозрачна т.к. она повторяла архитектуру реального железа - есть объект, у него есть список устройств, каждое устройство связано с контроллером и т.д...
А вот когда работаешь с большим объемом разнородных данных (миллиарды записей, десятки тысяч форматов) и у тебя есть несколько десятков тысяч бизнес-процессов, каждый из которых использует свой, очень ограниченный поднабор данных (выборка), то тут ООП уже не ложится. Просто потому, что никаких общих для всех базовых классов тут не построить - их будут слишком много и каждый из них получится или уникальным для конкретного процесса, или дико избыточным чтобы его можно было использовать в нескольких процессах.
Это советы бля решения, согласен, но часто увы бездумно их применяют и бездумно их требуют.
Да, есть альтернативные подходы, которые, часто, оказываются удобней https://returns.readthedocs.io/en/latest/pages/quickstart.html
Дальше начинаем смотреть на всякие https://github.com/ebonnal/streamable потом https://github.com/tobgu/pyrsistent https://github.com/miiohio/ziopy?tab=readme-ov-file и хочется менять язык))
Хорошо бы и промпт для ChatGPT приложить. Мы же на Хабре, интересуемся исходниками.
Универсальный ответ в 2025 году и далее: ))))
Вот пример промпта, который можно использовать для генерации подобной статьи:
Промпт:
Напиши юмористическую статью в стиле медицинского анализа о "болезни" под названием "ООП головного мозга". Описывай симптомы, примеры кода и методы лечения. Статья должна быть сатирической, но при этом содержать реальные примеры из мира программирования. Включи следующие разделы:
Симптомы болезни:
Чрезмерное использование паттернов проектирования даже в простых задачах.
Отрицание процедурного и функционального программирования.
Превращение каждой функции в метод класса, даже если это не нужно.
Примеры кода:
Простой код, который больной переписывает с использованием паттернов (например, замена простого условия на стратегию).
Пример переписывания простой функции сложения чисел в сложную иерархию классов.
Методы лечения:
Шоковая терапия (например, написание кода на чистом C или Go).
Введение в рацион функционального программирования (Haskell, Elixir).
Чтение кода известных программистов, которые ценят простоту.
Временный запрет на использование DI-контейнеров и фабрик.
Написание простых процедурных скриптов для осознания их эффективности.
Прогноз:
В лёгких случаях пациенты могут вернуться к нормальному программированию.
В тяжёлых случаях остаются архитекторами на Java и требуют рефакторинга даже у домашнего ToDo-списка.
Заключение:
Подчеркни, что "ООП головного мозга" – это опасное расстройство, приводящее к избыточной сложности кода.
Добавь шутку о том, что если коллега пишет адаптер для вывода "Hello, World!", то нужно срочно вызывать специалиста.
P.S.:
Упомяни, что текст написан ChatGPT, но он настолько хорош, что им стоит поделиться.
Опрос:
Добавь шуточный опрос в конце: "Действительно ли существует такая болезнь?" с вариантами ответов:
Да, иногда встречаюсь.
Я сам тяжело болен.
Автор просто не понимает ООП и не знает паттернов проектирования, пытаясь казаться умнее других!!!
Этот промпт должен помочь сгенерировать статью в нужном стиле.
промпт размером почти как сама статья))
Получилось очень похоже.
Отличия
Симптомы болезни:
Паттернизация всего и вся: ОГМ проявляется в виде маниакального стремления применить паттерны проектирования в каждой, даже самой простой задаче. Больной с ОГМ не напишет простой цикл – он реализует итератор. Ему не нужно условие
if
, ему нужна стратегия!ООП-фундаментализм: Наблюдается стойкое отрицание процедурного и функционального программирования. Любые попытки убедить пациента в преимуществах других подходов встречают категорическое сопротивление и аргумент “но это же не ООП!”.
Методизация всего: Любая функция, даже предназначенная для единичного действия, автоматически становится методом класса. “Почему бы не сделать класс
Printer
с методомprintMessage
?” - спрашивает больной, в то время как простойprint("message")
был бы в разы лаконичнее.
class AgeCheckStrategy:
def check(self, age):
pass
class AdultStrategy(AgeCheckStrategy):
def check(self, age):
if age >= 18:
return "Совершеннолетний"
class MinorStrategy(AgeCheckStrategy):
def check(self, age):
if age < 18:
return "Несовершеннолетний"
class AgeChecker:
def init(self, strategy):
self._strategy = strategy
def check_age(self, age): return self._strategy.check(age)
age_checker = AgeChecker(AdultStrategy()) # А если нужно будет для пенсионеров? А для школьников?
result = age_checker.check_age(19)
Хотелось бы увидеть результат этого промта от Deep Seek.
Пожалуйста)))
Опиши болезнь «ООП головного мозга»
Ответ DeepSeek
Болезнь «ООП головного мозга» — это шуточный или сатирический термин, который не имеет отношения к реальным медицинским диагнозам. Он может использоваться в юмористическом контексте для описания состояния, при котором человек чрезмерно увлекается объектно-ориентированным программированием (ООП) и начинает воспринимать реальность через призму программистских концепций, таких как классы, объекты, наследование и полиморфизм.
«Симптомы» болезни:
Мысли в терминах ООП: Человек начинает думать о повседневных вещах как об объектах с методами и свойствами. Например, «человек.поесть(еда)» или «кошка.издатьЗвук()».
Наследование в реальной жизни: Попытки применить принципы наследования к отношениям между людьми или предметами.
Полиморфизм: Восприятие разных действий как «перегруженных методов» одного и того же объекта.
Абстракция: Чрезмерное стремление к упрощению и обобщению всего вокруг.
Инкапсуляция: Желание скрыть «лишние данные» о себе или окружающих, как в программировании.
«Лечение»:
Переключение на другие парадигмы: Изучение функционального программирования или других подходов.
Отдых от программирования: Временное прекращение работы с кодом и занятие другими хобби.
Юмор и самоирония: Осознание, что это шутка, и умение посмеяться над собой.
Если вы столкнулись с этим термином в серьёзном контексте, вероятно, это ошибка или шутка. В реальной медицине такого диагноза не существует. 😊
Опиши более подробно симптомы с примерами кода на Python
Ответ DeepSeek
Конечно! Давайте рассмотрим «симптомы» болезни «ООП головного мозга» более подробно, используя примеры на Python. Эти примеры будут юмористически иллюстрировать, как человек, страдающий этой «болезнью», может воспринимать реальность через призму объектно-ориентированного программирования.
1. Мысли в терминах ООП
Человек начинает описывать повседневные действия как методы объектов.
Пример:
class Человек:
def __init__(self, имя):
self.имя = имя
def поесть(self, еда):
print(f"{self.имя} ест {еда}.")
def спать(self, продолжительность):
print(f"{self.имя} спит {продолжительность} часов.")
# Использование
я = Человек("Вася")
я.поесть("пиццу") # Вывод: Вася ест пиццу.
я.спать(8) # Вывод: Вася спит 8 часов.
2. Наследование в реальной жизни
Человек пытается применить наследование к реальным объектам или людям.
Пример:
class Животное:
def __init__(self, имя):
self.имя = имя
def издавать_звук(self):
pass # Абстрактный метод
class Кошка(Животное):
def издавать_звук(self):
print(f"{self.имя} говорит: Мяу!")
class Собака(Животное):
def издавать_звук(self):
print(f"{self.имя} говорит: Гав!")
# Использование
барсик = Кошка("Барсик")
барсик.издавать_звук() # Вывод: Барсик говорит: Мяу!
шарик = Собака("Шарик")
шарик.издавать_звук() # Вывод: Шарик говорит: Гав!
3. Полиморфизм
Человек видит полиморфизм в повседневных действиях, где разные объекты выполняют одни и те же действия по-разному.
Пример:
class Транспорт:
def двигаться(self):
pass # Абстрактный метод
class Машина(Транспорт):
def двигаться(self):
print("Машина едет по дороге.")
class Самолет(Транспорт):
def двигаться(self):
print("Самолет летит по воздуху.")
# Использование
транспортные_средства = [Машина(), Самолет()]
for транспорт in транспортные_средства:
транспорт.двигаться()
# Вывод:
# Машина едет по дороге.
# Самолет летит по воздуху.
4. Абстракция
Человек пытается абстрагировать всё вокруг, выделяя только самые важные свойства и методы.
Пример:
from abc import ABC, abstractmethod
class Напиток(ABC):
@abstractmethod
def приготовить(self):
pass
class Кофе(Напиток):
def приготовить(self):
print("Варим кофе.")
class Чай(Напиток):
def приготовить(self):
print("Завариваем чай.")
# Использование
напитки = [Кофе(), Чай()]
for напиток in напитки:
напиток.приготовить()
# Вывод:
# Варим кофе.
# Завариваем чай.
5. Инкапсуляция
Человек стремится скрыть «лишние данные» о себе или окружающих, как в программировании.
Пример:
class Человек:
def __init__(self, имя, возраст):
self.__имя = имя # Приватное свойство
self.__возраст = возраст # Приватное свойство
def рассказать_о_себе(self):
print(f"Меня зовут {self.__имя}, мне {self.__возраст} лет.")
# Использование
человек = Человек("Анна", 30)
человек.рассказать_о_себе() # Вывод: Меня зовут Анна, мне 30 лет.
# print(человек.__имя) # Ошибка: доступ к приватному свойству
6. Композиция и агрегация
Человек видит отношения между объектами как композицию или агрегацию.
Пример:
class Двигатель:
def запустить(self):
print("Двигатель запущен.")
class Автомобиль:
def __init__(self):
self.двигатель = Двигатель() # Композиция
def поехать(self):
self.двигатель.запустить()
print("Автомобиль едет.")
# Использование
машина = Автомобиль()
машина.поехать()
# Вывод:
# Двигатель запущен.
# Автомобиль едет.
Заключение
Если вы заметили, что начинаете думать в терминах ООП даже в повседневной жизни, возможно, у вас «ООП головного мозга»! 😄 Но не переживайте — это всего лишь шутка, и такие мысли лишь свидетельствуют о глубоком погружении в мир программирования. Главное — не забывать иногда «отключать» программиста в себе и наслаждаться реальной жизнью! 🚀
В целом автор (или чатгпт) действительно прав, но именно относительно довольно простых задач.
Любую же, хоть немного более сложную задачку, например всем известный калькулятор, уже лучше начинать писать с ООП головного мозга, чем без онного, так как чаще всего сегодня этот просто калькулятор, перерастет уже в инженерный калькулятор, и окажется где нить близко к кору проекта, где поддержка онного без ООП будет очередной головной болью.
А так, да. Крайности- это всегда трагедия. И ООП не исключение.
Ну вот на примере простого "калькулятора" я как-то показывал (те комментарии давно уже ушли в глубь стека, искать их лень сейчас) что можно и без ООП. И это будет ничуть не сложнее и точно также прозрачно. Более того, там все инициализируется в статике и без вызова многоуровневых конструкторов в рантайме.
Более того, есть достаточно сложные задачи где ООП не ложится на логику бизнес-процесса - этот подход добавит сложности и трудоемкости.
А бывает и наоборот... И с таким тоже работал.
сейчас сложно найти человека который все это знает и применяет. поавляющее большинство изучают оин патер - kiss. и весь их код - реализация в лоб невзирая ни н что.
сейчас сложно найти человека который все это знает и применяет. поавляющее большинство изучают один патерн - kiss. и весь их код - реализация в лоб невзирая ни н что.
Проблема этого явления кроется в том что многие люди считают что они знают будущее могут считать что легко угадают куда дальше пойдет то или иное решение. И действительно иногда это работает, и заранее подготовленная подушечка выручает через время, но бывает и наоборот. Я бы сказал что оптимальная практика в данном случае начинать решение задачи всегда с самого простого решения, да оно будет не расширяемым/оптимальным/т.п. но оно будет решать задачу на момент ее возникновения. Т.е. сегодня задача решить 2+2, делаем одну функцию которая просто делает 2+2 внутри. Через неделю бизнес понял что 2+2 не достаточно и надо решить задачу 2+2*x/100, окей мы вернулись к нашей функции и добавили параметр x и обновили формулу. Да пример примитивный но на самом деле в реальности такой подход довольно часто работает, я и сам его применяю. Новый модуль делается максимально просто потому что и бизнес не всегда понимает что в нем будет до конца, а потом по мере того как модуль начнет обрастать деталями можно рассмотреть какие подходы и паттерны могут помочь с его решением и если подходят то уже внедрять. Переписывать несколько раз новые модули это обычная практика. А вообще могу сказать что людей с похожими заболеваниями тоже много, например "Микросервис головного мозга" и "SOLID головного мозга".
Когда проблема в самом деле уровня "2+2", это звучит гладко и в любой момент можно внести дешевые правки. Но бывает и такое, что при решении вы закладываете архитектуру, по которой потом пишется тонна кода. И вот тут добавление постфактум некоторой гибкости, требующей перекраивания архитектуры, может стоить очень дорого. Тогда под давлением бизнеса с большой вероятностью начинается грязь с запихиванием невпихуемого, натягиванием сов неподходящих концепций, копипастой таких же коней, но без крыльев и т.п. И оправданий, что это не мы говнокодеры, а бизнес денег зажал на нормальный рефакторинг/БА накосячил/etc. Хотя потенциальные "точки роста" могут быть очевидны с самого начала.
Так что здесь проблема - в развитии навыка предсказывания будущего, а не в слепом следовании одной из крайностей. Это требует плотного погружения в предметную область, тесного общения с заказчиком и накопления некоторого багажа опыта.
в развитии навыка предсказывания будущего
что это за навык такой? как его развить? у Вас развит? Если да то расскажите сколько раз он Вам помог и в чем?
Это требует плотного погружения в предметную область, тесного общения с заказчиком и накопления некоторого багажа опыта.
Полное погружение в предметную область возможна при full time работе на одном проекте и прямые контакты всей команды с заказчиком что уже звучит мало реалистично и применимо скорее к продуктовым командам и финтеху (потому что и там и там заказчик обычно сидит если не в той же комнате то точно в том же здании).
Полное погружение в предметную область возможна при full time работе на одном проекте
Поэтому на достаточно сложных проектах обычно работают специально выделенные команды, а не мимопробегающие фрилансеры. А на типовых проектах - типовые сценарии развития, там настолько много внимания не требуется.
прямые контакты всей команды с заказчиком что уже звучит мало реалистично и применимо
Вполне укладывается в концепции SCRUM, где вся команда на равных обсуждает требования с PO.
потому что и там и там заказчик обычно сидит если не в той же комнате то точно в том же здании
Средства связи в наше время уже достаточно неплохо развиты, чтобы устраивать телефонференции хоть через океаны и материки.
Поэтому на достаточно сложных проектах обычно работают специально выделенные команды, а не мимопробегающие фрилансеры. А на типовых проектах - типовые сценарии развития, там настолько много внимания не требуется.
Ну т.е. "типовые" и "сложные" проекты никогда не пересекаются и существуют в каких-то разных мирах?
Вполне укладывается в концепции SCRUM, где вся команда на равных обсуждает требования с PO.
Обсуждают реализацию требований а не сами требования.
Средства связи в наше время уже достаточно неплохо развиты, чтобы устраивать телефонференции хоть через океаны и материки.
Ну окей Ваш заказчик доступен по Вашему местному времени с 23 до 3 часов ночи. Связь решила эту проблему? :)
Обсуждают реализацию требований а не сами требования.
И сами требования тоже. PO приходит с такой-то проблемой, а уже требования по тому, как эту проблему можно решить - обсуждается с командой и выводятся требования к задаче.
Например, может получиться так, что проблему решить можно, не разрабатывая ничего и не создавая новых задач. Такое тоже бывает
что это за навык такой? как его развить? у Вас развит? Если да то расскажите сколько раз он Вам помог и в чем?
Вы утрируете. В той или иной степени навык предсказывания будущего есть у каждого. Да хоть на уровне "завтра настанет утро и я пойду на работу". А развитие этого навыка по отношению к проработке архитектуры достигается за счет знаний, опыта, набитых шишек, а также погружения в предметную область.
Учитывая вашу дотошность, упомяну, что под предсказыванием будущего имеется в виду не "бл* буду точно произойдет!", а оценка вероятности возможных будущих событий, а также принятие соответствующих решений в тех случаях когда оценочная вероятность превышает, например, 50%
В нормальной команде должен быть такой предсказатель. И называется он "архитектор".
PO приходит с такой-то проблемой, а уже требования по тому, как эту проблему можно решить - обсуждается с командой и выводятся требования к задаче.
Еще раз - Обсуждают реализацию требований (т.е. как решить проблему с которой пришел заказчик) а не сами требования (то что заказчик пришел с этой проблемой в принципе и надо ли оно ему вообще). Не вижу здесь противоречий, Вы написали ту же мысль.
Вы утрируете. В той или иной степени навык предсказывания будущего есть у каждого. Да хоть на уровне "завтра настанет утро и я пойду на работу". А развитие этого навыка по отношению к проработке архитектуры достигается за счет знаний, опыта, набитых шишек, а также погружения в предметную область.
Это не навык предсказания будущего, это просто опыт. Навык предсказания будущего как раз был бы если бы Вы придя в новую для себя предметную область, новый тип проекта и новую область разработки могли что-то там "предсказать" и это работало.
то что заказчик пришел с этой проблемой в принципе и надо ли оно ему вообще
Обсуждают и то и то. В том числе могут ему ответить что ему это не надо. или надо но с другой командой
Это не навык предсказания будущего, это просто опыт. Навык предсказания будущего как раз был бы если бы Вы придя в новую для себя предметную область, новый тип проекта и новую область разработки могли что-то там "предсказать" и это работало.
Вы подменяете понятия. Предсказание будущего выходит далеко за рамки экстрасенсов. Любое адекватное предсказание будущего происходит на основе опыта. Вы предсказываете что завтра настанет утро - на основе вашего опыта, что оно уже много раз наставало в одно и то же время
Учитывая вашу дотошность, упомяну, что под предсказыванием будущего имеется в виду не "бл* буду точно произойдет!", а оценка вероятности возможных будущих событий, а также принятие соответствующих решений в тех случаях когда оценочная вероятность превышает, например, 50%
Уж простите за дотошность но обычно в контексте "предсказаний" единственный критерий их результата это "сбылось" или "не сбылось" причем он проверяется когда-то потом в будущем по факту сего действа. Все ладно не буду душнить сойдемся на том что это стоит все же более корректно называть "планирование будущего" чем предсказание :)
"Планирование" тоже неверный термин. Планирование это то на что вы, как правило, можете повлиять чтобы оно сбылось. А еще нужно делать предположения и прогнозы тех событий, которые произойдут или не произойдут независимо от вашей воли. Так что думаю правильно здесь будет употреблять термин "прогнозирование"
Ну вообще-то общение с заказчиком можно прервать с момента утверждения BRD (бизнес-требования) и до момента передачи ему продукта на бизнес-тестирование.
Если заказчик не может сразу внятно сформулировать что он хочет, процесс может превратиться в полный аджайл с неопределенными затратами сил и времени.
"типовые" и "сложные" проекты никогда не пересекаются и существуют в каких-то разных мирах?
Для меня это звучит противоположно. Наверняка есть множество градаций той или иной меры кастомности и сложности. Но что именно вы хотите показать этим вопросом?
Обсуждают реализацию требований а не сами требования.
Требования к приложению - это проекция хотелок заказчика на реалии приложения. Если эти хотелки можно реализовать лучше и/или дешевле иным образом, то требования нужно поменять. Это как раз одна из целей обсуждения с командой.
Ваш заказчик доступен по Вашему местному времени с 23 до 3 часов ночи.
Временные рамки сильно утрирированы, но поздние митинги (часов в 7-8 вечера по Мск) с заказчиком из США - это прям типичная ситуация. Кому-то нравится сдвинутый на вечер рабочий график, кому-то - нет.
Требования к приложению - это проекция хотелок заказчика на реалии приложения. Если эти хотелки можно реализовать лучше и/или дешевле иным образом, то требования нужно поменять. Это как раз одна из целей обсуждения с командой.
Вроде бы у меня была простая мысль а мы до сих пор не на одной волне.
Временные рамки сильно утрирированы, но поздние митинги (часов в 7-8 вечера по Мск) с заказчиком из США - это прям типичная ситуация. Кому-то нравится сдвинутый на вечер рабочий график, кому-то - нет.
Может быть и утрировано (хотя на одной из моих работа был близкий к этому график) но чтобы по Вашей логике все работало то вся команда как минимум должна любить сдвинутый вечер, иначе как она (команда) целиком будет встречаться с заказчиком?
чтобы по Вашей логике все работало то вся команда как минимум должна любить сдвинутый вечер
Именно так. Разумеется, за такие неудобства обычно платят деньги.
Ну вот у меня вся команда (практически) в Москве. Я в Екатеринбурге (+2ч от Москвы). Ну просто фактический рабочий день на 9-18, а 11-20. Меня устраивает. Плюсом - официально ненормированный рабочий день, три дня к отпуску (или компенсация деньгами в конце года). Ну и еще плюсом - можно оформить "выход в выходные" за двойную оплату. Не каждый раз, но раз в месяц это проходит - заявку согласуют без вопросов. А уж буду я реально в эти выходные работать или нет - никто проверять не будет. Это "компенсация переработок" такая неофициальная.
вся команда как минимум должна любить сдвинутый вечер
Это нормально, бывают например другие случаи. Все рабочее общение по московскому времени, но часть сотрудников улетели зимовать на Бали (МСК+5), и работают оттуда до позднего вечера/ночи
Для меня это звучит противоположно. Наверняка есть множество градаций той или иной меры кастомности и сложности. Но что именно вы хотите показать этим вопросом?
Что градировать проекты бессмысленно, в "типовом" проекте может появиться сложная задача и "сложный" проект может наполовину состоять из типовых задач.
Выглядит так, что вы пытаетесь вывести одно универсальное правило на все случаи жизни. Это так не работает, всегда будет компромисс.
Джуны делят мир на черное и белое, это нормально)
Универсальное правило о чем? Я вообще то наоборот говорю что все проекты разные поэтому градировать их на типовые и сложные или еще какие-то бесмысленно.
Расскажите как происходит "полное погружение" в предметную область, мне серьезно интересно. Вот допустим сегодня Вы начинаете новый проект знания о предметной области которого у Вас равны нулю. Какие нужны действия команды для погружения? Допустим Ваш новый проект связан с бухгалтерией, Ваши знания ограничиваются что бухгалтерия это что-то связанное с расчетом денег (ну допустим). Допустим заказчик присылает Вам 200 отчетов которые должны генериться Вашим проектом с минимальными объяснениями на бухгалтерском языке, все отчеты разные и нужны для разного. Какие Ваши и команды действия по погружению в предметную область и сколько времени это может занять? Как будет распределение погружения между командой, и если да то как потом погруженные сведения будут передаваться между командой? И в целом как выглядит процесс?
Бухгалтерские отчеты - это типовая задача, для этого даже полуфабрикаты в виде 1С есть, а то и готовые конфигурации. Да и то у местных успешных специалистов, насколько мне известно, ценится знание этой кухни. Бизнес обычно важен тогда, когда идет речь про автоматизацию бизнес-процессов. Скажем, если вы делаете систему онлайн-бронирования билетов между аэропортами, неплохо бы знать, как это должно работать с точки зрения процессов. Информацию вы вольны искать любым доступным способом, можете сами найти, может вам что-то передаст заказчик или предаставит специалистов, которые поделятся сакральными знаниями.
в целом как выглядит процесс?
Идеалистичную картину процессов можно подчерпнуть из литературы Эванса по DDD.
Информацию вы вольны искать любым доступным способом, можете сами найти, может вам что-то передаст заказчик или предаставит специалистов, которые поделятся сакральными знаниями.
Вопрос был как лично Вы и Ваша команда это делаете.
Бухгалтерские отчеты - это типовая задача, для этого даже полуфабрикаты в виде 1С есть, а то и готовые конфигурации
А что тогда не типовая задача?
Вопрос был как лично Вы и Ваша команда это делаете.
Так и делается, как описано, по ситуации.
А что тогда не типовая задача?
Еще раз выделю этот фрагмент:
Бизнес обычно важен тогда, когда идет речь про автоматизацию бизнес-процессов. Скажем, если вы делаете систему онлайн-бронирования билетов между аэропортами, неплохо бы знать, как это должно работать с точки зрения процессов
Мне кажется, у меня достаточно хорошо развит навык предсказания будущего кода. Помогают регулярные тренировки и понимание предметной области.
Но самое главное, что чем лучше умеешь предсказывать будущее, тем реже пишешь "расширяемый" код на пустом месте. Всё-таки намного проще расширять и модифицировать малое количество кода, условные 2+2, чем анализировать закрученные абстракции, которые требуют тотального и длительного погружения в голову автора.
И вот тут добавление постфактум некоторой гибкости, требующей перекраивания архитектуры, может стоить очень дорого.
Да. Но на практике может оказаться, что гибкость заложили не там где надо, а там где надо - её всё равно нет. А сделать гибким всё-всё-всё - надо очень сильно постараться и сильно усложнить проект.
Опять же, эта "гибкость" не идёт бесплатно.
Условно, я в коде находил примеры, когда на одни объекты навешивались слушатели, в другом месте была отправка событий в слушателей у каких-то объектов, при рефакторинге люди обновляли и то и другое, и так продолжалось пару лет.
После чуть более глубокого копания выяснилось, что множества "объектов с хотя бы одним слушателем" и "объектов, всем слушателям которых отправлялись сообщения" никак не пересекались. И весь этот код с интерфейсами, ивентами, реализациями слушателей и списком слушателей у объектов, который несколько лет лежал и рефакторился, обновлялся и накладывал какие-то ограничения на окружающий код, можно просто взять и удалить.
И это было ни разу не очевидно, потому что оно было запрятано за кучей слоёв абстракций.
Да. Но на практике может оказаться, что гибкость заложили не там где надо, а там где надо - её всё равно нет.
Это уже навык предсказывания будущего, который у заложившего эту гибкость был не очень развит, или же сам бизнес настолько непредсказуем что вот так вот оно произошло.
Но это не значит что все теперь давайте совсем без гибкости, писать одноразовый говнокод, с хардкодом и хардкором
на практике может оказаться, что гибкость заложили не там где надо
Да, может. Но крутость/ценность специалиста в том, насколько часто его прогнозы сбываются. Писать код - это самое простое в программировании =3
А сделать гибким всё-всё-всё
Обе крайности гарантируют провал.
Что-то вспомнилась статья про хлеб https://habr.com/ru/articles/153845/ где так же пытались угадать то, что хочет заказчик.
Опять эти проливатели света на то как все неправы :-)
Ну, поехали:
1. Клиническая полиморфофилия
В данном конкретном примере нормальное такое разделение кода и контента. Клиентский код просто инициирует отправку сообщения, а уже принтер в соответствие с определенными условиями может вывести текст в консоль, либо отправить в мессенджер, либо, например, изменить сообщение в зависимости от языка пользователя.
2. Фабрики
Создание экземпляров через фабрики выглядит избыточным, пока вы не начали покрывать свой код тестами и тут внезапно оказалось, что такой экземпляр создаваемый в методе тестируемого класса - не мокается.
3. Наследование
Честно говоря, в реальной жизни ни разу не видел, чтобы кто-то маньячил по наследованию. Нормальные ООП-шники все-же за композицию
4. Ну, мешать стили в одном проекте считается плохим тоном, не?
1. Клиническая полиморфофилияВ данном конкретном примере нормальное такое разделение кода и контента. Клиентский код просто инициирует отправку сообщения, а уже принтер в соответствие с определенными условиями может вывести текст в консоль, либо отправить в мессенджер, либо, например, изменить сообщение в зависимости от языка пользователя.
Это вообще не про ООП. Тут правильнее разделить на две части - интерфейсный клиент, который посылает сообщение в очередь (в общем смысле - реализация может быть любой - pipe, mailslot, unix socket, *DTAQ/*USRQ и т.п.) и фоновый процесс (демон, как хотите назовите), который берет сообщения из очереди и отправляет их куда нужно.
При этом один демон может обслуживать несколько клиентов в т.ч. и в сети. И объединяет их только тип транспорта и формат посылки в очереди. И написаны они могут быть и без ООП, на процедурных (в т.ч. и разных) языках. И вы в любой момент можете поменять демона, не трогая клиента или клиента не трогая демона.
2. ФабрикиСоздание экземпляров через фабрики выглядит избыточным, пока вы не начали покрывать свой код тестами и тут внезапно оказалось, что такой экземпляр создаваемый в методе тестируемого класса - не мокается.
А потом случатся масштабирование и внезапно оказывается что ваши фабрики не вывозят по производительности. И что толку с того что их удобно тестировать, если в реальном проме они не работают как надо?
Тут правильнее разделить на две части - интерфейсный клиент, который посылает сообщение в очередь
Это же уровень транспорта, не? Тут про это это вообще речи не шло, ИМХО.
Возьмем, например, очередь сообщений и требование, что сообщение success будет в консоль выводиться зеленым, а сообщение error будет выводиться красным.
В коде клиента можно делать по-разному (условно):
$messageBus->sendSuccess('Все в порядке');
$messageBus->send('success', 'Все в порядке');
$messageBus->send(new Message('success', 'Все в порядке'));
$messageBus->send(new SuccessMessage('Все в порядке'));
Какой вариант более гибкий?
Если брать какой-нибудь RabbitMQ, то там возможны варианты как это будет в самом сообщении храниться.
Но, кстати, при разбиении на клиент-сервер вы начинаете нести инфраструктурные издержки, потому что кто-то же должен следить, чтобы процесс демона не упал (а еще сложнее, что он не завис) и перезапускал его в случае если что-то пошло не так.
А потом случатся масштабирование и внезапно оказывается что ваши фабрики не вывозят по производительности
Что ж там за фабрики такие, которые не вывозят? Вы уверены, что именно они являются узким местом? Потому что у меня есть предположение, что компилятор все равно это упакует "как будто и нет никакой фабрики". Я не думаю, что там прям такой значимый оверхед на исполнение будет.
Это же уровень транспорта, не? Тут про это это вообще речи не шло, ИМХО.
Нет. Это уровень архитектуры. Когда посылает один, а за "что с этим делать" отвечает другой. То самое
Клиентский код просто инициирует отправку сообщения, а уже принтер в соответствие с определенными условиями может вывести текст в консоль, либо отправить в мессенджер, либо, например, изменить сообщение в зависимости от языка пользователя.
о которым Вы говорили изначально. Т.е.
Клиентский код просто инициирует отправку сообщения
это клиент, а
а уже принтер в соответствие с определенными условиями может вывести текст в консоль, либо отправить в мессенджер, либо, например, изменить сообщение в зависимости от языка пользователя
это демон, который вообще отдельным процессом работает.
Возьмем, например, очередь сообщений и требование, что сообщение success будет в консоль выводиться зеленым, а сообщение error будет выводиться красным.
В коде клиента можно делать по-разному (условно):
$messageBus->sendSuccess('Все в порядке');
$messageBus->send('success', 'Все в порядке');
$messageBus->send(new Message('success', 'Все в порядке'));
$messageBus->send(new SuccessMessage('Все в порядке'));Какой вариант более гибкий?
Абсолютно по барабану. Тут не нужна какая-то особая гибкость. Ну вот как у нас делается. Есть модуль логирования. Он принимает сообщения с параметрами "от кого", "точка логирования", "тип сообщения" (ошибка-бизнес-трейс), "код сообщения", "данные сообщения". Все. Дальше уже по своим настройкам он смотрит сочетание "от кого", тип сообщения и "код сообщения" и решает надо ли это сообщение куда-то выводить сейчас, если надо то куда - в системную очередь сообщений, протокол задания (системны joblog) или таблицу (логи у нас в БД хранятся).
Никакой гибкости не надо. Более того
$messageBus->sendSuccess('Все в порядке');
Потребует от вас на каждый тип сообщения писать свой метод. А типов сообщений (у нас) может быть несколько десятков.
$messageBus->send(new Message('success', 'Все в порядке'));
$messageBus->send(new SuccessMessage('Все в порядке'));
Потребует при каждой посылке сообщения вызова конструктора и деструктора (с образованием нового уровня стека, а может и не одного если у вас там иерархия классов. В hi-load системах ну такое себе... И да. Нам о таких вещах полагается думать. Чтобы на нагрузочном тестировании лишних вопросов не возникало - "а почему у вас логирование занимает 20% от общего объема времени и утилизации CPU?".
Так что остается
$messageBus->send('success', 'Все в порядке');
самое простое и ресурсоэффективное решение.
'success' - тип сообщения
'Все в порядке' - данные сообщения.
И да, все это пишется на языке, где никакого ООП. Чистая процедурщина. И работает очень быстро.
if ECLAllowLog(ECL_CHECK: 'T');
ECLLogText(ECL_CHECK: '': 'T': 'Найдено '
+ %char(Count)
+ ' совпадений. Res: '''
+ Res
+ ''', ResS: '''
+ ResS
+ ''', ResW: '''
+ ResW
+ ''', ResB: '''
+ ResB
+ '''');
endif;
Тут еще есть AllowLog - проверка нужно ли сообщение с уровнем Т (трейсы) вообще лоировать. Чтобы не тратить время на склейку логируемой строки в тес лучаях, когда по настройкам трассировка отключена.
Ошибки (сообщения типа Е) логируются безусловно, там не нужна проверка, там просто
ECLLogMessage(ECL_CHECK: '': dsError);
где dsError - т.н. "структурированная ошибка" (7-значный код ошибки + набор данных - из них уже формируется текстовое сообщение - по коду берется текстовка и там в нужные места подставляются данные).
Разделение функционала тут на уровне блоков. Все логирование вынесено в "сервисную программу" - это типа динамической библиотеки. Вся логика работы с логом там. Из бизнес-логики просто вызываются интерфейсы.
Если нужно поменять логику логирования - просто меняем сервисную (с сохранением контрактов), сам бизнес-код вообще не трогаем - она как работал со старой версией сервисной программы, так и будет работать с новой.
Клиентский код просто инициирует отправку сообщения, а уже принтер в соответствие с определенными условиями может вывести текст в консоль, либо отправить в мессенджер, либо, например, изменить сообщение в зависимости от языка пользователя.
С какими такими "определёнными" условиями и где сказано их (хотя бы возможное) существование?
Мы так можем докопаться до мышей начать предполагать всё что угодно. Что не отправка текста, а отправка документа со смайликами и поэтессами. Что надо отправлять не в одно место назначения, а во много. Что оно должно быть обвешано этикетками разрешений и пятью слоями цифровой подписи. И прочая и прочая. Только где хотя бы намёк на это в будущем в ТЗ?
Точно так же и с остальными фабриками абстрактных синглтонов - статья не о том, где это нужно, а о том, что в какой-то заметной доле случаев это не нужно.
И да, я вычищал подобную избыточность от астронавтов...
пока вы не начали покрывать свой код тестами и тут внезапно оказалось, что такой экземпляр создаваемый в методе тестируемого класса - не мокается.
Ну, в Питоне замокать можно всё - а примеры именно на нём. Для другого языка можно, по обстановке, или тоже мокать в рантайме (Java позволяет), или при компиляции подставлять что-то другое.
Сначала консольное приложение превращается в демона и print нужно писать не в консоль, а потом сообщение на разных языках для разных пользователей. И да, Message надо через фабрику делать, и разумеется всё через DI.
Ну это уже разнородный перебор. print не в консоль - переписывается в генерацию кода типа сообщения и параметров к нему, угу, но не дело демона заниматься языками, на это есть отдельный компонент, который эти логи будет читать и превращать в читаемый вид. И настолько прямого превращения консольного приложения в демона я не видел, обычно при этом в логике выворачивается чуть менее, чем всё.
Честно говоря, в реальной жизни ни разу не видел, чтобы кто-то маньячил по наследованию.
Такого много в коде 90-х/00-х. Там разобраться в каком именно классе реализован тот и или иной функционал - боль.
В принципе, данная статья хорошая иллюстрация того что бывает, если взять любую идею и довести ее до абсурда через карго-культ. Не будет лишним упомянуть и то, что острая фаза эпидемии ООП головного мозга уже прошла где-то во второй половине нулевых (по некоторым оценкам - в первой), и уже не столь часто поражает неокрепшие мозги все новых поколений программеров.
Функциональное программирование: вводить в рацион элементы Haskell и Elixir.
С этим осторожнее, возможны побочные эффекты (ФП головного мозга).
С этим осторожнее, возможны побочные эффекты (ФП головного мозга).
Распишете с примерами? (можно ответной статьёй)
достаточно посмотреть на LISP (и аналоги) например )
https://habr.com/ru/articles/258825/
Если начинать любой проект с вот этого, получится примерно то же самое:
-- мне просто надо сложить два числа!
-- подожди, надо сначала определиться с концепцией числа!
Эта статья про лямбда-исчисление, не про то, как нужно писать на лиспе. С тем же успехом можно взять любую статью про пи-исчисление и натягивать тезис, что в языках с многопоточностью программисты всё переусложняют
Ну LISP-ы -- это не Haskell . Можно писать без ухода в крайности. Вот, например, какая читабельность может быть на примере Hylang (Lisp на Python) : https://github.com/KMiNT21/hylang-ml-examples
Вырожденным (на самом деле нет) примером ФП головного мозга лично для меня является вообще не писать никакой код, а вместо этого написать очередную статью про связь между монадами и аппликативными эндофункторами с претензией на связь с академической средой.
Это не болезнь, это проблема когда парадигмы другого языка (в данном случае скорее всего из Java) тянут туда где их применение опционально и не нужно поумолчанию
писать на какомто языке надо так - как это принято, а не как человек привык в другом языке
я проходил через это когда переключился с Java на Питон...пару лет понадобилось чтобы познать дзен питона и начать программить "попитонячьи"
но в целом я за ООП подход, был и останусь всегда
У меня подобное с C и C#. Писал я на C и меня попросили на C#. Почитал про него и ничего не понял зачем нужно ООП
Как же хабр скатился от вдумчивых статей на тему того, как действильно правильно кодить, до чатгпт сгенерированных призывов особо не думать и решать всё в лоб. Это убожество ещё и в плюсе, эх
EDIT: Ваш нейросетевой спам настолько задрал, что я, пожалуй, буду собирать черный список адептов нейросетей и отсутствия ООП:
https://habr.com/ru/articles/860656/ - первая ласточка
https://habr.com/ru/articles/877844/ - эта статья
Да, такие разрабочики есть. Если рассуждать неформально, кажется, что им недостает некоего здравого смысла, а главный критерий качества кода - соотвтествие букве священного писания (на выбор). С опытом обычно проходит, но это не точно...
ООП предназначен для работы со сложным, с большим кодом и запутанной логикой. Ясно, что использовать его для "Hello, world" - это бред, как покупать внедорожник для езды в офис, а потом жаловаться на расходы и большие габариты, что усложняют парковку.
ООП хорош там, где можно предметную область уложить в рамки ограниченного набора базовых классов и производных от них. Без лишней избыточности. В таком случае архитектура вашего ПО становится прозрачно, понятной и соответствующей предметной области.
Если этого сделать не удается, то все будет только хуже.
А большой код и сложную логику можно декомпозировать и без ООП (если не стоит задачи сотворить монолит на несколько миллионов строк кода в одном бинарнике). Те же акторы. Это другой уровень архитектуры и к парадигме реализации никакого отношения не имеет.
ООП головного мозга -- это спрашивать про него на собесе, на дата-аналитика!))
А чего ж про конструкторы-то забыли и паттерн, прости господи, пограммирования "запихни весь код в конструкторы" с последующим "попробуй найди, где навернулось до main();"?
Ох как же припекло автору )
Красиво получилось.
а будет продолжение про "ФП с монадами головного мозга", или этих трогать опасно они буйные?
Есть стойкое ощущение, что пора создавать отдельный HabrGPT. Мало того, что статьи по ИИ натурально заполонили ресурс, так ещё и те, что вроде бы по другим темам, оказывается, им написаны. Камон, пипл...
Честно говоря, был шокирован, узнав, что это написал gpt... Настолько интересно и увлеченно, по-человечески, написано.
Уверен, что и большинство прочитавших ничего бы не заподозрили, не признайся автор статьи в конце!
Какой промпт для chatgpt использовался?
Напишите пожалуйста аналогичную статью про микросервисы.
фабрика фабрик для фабрик это какой-то бред.
Я использую Фабрику Декораторов Фасадов.
билдер фабрики DI-контейнеров
Вы таки считаете, что ситуации, когда это может понадобиться, не может быть?
Пример: многопользовательское приложение, для разных групп пользователей нужно по-разному предоставлять сервисы через DI. Билдер определяет правила для фабрики, которая будет создавать на каждого пользователя свой дочерний контейнер.
А с ним-то что не так? Создавать DI-контейнер - сложно, нужен билдер. Если контейнер многоразовый - значит, нужна фабрика. Вот и получается, что свой билдер фабрики DI-контейнеров есть почти в любой библиотеке, предоставляющей этот самый DI-контейнер.
поэтому я пишу на С и пока не вижу никаких проблем) - вот 3д толи движок толи игра на С и по коду кайф просто)
это странно, что не видите проблем. Ведь ООП на чистом С требует довольно много бойлерплейта. Особенно с виртуальными функциями.
Не поделитесь вашей реализацией?
А кто сказал что на чистом С надо писать ООП?
чего там делиться то - указатели на функции как поля структуры.
А вот парные функции типа lock - unlock надо вручную следить за всеми местами выхода и расставлять при выходе из области видимости, деструкторов не хватает очень.
В общем все вручную.
вход/выход из видимости - это же больше не про ооп, тут можно наверное сборщиком мусора обойтись. Вон в джаве нет деструкторов, и ничего - фабрики фабрик клепать это никак не мешает.
тут можно наверное сборщиком мусора обойтись
да нет, как же можно обойтись если вам надо unlock вызвать при выходе из функции. Сборщик мусора непонятно когда отработает. Ну в смысле только вручную тогда расставлять вызовы.
С ООП действительно связано косвенно, просто реализуется через объект на стеке у которого определен деструктор.
то что
Для этого в Java есть метод finalize(), который вызывается перед уничтожением объекта сборщиком мусора. Но стоит отметить, что использование finalize() не рекомендуется, так как его вызов не гарантируется и может привести к непредсказуемым результатам.
это не самое главное, в Java нет понятия "выделение памяти на стеке" как такового.
да нет, как же можно обойтись если вам надо unlock вызвать при выходе из функции.
Блок on-exit в который попадаешь при любом (даже аварийном, по системному исключению) для функции/процедуры не поддерживается компилятором?
ООП для этого не нужен совершенно.
вы что-то мне кажется не о том, какое нафик аварийное, системное исключение, я говорил про все ШТАТНЫЕ выходы из функции,
имеется ввиду писать или не писать unlock (например) перед каждым return или вы предлагаете выходить из функции через ексепшен штатно то есть всегда? Но ексепшены все равно придется вручную расставлять, не понимаю о чем вы.
Есть конечно вариант оформить такой блок кода в котором должен действовать лок как функцию, но кроме вызова такой функции придется лок-анлок писать каждый раз, потом это не всегда оправдано с точки зрения производительности, особенно в ембедед.
Я тоже про штатные выходы. В нашем стеке есть такое - блок on-exit
dcl-s wasError ind;
…
return;
on-exit wasError;
if wasError;
…
endif;
…
end-proc;
В этот блок вы попадаете всегда перед выходом из функции. Это единая точка выхода. Где бы вы ни поставили return внутри вашей функции, попадете в этот блок. Есть это обычный штатный return, индикатор wasError будет *off.
Если у вас случилось необработанное системное исключение (например, тупо на 0 поделили) - вы попадете в этот блок, но при этом индикатор wasError будет установлен в *on (дальше, если вам нужна информация, в программе есть блок PSDS - Programm Status Data Structure - там можете посмотреть информацию о том что случилось - код ошибки, в какой строке и т.п. и где-то ее зафиксировать).
Вот это я имел ввиду. Единая точка выхода для любых ситуаций и вариантов завершения функции. Где вы можете делать любые unlock'и, dealloc'и и все что вам нужно.
В нашем стеке есть такое - блок on-exit
здорово, но я про ваш стек совершенно не ничего не знаю. Хорошо что вы эту проблему решили для себя, но я писал про более-менее известные языки и конструкции языков.
Ну, я думаю, это можно как-то реализовать руками. Правда, скорее всего, сложно сделать это для выхода через исключение (в нештатных ситуациях), но для штатных (множественный return), наверное, как-то можно придумать что-то аналогичное.
Ну, я думаю, это можно как-то реализовать руками.
руками можно сделать что угодно, с этим невозможно спорить!
Вопрос в том зачем изобретать велосипед, зачем игнорировать то что уже многократно проверено и работает безотказно. Причем игнорировать даже как бы с претензией: "Это мне не надо, я сам придумаю лучше!". Это же пренебрежение не какой-то техникой программирования (в данном случае), это пренебрежение опытом людей которые это придумали многократно обосновали и успешно использовали. Нельзя так относиться к чужому опыту, мне кажется.
Вы правы. Но чужой опыт всегда надо транслировать на свои реалии.
ООП хорошо, но в нем есть подводные камни. При неаккуратном использовании резко возрастают накладные расходы - множественное динамическое выделение/освобождение памяти, множественные вызовы конструкторов/деструкторов и при наследовании там еще и цепочки вызовов возникают. Все это не бесплатно.
Понятно что в десктопном приложении всем этим можно пренебречь ради удобства разработчика, но в hiload системах, где ваш процесс (и это всего лишь один процесс из десятков тысяч на сервере, у которого утилизация CPU в период пиковых нагрузок доходит до 90%) сравнивает 98 000 000 строк одного набора с 8 000 строками другого набора по хитрому алгоритму, приходится задумываться о таких "мелочах" и следить за тем, чтобы не делать ничего лишнего, того, чего можно не делать.
Только вот для работы принципа RAII никакого динамического выделения/освобождения памяти не требуется. А вызовы конструкторов с деструкторами могут и должны быть встроены компилятором.
Даже когда вы передаете куда-то "безопасную копию" объекта? Или реализуете какой-то динамический список?
Да и лишние вызовы для десятка конструкторов-деструкторов в процедуре, которая вызывается 100 000 000 раз в сутки (примерно с такими нагрузками работает комплекс комплаенс-проверок для системы расчетов который я в своей время делал) - это уже будет заметно.
Я достаточно часто гоняю через PEX разные вариант реализации. И разница между динамикой и статикой даже на 1 000 000 вызовах уже заметна. И все эти лишние вызовы конструкторов-деструкторов тоже заметны в PEX статистиках. В наших условиях выиграть даже 5-10% в производительности и потреблении ресурсов CPU - уже хороший результат.
Если какие-то объекты необходимы и без них никак, то стараемся время их жизни выровнять на время жизни группы активации (практически время жизни задания в котором работает программа), но не самой программы (которая в рамках задания может запускаться многократно). Благо есть механизмы, позволяющие "прицепиться" к созданию и завершению ГА.
7 лет назад, когда пришел с десктопов на hiload на "больших" машинах все это тоже казалось избыточным. Но взгляды и подходы меняются под воздействием реалий.
Даже когда вы передаете куда-то "безопасную копию" объекта? Или реализуете какой-то динамический список?
О каких вообще безопасных копиях речь? Что же до списков - как правило, эти списки нужны для оптимизации, то есть для ускорения работы. Если список у вас что-то замедляет - он явно как-то неправильно используется.
Да и лишние вызовы для десятка конструкторов-деструкторов в процедуре, которая вызывается 100 000 000 раз в сутки [...]
Нет, я не верю что лишние вызовы конструкторов и деструкторов обязаны давать 5% разницы, как вы написали.
Вы считаете нормальным (судя по прошлым обсуждениям) использовать zoned decimal в ядре. Попробуйте как-нибудь проверить сколько этот формат у вас съедает производительности...
всё банальнее, возможно хуже или лучше - тут на любителя, суть в том что в С почти ничего нету, поэтому следующие конструкции будут практически с нуля за которыми надо следить, но это нагромождение как по мне удобнее чем конструкции ООП, (тоесть просто структуры и вызываем структуры, а в С++ я крайний раз смотрел реализацию, там где-то месяц потратил на перенятия подхода такого ведения проекта, в итоге отказался от всего даже от СМаке, но тут тоже моменты, у меня отправная точка началась с математики - первое я реализовал на С математику, перенял С++ подход, проверив математику, потом взглянул на всё это в масштабе и переписал частично на С) (ну получается все эти подходы на любителя, может удобнее в ООП со всеми этими хитросплетениями, меня просто тянет на POD подход, я давно так видеть стал мне почемуто так проще)
проблемы есть, но уже не те что в ООП
https://github.com/richKirl/TestDSAOpenglWorld там есть картинки последнее это наводящая картинка сразу на ноды, в целом тут есть о чем подумать даже без ООП, но затраты на подумать и там и там конечно обильные, тоесть сразу концентрация на списки и площадь респа допустим или этажа или участка квадратика - ноды подземелья
В ядре любой ОС это основная концепция для реализации полиморфных объектов, коих пруд пруди даже в ядре. Например, попробуйте реализовать поддержку разных файловых систем без этого. Да хотя бы файловой системы с каталогами и файлами.
Чтобы продать что нибудь ненужное сначала нужно это купить(~с)) Я к тому, что упрощать нужно и можно, если знаешь зачем и почему. Обратная ситуация когда не могут усложнить, когда требуется.
Хорошая статья, если не принимать на свой счёт))
Если ваш код выглядит как примеры из статьи, попробуйте написать что-то на Python без классов или поиграть с Elixir. Это как детокс для мозга!
Вообще, по теме - сложно оценить, насколько какой подход хорош, не зная жизненного цикла проекта. Так-то и вообще писать любой код может быть абсурдно, если нужно просто вывести конкретный текст на экране один раз в жизни проекта.
Но проблема таки существует. Есть разница, условно:
Потратить сегодня 15 минут, чтобы в будущем гарантированно сэкономить 20 раз по 5 минут - вот это хорошее ООП, сам такое постоянно применяю.
Потратить сегодня 2 часа, чтобы в будущем возможно сэкономить пару раз 15 минут - вот это частный случай ООП головного мозга.
Потратить сегодня 2 часа, чтобы в будущем ещё 20 раз дополнительно потратить 5 минут, спотыкаясь об ограничения и навороты, которые сам же себе и придумал (и коллегам подарил) - терминальная стадия ООПГМ. (Вообще, встречается не только с ООП, а в принципе с любой современной "бюрократией в коде")
И самое сложное тут - это доказать пациенту, что он болен. Он просто не думает категориями времени/затрат. Он ведь поработал? Поработал. Код стал структурнее? Стал. Ну и вот, значит стало лучше. Можно в плюс к этому нафантазировать всяких адских катастроф, которые обязательно постигнут всех "неверных", кто пишет менее структурно.
На деле, в командах обычно есть баланс "слишком заморачивающихся" и "недостаточно заморачивающихся", который со временем и частым обсуждением разногласий приводит к общему продуктивному знаменателю.
Потратить год чтобы систематизировать все сценарии бизнес-логики, написать 1000 базовых классов по 1000 свойств и методов в каждом а потом убедиться что каждый из 10 000 бизнес-процессов использует из этих тысяч от силы 3-5 (но каждый свои).
Или для каждого бизнес-процесса писать свой набор базовых классов которые будут использоваться только там и нигде более.
Потратить сегодня 15 минут, чтобы в будущем гарантированно сэкономить 20 раз по 5 минут
Когда я разбираю легаси код у меня впечатление что автор специально тратил усилия чтобы гарантированно нагадить тем кто придёт после него
Вообще, нет ничего плохого в ООП, паттернах, фабриках и других полиморфизмах. Проблемы возникают тогда, когда всё это пихают только ради украшательств без всякого понятия. Чтобы избежать таких проблем, достаточно руководствоваться одним простым правилом: пиши код так, чтобы им было удобно пользоваться. Именно пользоваться. Если для работы с экземпляром твоего класса тебе надо сначала инициализировать фабрику фабрик, потом фабрику, и в метод инициализации объекта передать 800 параметров, собранных вручную, то конечно это не архитектура, а г-но на палочке.
Полностью согласен - проблема не в ООП, а в его полном непонимании особенно молодежью, которую неправильно учили. Программиста надо начинать обучать не с бейсиков типа Python или PHP, а с ассемблера, тогда идея объектов ляжет на правильно подготовленную почву.
Проблемы возникают когда вводится новая абстракция, не имеющая аналогов в жизни, а только в воспалённом мозгу автора и, конечно, не документированная. А если бы её задокументировали, то в процессе автор бы понял что это нафиг не нужно. Где-то на пункте "обоснование"
Автору бы развить soft skills чтобы не писать токсичные статьи. И делать полезный контент для начинающих но увы.
Есть другая крайность. Чем грешат большинство Python разработчиков. У которых в принципе проблемы с понимание паттернов проектирования, ООП и архитектуры.
Везде использовать только функции.
Когда читаешь подобную простыню на 3 тыс строк, хочется сильно ударить такого разработчика учебником Java.
Что приводит к тому что большинство кода на Python вызывает у меня отвращение.
Но я просто посылаю разработчика читать про антипаттерны и запахи кода.
Куда и автору срочно надо сходить, а не писать подобный мусор.
На фоне фреймворков головного мозга, когда блокнот на электроне весит несколько гигов (Блокнот! Гигов! ?:№:№_№;9;%:;%:(;9 из какой это параллельной вселенной безумия???) и люди пользуются и нахваливают вместо того, чтобы авторам дурку вызвать — что ООП, что ФП довольно лёгкие заболевания :-D
Распространённое заболевание среди java разработчиков.
Есть даже шедевр на эту тему: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition
Статья очередного желторотика, у которого наступила стадия отрицания при осознании того, что программирование - это, оказывается, не про HelloWorld и не про a + b
Если ваш коллега пишет адаптер для вывода “Hello, World!”, немедленно вызывайте специалиста!
А какого именно?
Не говорите автору, что в го есть свое ооп)
А по существу, задолбали эти крайности. Программирование - это вечный поиск золотой середины. И довольно редко удаётся её найти, потому что тяжело угадать будущее. Видел много проектов, которые были изначально написаны на коленке без расчёта, что они вырастут... И лучше бы автор в свое время потратил лишний день на закладку нормальной архитектуры в начале, чем спустя годы тратить кучу времени на приведение проекта к нормальному виду
ООП плохо рефакторится. Поэтому да, нужно изначально продумать архитектору. Потому что потом ты её не поменяешь.
В ФП всё проще: пиши, рефактори, усложняй по мере надобности, но не раньше
В N+1-й раз напишу вам, что ФП не отрицает мутабельность. ФП отрицает неуказание эффектов в типах.
Так как я пишу это в N+1-й раз для сильно ненулевого N, то позволю себе спросить: зачем вы из треда в тред ходите с похожими мифами и тезисами, и как только вам указывают на неконсистентность или некорректность ваших высказываний, вы из треда пропадаете, чтобы через какое-то время снова появиться в другом треде с тем же самым?
в N+2 раз отошлю в энциклопедию
Там же рядом, кстати, написано, что википедия не явялется авторитетным источником. На фактологические косяки в википедии (особенно в русской версии) я натыкался чуть чаще, чем хотелось бы.
однако АДТ - это способ уклониться от указания и обработки эффектов в типах. Это попытка вернуться к динамической типизации :)
С чего бы?
Конечно, чем больше ваши АДТ (и чем менее точны типы — вы можете вообще писать всё в IO
, например), тем меньше вы можете сказать о поведении функции, глядя на её тип, но ФП бьёт за это по рукам и стимулирует выражаться точнее. В отличие от ООП.
Поэтому работать над ФП-проектами (на ФП-языках, конечно, а не когда ФП пытаются сделать, не знаю, на джаваскрипте или питоне) — одно удовольствие, а над ООП — погибель и статьи уровня исходной.
Знаете, с моей колокольни это вы ходите из треда в тред с похожими мифами про функциональное программирование, ну а я вынужден это комментировать.
Учитывая, что мой первый комментарий на этой странице был ответом на ваш, где вы уже что-то прокомментировали, то с вашей колокольни открывается какая-то альтернативная реальность. Но это, впрочем, не новость.
С бездоказательной заявки о том, что ФП лучше.
Только оно действительно лучше. Потому что оно стимулирует выражать вещи в типах, и это приводит к более поддерживаемому и надёжному коду.
Как доказывать, что рефакторить типизированный код проще, в рамках одного комментария на хабре — хз.
ну в вопросе кто более авторитетен: википедия или иуиуиу - я выберу первую.
Лучше бы было, конечно, выбрать разобраться в вопросе, но это путь для слабых духом. Сильные выбирают полагаться на авторитеты.
При этом обычные ЯП не чураются ФП подходов (например Rust) в то время, как ФП считают мутабельность нечистой (здесь сравнение с сектами уместно), греховной. И вынуждены сами преодолевать свои же рамки: сочиняя монады
Так я не понял, есть в ФП мутабельность или нет в итоге? А то её у вас то нет, то вон монады сочинили (хотя те же монадические парсеры на комбинаторах — тоже монады, и какое там состояние вообще, если это декларативный предметноспецифичный язык).
И чем монады — плохо? Может, у вас ООП тоже плохое, потому что там придумали, что всё надо декомпозировать на объекты, и даже пришлось для этого придумать специально классы, чтобы «преодолевать свои рамки»?
и АДТ.
Причём АДТ к стейту?
это Вы просто не пробовали писать ничего сложнее генератора ряда Фибоначчи (да и его, как правило, пишете неверно).
Ну хотя бы в интернете мне наконец-то расскажут, что я там пишу.
У вас там как, фабрика синглтонов печатальщиков hello world уже получилась, готовы перейти к гавкающим кошечкам и собачкам?
попробуйте например на языке SQL выразить следующую задачу:
Почему на SQL? Причём тут вообще SQL? С каких пор SQL — функциональный типизированный ЯП общего назначения?
нужно перевести 100 долларов с балланса пользователя 1 на баланс пользователя 2, при условии, что баланса у него достаточно и оба пользователя незаблокированы
Чё-т такое:
transfer :: UserId → UserId → Nat → AccountsMap → Validation [TransferError] AccountsMap
transfer id1 id2 amount accounts = do
let Just user1 = lookup id1 accounts
Just user2 = lookup id2 accounts
when (isLocked user1) $ Failure [AccountLocked id1]
when (isLocked user2) $ Failure [AccountLocked id2]
when (amount > balance user1) $ Failure [InsufficientFunds]
pure $ adjust (decreaseBalance amount) id1
$ adjust (increaseBalance amount) id2
$ accounts
transferSTM :: TVar AccountsMap → UserId → UserId → Nat → STM (Validation [TransferError] ())
transferSTM accountsTVar id1 id2 amount = stateTVar accountsTVar $ splitValidation $ transfer id1 id2 amount
А вы, кстати, забыли обработать возможность отсутствия юзера (тут я для честности пошёл по вашим стопам, хотя компилятор протестует) и не указали, какой из двух заблокирован (тут — решил указать). Плюс непонятно, может ли обломиться update
или нет (и типы этого не скажут), и что в таком случае будет с транзакцией — закроется сама? Будет висеть? account1
, который вернулся из accounts.get
— это глубокая копия, или какой-то референс? При его обновлении оно обновляется в хранилище? Как связано transaction
и accounts
? Какие ещё вещи прикрываются transaction
? Что будет, если я верну account1
из функции вне transaction
и попробую его подёргать? Рантайм-ошибка? Обновится вне транзакции? Обновится ссылка вникуда и это будет noop?
А хрен его знает — ответ на все эти вопросы. Нужно написать больше тестов.
Плюс, это не оформлено у вас в функцию, и непонятно, как это тестировать. У меня же отдельно бизнес-логика в чистой функции без состояния, которую тестировать очень просто (включая property-тесты), а вся грязь-мутабельность — отдельным однострочником (который тестировать просто не нужно).
Когда у вас объём кода перейдёт экранов за пять, подумайте о том, что в реальной жизни и условий перевода денег больше и комиссии всякие встречаются (а не только блокировки) и так далее.
Не перешло — даже меньше получилось, чем у вас, хотя вы о тестировании, о заворачивании в какой-то семантически полный кусок кода вроде функции или класса там, и так далее, даже не заморачивались.
Не получится об этом подумать :(
Я указал на бездоказательность утверждения, Вы продолжили напирать на эмоциональную сферу.
Я не мог продолжить, потому что это был мой первый тезис вам на эту тему.
Что эмоционального в том, что код поддерживать проще, и что код надёжнее?
Типы к функциональному программированию отношения не имеют.
Сегодня — имеют.
Определение ФП из 60-х как того, где можно передавать функции в функции, к счастью, уже в прошлом (вместе с подавляющим большинством языков, где это не так). В C++, в питоне, в го вроде как, и так далее, можно передавать что угодно куда угодно (пусть иногда и с костылями), но это очевидно не делает их функциональными языками, поэтому с этим определением перестали бегать лет 10 назад.
Теперь перекладыватели жсонов (или балансов) с синдромом Даннинга-Крюгера (да, я знаю, что его не существует) нахватались звона про «иммутабельность» и вместо прошлых рассказов про «функции передавать в функции возвращать функции из функции ситизены первого класса» бегают с этой иммутабельностью, снова не понимая, зачем это надо, причём это к программированию, и какие у неё границы применимости и средства обеспечения. Более того, синдром Даннинга-Крюгера не позволяет перекладывателю хотя бы попробовать совместить свои утверждения с реальностью, где и в хаскеле, и в идрисе мутабельность есть (и не только монадами), что не делает их менее функциональными, и хотя бы попытаться подумать, что же в их по верхам нахватанных «знаниях» не так. Ведь если факты противоречат теории, то тем хуже для фактов.
Более того, этим людям плевать, как термины используются на практике в каком контексте — ведь они что-то где-то услышали, а разбираться-то после этого уже не нужно.
Поэтому да, то, что сегодня практикующие ФП люди понимают под этим самым ФП — это про контроль эффектов и инвариантов компилятором (и это, очевидно, ровно то определение, которое имел в виду ваш исходный собеседник, говоря о простоте рефакторинга). А контроль эффектов и инвариантов при компиляции мы умеем делать только типами (практически по определению типов), поэтому ФП — это про типы.
Есть Go с типами
На которые смотреть больно.
Есть LISP с динамической типизацией
Про который есть шутка: lisp — это такой язык, про который сишники говорят «а, это тот ненужный функциональный язык», а хаскелисты говорят «а, это тот ненужный императивный язык».
А если без шуток, то рефакторить код на лиспе — так себе удовольствие. Почему? Потому, что типов нет.
В общем, отношения к типам у всех разное и ФП здесь не несут ничего особенного.
А в эрланге «нет мутабельности»?
Понимаю, что поскольку энциклопедия противоречит вере (для сектантов это типично), то аргумент о неавторитетности энциклопедии будет базовым.
Какой вере? Это вопрос терминов, не более. Хотите называть лисп или эрланг функциональными — я не против, но их не имеют в виду, когда говорят о лёгкости рефакторинга. Человек, разбирающийся в теме, способен выполнить разрешение неоднозначностей, чтобы понять, какой сорт функциональщины имеется в виду, вы — нет, только и всего.
Вероятно именно поэтому Вы ходите из треда в тред с рассказами
о боге, хрусте французской булки, превосходных качествах функционального программинга.
Вы всё ещё не заметили, что я отвечал на ваш комментарий другому человеку, или вы просто теперь будете прямо врать?
А, и, кстати, я с непривычки не осилил поместить весь контекст вашего вопроса в голову, особенно на фоне императивного кода без единого типа (говорю ж, ФП расслабляет), и поэтому я не возвращаю новые балансы юзеров — не заметил это требование, сорри (которое бы, отмечу, заметил компилятор, если бы я где-то использовал эту функцию, где нужны балансы).
Сначала я хотел просто исправить комментарий, а потом подумал, что это будет хорошим упражнением для демонстрации рефакторинга. Итак, в текущем варианте я возвращаю новую мапу, которая функцией splitValidation
приводится в удобный для stateTVar
формат. Тип splitValidation
:
splitValidation :: (s → Validation e s) → s → (Validation e (), s)
Мне надо возвращать дополнительные данные, поэтому я изменяю его на
splitValidation :: (s → Validation e (res, s)) → s → (Validation e res, s)
и смотрю, что скажет компилятор. Компилятор недоволен вызовом transfer
под splitValidation
, потому что splitValidation
возвращает не то:
• Couldn't match type: AccountsMap with: ((), AccountsMap)
Оки, меняем тип transfer
под splitValidation
на ((Int, Int), AccountsMap)
, и заодно меняем возвращаемый тип transferSTM
на STM (Validation [TransferError] (Int, Int))
.
Дальше я меняю последние три строки на, например:
let user1' = decreaseBalance amount user1
user2' = increaseBalance amount user2
pure ((balance user1', balance user2'), insert id1 user1' $ insert id2 user2' accounts)
и всё. Мне ни разу не потребовалось даже запустить тесты.
А, нет, не всё. Так как я привык к equational reasoning'у, дваinsert
подряд автоматически вызывает у меня вопрос: а что должно произойти, если id1 = id2
? С этим вопросом можно либо пойти к аналитику, либо постулировать, что переводы себе — noop, добавив, например, в начале ещё одну проверку ровно на это, либо потребовать аргумент-доказательство, что эти два пользователя разные (но тут надо будет хаскель заменить на идрис), что будет самым адекватным решением (потому что если мы разрешим id1 = id2
, то это сломает ряд интуитивно очевидных законов, которым должна подчиняться эта функция).
Типы и иммутабельные рассуждения снова помогают, и я ведь всё ещё не написал ни единого теста. А учитывая, что люди часто пишут тесты уровня «проверили один-два оптимистичных случая, вроде норм», то тестам доверия нет.
критерий "лучше" "хуже" без приведения доказательств - это только эмоциональная оценка.
Окей, я запомню это определение в разговоре с вами, равно как и вашу любовь к доказательствам очевидных для практикующих людей утверждений.
мы же с Вами выяснили, что сами по себе типы присутствуют во многих языках и это от ФП или НП не зависит
Нет, мы это не выяснили. Это вы сделали некорректную бинаризацию.
Речь о выразительной силе этих типов. Ежу понятно, что типы есть и в C (или в Go вон), но они там скорее помогают машине сгенерировать более эффективный код, а не человеку писать код более эффективно.
вот вот, нахватались звона про типы и айда этот звон повсюду распространять
Разница в том, что в моём случае это не звон, и я понимаю, о чём говорю.
Ещё раз: функциональный стиль программирования потому и сектантский, что базируется на понятиях "чистый" и "греховный", а греховность она именно от мутабельности
Ага, а ООП — сексистский, потому что объектифицирует, да?
«Чистый» — это исключительно технический термин, точно так же как «open neighborhood» — термин, а не приглашение пожить рядом с математиком-топологом. Пытаться в этом найти какие-то скрытые смыслы — это трэш уровня задорновщины.
чтобы не мараться в мутабельности вы и мучаетесь с рекурсиями и всеми прочими "миссионерскими" позами
Зачем? Где в коде выше рекурсия и какие-то позы? Получилось короче, понятнее и тестируемее, чем у вас, пока вы там, чтобы сократить количество кода, даже решили скипнуть оформление этого в какой-то цельный кусок, опять же.
ага, будь Ваша воля - на кострах бы жгли?
Будь моя воля, я бы ушёл затворником в леса, лишь бы не пересекаться с перекладывателями жсонов с Даннингом-Крюгером. Но, к сожалению, выращивать еду я сам не умею, а оттуда всё по наклонной.
Увы, но JSON'ы придумали чтобы разработчику было удобнее, а не чтобы оно быстрее и эффективнее работало...
Представляете ситуацию - "мы придумали инструмент чтобы строителю было удобно строить дом, а что дом при этом получается кривой, так извините - наш удобный инструмент иначе не позволяет".
Ну не совсем так. Есть определенные области где, к счастью, производительность имеет значение.
Вот тот же Чернобыль. Один из факторов, усугубивших ситуацию, была недостаточная производительность компов (в те времена), не позволившая в реальном времени обрабатывать данные по датчикам нейтронных полей в АЗ и показывать реальную картину что там сейчас происходит. Т.е. дежурная смена в экстренной ситуации действовала вслепую и без рекомендаций что надо делать для стабилизации АЗ (в результате запаниковали и сделали только хуже).
Банки те же... В 90-е деньги по межбанку могли идти 2-3 дня. Сейчас если не проходят за минуту, клиент уже начинает нервничать.
И, кстати, мы на уровне ядра АБС JSON стараемся не использовать. Тем более, в высоконагруженных сервисах. Потому что это лишние затраты на кодирование-декодирование, лишняя нагрузка на сервер, особенно в пиковые периоды.
И таких ситуаций достаточно много на самом деле в разных областях. Просто далеко не все разработчики с этим сталкиваются. Для многих основной принцип - "пусть криво-косо, зато на полчаса раньше других". Правда, пока самих не касается. Кода MS выпускает "скорей-скорей" очередную версию винды ("баги потом поправим") - это почему-то возмущает.
Начнем с того, что исключения крайне ресурсозатратны. Об этом уже даже на С++ митапах стали говорить. Мы это видим на своей, так сказать, шкуре, на статистиках PEX (Performance EXplorer). И, несмотря на то, что наша платформа поддерживает работу с исключениями на уровне системы (и не важно на каком языке оно там написано) и с управлением куда это исключение пробрасывать (на тот же уровень стека, выше по стеку и т.п.) и то, что все исключения автоматически фиксируются в логе задания (joblog) с указанием не только функции где оно возникло, но и строки кода, мы этим не пользуемся. Потому что hiload, а это очень сильно грузит систему (когда в ней одновременно крутится несколько десятков тысяч процессов)
Что касается ошибки. Мы работаем со структурированными ошибками. Код + данные. Это немножко больше, чем "где-то что-то пошло не так". Если случилось то, код будет один, если это - код будет другой. И по куду всегда можно понять что именно случилось и где. В вашем случае при ошибке в baz будет генерироваться один код, при ошибке в bar другой, при ошибке в foo - третий. Т.е. вы всегда будете знать что именно случилось у вас в системе.
Текстовки ошибок хранятся в специальных файлах сообщений
Опц ИД сообщения Серьезность Текст сообщения
ECL0001 20 ECL0001 Список &1 Файл &2 не найден
ECL0002 20 мо
ECL0003 20 ECL0003 Список &1 Неверный формат даты &2
ECL0004 20 ECL0004 Список &1 Некорректный тип клиента &2
ECL0005 20 ECL0005 Записи для списка &1 отсутствуют в &2
ECL0006 20 ECL0006 Список &1 на дату &2 не является после
ECL0007 20 ECL0007 Ошибка &1 обработки XML сообщения &2
ECL0008 20 ECL0008 Перечень еще не загружен
ECL0009 20 ECL0009 Перечень отозван
ECL0010 10 ECL0010 Данные уже реплицированы в ELP. Подтв
&1, &2, &3 - места для подстановки данных из структуры код+данные
Выглядит это так:
// Если запись присутствует и файл загружен и не откатывался
if dsGZHDRecord.GZRDY = 'Y' and
dsGZHDRecord.GZRLB = *blanks;
dsError.@ERM = 'ECL0002';
dsError.@PM1 = kLST;
dsError.@PM2 = %char(z7LDT);
endif;
Заполнили и вернули структуру dsError. Дальше по ней уже можно получить полный текст
ECL0002 Список PPT Файл 1250131 был загружен ранее
(kLST = 'PE', zLDT = 1250131)
При логировании ошибок нашими средствами мы дополнительно вводим параметр "точка логирования" - ее можно передать явно, если не передана, то сервис логирования сам по стеку определит откуда был вызов функции логирования.
Хотя в 99% случаев это не нужно т.к. по коду ошибки и так понятно что пошло не так и где.
И есть еще момент. Когда вы обрабатываете 1 000 000 записей, ошибка при обработке одной не является блокирующей для остальных 999 999. Ее просто нужно зафиксировать в логе и перейти к обработке следующей записи. Лог потом анализируется и там уже разбирается что и почему.
И да, 90% кода у нас работает в фоне. Никаких "пользователей" там нет. Это АБС банка.
начнём с того, что уже в этом предложении неправда.
Начнем с того, что на митапах С++, посвященных hiload системам эта тема уже поднимается.
Ну и на своем нагрузочном тестировании мы все это исследовали со всех сторон.
А между тем, этот код и есть де-факто код поддержки исключений.
Код поддержки исключений все-таки немножко сложнее. Если вы в хорошем профайлере посомтрите сколько всего вызывается внутри при генерации исключения, то поймете. А longjump это уже завершающий этап.
в стеке
foo(bar(baz(42)))
ошибка ловится функцией foo, но источником ошибки является неверный код в функции bar, а при этом ошибка генерируется функцией baz.
Почему вы так решили? Ошибка внутри функции ловится там, где она возникла. Что с ней делать дальше - уже ваша работа.
У меня ощущение, что все, что я рассказываю просто не воспринимается - вы пытаетесь запихать все в какие-то свои паттерны. А это неверно. Тут совсем другой принцип.
В наших механизмах (условно) возникающие в baz ошибки будут иметь код BAZnnnn, в bar - BARnnnn, в foo - FOOnnnn. И куда бы вы ее не прокинули, по коду всегда увидите что именно случилось и где.
Я больше скажу - можно перехватывать системные исключения (типа деления на ноль, к примеру) и заворачивать их в структурированную ошибку.
А сама структурированная ошибка - это просто параметр процедуры. Который заполняется если ошибка была. Или осатется пустым если ее не было.
Ну вот прототип обычной процедуры
DCL-PR ECLGenerateEID extproc('ECLGenerateEID');
*n char(10) const; // Маска файла - вход
*n char(15) const; // ID субъекта - вход
*n char(5) const; // список - вход
*n char(3); // ID элемента - выход
*n char(37); // Ошибка - выход
END-PR ;
Последний параметр, char(37) - это и есть та самая "сруктурированная ошибка". Если там пусто - все ок. Если нет... Значит смотри что случилось по кодлу ошибки ее данным.
Системные API могут использовать как простую структуру
typedef struct Qus_EC
{
int Bytes_Provided;
int Bytes_Available;
char Exception_Id[7];
char Reserved;
/*char Exception_Data[];*/ /* Varying length */
} Qus_EC_t;
Это заголовок, содержит только код ошибки, по факту выделяется больше памяти чтобы еще под данные место было,
так и расширенную
typedef struct Qus_ERRC0200
{
int Key;
int Bytes_Provided;
int Bytes_Available;
char Exception_Id[7];
char Reserved;
int CCSID;
int Offset_Exc_Data;
int Length_Exc_Data;
/*char *Reserved2;*/
/*char Exception_Data[];*/ /* Varying Length @B1A*/
} Qus_ERRC0200_t;
Но суть та же. Код Exception_Id + данные.
Если случилось системное исключение, то даже если его перехватили и обработали, оно все равно сохранится в joblog
RNX0112 Escape 50 28.01.25 10:20:04.739072 QRNXUTIL QSYS *STMT SPSFRR ALIBOP1 *STMT
From module . . . . . . . . : QRNXDATE
From procedure . . . . . . : SigExc
Statement . . . . . . . . . : 4
To module . . . . . . . . . : SPSFRR
To procedure . . . . . . . : CONVERTDATA7TO8
Statement . . . . . . . . . : 625
Message . . . . : Date, Time or Timestamp value is not valid.
Cause . . . . . : The Date, Time or Timestamp value is not valid. Some
examples of values which are not valid are: -- A date of 1994/02/31, which
is not possible. -- A time of 01/03:04, which does not have correct
separators. Recovery . . . : Correct the value of the Date, Time or
Timestamp field
А тут уже полная инфа - что случилось, в каком модуле, в какой процедуре и строка кода. Точнее, ошибка случилась в системном API QRNXDATE, который был вызван в модуле SPSFRR в процедуре CONVERTDATA7TO8 в строке 625. Ну а дальше идет конкретика - что именно не так (а теперь скажите - ваши средства дают такую диагностику?) А еще может быть сохранен дамп где все будет еще подробнее, включая значение всех переменных в области видимости). Как вам такое?
в стеке
foo(bar(baz(42)))
ошибка ловится функцией foo, но источником ошибки является неверный код в функции bar, а при этом ошибка генерируется функцией baz.Этот паттерн (делегировать ошибки выше) настолько частый, что Go'шный код им переполнен чуть ли не на 40%, а Rust даже оператор "вопросик" ввёл.
То есть сказать "вы так не делайте" будет некорректно.
И вот в этом месте ни коды ошибок ни их тексты не помогают.
Вы не можете понять простой вещи. Где бы ни случилась ошибка, всегда по ней будет информация о том, что именно случилось. Потому что любая функция или процедура всегда содержит параметр "ошибка". А вызывать функцию, предавая ей аргументов другую функцию с аргументом в виде третьей функции - ну тут, как говорится, "кошка скребет на вой хребет". Вы сознательно усложняете себе жизнь а потом жалуетесь что иначе никак. Хотя бы потому, что если в baz случилась ошибка, то результат, возвращаемый ей в bar уже не валиден. И все, что дальше с ним будет происходить только усугубит ситуацию. А дальше все это станет еще хуже в foo.
На самом деле тут после каждого вызова должен быть анализ ошибки и принятие решения - что за ошибка? Что делаем дальше - завершаем работу всей цепочки, игнорируем (бывает и такое) или идем по некоторой дефолтной ветке с возвратом дефолтного значения (при этом ошибка все равно фиксируется - мы должны знать что тут работали по паллиативному варианту).
И одна и та же функция может вернуть несколько разных типов ошибок. В зависимости от того, что там реально случилось.
Как пример
dcl-proc ParseInputParams;
dcl-pi *n;
Mode char(1) value;
Tp char(1) value;
ChkTp char(1) value;
pChkData pointer value;
dsParams likeds(t_qdsInputParams);
dsError likeds(DS_ErrorMessageData);
end-pi;
Может вернуть три разных ошибки
Код KSM2009 - некорректные значения параметров Mode, Tp или ChkTp (с указанием какой именно параметр некорректен - код ошибки один, данные ошибки разные.
Код KSM2003 - недостаточно входных параметров для продолжения работы
Код ECL0036 - набор параметров противоречив и непонятно что делать со всем этим.
Естественно, что мы можем коды ошибок создавать сами (и добавлять из в message файлы сами). Хоть для каждой функции свои коды делай и тогда точно по коду узнаем в какой именно функции произошла ошибка).
Но тут надо понимать что ошибка нужна во-первых для того чтобы не продолжать выполнение программы с некорректными или неопределенными данными (и плодить дальнейшие ошибки), во вторых, диагностировать что именно помешало процессу выполняться дальше, и, в-третьих, обозначить таки, что процесс не выполнен.
у Вас есть реализация какого-то алгоритма и Вы знаете, что можете улучшить её перфоманс, а ещё сравнить этот перфоманс с перфомансов другой части.
то есть Вы говорите о сравнении объекта с самим собой. Сейчас же мы говорим о сравнении объекта с другой реализацией объекта.
Я говорю о разных способах реализации одного и того же процесса. С исключениями или без.
Так вот, ручная обработка ошибок не может быть быстрее исключений. Поскольку обработка исключений может быть сделана (компилятором) точно так же, как делается обработка ошибок.
Вы упускаете один момент. Исключение тоже нужно обрабатывать. Из примера выше - вы обнаружили неконсистентность входных параметров. Выбросили исключение. Что дальше? А дальше вы должны что-то сделать. Зафиксировать его так, чтобы остались какие-то следы - процесс прерван потому что...
Т.е. исключение - это просто механизм посредством которого вы попадаете в блок принятия решения "а что делать дальше?". Не более того. И здесь для принятия правильного решения вы должны иметь как можно более полную информацию - что именно произошло (код ошибки - мы их генерируем сами по нашим потребностям десятками) + какая-то дополнительная информация (данные ошибки). Т.е. фактически то же исключение (можно же наплодить много разных типов исключений, в т.ч. и данными), но без всех накладных расходов (даже вызов констркутора-деструктора для объекта "исключение пользовательского типа" - это уже накладные расходы для hiload систем)
Так вот попасть в это блок можно или возвратом структурированной ошибки, или выбросом исключения и его перехватом. И возврат ошибки и по времени и по ресурсам более выгоден. Вы просто никогда не смотрели все это профайлером. А мы это делаем постоянно. И там четко видно что выброс и перехват исключения - это достаточно долгая история и там много чего внутри происходит. А формирование ошибки - это просто присвоение переменной некоторого значения и выход.
Ошибка может возникать по двум причинам:
ошибка связана с входящими данными. то есть штатная ошибка.
ошибка связана с ошибкой в вызывающем коде: bar передаёт в baz такое значение, которое недопустимо. Чтобы исправить ситуацию нужно фиксить не baz, в которой ошибка возникает, а bar, который приводит к выходу из недопустимых диапазонов значений
соответственно: место возникновения ошибки, место причины ошибки и место обработки ошибки в общем случае находятся в трёх различных
Ок. По п.2. совсем весело. Если baz обнаружил что из bar ему передали некорректное значение, он вернет ошибку со соответствующим кодом. И я это увижу безо всяких исключений. Более того, он в тот же bar вернет ошибку "ты мне какую-то хтонь на вход передал". И вот оно - то, о чем вы говорите. Ошибка возникла в bar, baz ее зафиксировал и вернул туда, где она возникла - обратно в bar. Т.е.
bar {
a = 56;
baz(a, error);
if error <> *blanks;
// и вот тут мы уже можем увидеть что значение
// 56 почему-то не понравилось
endif;
}
Ну как-то так примерно. Очень схематично.
Суть в том, что в структурированной ошибки мы имеем не только код, но и ту информацию, которую считаем нужным добавить туда по контектсту. Любую информацию. Например, пытаемся найти запись по ключу, не находим и формируем ошибку с кодом "запись не найдена", а в блоке информации указываем в какой таблице и для какого значений ключа искали.
и в этом месте реализация исключений всегда даёт возможность разработчику попялиться в стек, а реализация обработчиков ошибок в общем виде - нет.
Да ладно? Вы серьезно? Ваш замечательный (во всех отношениях) язык не позволяет в любой момент программно раскрутить стек вызовов? Мне вас искренне жаль...
Я вот могу сделать это в любом месте (используя системное API). На любую глубину. Вплоть до точки входа в саму программу. И что мне мешает это сделать или в обработчике ошибок, или в месте где ошибка фиксируется, или и там и там, если это нужно?
Другое дело, что в подавляющем большинстве случаев это не требуется. Более чем достаточно знать что "вот такие вот данные вызвали вот тут вот такую вот ошибку". Для более детального анализа уже используются более сложные механизмы. То же логирование, трейсы и т.п.
Я бы с большим любопытством посмотрел бы как вы "пялитесь в стек вызовов" на проде (куда доступ есть у очень ограниченного количество людей и вы в это число не входите).
Или пытаетесь поймать ошибку в обработке сотни миллионов записей в десяти параллельных заданиях (такая вот у нас реальность).
Итого. Я работал с исключениями. И 25 лет на С++ и сейчас есть механизм для работы с ними. И работал (и работаю) со структурированными ошибками. Так что все на основе личного опыта и сравнения.
И могу сказать что исключения более тяжелый механизм. И не дающий никаких преимуществ. Ну кроме того, что "так проще", но и это иллюзия на самом деле. Потому что в реальной жизни основное - не поймать ошибку, а правильно ее зафиксировать для последующего анализа. Потому что происходит она не на вашем компе когда вы за монитором, а где-то на далеком сервере откуда вам дадут только логи. И вот что там увидите, с тем и работайте.
СЕМАНТИКА ЯЗЫКА НЕ ИЗМЕНИТСЯ.
К сожалению, поменяется. Возврат через ифы и раскрутка стока имеют незначительные, но всё же различия наблюдаемого поведения. Они не имеют значения сами по себе, но это сломает обратную совместимость.
Также не стоит забывать, что сбор стэктрейса – процесс весьма недешёвый. Поэтому обработку (штатных) ошибок имеет смысл делать без них (особенно в нагруженных местах). А для багов(=нештатных ошибок) использовать механизм исключений со стэктрейсом, как в Яве - вполне разумно. Но в тех же го и расте такой механизм есть - паники.
новых версиях языка лонгджамп нужно удалить и сделать реализацию на возвращаемых значениях
Как реализацию регекспов в плюсах сделали быстрее (не сделали – сломает abi)?
то в новых языках
А в новых так и делают. В том же zig есть call-trace - он автоматом генерирует «путь от места обработки до места возникновения».
В том же расте, тоже так. И, к слову, там на практике есть три варианта обработки ошибок: anyhow(со стэктрейсами, похожи на исключения), thiserror (фактически calltrace) и сырые легковесные ошибки. И чаще используются первые два варианта, а не третий, как Вы намекали выше.
Этой проблемой НЕ страдают системы с исключениями (ибо как правило язык с исключениями даёт возможность в исключении получить и распечатать стек вызова функций)
Мой опыт показывает, что баг чаще возникает в месте, вообще не связанном по стэку вызовов. Оно даже может быть выше функции foo, которая залогировала проблему.
Поэтому, стэктрейс помогает далеко не всегда, как Вы здесь утверждаете, а только в простых случаях. Не берусь утверждать, насколько простые случаи распространены – зависит от предметной области и YMMV.
И как уже писал выше, в других языках, как правило, есть инструменты для диагностики подобных простых случаев. Эти инструменты используются в хоть сколько-нибудь серьёзных проектах (если его не джуны пишут, которые языком пользоваться не умеют – но тут ничего не поделать)
Все верно, кроме одного
Если вторая форма реализуется (при необходимости) через первую форму, то как минимум производительности у обеих форм могут быть одинаковы.
Это не так. Для вас try/catch это просто строки на экране. Но в реалии они раскручиваются в намного более сложные конструкции.
И тот же throw будет в исполняемом коде намного сложнее выглядеть, чем присвоение значений полям структуры ошибки.
1)
и главное: вторая форма может быть реализована через первую.
2)
Таким образом разговор о том, что вторая форма хуже по производительности бессмысленен. Если вторая форма реализуется (при необходимости) через первую форму, то как минимум производительности у обеих форм могут быть одинаковы.
Повторюсь, в этом принципиальная ошибка. Посмотрите на код, который генерирует современный компилятор C++ для исходного кода с исключениями. Узрите там: 1) отсутствие обработки исключений в основном коде, 2) cold-секции кода, в которые сброшена обработка исключений, 3) таблицы "этот диапазон адресов кода - вот на этот catch" в секциях данных. После этого можно вернуться к обсуждению.
А sjlj-exceptions массово не используются уже около 15 лет. А вы всё про longjmp вспоминаете.
если одна система может быть реализована через другую, то профайл будет строго одинаков.
Нет.
совершенно не важно какой он код генерирует.
Из башни из слоновой кости - возможно.
то в случае, если будет доказано, что в форме А или Б производительность выше, можно переписать имеющийся компилятор.
Переделать код, который явно проверяет ошибки в возвращаемых значениях, на исключения в современном стиле - такого компилятора ещё нет и, похоже, не скоро будет.
начнём с того, что уже в этом предложении неправда.
начать надо с того что это должно быть не важно, потому что когда у вас произошел ексепшен это должно значить что работу продолжать невозможно, а значит ресурсы не нужны особо и их в общем то некуда девать.
Если у вас так код написан что он постоянно спотыкается-падает это значит вам вообще на все наплевать и рассуждать о потраченных ресурсах смысла не имеет.
Не совсем корректно. Ошибка может быть, например, при обработке одной записи из 1 000 000. Просто неконсистентность данных в этой записи, не позволяющая обработать ее в соответствии с заданной логикой. Но это не значит что вы должны все бросить и не пытаться обработать остальные 999 999 записей.
Или такая ситуация. Вот у нас есть такая штука как User Queue - очередь данных. Работа с ней идет на достаточно низком уровне - MI - машинные инструкции. И есть там такая MI - DEQWAIT - чтение из очереди с таймаутом. Так вот, если очередь пуста, а таймаут истек, вы получите системное исключение. Которое абсолютно не значит что все пропало и работа дальше невозможна, но только то, что сейчас в очереди ничего нет (а через секунду может и появится что-то).
Т.е. тут опять - ловим исключение, смотрим что это за исключение, если это истечение времени таймаута, то просто говорим что "читать нечего сейчас" и продолжаем работать дальше.
И подобных ситуаций на практике очень много.
Т.е. тут опять - ловим исключение, смотрим что это за исключение
так это третий вариант - это внешнее исключение, если исключение внешнее, тоже нет смысла рассуждать сколько ресурсов оно потребляет, повлиять вы на это не можете. Это фактически часть внешнего интерфейса, какой дали интерфейс через тот и работаем.
Так вот, если очередь пуста, а таймаут истек, вы получите системное исключение.
Это откровенно нецелевое использование механизма исключений. Может, кому-то из разработчиков этого механизма было удобно так делать, но само по себе это плохая практика.
Надеюсь, оно при этом хотя бы ловится предельно близко к источнику, желательно сразу вокруг функции, которая это сделала.
Я такое наблюдаю в Python в некоторых интерфейсах, например, модуль socket - там нет иного способа получить ошибку из большинства функций. К некоторым сделали затычки - connect_ex(), например - видимо, потому, что кто-то пожаловался, а для какой-то задачи было важно сделать тысячи коннектов в секунду (хм, для какой?) А вот для recv(), send() уже нет. Или там же list.index(). Вот нравится им так, производительность не сильно интересует.
И подобных ситуаций на практике очень много.
Увы. Это не пример для подражания.
с другой стороны, программы могут быть построены, что по error-ветке идёт значительная часть трафика: скажем - десять процентов.
я не спорю что так бывает, но программы приходится так строить по внешней причине, потому что когда у вас постоянно валятся ошибки они валятся снаружи или от железа или от внешних софтовых компонент-библиотек, главная проблема тогда опять же не в эксепшенах, а в глючности этих внешних компонент. Если у вас флешка на каждое десятое обращение будет возвращать ошибку вы ее просто выкинете и купите новую, к сожаления на работе с таким материалом иногда приходится долго и нудно работать и долбить начальство что с этим надо бы что-то сделать, но начальство не может и не хочет решать главную проблему, а пытается заставить вас играть в Кулибина.
с другой стороны, программы могут быть построены, что по error-ветке идёт значительная часть трафика: скажем - десять процентов.
я не спорю что так бывает, но программы приходится так строить по внешней причине, потому что когда у вас постоянно валятся ошибки они валятся снаружи или от железа или от внешних софтовых компонент-библиотек, главная проблема тогда опять же не в эксепшенах, а в глючности этих внешних компонент. Если у вас флешка на каждое десятое обращение будет возвращать ошибку вы ее просто выкинете и купите новую, к сожаления на работе с таким материалом иногда приходится долго и нудно работать и долбить начальство что с этим надо бы что-то сделать, но начальство не может и не хочет решать главную проблему, а пытается заставить вас играть в Кулибина.
Начнем с того, что исключения крайне ресурсозатратны. Об этом уже даже на С++ митапах стали говорить.
В современных реализациях это только в случае срабатывания исключения. Если его не происходит, то выполняется основной путь, в котором минимизированы посторонние затраты. Код обработки исключений выносится в отдельную секцию объектного файла (например, если основная секция .text, то эта будет .text.cold), и там сидят все сложности. Дополнительно это помогает не грузить кэши процессора кодом, который эквивалент всего этого "if(err!=nil)...". Размотка исключений тоже выполняется по таблицам, сгенерированным компилятором, и не требует вписывания в основной код. (Кстати, это касается не только C++. В Win32 structured exception handling (SEH) было на коде, в 64-битном перешли на таблицы.)
"На C++ митапах", если это знают, то в основном говорят о том, что не надо исключения использовать для нормального пути выполнения. Ну а если не знают - их недоработка.
Что касается ошибки. Мы работаем со структурированными ошибками. Код + данные. Это немножко больше, чем "где-то что-то пошло не так".
В исключениях умные люди делают точно так же.
то сервис логирования сам по стеку определит откуда был вызов функции логирования.
И это по цене сравнимо с обработкой исключения.
Допустим в baz ошибка, которую мы поймали и обработали в foo.
А проблема в том, что источником ошибки может быть неверный код в функции bar. А когда пользователь смотрит на текст ошибки выброшенный функцией foo, он bar и baz не видит.
Не понял, мы ж ошибку поймали и обработали, что там ещё foo
выбрасывает, что связано с bar
и baz
?
Но вообще это всё оттого, что у вас все ошибки — текст, и вообще всё — текст, даже в примере выше, который якобы должен был показать хорошие практики программирования. Естественно в таком случае у вас будет боль и страдание.
А если вместо текста ошибки (обработка которого, сюрприз, не проверяется компилятором) использовать нормальные типы для этих ошибок, в контексте которых понятно, откуда они берутся, то и проблемы такой нет. Оказывается, что вам больше не нужны коллстеки (которые компилятором могут быть обезображены до неузнаваемости из-за инлайнинга, например, или которые вообще могут быть иррелевантны в случае глубоко асинхронного кода), чтобы понять, что вообще происходит.
Но да, продолжайте сообщать об ошибках строками и винить тупых непониматоров из раста, зачем-то впихивающих специальный синтаксис для нормальной обработки ошибок. Бездна вашего непрофессионализма настолько глубока, что мне стрёмно за яндекс даже несмотря на то, что я к нему отношусь негативно по политическим причинам.
А если вместо текста ошибки (обработка которого, сюрприз, не проверяется компилятором) использовать нормальные типы для этих ошибок, в контексте которых понятно, откуда они берутся, то и проблемы такой нет.
Ну у нас ее и так нет. Потому что мы сами создаем нужные нам коды ошибок (код ошибки + ее текстовое описание с возможностью подстановки пользовательских данных). И можем для каждой отдельной функции делать свои коды. И знать что вот этот код может возникнуть только там и нигде более.
И есть API которое возвращает полный текст (с подстановкой) по коду + данным. Что очень полезно для логирования. Потому что у нас практически все работает "где-то там" и в фоне. Что там пошло не так мы видим только по логам.
оно текст пока оно нужно как текст.
Оно нужно как текст только при записи в логи. Делать текст до этого не нужно совершенно.
в случае catch - блок кода может запросить специальными вызовами дополнительную информацию о контексте места возникновения ошибки: стектрейс, файл и проч. просто это всё скрыто ровно до тех пор, пока не нужно.
И рантайм раскрутит обратно все заинлайненные функции, и соотнесёт точку создания экзепшона в асинхронщине со всеми точками запуска всех асинхронных вещей?
Магия! Очень здорово! Можете меня научить делать эту магию на C++? Вот я пишу где-то
try
{
doStuff();
}
catch (const std::exception& e)
{
// *
}
Что мне надо написать вместо звёздочки, чтобы получить вот это вот всё? doStuff
не знает, что и где будет вызвано, и вообще это может быть в глубине корутин.
Я, кстати, как раз на днях дебажил крэш из-за того, что некоторый awaitable в await_suspend подписывается на событие (возобновляющее соответствующую корутину) и ожидает, что await_resume будет вызвано только тогда, когда это событие произойдёт (и корутина будет возобновлена «симметрично» этому await_suspend), а это не так. Получить адекватный стектрейс там невозможно даже в отладочной сборке из-под gdb, потому что всё падает, будучи вызванным сильно из другого места.
Как в этом случае поможет ваша магия? Куда нажать?
заглянул я в Rust в ошибки - не увидел объектов, которые позволят мне собрать стектрейс. Вот, скажем в yaml парсере их нет.
Он возвращает характерную для парсера ошибку. Нужен стектрейс — вызовите backtrace()
(или как его там в расте) при обработке (которая может свестись к одному вызову higher-order function, типа withBacktrace(parse(...))
). Собирать стектрейсы всегда и везде — смерть для производительности.
Впрочем Вы постоянно этим грешите, что поделать.
Очередное бездоказательное утверждение на эмоциях с вашей стороны :]
Ответ на мой вопрос о том, как блок кода может запросить упомянутую вами информацию, будет, или тезис о бектрейсах в исключениях снимаем?
то есть то, что в НЕКОТОРЫХ случаях работающий инструмент был не (совсем) применим
В каких «НЕКОТОРЫХ»? Это стандартный случай для работающего в проде (сиречь собранного с оптимизациями, и так далее) кода на C++.
ах да, вы же считаете нормальным загнать всех разработчиков в клетку типов только за то, что в 1% случаев они в этих типах ошибались.
В какую клетку, зачем загонять? Что это за фантазии проступают через текст?
просто сама пардигма с async/await ущербна, что поделать. есть лучшие парадигмы - stackful.
ИМХО количество мест, где stackless полезнее, особенно для уже имеющихся языков с имеющимися кодовыми базами, куда выше, чем где stackful полезнее.
в том и дело, что мне НЕ нужно собирать трейсы всегда и везде.
Ну так тем более, собирайте только там, где нужно. В чём вопрос-то?
Если откуда-то трейс не нужен — просто не записывайте его, точно так же, как вы сейчас его не записываете, если он вам не нужен.
я пожаловался что с переходом на return error мы утратили весьма удобный инструмент
Только его и не было.
который помогал в отладке
При разработке или на проде? В первом случае вы код прямо пишете сейчас новый, какие там глубокие стектрейсы для ваших кеширующих микросервисов? Во втором случае, может, есть какая-то связь с отсутствием выразительных типов?
Да не, снова ерунда.
вы НАМЕРЕННО задали вопрос из области "были примеры, где этот инструмент не работал".
После ваших просьб в разговоре об ФП написать аналог вашей функции на SQL (до сих пор лолирую) или черри-пикинга отдельных библиотек в отдельном языке я на вашем месте бы не стал про это даже вспоминать.
я подтвердил, что да, были примеры, где этот инструмент не работал. и зафиксировал, что вы уводите дискуссию от первоначального пункта 1
Да не, вы просто инструменты путаете. Вы говорите об экзепшонах, когда дело в какой-нибудь там виртуальной машине для джавы или дотнета (в которых я не шарю совсем, поэтому не смогу поддержать разговор), которая может выборочно и чуть ли не ретроспективно собирать трейсы, деоптимизировать, и так далее.
Соответственно, законы физики и теории вычислимости не мешают тому же .NET CLR записывать трейс каждый раз, когда вы в F# возвращаете его аналог Left
, или мне наваять за пару вечеров плагин к ghc, который каждой функции будет добавлять констрейнтHasCallStack
, чтобы потом его захватывать в моей собственной иерархии ошибок.
Просто это всё нафиг никому не нужно, потому что ценность трейсов в сильно типизированных языках переоценена. Это как жаловаться, что JS безапелляционно лучше C++, потому что в JS есть eval
.
с debug-данными
с трейсом всех ошибок
с профайлом всех запросов по всему стеку
Чем меняют поведение наблюдаемой системы и её перф-характеристики.
Там, где я работал и где скорость ответа действительно была важна и приводила к более плачевным денежным исходам, чем «юзер монополиста подождёт вызова такси не 5 секун, а 10, и с вероятностью 1% в следующий раз предпочтёт идти пешком», это не работало. Работает только снятие полного дампа трафика и дальше уже существенно более тяжёлая артиллерия, чем дебаг-данные, трейсы ошибок и профайлинг средствами языка/окружения.
Впрочем, это так, лирика. Лучше скажите, как тут помогают трейсы исключений? Вы до таймаутов и прочих проблем на проде на них вообще забиваете, что ли, лол?
чуть глаза поднимите прямо на процитированный Вами текст, в нём написано в какую. прямо в Вашей цитаете есть ответ.
Так там нету.
вынуждены писать более сложный код
Что сложнее? Как раз проще: понятно, что может завершиться ошибкой (и какой), а что — нет.
от повышения сложности код становится менее надёжным, а не более, как хотелось бы
Бездоказательное утверждение.
а новых взамен не получили
Получили статические гарантии. Это лучше.
Ха-ха! И что с того?
Ставь лайк, если любишь искать ключи где светлее, а не где потерял.
Ведь мир-то (весь мир!) состоит из мутабельных объектов.
Бездоказательное утверждение, отсылающее к эмоциям. Причём тут вообще мутабельность и объекты?
Кому в голову такое придёт?
Соломенным чучелам, с которыми вы активно спорите.
трейсы исключений помогают в сложных случаях, когда отсутствует понимание: где находится источник ошибки.
В случае таймаутов от этих трейсов (вызывающего сервера, отваливающегося по таймауту вызываемого) толку, как с трейсов изнутри grep для понимания, где в приложении, пишущем логи, по которым вы грепаете, проблема.
У меня всё больше впечатление, что к разработке софта вы относитесь как-то сбоку, как ПМ или скрам-мастер там, не знаю.
ну вслух, что ли мою фразу перечитайте. слово прямо рядом со словом "клетка" стоит.
То есть, это вы сказали, не я. QED.
ибо помимо аннотаций типов, мы теперь вынуждены иматься с return ошибок
Чем это сложнее, чем throw ошибок?
с написанием дублей методов для типов с их матчингом
Что это вообще значит?
и сборкой многих типов в единый АДТ
Чего там собирать? Это O(1) от размера кодовой базы и O(n) от количества типов ошибок, которых в стандартных оперднях очень мало.
(который является попыткой инкарнации динамической типизации)
Вы это повторяете в который раз, но в который раз отказываетесь пояснить, какое отношение АДТ имеют к динамической типизации.
То, что в инте может быть 0, может быть 1, а может быть 42 — это случайно не проявления динамической типизации, кстати?
вот в Python, например, программист не думает об управлении памятью, а в Rust вынужден постоянно об этом думать.
Да, абсолютно бессмысленное упражнение, ведь между кодом на Rust и Python нет никакой разницы по характеристикам исполнения.
а отсылка про то, что такое наблюдение влияет на наблюдаемый объект, она разве не про мутабельность была?
Эта отсылка была к тому, что ПО с дебагами и прочими трейсами ведёт себя совсем не так, как без них.
ага, это просто цена за инструмент. просто нужно о ней знать и всё.
Попробуйте в следующий раз дочитывать комментарии до конца. Или хрен с ними с комментариями, фразы-то до конца дочитывайте.
и О(N^2) на время разработки
и О(N^4) на поиск багов
А использовали бы типы — не было бы такой ерунды у вас, как вы описываете.
Возможность написать функцию, которая будет обрабатывать сразу много разных типов.
Нет, конечно. Она всё ещё обрабатывает ровно один тип.
а в языке динамической типизации эту сборку осуществляет компилятор, а не программист. вот и вся разница.
А кто в языке с динамической типизацией осуществляет проверку, что вещи вне этого списка не придут, и кто отличает String как Real от String как String?
Это свойство мутабельности мира.
Мутабельность к этому не имеет вообще никакого отношения.
фотон без наблюдения ведёт себя иначе, чем если за ним наблюдают.
И там, где это важно, это пытаются минимизировать. Вы же даже не пытаетесь это сделать, а ставите те эксперименты, которые легче поставить (напихать трейсов и потом с умным видом сидеть их разглядывать), а не те, которые относятся к вашему предмету исследования (почему тормозит).
это именно про типы и было.
Абсолютно верно, без типов этой проблемы нет.
Этот тип является суммой множества типов
Вы даже сами сказали, «этот тип». Тип один, всё.
причём крайне разнородных, таких, как "контейнер сложных документов"
Обычная индукция/рекурсия, не вижу проблем.
и "ошибка".
Если это не часть семантики yaml, то это они зря, конечно. Но это вопрос конкретной реализации, а не теории типов.
в реализации этого типа под капотом точно та же структура, что получается при динамической типизации
Да, и? Вы думаете, тут цель — выпендриться и иметь в рантайме какое-то другое представление (что, кстати, делают с GADT'ами, но вы ещё не там)? Тут цель — это проверки в компилтайме, до запуска.
а просто когда у вас список становится большим, а он вон на простых примерах уже большой, чтобы возникнуть проблемам
Какие проблемы возникают?
вы
вашу жалобу
Давайте позовём администрацию и спросим, жаловался ли я вообще или нет? Кого там для этого тегать надо?
Что будете делать, когда администрация скажет «нет»? Сможете публично сказать «я, ednersky, на словах зрящий в суть и указующий путь, а на деле истеричный и люблю привирнуть»?
вы слили дискуссию
Из всех комментариев сливались только вы, напрочь игнорируя то, что я вам пишу, а теперь нашли повод вообще не отвечать по существу (хотя, видимо, вы настолько своей клоунадой всех задолбали, что на вас таки действительно кто-то пожаловался).
И, кстати, мы на уровне ядра АБС JSON стараемся не использовать. Тем более, в высоконагруженных сервисах. [...]
Только вот JSON вроде и медленный - но куда быстрее XML. А банки почему-то этот самый XML ещё как любят...
Ну да, не в ядре.
Вот тот же Чернобыль. Один из факторов, усугубивших ситуацию, была недостаточная производительность компов (в те времена), не позволившая в реальном времени обрабатывать данные по датчикам нейтронных полей в АЗ [...]
Там проблема была не в этом (уж показать значения железо бы как-нибудь смогло), а в отсутствии этих самых датчиков. Просто не считалось необходимым знать текущее состояние нейтронного поля при управлении реактором.
Еще раз. Это моя институтская специальность. И мы получали не пропаганду в прессе, а реальные отчеты комиссий разного уровня, далеко не все из которых публиковались (а многие были под грифом, как минимум ДСП - мы-то подписку давали еще с первого курса). Потому что нас учили на всем этом (в том числе).
Из наших, кстати, туда и ликвидаторами ездили ребята. Нашей специальности и "соседи" из "дозиметрии и защиты".
Там проблема была не в этом (уж показать значения железо бы как-нибудь смогло), а в отсутствии этих самых датчиков. Просто не считалось необходимым знать текущее состояние нейтронного поля при управлении реактором.
Точно знаете? :-)
Если что, моя институтская специальность - "Техническая физика", 0311. Это как раз те самые "Ядерные энергетические установки". И как раз в том самом 86-м году (правда, осенью) я делал курсовой (по профилирующему номерному спецкурсу) на тему "Теплогидравлический и ядерно-физический расчет РБМК-1000".
Благо, в силу профильности специальности и наличия соответствующих допусков первые же отчеты МАГАТЭ мы получили практически сразу. С полным хронометражом. И разбирали их на спецкурсах детально.
Текущее состояние нейтронных полей считалось необходимым знать всегда. И на каждой станции была соответсвующая служба мониторинга (это как раз наша специальность - мы не столько эксплуатационщики, сколько такие вот мониторщики или конструкторы в конторах типа НИИАР). Просто мощности вычислительные в те годы были таковы, что не позволяли физически в реально времени все это видеть. Поэтому и списывали на то, что "нет необходимости".
Только вот JSON вроде и медленный - но куда быстрее XML. А банки почему-то этот самый XML ещё как любят...
Ну да, не в ядре.
Все что не в ядре вообще может как угодно работать. Это в лучшем случае business critical. А ядро - missiоn critical. Там другие законы.
И да. Тот же росфин шлет нам списки террористов в XML безумных форматов на десятки мегабайт. Но для себя мы их разбираем и раскладываем уже по своей БД так, чтобы все этим можно было быстро пользоваться для разного рода комплаенс-проверок (в т.ч. и в рамках контроля платежей - это вообще дико нагруженный процесс - там под соню миллионов в стуки проходит).
Точно знаете? :-)
Архивы я не изучал и со свидетелями не общался, но ряд обсуждений тут на Хабре сложились в довольно непротиворечивую картину того что произошло, которую никто вменяемо не опровергал.
А вот ваши рассуждения странные. Для показа данных с датчиков вычислительные мощности вообще не нужны - всё можно хоть на аналоговых лампочках сделать. Единственная причина почему вычислительные мощности в принципе могли потребоваться, да в таком количестве что их, видите ли, не хватило - попытка вычислять нейтронное поле на основе модели.
Там не один и не два датчика. Сотни, если не тысячи. И их информацию нужно обсчитать чтобы получить распределение плотностей нейтронных полей по объему АЗ.
Суть вот в чем. Очень на пальцах, т.к. там сложная математика которую я грешным делом уже подзабыл за давностью лет, осталась только суть. Это самое распределение должно быть как можно более равномерным. Но по мере выгорания топлива (а оно все равно выгорает неравномерно, плюс этот тип реактора конструктивно близок к "промышленным", используемым для наработки плутония для "изделий" и поддерживает частичную перезагрузку топлива без полной остановки - ну не все время, но несколько циклов выдерживает) это распределение "кривеет". И до каких-то пор его можно выравнивать управляющими стержнями. Но нужно точно считать какой стержень в какую позицию выставить. В штатном режиме все это происходит не сильно быстро и не требует реалтайма (но за этим постоянно следили и следят). Но здесь поля уже были кривыми и реактор останавливали для полной перезагрузки. И когда начался "разнос" никто не мог быстро сказать как поставить стержни. Их просто сбросили. Да еще часть заклинила. Поля окончательно окривели и появились точки локального перегрева. А на фоне недостаточной мощности ГЦН и снижения потока теплоносителя пузырьковое кипение в каналах перешло в пленочное (образование сплошной паровой пленки на стенках канала - в обычных условиях поток теплоносителя не дает ей формироваться). Что резко ухудшило теплосъем и привело к повышению температуры в каналах выше 900С. Которые, к слову, изготовлены из циркония. А он относится к группе щелочных. Правда, не столь "ядреный" как натрий - его щелочные свойства (в частности, воспламенение при контакте с водой) проявляются только при достаточно высоких температурах. Но тут именно это и произошло. Вот так и начался пожар в АЗ.
Ну а дальше уже тепловой взрыв с выбросом части (по более поздним данным большей части) топлива в атмосферу.
Вы путаете две разные вещи автоматическое управление реактором, где и правда требуется куча математики, и аварийную защиту.
Если мы считаем, что "кривое" распределение поля недопустимо - значит, защита должна срабатывать как только поле достаточно "искривилось". Отличить "кривое" поле от равномерного намного проще чем правильно управлять стержнями на основе данных с датчиков поля.
PS а вот за упоминание плёночного кипения спасибо, оно и правда могло там всё усложнить
а дом, который "не кривой" Вы в современное время сами откажетесь покупать. Ибо дорого.
Я вижу проблему несколько в другом: JSON слишком ригиден и ограничен. Например, ключи - только строки. Или запрет hex-чисел в синтаксисе - на одном преобразовании из внутреннего формата во внешнюю десятичку и наоборот можно много потерять. А для плавучки ещё дороже. А для людей - нет комментариев. В целом, ни в одну сторону (простыми мерами, сохраняя текстовость, повысить эффективность для машины), ни в другую (обеспечить больше возможностей для человека). Сделать нечто среднее, собрав недостатки обеих сторон и почти полностью потеряв достоинства - это надо было суметь.
Так чё там как, в эрланге мутабельность-то есть? А то вы ключевой вопрос пропустили, а он ключевой потому, что:
вы говорите, что википедия — топ авторитет, и вся ваша дискуссия опирается на этот тезис
википедия говорит, что эрланг — это ФП
википедия говорит, что ФП — это чистота
известно, что в эрланге есть мутабельность (ETS и
erlang:put/2
вместе сerlang:get/1
)
Найти противоречие в этой системе утверждений (при некоторых дополнительных естественных вводных) смогёте?
Или можно взять ML (или ocaml) — википедия говорит, что он functional programming language, хотя там мутабельность огого (и, из моего опыта, разбираться в коде на окамле сильно неприятнее, чем на хаскеле).
"очевидный" - является аргументом
Нет, это констатация факта.
Исходный оратор сказал про лёгкость рефакторинга в ФП, я (практикующий ФП) понял, о чём он и какое определение ФП он имеет в виду, и девять из десяти (или 99 из 100) других практикующих ФП, поймут и/или уже поняли, о чём он (потому что те практикующие, что есть на хабре, уже правильно понимали аналогичные высказывания в других тредах, или сами их высказывали).
Эта ситуация подходит под семантику фразы «практикующим очевидно, что…».
Вы понимаете о чём говорите (но, увы, доказывать не собираетесь)
Эмпирический пример с type-driven-рефакторингом выше, где мне вообще не пришлось включать мозги, чтобы поправить код под изменившиеся семантические требования.
Более того, пример выше показывает, что для придуманной вами же задачи мне потребовалось меньше кода, код чище, тестируемее, и так далее. К вашему коду можно задать пару дюжин вопросов, не связанных с синтаксисом и семантикой самого языка, о поведении и состоянии разных стейтментов, к моему — сильно меньше.
Да вот беда: от перекладывания JSON'ов есть реальная польза
Я не зря написал про подмножество перекладывателей — тех, у кого при этом ещё воинственная безграмотность.
а от Ваших типов её нет.
А у этого утверждения есть какие-то доказательства, или вы доказательства только от окружающих требуете?
Видите ли, когда вы вот тут текст публикуете, то он тоже где-то в виде JSON'ов перекладывается туда-сюда.
Какая польза от опубликованного здесь мной (или вами) текста?
Правильнее было бы сказать, что, конечно, ФП позволяет делать код, в котором проблемы несоответствия типов и автовывод типов транслятором делаются легче, чем в ИП. Но ИП не отрицает использование типов. От типов как раз есть реальная польза - контроль корректности выражения программистом своего замысла. Но их бы побольше, по описанным причинам, именно в ИП...
Ещё раз: функциональный стиль программирования потому и сектантский, что базируется на понятиях "чистый" и "греховный", а греховность она именно от мутабельности
А можно статью какую-то про греховность в ФП? Я просто не очень шарю, хочу в своём пет-проекте по подсчёту факториала с помощью зигохистоморфного препроморфизма использовать
ну не знаю, например привести оценку частотности ошибок с типами.
Это не показывает простоту рефакторинга, потому что то, что компилятор закрывает типами, перекладыватели жсонов вроде вас покрывают адовой кучей тестов (причём, дублируя по факту реализацию, потому что мыслить в терминах свойств вы неспособны). Поэтому вам и нужно по 10 сторипоинтов на перекладывание одного жсона.
По моей статистике ошибок в логике - 99%, а ошибок в типах не более 1%.
По моей статистике 99% ошибок, которые я делаю в C++, исключаются системой типов хаскеля, и 99% ошибок в хаскеле исключаются системой типов идриса.
вон в Вашем неправильном примере выше если заменить
Не понял, это что за аргумент? «Есть класс ошибок, которые данная система типов не ловит, поэтому, несмотря на то, что она ловит другой и большой класс ошибок, она ничем не полезна»? Ппц, сгорел сарай — гори и хата.
то Ваш расчудесный компилятор вообще ничего не заметит, однако Вам на это всё равно, главное ведь то, что компилятор замечает.
Расчудесный — заметит. Я просто пишу рядом
transfer-withdraws : (from to : UserId)
→ (amount : Nat)
→ (accounts : AccountsMap)
→ transfer from to amount accounts ≡ Success accounts'
→ balance (lookup from accounts) ≡ balance (lookup from accounts') + amount
transfer-adds : (from to : UserId)
→ (amount : Nat)
→ (accounts : AccountsMap)
→ transfer from to amount accounts ≡ Success accounts'
→ balance (lookup to accounts') ≡ balance (lookup to accounts) + amount
transfer-no-effect : (from to other : UserId)
→ other ≢ from × other ≢ to
→ (amount : Nat)
→ (accounts : AccountsMap)
→ transfer from to amount accounts ≡ Success accounts'
→ balance (lookup other accounts') ≡ balance (lookup other accounts)
transfer-preserves : (from to : UserId)
→ (amount : Nat)
→ (accounts : AccountsMap)
→ transfer from to amount accounts ≡ Success accounts'
→ totalBalance accounts ≡ totalBalance accounts'
и доказываю их как теоремы. Всё, тесты на эти случаи больше писать даже не надо, потому что это лучше, чем соответствующие тесты — у них здесь есть квантор всеобщности, а ваш средний юнит-тест проверяет один-два примера, и всё.
Но это агда или идрис. Тем временем даже в хаскеле типы могут помочь, потому что я могу записать эти свойства как утверждения для quickcheck'а, который по типам сам выведет и сгенерирует произвольное количество тестовых примеров и проверит, что эти условия выполняются. Да, не доказательства, но для обычных опердней сойдёт.
Я не опровергал то, что типы ловят какой-то класс ошибок. Я говорил о том, что этот класс куда менее частотный, нежели логические ошибки.
В языках с максимальной свободой типизации - примерно наравне, или даже больше. Особенно при плохих практиках.
Да это следует из моего опыта.
А это - из моего. Из таких языков почти 100% Python. Был ещё Erlang, там было реже, но тоже бывало.
вон в соседней ветке я проводил опрос
Anecdotal evidence на основании активных комментаторов данной статьи недостаточно важнее, чем представление одного конкретного вас или меня.
Но важнее то, что пока вы не гарантируете типы, в языках типа JavaScript, Perl, Tcl, тысячи их, или в намеренно либеральных к сочетаниям типов библиотеках, как NumPy, вы можете просто не суметь отлавливать логические ошибки: вы будете получать типичное garbage in - garbage out и не понимать, откуда эти странные значения в принципе берутся. Только после стабилизации, что же может выполняться, через типизацию, логические ошибки становятся тестируемыми. И только после обеспечения тестируемости вы полноценно можете искать логические ошибки как через тесты, так и через верификацию (включая визуальную верификацию, сиречь вычитывание кода человеком).
во взаимно непересекающихся пространствах.
С точки зрения вервия простого, веревистого - возможно. А вот для человека, который пишет этот код, между ними есть плотная практическая связь.
В очередной раз отмечу, что вам уже полдюжины раз было показано, что это не так, и что [сильные] типы ловят и логические ошибки.
И >=
вместо >
, и +
вместо /
, и деление на ноль, и многое другое — всё это я вам писал в комментариях.
Данная ошибка в общем виде типами не решается:
Показывал, как решать ровно это (только там у вас деление было).
То есть реально в решении работали не типы, а сравнение программы с образцом
С каким образцом? В типах, как и всегда, выражаются ограничения и условия на поведение функции — ровно то, для чего они и предназначены.
деление было примером к неожиданным ексепшенам
Вы там заодно функцию деления назвали add
, а я показал, как типы это ловят (а тесты — нет).
Вы предложили ключевое слово которое сообщает компилятору "здесь нет ексепшенов".
Нет, я предложил не это.
ограничения в общем виде описываются алгоритмом, а не типом
И снова нет. Ограничения — это логические утверждения. Это не алгоритмы.
а условие (что Вы предлагали) a + b == b + a
Я предлагал проверять ассоциативность, а не коммутативность, если что.
далеко не всегда выполняется. Если a и b - строки, а язык у нас, например, Rust который конкатенирует оператором "плюс" - например.
Конкатенация строк не является сложением чисел (которое мы обсуждали), ломающие новости! А проблема-то в чём?
и не все, далеко не все, алгоритмы описываются простым декларативным выражением.
Например?
требовалось
Если это требовалось, то программист дальше формализует эти требования, доказывая требуемые свойства этой функции. В данном случае у него это не получится.
логические утверждения != алгоритмы
Ограничения тоже ≠ алгоритмы. Логические утверждения — это свойства алгоритмов.
«Алгоритм Дейкстры находит кратчайший путь» — это утверждение про алгоритм (и его можно доказать, а не тестировать). «Веса должны быть неотрицательными» — это утверждение про входные данные алгоритма (и их тоже можно выразить в типах). «Алгоритм расчёта квадратного корня даёт ошибку не больше одного ULP» — это тоже утверждение, его тоже можно доказать (а не тестировать 2³² вариантов, как предлагали в соседней статье, или случайную выборку из 2⁶⁴).
Извините, я по памяти писал - точно не помню, коммутативность Вы предлагали верифицировать или ассоциативность - это не принципиально. Они оба не подходят.
Почему? Конкатенация строк ассоциативна.
требовалось сравнить две строки с учётом равно (>=)
Это отношение рефлексивно. Это можно выразить в типах.
но пользователь написал жёсткое сравнение:
Это — не рефлексивно. Доказать рефлексивность не получится.
В реальности именно с математикой сталкиваемся редко: чаще всякая фигня вроде "взять с полки пирожок, если он там есть, если нет - выбрать другую полку и взять с неё"
И у этого тоже есть требования и инварианты.
В реальности именно с математикой сталкиваемся редко: чаще всякая фигня вроде "взять с полки пирожок, если он там есть, если нет - выбрать другую полку и взять с неё"
Так база математики она вот именно такая простая. Посмотрите те же числа Пеано. А вот то, что с помощью этой базы строится, уже крайне многообразно, сложно и красочно.
И, кстати, все эти доказательные подходы более-менее удобно работают на уровне не сильно ушедшем от "взять с полки пирожок, если он там есть".
погодите. Вы пока не объяснили: зачем вы постоянно программируете ряд Фибоначчи, а уже какие-то новые числа пихаете.
Я не программирую ряд Фибоначчи.
в эпоху микросервисов "взять с полки пирожок" эквивалентно выполнению запроса к двум удалённым системам
Типизируется элементарно. В больших конторах всё это типизировано лет 20 как. Что характерно, используются более-менее типы из языка ML, но, разумеется, с косяками, т.к. делали "зрелые промышленные практики" незнакомые с теорией, а потому переизобретающие велосипед.
погодите. Вы пока не объяснили: зачем вы постоянно программируете ряд Фибоначчи, а уже какие-то новые числа пихаете.
Числа Пеано в программировании не нужны.
Судя по тезисам, погуглить что такое числа Пеано вы не удосужились. Безграмотность настолько воинственная, что уже больше на троллинг похоже чем на реальное обсуждение
это не безграмотность, это офтопик по отношению к обсуждаемой теме
@vkni привёл числа Пеано как пример того, что в математике сложные вещи получаются из простых. Если бы вы открыли хотя бы википедию, вы бы увидели, что числа Пеано в этом контексте это просто натуральные числа. Но этого не сделали, проигнорировали, что пример был про математику, и начали говорить, что числа Пеано в программировании не нужны.
в программировании эта фигня не нужна (равно как и ряд Фибоначчи)
Покажете полезную программу никак не использующую натуральные числа?
А в математике и философии я интересуюсь только тем, что и правда интересно.
Трудно представить область математики, которую можно серьёзно изучать и при этом ни разу не столкнуться с аксиомами Пеано
Покажете полезную программу никак не использующую натуральные числа?
Это старый спор насчёт того, что нужно программисту, а что не нужно. Как правило, выходцы из Пажеского корпуса начинают рассказывать, что гимназии неважны. @ednersky тут являет некоторое исключение, т.к. он, очевидно, закончил ремесленное училище.
Впрочем, это неважно - очевидно, что обобщённому программисту в вакууме вообще ничего не нужно. А для успешного заработка денег вообще и программировать не нужно - есть проституция, спекуляция, мощенничество и т.д.
Тут уже пошёл вопрос статуса - вы же понимаете, что не может автор этой статьи сказать "да, я лох, да, правильное использование статической типизации при перекладывании JSON'ов и прочем кровавом ынтерпрайзе сокращает время разработки". А даже если и так, то это может быть банально невыгодно исполнителю сокращать время разработки, вот он и вертится, как уж на сковородке со своим несчастным Rust'ом (на кой хрен нужно заменять Рустом Пёрл - непонятно).
только проблема в том, что НИКТО не видел этого "правильного" использования типизации.
Это глупая ложь.
Правильное использования для перекладывания JSON'ов это описание типов для сериализации в одной библиотеке, а дальше автоматический вывод сериализующего кода. Например, в OCaml используем ppx_deriving_yojson, в Haskell - Aeson с его deriving пакетом, в Rust - serde.
Документация по Yojson с примерами правильного использования - https://github.com/ocaml-ppx/ppx_deriving_yojson
Вы таки будете смеяться, но в HEB, крупнейшем техасском (и одном из крупнейших американских) продуктовом ритейлере
As of 2022, the company had a total revenue of US$38.9 billion.[8] H-E-B ranked number 6 on Forbes' 2022 list of "America's Largest Private Companies".[9] The company also ranked number 3 on Forbes' 2024 list of "Customer Experience All-Stars."[10] H-E-B was named Retailer of the Year in 2010 by Progressive Grocer.[11] Supermarket News ranks H-E-B 13th on the list of "Top 75 North American Food Retailers" by sales.[12] Based on 2019 revenues, H-E-B is the 19th-largest retailer in the United States.
очень много бекенда на хаскеле.
и программировать чередование чисел, не важно натуральных, фибоначчи, факториалов или ещё чего, ни в энтерпрайзе не требуется ни в не энтерпрайзе.
При этом вы топите за тесты и ООП, хотя никто не видел никакого правильного ООП, и тесты нафиг не нужны ни энтерпрайзу, ни его пользователям. Плюс, все эти тесты тестируют 2+2 = 4, очень классно.
паясничать перестаньте, лучше "полезно" проясните.
Да не, почему, я ж не спорю, ненужная на практике фигня. Точно так же, как и тесты — ненужная на практике фигня (с чем спорите уже вы).
чувак написал: "полезно" и выдал такую хрень, что без поллитры никто не разберёт, которая на экране не помещается!
Показал ради лулзов основания математики на расте, чем плохо? Это как судить об ООП по fizzbuzz enterprise edition (только, в отличие от ООП, математика интересна и полезна).
понятно почему вас на работу не нанимают.
Почему? Нанимают же. Я прям сразу говорю, что не пишу тесты, и мне сразу оффер дают на ×2 от верхней границы вилки.
Воинствующая безграмотность это писать, что Х не нужно в программировании не зная и даже не погуглив что такое Х.
а когда фигня действительно оказалась ненужной
Статья, которую вы смотрите называется "Числа Пеано на системе типов Rust". И сражаетесь вы, соответственно, с тезисом "арифметика на типах нужна для программирования", подменяя начальный: "арифметика нужна для программирования"
прислал сюда ссылку: X действительно не нужно в программировании.
X на типах не нужно в программировании
почему я (опять) оказался прав?
Наверное, потому что подменять тезисы и побеждать ветрянные мельницы гораздо проще чем оспаривать реальные тезисы ваших оппонентов.
А что такое типы? Это тот же самый ООП, только чуть более широкий, ибо простые типы тоже могут рассматриваться как объекты.
Настолько плохо, что местами даже хорошо
кроме того, в спорах выше вы мне типы именно и рекламили.
Не припомню такого. Если только вы не считаете рекламой мои комментарии о том, что ваши представления про системы типов не соответствуют действительности
неа, я сражаюсь с тезисом, что типы жизненно необходимы для программирования. что их непременно надо повсюду запихать, а всех кто сопротивляется - посадить в клетку
Правильно! С такими вредными тезисами нужно бороться. Только вот не могу вспомнить кто же этот тезис высказывал...
Возмём определение системы типов (TaPL, B.C.Pierce) :
A type system is a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute.
Классы не являются частью этого процесса. Классы могут существовать без системы типов (python, ruby, javascript), система типов может существовать без классов (c, haskell, agda, rust). Иногда классы добавляются в систему типов (typescript, c++, java), но в общем случае это абсолютно разные вещи, которые решают разные задачи. И это если забыть, что в прошлом комментарии вы писали про то, что типы это объекты, а в этом сравниваете их с классами.
Нет, я просто хочу хоть как-то добиться от вас признания, что тесты не нужны, потому что менеджерам и клиентам на них пофиг.
Тесты уменьшают скорость, конечно, потому что тесты заставляют писать по пять экранов моков, прокладок и тестов на то, что без тестов делается за 10 строк. При этом от тестов никакой пользы, потому что они не ловят ошибки просто по определению: тесты тестируют положительный сценарий у разработчика в голове.
и вот тогда каждый написанный тест даёт максимум эффекта. то есть имеет максимум соотношения "качество/цена".
Которое не то что ноль, а отрицательное.
Ну про отрицательный тут таки преувеличение. Описанные оппонентом тесты безусловно необходимы, не только для самоподтверждения разработчику корректности итогового результата, но и для заказчика (который не будет верить всем рассказам про великолепные внутренности, и будет прав). Ещё они помогают в преодолении лени по отношению к конечному результату :)
Но такие тесты в большинстве случаев отрабатывают только то, что называется happy path, и неспособны отловить то, что за его пределами - а на их уровне отработать все возможные проблемы внутри уже невозможно.
А вот отказываться от защиты внутренней кухни от затрат сотен часов на поиск тупейших ошибок, которые могли быть выловлены в разы дешевле... [censored] и отвага.
Для большинства разумных участников - кэпствую, конечно, но мне про "отрицательное" таки сильно не понравилось.
это вот то же самое, что записывать видеофайл на видеокассету с целью смотреть видео
У варианта с видеофайлом есть колоссальный плюс: содержимое в цифровом виде защищается всякими контрольными суммами и помехоустойчивым кодированием. Конечно, и цена за это - снижение плотности. Так что смеяться не стоит.
Аналогично с типами в ЯП - дополнительные усилия по типизации там, где могут быть проблемы без этого - окупаются многократно.
Утверждение 1: типы может выводить компилятор. Самостоятельно. Точка.
Ну, расскажите, как он это сделает без объёмной подсказки, если, например, во входных данных currency=1 означает доллары США в центах, currency=2 - те же доллары в тысячных цента, currency=890 - в йенах.
Цена описания правил такого понимания будет у большинства средств выше, чем цена написания собственно типов и кода их генерации.
Утверждение 2: констрейнты можно задавать и без типов. констрейнты (where) и типы - разные вещи.
Тип собственно и есть данное к приложенным к нему констрейнту. Констрейнт не обязан быть только на входе в функцию, или где вы там это where применили.
Опросы вносят искажения. Вряд ли кто-то прямо считал ошибки и вёл статистику.
Поэтому, во-первых, в ответах сказывается эвристика доступности: если оказываемое воздействие больше, то, скорее всего оно лучше запомнилось и считается более вероятным.
Во-вторых, ошибки типизации могут восприниматься как более постыдные, и о них можно и умолчать, ведь логические тоже есть.
В-третьих, не все ошибки типов ещё выстрелили :)
но что на самом деле она связана с двусмысленностью оператора "плюс", который используется и как математический и как строковый
У меня много лет с перерывами основной язык - Python. Там нет проблемы что 5+'3' == 53, там сильно более жёсткая типизация (в основных типах). И всё равно надо очень тщательно следить за тем, что передаётся, именно на уровне типов. В одной ветке ты присвоил переменной число, в другой - строку. Если это явный трюк типа `int(os.environ.get('MOO', 1))` (вместо '1' в дефолтном), то ещё понятно. Если быстро глазами не находится - начинать плакать и искать поэтапно, в тяжёлых случаях расставляя assertʼы. Ещё хуже, когда (чаще всего из-за как раз "ООП головного мозга") в возвращаемом типе универсальный Iterable[Any], а какая-то функция вернула вместо списка строк - одну строку, и цикл жизнерадостно перебирает символы строки. И таких примеров много.
И существенно, что нечёткая типизация не позволяет доверять тестам. В вашем примере с "when (amount < balance user1)" само сравнение начинает быть корректным только тогда, когда мы твёрдо знаем, что с обеих сторон числа (или объекты одной размерности) Мы не можем доверять коду, который позволит сравнить доллары с апельсинами по правилам сравнения колёсной формулы паровозов. Сначала - необходимые ограничения, потом - уверенность в том, что код в их рамках выполняет то, что нужно.
Так что тут я Larry Wallʼу не поверю, считаю его заведомо предвзятым - как автора Perl и соответственно выгнутым в свои подходы. Ну а что "доказать" в науке можно что угодно - это исторически известно в тысячах примеров:)
Окей. Так как вы топите за ООП и википедию, то поясните, пожалуйста, где в примере выше у вас наследование, полиморфизм, инкапсуляция, и так далее? Выходит, на деле даже для такого важного перекладывателя жсонов, зрящего в суть и указующего путь, как вы, ООП неприменим?
а мои оппоненты постоянно пытаются что-нибудь мне да запретить: то ООП (который, кстати, инкарнация ваших замечательных типов)
Так вы же ООП не используете, потому что как доходит до дела, то все эти три столпа оказываются ненужными. О чём тут ещё говорить?
то обобщённые алгоритмы (динамическую типизацию)
Что? Как связаны обобщённые алгоритмы и динамическая типизация?
Функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных и результатов других функций, и не предполагает явного хранения состояния программы.
Соответственно, не предполагает оно и изменяемость этого состояния
Но википедия, конечно, очень авторитетная, а ФПшники это просто сектанты
а в мире мутабельно абсолютно всё
Мощное обобщение, но бессмысленное.
А вот если рассуждать в рамках конкретной задачи, то всё выглядит немного иначе, безотносительно ООП, ФП или что-то еще мы применяем.
Есть молоток и у него одна функция "забивать", ему плевать на всё, он просто забивает то, что ему подсунут. Можем условно считать его иммутабельной функцией.
Есть гвозди. Они могут быть разной длины, толщины, могут забиваться в дерево, вату, гипс, голову, могут гнуться, ржаветь, могут быть из стали, пластилина, стекла и т.д. Так вот гвозди - это условно данные и они мутабельны.
Как мы можем видеть в рамках конкретной задачи, функция немутабельна, а обрабатываемые данные еще как мутабельны.
О. Свидетели простоты ФП подъехали. А куча глобальных переменных и километровые названия функций - и это уже в средненьком проекте - ну это так, ерунда )
Прочитав статью многое понял из ООП и паттернов проектирования. Спасибо автор, наконец то понятно. 🤝 Теперь нужна статья "SOLID головного мозга".
Это не только с ООП, это и со всеми другими практиками доведенными до абсурда и применяемыми без контекста и понимания "зачем и почему".
Красивая идея описать всё, как медицинское заболевание. Посмеялся, плюсанул.
А если серьезно, в одном из комментариев выше промелькнула мысль о том, что паттерны надо уметь применять, что есть задача, и есть её стандартное решение, которое опробовано практикой. А так же была другая мысль, о том, что сильно всё зависит от проекта, где-то надо делать проще, а где-то заложиться. Все эти мысли правильные.
Добавлю ещё 5 копеек, лет 10 назад в одном из выступлений архитекторов ПО встретил редкий термин "оси вариативности"(честно, не нашел откуда он его взял, но термин мне понравился). Вся суть в том, что должна быть какая-то чуйка, понимание бизнес-задач, когда и где нужно заложиться в плане универсальности(заложить абстракцию, сделать стратегию, т.д. и т.п.), а где лучше сделать проще. И от того, насколько мы угадали зависит сложность получившегося ПО. Либо всё клево, часть работы мы сделали заранее(и сэкономили итерацию рефакторинга), либо избыточная сложность усложняет тесты, усложняет реализацию фич и всюду мешается и приводит в конечном случае к не стабильности ПО.
Вот как раз "ООП головного мозга", является прямым следствием непонимания что будет делать ваше ПО. Лечить его нужно не функциональным стилем, а прокачиванием совсем других скилов...
Вместо `print("Hello, World!")` больной пишет
Пишет ООП код на Python. 🥴
У меня наоборот - слишком мало ООП абстракций в коде:) Пишу на Си, на "упрощенном" С++ (обычно как потребитель классов тех или иных библиотек, таких как Qt, и практически без шаблонов), на Go (тоже практически в стиле Си, почти без параллелизма). Наверное потому что проекты обычно маленькие, и так всё понятно. Иногда это приводит к отрицательным последствиям - типа такого что появляется один "God Class/Object" в котором свалено всё в кучу.
Абсурдные примеры, хочется показать что прав, приведи пример хоть с какой-то адекватной сложностью. Но скорей всего после него тебя задушат, ибо там уже начинается архитектура))
Как встарь говорили -
Заставь дурака богу молиться - он себе весь лоб расшибет.
Лет много прошло, новые технологии, а человек то тот же.
1. Шоковая терапия: заставить пациента написать проект на чистом C или Go
Для начала лучше прибегнуть к медикаментозной терапии, а именно: 1С!
Не вижу ссылки на источник :)

Ну да, ну да... Видимо автор только на таких проектах и работал, которые "Hello, World" выводят, или имеет крайне малый опыт, раз такую дичь задвигает. Видимо не работал на динамичных, постоянно развивающихся проектах, когда бизнесу нужно сегодня одно, а завтра другое. Сегодня вот в этом модуле запили ка мне фичу, а завтра вон в том такую же, но с перламутровыми пуговицами. И вот сидит адепт процедурного стиля и жмёт контрол-цэ - контрол-вэ, и boolean аргументы в функции добавляет, да if - elsamи брызжет направо - налево, один в один складывает, да циклами внутри разбавляет. Нах этот полиморфизм, говорит, всё ж и так понятно, если не if, то значит else... Можно ещё и голый sql где-нибудь вставить и массив вместо сущности вернуть, ну а хули, те же данные всё равно, зачем нам всё усложнять какими-то сущностями, какими-то репозиториями. Надо нам вот прямо в этом месте айди пользователя вытянуть, ну запиздячим запросик к бд. А потом, а чёй-то у нас баги лезут там, откуда не ждали? Нипанятна.
Да, для меня отрицание принципов SOLID и ООП в целом === непрофессионализм, неопытность, недальновидность. У меня от таких адептов процедурного стиля уже глаз дёргается, когда приходится разгребать их спагетти легаси. Я не отрицаю процедурный стиль в целом, но максимум, для чего он годится - это небольшие изолированные скрипты, которые дёргает крон.
Ну само собой относительно применения ООП - всё хорошо вмеру. Главное - соблюдение баланса между следованием фундаментальным принципам ООП и здравым смыслом.
Это троллинг высшего уровня. Автор разбирается в паттернах и приводит простые примеры когда они не работают и по сути высмеивает их. Я убежден что суть это поста посмотреть как реагируюи другие и понять количество людей неразбирающихся в ооп и петтернах, и для чего они нужны.
Те кто согласен со статьей явно не имеют дела с какой то +/- средней сложности проекта. Я бы посмотрел на вас когда пришлось вам отказаться от абстрактной фабрики и от следованмя solid и пришлось сопровождать огромный класс который отвечает за все ну или понесколько раз добавлять идентичные условия в разные формы. В итоге получив сложную в сопровождении систему с необхожимостью затрат большого времени на тестирование.
Помню как на интервью выдал код с простой функцией и собеседнику не понравилось.
На что ответил , что функцию без состояния расширять композицией, а если будет состояние ок тогда уже и подумать можно.
В общем не понравился такой ответ, как это без фабрик и без интерфейсов :)
@Boomburumа не планируется авторов постов обязать ставить какую-то плашку "написано генеративной сетью" или прикручивать анализ, который будет навешивать на пост плашку вида "с вероятностью 78% пост написан нейросетью"? Кто-то из ваших авторов писал, что соответствующую автоматику вы уже тестировали - интересно, скоро ли в продакшен)
Добавлю ещё некоторые причины взникновения болезни, когда болезнь "ООП головного мозга" выбирал явно не поциент, а люди его обучающие:
1. на собес приходят SOLID-зубрилки.
Которые отвечают про каждую букву, но вложенный цикл, от цикла таплом по зипу не отличают.
2. А давайте ООП учить первым подходом программирования.
Неотвеченные 20 лет вопросы типа: "наследование должно происходить по цвету, типу шкуры, типу пищеварения?" никого из этого лагеря не смущают.
По-моему ООП вообще первое время (полгода - год обучать не надо). Давать знания надо ровно в тот момент, когда необходимость в этих знаниях возникает (можно форсировать подкинув задачу, но не надо начитывать теорию).
Пусть пишет свои обучающие программки в процедурном стиле.
Через пару месяцев захочет вернуть две переменных х и y - сам допетрит до структур.
Через пару месяцев поймёт, что с TPoint (надеюсь в C# codestyle такой) удобно что-то делать "изнутри" - сам поймёт что такое класс.
Через пару месяцев неправильно спроектирует иерархию "геометрических фигур"
И вот только когда человек способен сам сформулировать вопрос - ему пора давать книжку по ООП.
Сталкивался я с кодом сильно обмазанным солидом и с маджик классами где один метод на несколько тысяч строчек кода делал всю магию . И всё это выглядело одинаково отвратительно. Код помимо своей эффективности должен писаться так, чтоб его можно было поддерживать, и это как минимум чтоб его можно было читать и понимать. И здесь скорее нужен баланс между кодом "на будущее" и кодом "когда-нибудь порефакторю эту лапшу", так как обе крайности стоят довольно дорого. А ООП не ООП это дело второстепенное.
Декомпозиция и композиция - меч обоюдоострый. Заигрываются и те кто слишком избыточно обобщают и те кто увеличивают вариативность.
Все должно быть предельно просто, но не проще.
Примеры хоть и утрированы, но встречаются. Но мне кажется лапша встречается чаще.
Снова уже знакомый автор со знакомым почерком... Сначала объявил войну самой логике, выкатив статью о том, что алгоритмы не нужны. А теперь против ООП, причем так и не смог самостоятельно привести примеры для аргументации, а просто привёл утрированные примеры. Вероятно побоялся привести реальные примеры, в которых его вкусовщина выдала бы в нем некомпетентность. Против алгоритмов, против ООП - зачем полез в университет? Стыдно за таких коллег. Основная проблема таких борцунов и проливателей света в том, что они абсолютно не владеют темой, против которого борятся.
Создаётся впечатление по статьям, что в России есть силы, которые хотят уничтожить некогда сильную индустрию через ложные постулаты и борьбу с искусством программирования. Новое поколение разработчиков не способно уже конкурировать с индусами. Примерная история у всех: вайти курсы, проскочить собесы вызубрив ненавистные паттерны и алгоритмы, никогда не применять паттерны и алгоритмы(потому что не поняли вызубренное) и кричать на каждом углу "они не нужны". Мало того ,что вайтишник за эти годы доросли до сеньоров и тимлидов по выслугу лет, так теперь ещё и до вузов добрались...
Хуже только функциональное программирование головного мозга)
обычно подобное несут люди, которые не понимают, что такое Unit-тесты. Они же вас обвинят, что у вас Mockito головного мозга, DI головного мозга итп. А в крупных организациях требуют до 70-80% покрытия... по правде, сейчас таких много...
Даже рядом не сяду с функциональщиком.
Весь его мир был построен на классах, объектах и интерфейсах. И если кто-то говорил «стул», Иван уже представлял «интерфейс Сидящего»....
Он понял, что у него так много классов, что он сам уже забыл, какой метод за что отвечает. Он присел на стул и понял, что не может найти метод для того, чтобы встать с этого стула.
Сбросьте вот такие вот требования к кодингу и ниспадут требования Windows для исполнения программ))) Всё за хитрых хотите зайти.
Болезнь «ООП головного мозга»