Как стать автором
Обновить

Комментарии 634

Бедная похапешечка, сколько раз её убивали.

Моя теория умирания языков программирования - у языков программирования может быть очень сильная инерция. Главное, что надо понимать - хорошесть/плохость языков - штука условная и почти всегда любой "плохой" язык тоже вполне сносен.

Если язык "взлетел", оброс большим количеством проектов, в том числе коммерческих, умирать он будет долго, медленно. Даже если мы представим, что вдруг внезапно всем очевидно, что (неважно, но для примера, допустим) Golang 100% лучше, чем PHP, все равно, PHP может умирать еще десятилетия!

Если в бизнесе уже есть приложение на умирающем языке, никто в здравом уме не будет его переписывать просто потому, что язык уже не моден. Работает - и хорошо! Соответственно, есть спрос на программистов, работающих на этом языке (кто-то же должен обслуживать и развивать это ПО). Более того, этот похапешный отдел скорее всего и новые проекты будет на похапе делать (потому что мозгу так проще, иметь в голове одну модель языка, а не переключаться). Можно даже в каком-то смысле воспринимать ЯП как вирус - если контора заболела каким-то "плохим" языком программирования в детстве - есть риск, что она будет так болеть вечно. И еще может быть в ходе агонии породит новые проекты на этом "условно умирающем" языке, в том числе и более-менее успешные.

Помните, такой язык как Perl? Если про PHP можно спорить про его умирание, то Perl, наверное, более мертв (чем PHP)? Однако, до сих пор SpamAssassin на множестве почтовых серверов в мире - на перле. И инсталляшки дебиана его, вроде используют. И чтобы с ними как-то что-то делать - востребованы программисты на Perl. Инерция.

Perl с сетью хорошо работает. хBSD во многом жива по тем же причинам.

буквально вчера на одном сайте видел в адресной строке что-то типа /cgi-bin/stat.pl?ses=111&uu=222&pp=333

Так что даже web приложения на perl где-то ещё работают

Вовсе не факт. Иногда старые URL-ки просто сохраняют для совместимости (чтобы работали старые ссылки), а на деле под капотом может быть PHP или Python + Django или что угодно ещё, ведь маршрутизацию URL можно как угодно настроить.

Мы с друзьями делали URL-ки, заканчивающиеся на *.jsp, и ещё в заголовках отдавали «X-Powered-By: Apache Tomcat» вообще чисто забавы ради, т.к это был бэкенд.

О да, я хулиганства ради на Node.js делал урлы, заканчивающиеся на .php.

Факт - uu pp и ses это штаны от моего проекта, которому 15 лет минимум (лом смотреть сколько) - и им пользуются провайдеры

Да, и половина экономики США держится на COBOL.

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

Да тут скорее анекдот про неуловимого Джо больше подходит... У них всё было хорошо, потому что они пропустили время перехода на актуальный софт. Поэтому все, кто искал уязвимости их и не находили: они ж искали в новом софте, не ожидая, что там что-то древнее. Ну, теперь после новостей, все, кто хотел, уже знают какие уязвимости надо проверять для win3.1 и win95 - всё давно описано и легко гуглится. Так что я не удивлюсь следующей очевидной новости про эту компанию)

А вторая на excel

Моя любимая БД =)

Буквально вчера понадобилось обрезать PDF от пустых полей по краям. Подходящий инструмент оказался уже на компьютере: pdfcrop из поставки LaTeX, тоже написан на Perl.

Люблю php. Начинал с него. Он ни беден, ни велИк, он - самодостаточен, красив и мудр. И его не победить,...наверно :).

Про PHP я могу сказать только одно... но пятью разными способами

Это про Perl, там способов будет 10, 5 из которых вообще даже автор не прочитает через месяц

Но ведь PHP создан, чтобы умирать. А среди меня PHP давно заменён на Rust. Всё, что я когда-то скриптовал на PHP, теперь делаю на Расте.

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

Rust один из лучших языков для бекенда

Серьезно?) Один из лучших языков для того чтобы шиппить в прод в 20 раз медленнее чем на любом другом япе

При наличии опыта разработка на Расте не медленнее, чем на других языках. Я тоже заблуждался как вы когда-то.

А что там не так с шиппингом? С помощью sccache полная сборка среднего проекта будет длиться минут 5-7 на ci.

Абстракции там хорошие, тулзы удобные, поддержка со стороны ide хорошая. Библиотеки вполне себе есть в наличии.

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

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

Что угодно, лишь бы не осваивать дотнет, ага.

То чувство, когда с .NET переходишь на Go

А чем продиктовано такое решение?

Причины лежат скорее в области ощущений, чем логики

  1. win 10 для меня последняя версия винды, которую я ставил себе на домашний комп. Был и есть ноут с линуксом, но через 3-5 лет и домашний десктоп перекочует на линукс. Просто потому что win 11 не нравится, а у 10ки поддержка рано или поздно будет становиться плачевной. Да и в целом дистанцируюсь от мелкомягких. А .NET и линукс не до конца дружат - gui уже сколько лет не могут завести например.

  2. В целом, сама ситуация с языком - область задач, для которых используют go в коммерческой разработке, наиболее пересекается с областью задач, которые мне было бы интересно решать (эффективные сетевые приложения). Мало легаси - язык то молодой. Востребованность go программистов. Просто нравится результат работы компиляции в виде одного простого бинарника без всякой лабуды

На самом деле легаси в go больше чем должно было бы быть. Скажем, api модуля os было стянуто с Питона, где оно на тот момент уже было странным.

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

Про кроссплатформенность это несерьезно начиная с появления .Net Core.
Для UI есть Avalonia которая хороша чуть более чем полностью, это если про десктопы говорить. Про веб я вообще молчу, упомяну только что есть офигенный Blazor.
Cовершенно непонятно, чем шарпы неэффективны в "сетевых" приложениях, может речь идет о физических уровнях OSI? Ну и про легаси это веселый пассаж.

Интересно с каких пор Avalonia стала частью языка или хотя бы стандартной библиотеки. Или для вас библиотеки и языки вещи тождественные?

чем шарпы неэффективны в "сетевых" приложениях,

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

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

Дотнет умеет в собираться в натив и бандлиться в один файл.

Уметь и делать из коробки - разные вещи

Почитать две статьи и выставить нужные флаги при сборке - великий труд, действительно)

Собирать в один файл умеет штатная сборочная система, входящая в состав .NEt SDK. Если это вас не устраивает - то что вообще в вашем понимании является "коробкой"?

Golang или Rust для бека? (Хочется уже куда то двигаться, а выбрать не получается 😅)

Раст, конечно. Поначалу будет трудно, но потом очень легко.

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

Rust. Очень хорошо спроектированный язык (в отличие от мистера "дженерики не нужны" 😀). Очень хорошая интеграция с VSCode, очень хороший тулинг из коробки. Для бэка есть фреймворки (axum, actix), а есть и более простые либы. Есть и ОРМ (diesel), есть query-builder (sqlx).

Я сам фронт, но недавно начал пилить свои проекты, там в основном бэк. Можно почитать https://llogiq.github.io/2024/03/28/easy.html , чтобы меньше было затыков в начале, ну и Copilot или аналогичный чатбот сильно упрощает процесс починки ошибок и написания кода.

Из минусов - порой очень сложные для понимания ошибки (на первые разы), иногда сложности с тяп-ляп и в прод (а иногда оно надо), долгая компиляция, непонятки с borrow checker'ом. Но со временем, когда подучишь язык, всё становится намного проще.

Golang скомпилированный под локальную машину выполнял команду - 452.430ms
PHP в docker-образе выполнял команду - 390.859ms

один вызов я так понимаю? то есть заодно померяли инициализацию виртуальной машины тоже?

> Так же провел замеры при работе http сервера и PHP показал себя с хорошей стороны, в docker-образе, с подключением к базе данных, вставкой строки в базу и последующим селектом данных и базы и передачей json ответа http сервер на php выдержал 10.000 rps, со средним временем ответа 40мс, максимальным 500мс и минимальным 200 микросекунд, но об этом более детально напишу в следующих статьях.

Так а что тут мерялось? Подключение к БД и вставку строки?

Это просто пример, в котором PHP обогнал golang. В реальной жизни совершенно другие задачи. Замеры были только выполнения кода по десериализации json, не более того.

На счет http-сервера - это спойлер к будущей статье, в которой уже более детально об этом расскажу

10000 rps 40мс со вставкой в бд и чтением на коленке? А чем нагружали?

Что-то тут нечисто. 10т реквестов на одной машине уже нетривиально сгенерить. Хочется посмотреть как это делалось.

k6 использовал для нагрузки

база postgresql, для подключения использовал пул соединений

На одной и той же машине?

да, все в docker

И операционной системе хватило сокетов?

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

Если вопрос к http2, что количество сокетов было снижено, то - нет, запросы были http первой версии

Ну измерения так всеравно не делают, надо делать показательную выборку и смотреть перцентили, модальность, ошибку, доверительный интервал, коэфф экцесса и т.д., а не только среднее и мин/макс.

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

Так у го нет виртуальной машины

помню и node.js тоже был с такими же амбициями, так что и go переживет )

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

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

был случай, когда я проходил собеседование в одну из компаний экосистемы Сбера, прошел все мыслимые и немыслимые собеседования со специалистами и менеджерами проекта, на этапе подготовки офера, заявляют, якобы пых не входит в список доверенных ЯП по версии службы безопасности, мол надо будет перевести проект на js и чуть ли не за месяц. Я немного охренел, говорю, если такая позиция у СБ, давайте тогда на шапры переведем, это будет более грамотная миграция, в общем офер не получил. Через неделю получил офер в другую компанию из той же экосистемы, где пых был в почете :)

Последние несколько лет на рынке, по моему сугубо личному мнению, golang вытесняет PHP

Здесь не согласен, просто, подавшись хайпу, народ то на node.js, то на go, то на битрикс делает, потом волна спадает, приходится искать специалистов для поддержки проекта, а мигрировать на новый стек, это очень дорого. Поэтому компании едят кактус и плачу, но продолжают есть.

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

Другими словами, дело в людях, а не технологиях. Если в компанию придёт команда со знанием стека руби, и при этом заимеет внутри-политически сильные позиции, значит в компании будет основным ЯП Руби, и совершенно плевать, насколько он подходит и выгоден ли для решаемых задач. Главное, что команда его знает, и не знает ничего другого. Значит будет Руби, а не Go или PHP. Даже если придётся городить костыли.

Смена технологий меняется с переосмыслением технологий у людей.

у людей переосмысление тоже не особо происходит, смена технологий происходит скорее со сменой людей

Мы примерно об одном говорим, но, но есть нюансы. Компания это не всегда технически подкованные люди, им все равно на чем написано, чистый там или грязный код, им нужен продукт, причем вчера И тут попадается некий разработчик, более голосистый, более убедительный, кстати про это были статьи на хабре. Он убеждает, что надо сделать, условно на чем-то, что по его словам эта технология, за которым будущее и все крупные вендоры перешил на него. Проект запущен, разработчик уходит и тут возникает вопрос, а как его поддерживать и развивать? Наступает момент истины, надо кого-то искать, hh начинает пестрить вакансиями, а переехать куда-то это очень накладно и болезненно для компании.

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

давайте тогда на шапры переведем, это будет более грамотная миграция

Я правильно понимаю, что в вашей картине мира, миграция на C# - это единственное решение всех проблем и аргументации мы, конечно, же, не услышим, вы просто так чувствуете?

  1. Есть иммутабельные типы. Да и вообще с типами в C# заметно лучше. И проблем с null/undefined заметно меньше в сравнении с js/ts/php.

  2. Интерфейсы есть с незапамятных времен, в пых только недавно завезли трейты, в чистом JS их вообще нет. Барбару Лисков без них ублажать заметно сложнее и багованнее.

  3. Потенциал к появлению SIMD инструкций в коде и как результат больший перформанс по сравнению с двумя остальными.

  4. Время компиляции TS vs C# сдаётся мне будет на стороне C#.

в пых только недавно завезли трейты

Ничего, что трейты появились в PHP 5.4 аж 12 лет назад?

Это не те трейты. Хотя я тоже не понимаю причём тут трейты...

Это не те трейты

Вмысле НЕ ТЕ?

В смысле в PHP трейтами зовутся миксины

Нет. У трейтов в PHP состояния как у миксинов в руби нет.

Не эксперт в PHP. Видел какой-то недавний релиз (~год назад), который делал некоторый пример с трейтами и видимо я их таки спутал с интерфейсами. Тем не менее и интерфейсы и трейты помогают с композицией. Но, учитывая что типизацию допиливают по сей день и интеграция с фреймворками вроде не самая глубокая - С# в этом смысле побеждает.

Не знаю, но на JS/TS достаточно комфортно писать backend. Основная проблема в том, что экосистема еще недостаточно развита. Из перечисленных вами критична только проблема с SIMD инструкциями, но такому языку это не особо и нужно. С# тоже как язык очень хорош, но если нужна производительность, то тогда уже сразу можно использовать Rust. С# выглядит как полумера.

Это не полумера, так как сложность написания кода на языке с GC и без очень сильно различается

Языки с GC все же не так кардинально отличаются друг от друга. Если использовать кластеризацию и современный фрейморк в Node.js, то он может работать не в разы медленнее Golang. Аналогичное хочет показать и автор статьи, как мне кажется. Как правило, путылочным горлышком оказываются компоненты вроде БД, которые и так написаны на C/C++. В таком случае использовать ещё один язык с GC не особо эффективно

Погодите, но вся эта "кластеризация" не в вакууме же крутится? Есть конкретные железяки, на которых это все запускается, и эти железяки стоят вполне реальных денег. И вот в производительности на рубль стоимости железяки нода сливает, простите за откровенность, едва ли не любому конкуренту.

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

К тому же железо далеко не единственный фактор. Например, программист может стоить дороже и послать его потом делать интерфейс или администрировать сервисы будет сложнее, бутылочным горлышком в итоге окажется БД или какая-нибудь Kafka, из-за чего даже переход на язык вроде Rust ничего не кардинально не поменяет, если только не переписать Kafka на Rust.

Об этих вещах я и говорю, подразумевая "полумеру".

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

Вот тут, к сожалению, проще завалить ресурсами, чем пытаться производительную апишку на C++ написать. Изкоробочная многопоточность сроляет гораздо больше, а с памятью пооптимизировать и в GC-языках можно.

 Если заменить один язык с GC на другой, то даже не факт, что код в итоге будет работать быстрее

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

Весь движ с производительностью веб-апишек же строится не вокруг оптимизации атомарного запроса (по факту, пользователю насрать, 30 милисекунд исполнялся его запрос, или 32), а вокруг возможности выполнять тысячи запросов параллельно за адекватное время. И вот тут один GC-язык от другого может отличаться настолько разительно, что вы офигеете. Прям в разы, если не на порядки.

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

Как раз факт экономии и будет. "Мощное по современным меркам железо" стоит достаточно ощутимых денег (особенно, если вы арендуете мощности). В момент, когда деньги на ресурсы начинают считать, вдруг начинает ролять тот факт, что Go на железке, на которой Node вообще не запускается, умудряется вывозить больше RPS, чем нода в лучшие свои годы.

программист может стоить дороже и послать его потом делать интерфейс или администрировать сервисы будет сложнее

А вы, простите, по какую сторону баррикад?) Нормального бекенд-программиста хрен вы заставите делать интерфейс или работать сисадмином.

 бутылочным горлышком в итоге окажется БД

Да, скорее всего, она. Хороший бекендер, к слову, в большинстве случаев знает, что с этим делать.

или какая-нибудь Kafka

А вот это, как раз, вряд ли)

даже переход на язык вроде Rust ничего не кардинально не поменяет

На Rust - абсолютно точно не поменяет. Rust вообще про другое.

SIMD инструкциями в C# можно пользоваться уже довольно давно. Мне странно, что вы говорите о Rust, но Rust - со всеми своими проблемами совершенно точно не лучше классики C/C++.

Rust ещё можно выучить как второй язык, C++ - нет. Я в основном JS/TS разработчик и для меня это ключевой момент

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

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

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

Засчитывается, вопросов нет)
В целом, это согласуется с другим тезисом, который привели в комментариях:

всё зависит от того, адепты какой платформы сидят у руля

В этот раз вы были не у руля.

подавшись хайпу, народ то на node.js, то на go, то на битрикс делает

Извините, а когда Битрикс не на пыхе стал работать? Я что-то пропустил?

Для сравнения Golang и PHP вы взяли дополнительное расширение для PHP - Swoole.

Подскажите, это расширение 100% совместимо с популярными PHP фреймворками, на которых пишут современные веб приложения - Symfony (особенно интересно, т.к. люблю этот фреймворк), Laravel, Yii. Имеется ввиду, что приходится расширять код фреймворков, где расширение кода не предусмотрено, или перегружать функции из стандартной библиотеки PHP, т.е. "черные" хаки.

Спасибо.

Да, для ускорения использовался Swoole, он работает на основе event-loop (очень похоже на nodeJS), за счет этого получилось быстро выполнить программу.

Полная документация по swoole есть на их официальном сайте https://wiki.swoole.com, также есть большое количество composer-пакетов, которые позволяют подключить этот модуль к проекту.

Важный момент, простое подключение swoole в рабочий проект будет равносильно использованию php-fpm или apache или другим web-серверам.

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

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

Потому что дешевле и проще держать один стек

Рекомендую посмотреть в сторону hyperf. После Laravel вполне себе привычно. 3 года - полет отличный.

Помню как здесь, лет 10 назад был хайп вокруг Ruby, который вот-вот убъёт php. Как дела?

Нашел свою нишу и вполне себе живет до сих пор - на нем написаны GitHub, GitLab, Shopify, Stripe.

Один Redmine чего стоит.

Это - ниша? Вот, например, как я понимаю сравнение python и golang: на Python проще, легче, быстрее и дешевле сделать почти все. И модернизировать - тоже проще. Но жрать ресурсов будет больше и работать медленнее. Соответственно, любой новый проект лучше делать на пайтоне, а дальше по ситуации - если мы готовы почти "заморозить" функционал, но хотим снизить расходы на железо - то переписываем узкие места на golang. Ниша пайтона - быстрая разработка, ниша golang - высокая производительность и параллельность. Их нельзя поменять местами, их ниши - объективны.

А в случае перечисленных выше проектов, я не вижу, чем они особенны, почему там уместнее руби, чем тот же пайтон? Это не ниша, а исторические причины - первый программист в компании на момент создания проекта предпочел Ruby. Выбор во многом эстетический и субъективный.

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

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

на Python проще, легче, быстрее и дешевле сделать почти все

Очень спорно. Давно когда-то на нём писал достаточно долго, потом лет на 5-6 забросил. Захотел на днях скрипт сделать, но как-то знатно офигел, насколько же там всё неудобно и непривычно. Долго ковырялся, забил на питон. При этом, на расте, который я изучал меньше года, всё вышло куда проще и быстрее.

 Соответственно, любой новый проект лучше делать на

том, что хорошо знаешь (если команда - где больше экспертизы) и где получше экосистема.

Соответственно, любой новый проект лучше делать на

том, что хорошо знаешь (если команда - где больше экспертизы) и где получше экосистема.

Вот тут на 100% согласен - свой первый web-проект писал вообще на компилируемом Quick Basic (да-да, IIS, обвязки stdin/stdout для CGI, вот эти все архаики), потом переписал на asp, а потом лет на 10+ вообще отошёл от бэка, а когда в конце нулевых понадобилось набросать служебный отчётик по трафику, понял, что ни qb, ни asp на FreeBSD поднять в разумные сроки не выйдет, пришлось срочно осваивать PHP, и очень даже "зашло", правда, в процедурной, а не объектной нотации (уточню - я не разработчик ПО - все мои поделки - сугубо вспомогательные, и программирование я изучал с 1991 по 1994 годы, так что "музыканта" прошу не ругать). Но то, что доводится сейчас встречать в продакшене, порой вызывает оторопь - операция, занимающая порядка 3 секунд может выполняться часами, обычно из-за неоптимальных алгоритмов - чаще всего гоняют по медленным маршрутам (soap/wsdl, ole и прочие коннекторы с дорогой инициализацией) сотни мелких запросов. Но опять же, это моё видение со стороны эксплуатанта - возможно, чего-то не знаю.

на Python проще, легче, быстрее и дешевле сделать почти все

переписываем узкие места на golang

Нужны либо питонисты со знанием Go, либо отдельныая команда. Это уже не просто и не дешево.

Для всех тех, кто пришел сюда прочитав заголовок, но не читая статью. Пых давно убит морально. Статьи про fractal of bad design и картинки с молотком из двух гвоздодёров сделали своё дело. Он не существует в информационном поле. А точнее существует в виде "какой-то старпёрский язык для говнокода". На Stack Overflow даже индусы сейчас не задают вопросов по пхп, передав эту пальму синьор-девелоперам из Центрально-Африканской республики. Да, сейчас он не имеет ничего общего с тем языком, про который писались ругательные статьи. Да, он все так же является основным языком бэкенд разработки веб-приложений. Но информационную войну он проиграл давно и прочно, и это со временем скажется и на его использовании.

Да, он все так же является основным языком бэкенд разработки веб-приложений.

Сомнительное утверждение. В бэке, в плане старта каких-то новых проектов, его уже давно подвинула компания состоящая из nodejs, .net, java, python. Разумеется я сейчас говорю про глобальный рынок и про какие-то custom решения, а не поднятие интернет магазина методом накидывания плагинов на wordpress.

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

Адепты Laravel с вами не согласятся

Но да, публичности бы им явно побольше

Речь, разумеется, не про адептов, а про остальной мир. В /r/php регулярно, раз в месяц, появляются посты "ой, я весь колледж слышал со всех сторон, что пхп - это параша, а сейчас пишу и нормальный язык". Есть в мире такая штука, как клише. Например, что Раша - это медведи и балалайки. И ты хоть усрись доказывая, что в Канаде медведей на бэкъярдах на порядки больше, но уже сложившуюся картинку не перешибёшь.

А балалайки у медведей в Канаде есть?
Вот то-то и оно.

Плюс всякие новые предложения (CodeRun от Яндекса, Giga IDE от Сбера и т.д.) уже не добавляют PHP в список поддерживаемых языков. Что тоже формирует определённую картину мира разработки.

Учитывая, что GigaIDE это Intelij IDEA CE, к которой поддержка PHP (в виде php storm или php plugin) является коммерческим продуктом, то неудивительно, что их там нет.

Golang скомпилированный под локальную машину

Операционка то какая? Тестируйте в одинаковом окружении.

Вроде же PHP всегда славился низким порогом входа, а тут в примере создаётся асинхронный event loop, который вот совсем не простой для понимания.

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

Go то хотя и простой язык, но ошибаться на нём тоже довольно просто.

У нас в проекте основной код на php и а асинхронная часть, которая должна постоянно на вебсокете висеть на go. Практика показала, что застрелиться на go кратно проще чем на php (именно в асинхронной части), и go имеет некоторые особенности, которые вызвали сложности у разработчика php (но не критичные). Вобщем разраб php вполне может на go, и в обратном направлении наверное тоже. Деплой в тестирование нам порнавился на go меньше, то, что нельзя прям на серваке открыть скрипт и поправить, а нужно компилировать для нас сильно замедляет (но у нас маленький проект в котором нет всей стандартной цепочки разработки как у корпоратов, им я думаю этот пункт неважен). Но в целом связка php-go выглядит вполне работоспособной.

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

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

В какой-то момент в пыху завезли статическую типизацию, но пользуются ей далеко не всегда и не охотно, люди не привыкли) Но зато язык все ещё для вкатунов, и те самые пол интернета, ранящиеся на нем, чем гордится все сообщество вкатунов php, использует lamp с хостингом.

P.s.: разумеется, что язык - лишь инструмент. И если знать, что ты делаешь - то все ништяк с любым. На том же PHP нормальные люди с IQ не как у картошки используют Лару, симфони, yii2 или ещё что-то, адекватно проектируя систему и код-стайл. Но то, что некоторые технологии позволяют все подряд и плодят убогое ПО уже 30 лет подряд - это проблема!

Но ведь Swoole написан на Go... А если я PHP скомпилирую под .NET (PeachPie), сравнение будет валидным?

Вообще "сравнение" максимально нелепое, сравнивать надо в тех кейсах, которые имеют какой-то практический смысл. А именно, производительность приложения, надёжность, отказоустойчивость, стабильность, качество и скорость разработки, не требовательность к ресурсам, независимость от других инструментов. PHP не помрёт ещё много лет, но проблемы у него имеются. Отрицать это, значит не заниматься развитием языка, и тем больше с него уйдёт разработчиков. Надо признавать факты и работать над улучшением, может даже изменить слегка парадигму под современные условия и требования. А не ходить и прикалываться "опять хоронят". Эти звоночки не с потолка берутся.

Статья про то, что - все проблемы PHP можно победить - имея хороший инструмент (стек) и код. Но например у меня опыта в стеке, который победит все проблемы PHP - нету.

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

Да, согласен надо искать инструменты, которые дают серьезные преимущества, т.е. прорывные инструменты, а не побеждать проблемы.

Статья - инфоповод. Попытка что-то "пережевачить" (от слова жевачка для мозгов) идею - мысль, посмотреть ее с разных сторон еще раз что-то обсудить. Если бы на хабре не минусили, тут были бы такие интересные дискуссии в комментариях.

Swoole не написан на go, возможно вы сравниваете с roadrunner

https://github.com/swoole/swoole-src - ссылка на модуль

Да-да, перепутал с RR. Но тут C++, тоже по сути ни разу не PHP.

Получается, что код на Go полностью самодостаточен, он компилится и работает, ничего не требует для своей работы (кроме непосредственно ОС). А тут надо ещё прикорячить Swoole/RR/php-fpm+Nginx, которые писаны совершенно на других ЯП. И зачем такие сложности? Тут собственно и ответ, почему Go.

А по тестам. Давайте подадим нагрузку 1000 запросов в одно время на PHP и Go с обращениями в БД, посмотрим что получится? Какой будет профиль утилизации, сколько запросов вообще не отработает или отвалится?

а еще у go есть https://github.com/valyala/fasthttp


Я понимаю, докеры и все такое. Но бинарник на go это и вебсервер сразу.

код на Go полностью самодостаточен

Какое практическое преимущество это дает на бэкенде, обмазанном контейнерами?

которые писаны совершенно на других ЯП

При прочих равных, какая разница, на каких языках написаны вспомогательные инструменты?

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

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

производительность, так как любой лишний маршаллинг это затраты

Единый язык не гарантирует отсутствие маршаллинга, тот же PHP может быть на каком-нибудь трансляторе. Тут, наверное, подразумевался единый процесс. Go же по дефолту использует libc?

разработчики могут в равной степени участвовать в развитии всей платформы

Больше похоже на маркетинг, если честно) Порог вхождения в такие проекты достаточно высок, чтобы отсеять начинающих.

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

Появилась необходимость - появились и инструменты. Размер комьюнити играет важную роль.

Какое практическое преимущество это дает на бэкенде, обмазанном контейнерами?

Имхо, как минимум, в процессе разработки, у меня не уходит полгода на то, чтобы собрать окружение для проекта. Да и билд/деплой быстрее, и контейнеры поменьше (мы же сервера таки платим, да?). Ну и вдогонку: сторонние зависимости за собой тянут и определенные риски.

С тем же php, допустим, если мы в кубере живем... В контейнере с приложением будет сам пых + вебсервер (вероятнее всего nginx+phpfpm?). При этом перед этим всем будет еще балансировщик куберный и ingress (зачастую тот же nginx). Вуаля, у нас 2(два) nginx'а до сервиса на ровном месте, удачной отладки!

у меня не уходит полгода на то, чтобы собрать окружение для проекта

Тут один раз делается шаблон, который потом копипастится для каждого нового сервиса. Да и всякие БД, кафки и прочие инфраструктурные вещи в любом случае придется разворачивать, +1 инструмент погоды не сделает.

билд/деплой быстрее

Самый быстрый билд - его отсутствие =3 Один файл скопируется быстрее, чем тысяча, но это крохи, которые игру не перевернут.

При этом перед этим всем будет еще балансировщик куберный и ingress

Разве для Go-приложения это не потребуется?

у нас 2(два) nginx'а до сервиса на ровном месте

Go сам себе веб-сервер, тоже второй в этой цепочке, тогда какая принципиальная разница?

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

А потом этот шаблон правится под требования каждого последующего.

А потом в очередной раз вы начинаете билдить новый проект, и вдруг `FROM nginx-phpfpm:5.13.67` перестает работать, потому что версию депрекейтнули и выкинули. Вы берете посвежее, а там код поломался... Как минимум, этих болячек в го нет.

Самый быстрый билд - его отсутствие =3 Один файл скопируется быстрее, чем тысяча, но это крохи, которые игру не перевернут.

Инфра под билд, очевидно, уже не роляет?

Go сам себе веб-сервер, тоже второй в этой цепочке, тогда какая принципиальная разница?

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

шаблон правится под требования каждого последующего

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

NGINX вполне может посчитать законным вернуть ответ из своего кеша

Сконфигурируете так, чтобы не мог.

между куберным ингрессом и приложением мы воткнули еще один ингресс - это точно хорошая идея?

А зачем еще один ингресс? Веб-сервер - это часть PHP-приложения.

В то время, как на Go писалась бы базовая машинерия с сервером.

if err := http.ListenAndServe(":8080", router) {
  log.Fatal(err)
}

Вы про это?

Сконфигурируете так, чтобы не мог.

Ну, т.е. я его сконфигурирую сначала в кубере, а потом еще в контейнере, второй раз. Это прям хорошо? Или одного раза, возможно, было бы достаточно?

А зачем еще один ингресс?

Ingress-контроллер - это штука, которая маршрутизирует трафик снаружи внутрь. Просто термин для обозначения реверс-прокси.

Собственно, один у вас на входе в кластер, а еще один - ваш nginx внутри контроллера. Причем на входе в кластер, с вероятностью процентов 80 - тоже nginx.

Вы точно уверены, что 2 реверс-прокси в цепочке обработки вызова - это очень хорошо?

Веб-сервер - это часть PHP-приложения.

Часть приложения - это то, что есть в его кодовой базе. NGINX'а там нет.

В чем проблема? Ну, например, был у вас образ на базе какого-нибудь nginx-phpfpm:7.10, вы переползли на awesome.new.containers.com/nginx-phpfpm:7.10 - просто другой билд того же инструмента. Вы даже не задумывались, что там nginx с другими правилами внутри может быть, а грабля в лоб уже ударила.

В Go такое исключено. И это - хорошо!

Вы про это?

Суть такая, да. Для реального приложения кода будет больше.

его сконфигурирую сначала в кубере, а потом еще в контейнере, второй раз. Это прям хорошо?

А в чем может быть проблема? Про копипасту конфигурации - ранее уже говорили.

Часть приложения - это то, что есть в его кодовой базе. NGINX'а там нет.

Веб-приложение без веб-сервера не работает, очевидно. У PHP туда еще рантайм входит.

Вы точно уверены, что 2 реверс-прокси в цепочке обработки вызова - это очень хорошо?

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

был у вас образ на базе какого-нибудь nginx-phpfpm:7.10, вы переползли на awesome.new.containers.com/nginx-phpfpm:7.10 - просто другой билд того же инструмента.

Какая-то надуманная проблема. Почему меняя образы вы не задумываетесь о том, что они могут быть разными? Это ведь не повышение минорной версии, а совершенно другой образ.

В Go такое исключено

Точно так же версия Go может бать пропатченной с потерей обратной совместимости. Кто же запретит?

Для реального приложения кода будет больше.

Да не намного. Вся разница: я копипащу нужные куски кода, вы - удаляете ненужные (попутно хороня где-то в недрах приложения неиспользуемый код)

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

nginx - это реверс-прокси. И он там есть. Да, в PHP он используется как application server, но это не отменяет того факта, что он - реверс-прокси, со всеми вытекающими а ля политики кеширования и т.д. и т.п.

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

Точно так же версия Go может бать пропатченной с потерей обратной совместимости. Кто же запретит?

А какое отношение версия Go имеет к продовому контейнеру? В нем НЕТ никакого Go, в нем бинарь лежит. С "потерей обратной совместимости" у меня бинарь тупо не соберется.

в PHP он используется как application server, но это не отменяет того факта, что он - реверс-прокси

HTTP не проксируется, а передается сразу в приложение. Это другая функция относительно того реверс-прокси, что стоит снаружи.

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

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

Если вся эта проблема лежит исключительно в философской плоскости, то это не стоит внимания.

А какое отношение версия Go имеет к продовому контейнеру?
В нем НЕТ никакого Go, в нем бинарь лежит

Бинарь не из воздуха берется. Такие вопросы странно читать. Слышали что-нибудь про multi-strage builds?

С "потерей обратной совместимости" у меня бинарь тупо не соберется.

У Go же такой мощный компилятор, что ловит все виды ошибок, ага)

У Go эта "избыточная штука" компилируется прям в бинарник.

В Go это не избыточная штука, гошный сервер сииильно меньше. Он умеет те самые, в лучшем случае, 10% от того, что умеет NGINX, которые нужны приложению.

Разница с хостящемся приложении будет лишь в том, что она здесь снаружи.

Так я же именно про то и говорю, что она снаружи! В этом и проблема!

Кеширование, маршрутизация - это часть логики веб-приложения. Та самая часть, которая у вас находится снаружи, а у гоферов - прямо в коде. Проблема с запросом - я смотрю в лог приложения, php-кодеры - в лог приложения и в лог контейнера. Проблема наша, надо чинить? Я чиню в коде приложения, вы - в коде приложения, в релиз-ноутсах базового образа, в конфигах nginx'а, в рантайм-зависимостях, лежащих в контейнере и б-г еще знает, где еще.

Если вся эта проблема лежит исключительно в философской плоскости

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

Бинарь не из воздуха берется. Такие вопросы странно читать. Слышали что-нибудь про multi-strage builds?

И в чем, простите, проблема с multi-stage билдами? Не, с PHP понимаю - в релизном контейнере может оказаться не та версия PHP, например, или еще что-то из обвеса, называемого "рантайм".

В Go из рантайм-зависимостей - только glibc. Там буквально голый linux-контейнер с одним бинарем внутри.

У Go же такой мощный компилятор, что ловит все виды ошибок, ага)

Ошибки совместимости, внезапно, ловит. Вот прям буквально не соберется.

гошный сервер сииильно меньше. Он умеет те самые, в лучшем случае, 10% от того, что умеет NGINX, которые нужны приложению.

Если это важно для конкретного случая, то можно поискать сервер меньшего размера.

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

Можно сделать и внутри PHP-приложения, аналогично Go.

Проблема с запросом - я смотрю в лог приложения, php-кодеры - в лог приложения и в лог контейнера.

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

В Go из рантайм-зависимостей - только glibc.

Ну вот будет у вас патченная glibc. Если вы решили побалоываться альтернативными образами для PHP, почему бы и здесь не упороться?)

Ошибки совместимости, внезапно, ловит. Вот прям буквально не соберется.

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

Если это важно для конкретного случая, то можно поискать сервер меньшего размера.

Apache, NGINX, Roadrunner - поиск окончен.

Можно сделать и внутри PHP-приложения, аналогично Go.

От того, что вы сделали роутинг и кеширование (а кеширование, кстати, как? Пример кода inmemory-кеша в php-приложении покажете?) внутри php-приложения, аналогичные механизмы снаружи не пропадут.

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

А! Понял! Логов контейнера хватит всем! А мы зачем-то с какими-то метриками возимся, с трассировками, кастомными логгерами. Логи централизованно собираем, графаны всякие поднимаем. А надо просто в логи контейнера взглянуть, и все будет понятно!

Спасибо, мил человек, просветили!

Ну вот будет у вас патченная glibc. Если вы решили побалоываться альтернативными образами для PHP, почему бы и здесь не упороться?)

Окститесь, мил человек. Glibс - это API ядра Linux. Не, не исключено, конечно, что ядро "поломатое", но тогда, извините, неработоспособное приложение - наименьшая из ваших проблем.

В PHP же ровно та же glibc в зависимостях. С "пропатченной glibc" у вас и php не заведется. Разница в том, что у вас поверх glibc еще собственно php "нужной системы" иметь нужно, фреймворк, вспомогательные библиотеки, веб-сервер и тонну всяких очень нужных вещей. Меньше зависимостей = меньше головной боли. Мне казалось, это очевидно.

Выяснять то, что вы подразумеваете под ошибками совместимости мне лениво) 

Ну ладно, прямо тут вам напишу, чтобы вы на гугл времени не тратили.

Смотрите, есть совместимость уровня API и совместимость уровня ABI. В первом случае Go не скомпилирует код, во втором - требуется только совместимость с glibc (и компилятор на пару с Торвальдсом дают определенные гарантии). Ну, в смысле, с неподходящей версией ядра гошный сервис, вероятнее всего, просто не стартует. Падать во время работы на проде - моветон.

Пример кода inmemory-кеша в php-приложении покажете?

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

Логов контейнера хватит всем! А мы зачем-то с какими-то метриками возимся, с трассировками, кастомными логгерами.

Логи в контейнере как-то запрещают подключать метрики?

Glibс - это API ядра Linux.

Это должно помешать ее пропатчить? Или компилятор Go.

Меньше зависимостей = меньше головной боли

Да, но это не повод выдавать некоторые риски за нестерпимую боль.

компилятор на пару с Торвальдсом дают определенные гарантии

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

Асинхронщину же на PHP делают, предполагаю, что должны быть средства и стейт иметь.

PHP "создан, чтобы умирать". Т.е. буквально на каждый запрос создается новый процесс операционной системы, который умирает сразу по завершению запроса. Бонусы - насрать на память, даже сборщика мусора нет, потому что все подчистит ОС. Минусы - нет средств иметь стейт приложения.

Логи в контейнере как-то запрещают подключать метрики?

Логи в контейнере и логи приложения - это разные сущности.

Это должно помешать ее пропатчить? Или компилятор Go.

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

При чем тут левый билд-то? При чем тут специально сломанные компиляторы/ядра? Официальный билд ломает совместимость. PHP 7.4 рантайм не гарантирует работоспособность кода под версию 7.3. Да хрен бы с ним, 7.3.1 вполне может не заработать на 7.3.2-рантайме. На официальных билдах.

Да, но это не повод выдавать некоторые риски за нестерпимую боль.

Я не выдаю за нестерпимую боль наличие дополнительных рисков. Я утверждаю, что отсутствие дополнительных рисков - абсолютно точно преимущество.

Да-да, перепутал с RR. Но тут C++, тоже по сути ни разу не PHP.

Вы так говорите, как будто PHP должен быть написан на PHP. А на самом деле ядро и модули для него пишутся на C, C++, наверное ещё немного на чем-то. Собственно, как и Go, вот сюрприз.

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

на с++

Если вопрос нужны ли такие статьи, то ответ «да» возможен при одном условии. Надо разобраться и рассказать почему такие «неожиданные» результаты.

Можно конечно просто вкинуть как есть, но если в комменты придет профи, то будет неприятно)

это неадекватный тест и сравнение совершенно разных ситуаций с т.з. процессора, отсюда и слоновый проигрыш go. Напишите асинхронно с пулом и go сделает php на пару порядков.

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

я когда щас смотрю на php код - диву даюсь от сложности ... классы, наследование, абстрактные клаcсы, трейты, статические методы, статические поля, магические методы, магические поля, анонимные классы, методы классов под флагом final, магия вокруг клонирования объекта, ... все это безусловно продиктовано потребностями реального мира. Но в довесок приходят фреймворки без которых никуда в мире php...и они приносят еще несколько слоев сложности...как на уровне архитектуры так и на уровне оберток к стандартным функциям php...

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

ушел из php уже лет как 10...даже не представляю какой кошмар сейчас представляет из себя собеседования на вакансию php-разработчика..

НЛО прилетело и опубликовало эту надпись здесь

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

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

НЛО прилетело и опубликовало эту надпись здесь

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

ушел из php уже лет как 10...даже не представляю какой кошмар сейчас представляет из себя собеседования на вакансию php-разработчика..

Я не ушёл из php 10 лет назад и да - Вы правы, там кошмар:

  • В требованиях (99% встречающихся вакансий) могут присутствовать как минимум 3 ведущих фреймворка. И если ты не изучал их все (в свободное время), на тебя смотрят как на какого-то неправильного php-программиста.

  • Если ты работал с Laravel, но не работал с Symfony- тебя не возьмут на работу.

  • Если ты работал с Larvael 7, но не работал с Laravel 9 - тебя не возьмут на работу.

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

Но в довесок приходят фреймворки без которых никуда в мире php...и они приносят еще несколько слоев сложности...

Совершенно верно.

Вообще, PHP хороший язык, но графоманы-фрейморкоклепатели очень, очень сильно усложнили жизнь программистам на этом языке, ибо теперь знаний одного языка недостаточно. Все эти фреймворки нужно изучать, но по сути это всё не стандарт, а филькина грамота, которая сегодня популярна/востребована, а завтра - нет. И тратить время на изучение фреймворка просто из принципа не хочется.

графоманы-фрейморкоклепатели очень, очень сильно усложнили жизнь программистам

Очень, очень неортодоксальная точка зрения.

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

Но вот в какой-то момент мы решили немного отрефакторить тот лютый легаси монолит, что писался разными людьми в разное время в течение 15+ лет. И если один из нас двоих (не я) еще чуть понимал, что там и как работало, то мне было сильно проще и быстрее взять и переписать один из древних разделов на сайте просто с нуля на Laravel. Попутно рефакторя в него же какие-то общие фичи типа регистрация-авторизация-профили и т.п. И теперь мы можем в принципе остальное довольно быстро переписывать под Laravel тупо не разбираясь как оно там раньше работало, а просто повторяя функционал, но во многом проще, быстрее и главное – единообразнее

Молодой программист: Убежден, что фреймворки существуют по технической причине: чтобы иногда можно было не писать код, а использовать его из библиотеки/фреймворка, где за тебя его уже сделали. Иногда 90% кода проекта - это фреймворк, а программисты написали только 10%.

Старый программист: Убежден, что фреймворки нужны по бизнес-причине, чтобы ты нанял нового программиста, и он в первый же день уже знает 90% кода твоего проекта, потому что это код фреймворка.

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

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

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

И есть еще какая-то идея у общественности, что если ты не используешь фреймворк в своем проекте - то ты какой-то ущербный.

Это нормально сейчас. У нас, в JS/TS мире 4-5 актуальных orm и, на мой взгляд, 3 актуальных веб-фрейморвка. В каждой вакансии обязательны знания одной из комбинаций. Иногда нужен fullstack-разработчик, тогда в комбинацию добавляются браузерные фреймворк.

В мире JS/TS из фреймворков в понимании PHP/Java всего два - NestJS и Adonis, остальные - это скорее просто удобный роутинг + middlewares

Посмотрите в сторону JS - там фреймворков x10 от пхп-шных

7 лет опыта на PHP, этим летом уволился, искал работу. 30+ собеседований, всего 2 оффера. Еще штук 20 компаний отказали сразу, без собесов. 3 года назад, когда у меня было намного меньше опыта, взяли на второй день после активации резюме на Хабр.Карьере и hh.ru.

Это просто рынок труда в целом сдох, а не сам язык.

Искали на php также?

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

Это не просто популярное мнение, а философия, заложенная в сам язык)

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

ногострелов там слишком дофига для простого языка

Да, тут можно подискутировать по поводу определения простоты. Лично я больше склоняюсь к утверждению, что Go намеренно простой в плане синтаксиса: есть ограниченное число конструкций, которые надо изучить, чтобы понимать любой код.

В плане синтаксиса - пожалуй, соглашусь, сам всё-таки обжигался только на семантике.

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

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

Лично я считаю Go одним из самых "немагических" языков, но возможно я какие-то вещи уже считаю нормой.

Да начиная от того, как работают каналы(что будет если прочитать/записать в закрытый канал, а если в nil) и заканчивая особенностями ΙΟ операций в горутинах, мультиплексером, парковой треда на сисколах и т.д. Да даже на уровне читабельности кода, есть вещи, про которые если не знаешь, то не догадаешься. Ну пример:

Что будет выведно на экран?

func main() {

  var i int = 1

  defer fmt.Println("result =>",func() int { return i * 2 }())

  i++ 

}

А тут?

func main() {
  var data *byte
  var in interface{}  
 
  fmt.Println(in, in == nil)
  
  in = data

  fmt.Println(data, data == nil)
  fmt.Println(in, in == nil)
  fmt.Println("eq", data == in)
}

что будет если прочитать/записать в закрытый канал, а если в nil

Это просто надо знать)

Дело в том, что это слишком специфичные вещи, чтобы в массовом сознании для них сформировался какой-то там "интуитивный подход". Вы же наверняка мне хотите сказать, что вот в языке N это происходит по-другому, и эта реализация вам кажется более интуитивной, верно? Только, скорее всего, когда вы знакомились с языком N, вы не задумывались о том, что интуитивно, а что нет, вы просто учили как это работает.

Пример 1: вы отложили Println, который инициализировался в моменте. Это ожидаемое поведение, ИМХО.

Пример 2: да, больно. Но вы изначально допустили магию в виде interface{}. В языке со строгой типизацией интуитивно понятно, что пустой интерфейс будет вести себя непредсказуемо. Какая практическая задача может стоять за таким кодом? Надеюсь, не парсинг JSON же?

1) Так в первом примере что выведет, 2 или 4? И почему это ожидаемо?

2) Дело не в том пустой интерфейс или нет, а в том, что у него появляется значение, даже если туда присвоить пустой указатель. И это абсолютно неочевидно, очередной пункт, что надо просто "знать". И кейс с выстрелом себе в ногу может быть тривиальным и бытовым:

func do(in MyInterface) {
	fmt.Println(in == nil)
	if in != nil {
		in.Do()
	}
}

type MyInterface interface {
	Do()
}

type MyData struct {
	name string
}

func (c *MyData) Do() {
	fmt.Println("Do from MyData", c.name)
}

func main() {

	do(nil) // true
	var data *MyData = nil
	do(data) // false and panic
}

В любом языке есть вещи, которые нужно просто знать. Потому что на определённом уровне погружения в язык заканчивается "common sense" и начинается фантазия разработчиков. И вероятность того, что это фантазия будет совпадать с вашей, стремится к нулю. Субъективное удобство пользования языком зависит от того, насколько глубоко эти вещи совпадают. Так работает наше мышление, отдельные языки тут не причём.

Вы же, надеюсь, не хотите своими примерами убедить меня в том, что C# вам кажется интуитивнее, потому что его вы выучили первым?

Смотрите, мы же начали с того, что "Go простой язык" и вы сами попросили примеры того, где все совсем не так просто. Речь тут не про то, что условный C# понятнее или нет, а про то, что широко рекламируемая "простота" Go обманчива и там тоже есть свои подводные камни, как и в других языках.

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

Естественно. Ваше мышление не может на 100% совпадать с мышлением Роба Пайка. Однако так ли велико количество и "глубина" этих подводных камней в сравнении с другими языками?

BTW, я выше уже отвечал собеседнику, что под простотой Go чаще рекламируют простоту синтаксиса. Притом чаще простоту чтения, а не написания: все эти бесконечные if err != nil нужны для очевидности, не для удобства. То, что язык всё равно требует изучения, чтобы на нём грамотно писать - мысль само собой разумеющаяся.

Однако так ли велико количество и "глубина" этих подводных камней в сравнении с другими языками?

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

если есть хоть один "подводный камень", значит никакой простоты уже нет

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

На мой взгляд это сложный вопрос, который будет упираться в субъективное воспритие

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

Ну тогда ни один язык не может называться простым

Паскаль простой язык. Но да, вряд ли вы захотите на нем писать сложный энтерпрайз код.

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

Большое это сколько? Опять же, у меня есть знакомые, которые наоборот перешли с го на .net с аргументацией что на C# писать проще(за счет библиотек, синтакс сахара, linq и т.д.). А есть те, кто с го перешел на kotlin, тоже имея свои резонные причины. Мы так просто упремся в anecdotal evidence. На мой взгляд го это хороший инструмент для определенных задач и он точно внес свой вклад в развитие разработки как таковой, занял свою нишу, но его маркетинг склонен несколько преувеличивать его достоинства, вроде простоты разработки и универсальности.

перешли с го на .net с аргументацией что на C# писать проще(за счет библиотек, синтакс сахара, linq и т.д.)

Заметьте, это буквально подтверждает то, что я написал выше: Go проще читать, не писать на нём.

Но я соглашусь, что если Go рекламируют как "простой во всём язык" - это неверно.

Go проще читать, не писать на нём.

Да вот что-то как-то нет. Я когда-то давно писал на C, потом на плюсах, потом на питоне, потом на js. Код на го не могу читать. Надо прям учить синтаксис, он как сны в 40, вроде всё знакомое, но какой-то бред происходит. При этом, новый для меня C# и rust читаются очень просто. Даже F# с хаскелем читаются проще го.

Код на го не могу читать.

Ну, это наброс. Даже прокомментировать толком нечего. Не любите Go - так и скажите.

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

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

Вот хелловорлды очень простые и понятные, тут не поспоришь.

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

Вся беда в экстраполяции. Мы берем свои знания об интерфейсах в Java, C#, TS и б-г знает откуда еще и пытаемся экстраполировать на гошную реализацию. И тут, внезапно, оказывается, что у языка есть специфика, и совы не то, чем кажутся.

Только есть один нюанс. Специфика - она во всех языках есть. В JS можно приводить значение к булеву ( if (value) вернет false для null и undefined, в остальных случаях true), и это тоже надо знать. И сравниваем мы с nil, а не с null, как у соседей. Разработчики языка специально ввели nil, чтобы явно обозначить, что это не совсем то же самое (или совсем не то же самое), что null.

Ожидание от слайса поведения вектора - тоже частая беда. Он не вектор, был бы вектор, вектором бы и назывался.

Каналы ругать? Ну это совсем зря! С ними-то откуда непонимание, если у соседей их просто нет? Откуда экстраполяция ложных предположения возникает? И поведение каналов - строго логично. Паника на запись в закрытый канал - ну, вы ровно перед ней закрыли канал (т.е. буквально пообещали больше в него не писать). Чтение zero-values из закрытого канала - гениальное решение! Читателей может быть больше 1, а каналы часто используются для оповещения соседних горутин о завершении работы. Если надо оповестить 10 горутин о завершении - положить в канал 10 сообщений? А если 100? А если мы не знаем, сколько у нас подписчиков?

В языке 21 ключевое слово, и да, под буквально каждым из них "закопана какая-то грабля" с точки зрения экстраполяции опыта из других языков. Вопрос в том, что слов всего 21, поэтому мы четко знаем, что есть 21 механизм и ровно 21 грабля, которые надо знать. Это и называется "простота" с точки зрения Го. Сколько грабель закопано в C#, Java, C++, PHP и т.д. - науке пока не известно.

Мы берем свои знания об интерфейсах в Java, C#, TS и б-г знает откуда еще и пытаемся экстраполировать на гошную реализацию.

Спасибо. Это буквально мысль, которую я хотел донести.

  1. "Го - простой язык"

  2. Четыре абзаца текста про специфику, которую "надо знать, а не экстраполировать свой предыдущий опыт

Выглядит убедительно, да. Ну тогда и С++ простой язык, просто у него есть своя специфика, которую надо знать.

языке 21 ключевое слово

В брейнфаке 7 ключевых слов(символов). "Простота" программ на нем удивительна. Но вообще да, придумать собственное определение простоты и потом с ним заявлять что ты всех "победил" - отличный маркетинговый прием.

ровно 21 грабля

Если у вас на каждое ключевое слово в языке заложено по проблеме это не звучит как что-то хорошее. Ну и про "ровно" - особенности preemptive и cooperative шедулинга они к каком ключевому слову относятся? А network poller starvation на большом числе сокетов это какое слово?

С ними-то откуда непонимание, если у соседей их просто нет

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

Сколько грабель закопано в C#, Java, C++, PHP и т.д. - науке пока не известно.

Эти языки никто и не называет "удивительно простыми".

Эко вас порвало, когда вам по существу ответили, а не нейтрально, как я)

Вам чем-то лично гошники насолили? Может, кто жену увёл или работу?

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

Ой, как нехорошо.
Я бы, конечно, поинтересовался на чём вы пишете свои проекты, но кажется, что корень проблемы в противопоставлении "коммерческий проект" и "персональные проекты". В своём проекте для вас в любом случае всё будет понятно и очевидно - мне даже неловко это проговаривать, если честно. Все взрослые люди, а у вас получается какие-то детские обиды: "меня заставляют писать на Go, поэтому Go - плохой".
Давайте не будем так.

В своём проекте мне "будет понятно и очевидно" не потому что он мой, а потому что в нём я могу выбрать язык, компилятор которого меня подстрахует в существенно большем количестве случаев, чем это можно позволить себе в коммерческом. И да, Go для меня "плохой" не потому что "заставляют", а потому что я вижу разницу между "поменял поле, пробежался по ошибкам компилятора, собралось - работает" и "поменял поле, полчаса бегаю по подсказкам от IDE, но, пока не пройду через QA - всё равно не уверен, что поправил всё, что требовалось".

Go для меня "плохой" не потому что "заставляют", а потому что я вижу разницу между "поменял поле, пробежался по ошибкам компилятора, собралось - работает" и "поменял поле, полчаса бегаю по подсказкам от IDE, но, пока не пройду через QA - всё равно не уверен, что поправил всё, что требовалось".

Погодите, а что не так в го с изменением поля? Мне казалось "поменял поле, пробежался по ошибкам компилятора, собралось - работает" - это строго поведение Go, он же явно-строго-статически типизированный язык!

Поспешил, виноват - не "поменял поле", конечно же, а "добавил поле".

А в каком языке добавленное поле поломает компиляцию? Мы опять про Rust?)

Ну ладно, подключите линтер - отлично справляется с вашей задачей. Матерится исправно ровно на вашу ситуацию.

...в любом, в котором есть нормальное создание объекта литералом? Да хоть в том же TypeScript, как минимум (хотя, да, личные проекты у меня в основном на Rust, но он в этом плане заведомо не уникален). А линтер здесь-то поможет, а в куче других мест наругается впустую, потому как оставлять поля структур незаполненными - это, насколько я вижу по действиям коллег, вполне себе идиоматичный Go.

export class SomeClass {
    public someField: string;
    public someAnotherField: number;
    public newField?: string; // это новое поле
}

const instance: SomeClass = {someField: 'text', someAnotherField: 0};

"Ну, Андрий, помогли тебе твои ляхи?" )

Да, система типов в TS, C# со товарищи гораздо более развесистая, и даже, возможно, некоторые плюшки оттуда (постепенно и микроскопическими дозами, чтобы ничего не сломать) будут появляться в Go. Сам по себе он не то чтобы концептуально-великий язык. Он утилитарный, с ровно одной фичей, сделанной действительно хорошо: M:N асинхронность. Остальные части языка в лучшем случае неплохи (а некоторые и откровенно ниоч).

Конкретно эта беда реально закрывается линтером, если в логике вашего приложения это действительно беда (чаще не закрывается). И фишка TS, которой в этом кейсе не хватает Go - это не "нормальное создание объекта литералом", а type-safety.

В Го приняли подход с zero-values, в массе языков посвежее или одного с ним возраста - стали копать в сторону опциональных значений. По сути, гошный тип можно привести к тому же поведению, что и TS, но с линтером и приседаниями.

Вопрос в том, что вы получаете от языка. От TS - развесистую (но сиииильно не идеальную) систему типов (которая накручена поверх JS, и потому вполне неиллюзорно имеет дыры. Хотите то же самое, но без дыр - вам в C#). От Go - RPS.

Если заради RPS вы готовы написать валидацию и покрыть мутные места тестами, написать ручками конструкторы и вообще поприседать - вам в Go. Если вы пишете сложную бизнес-систему с иерархией бизнесовых сущностей, сложной доменной логикой, кучей взаимосвязанных циферок и прочими прелестями денежных транзакций - ну, вам на Go делать особо нечего, для вас есть C# и Java (не TypeScript, он не для вас)

public record SomeClass(
  string SomeField, 
  int SomeAnotherField,
  string? NewField = null
);


SomeClass instance = new("text", 0);
// или, если надо явно указать типы
SomeClass instance = new(SomeField: "text", SomeAnotherField: 0);
// или
var instance = new SomeClass("text", 0);
// смотря что имеет большее значение семантически

public newField?: string; // это новое поле

Это новое поле, явно помеченное как опциональное. Убираем опциональность, смотрим на сообщения об ошибках, правим, если надо - возвращаем опциональность обратно.

система типов в TS, C# со товарищи гораздо более развесистая

А тут не про "развесистость" системы типов вопрос. Тут вопрос про строгость проверок. Собственно type-safety, да.

Если заради RPS вы готовы написать валидацию и покрыть мутные места тестами, написать ручками конструкторы и вообще поприседать - вам в Go.

Ну вот так и живём, да. Что не отменяет того факта, что в других языках это "покрыть мутные места тестами и вообще поприседать" было бы нужно в заметно меньшей степени. Объективно это просто один из многих trade-off-ов, конечно.

А тут не про "развесистость" системы типов вопрос. Тут вопрос про строгость проверок. Собственно type-safety, да.

Собственно null-safety - это как раз про развесистую систему типов. И вот тут Go не особо хорош. Там, где она реально аффектит - Go малоприменим. Где болячки закрываются линтером - хватает Go.

в других языках это "покрыть мутные места тестами и вообще поприседать" было бы нужно в заметно меньшей степени

Совершенно верно. Просто в кейсах "просирания максимального количества RPSов в единицу времени" придется приседать уже в других языках. Так и живем, trade-off)

Просто изначально был разговор про PHP. Там мы и с RPS-ами поприседаем, и с этим кейсом. И все это от души, и безрезультатно.

Во-первых я бы не сказал что спорные заявления типа "в других языка нет каналов" это ответ по существу.

Во-вторых фразы "эко вас порвало", "жену кто увёл" говорят скорее о том, что "порвало" вас, раз вы не можете сохранить нормальный тон дисскусии и переходите на хамство. Вы бы еще про "мамку" что-нибудь написали, стыдно должно быть, вот он, уровень аргументации GO разработчиков.

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

Ну тогда и С++ простой язык, просто у него есть своя специфика, которую надо знать.

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

Если честно уже после этого "аргумента" надо было прекращать с вами пустословить:

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

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

вот он, уровень аргументации GO разработчиков

А это вообще что? Классификация разработчиков по языкам программирования? Элитаризм так и прёт и скрывать не получается?

В брейнфаке 7 ключевых слов(символов). "Простота" программ на нем удивительна

Но я ведь ровно о том и говорю: язык - простой, писать на нем программы - сложно. Сложнее, чем на PHP, например.

Если у вас на каждое ключевое слово в языке заложено по проблеме это не звучит как что-то хорошее

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

Сравнение интерфейса с nil ругаете? Оно ведет себя не так, как сравнение с null? Присмотритесь внимательно, разработчики языка специально "соломку подстелили", nil в язык вместо null ввели. И даже статью написали, что одно от другого отличается. Не помогло(

Слайс не вектор? Ну, он и называется не "вектором". Ошибка не исключение? Дык, и называется не исключением. map называется map? Дык, оно и ведет себя ровно так, как у соседей! Оно даже ссылочное!

особенности preemptive и cooperative шедулинга они к каком ключевому слову относятся

А они, вообще-то, не к языку относятся. Они в разделе "детали реализации, ограничения платформы".

Эти языки никто и не называет "удивительно простыми".

Дык, у них и спецификация не на 20 страниц?

Го - простой язык. Самый простой, пожалуй, из существующих энтерпрайзных языков.

Он не в понимании простой, он по своей организации простой, компактный и достаточно непротиворечивый. Чтобы на Го писать грамотный идиоматичный код, достаточно пройти Go Tour, внимательно прочитать (и понять, что там написано) Effective Go и... и все.

Для того, чтобы написать хорошее приложение, этого, конечно, недостаточно. А чтобы идиоматичный код - вполне. В Java вы типы коллекций перебирать дольше будете, например.

 язык - простой, писать на нем программы - сложно

 Го - простой язык. Самый простой, пожалуй, из существующих энтерпрайзных языков.

Он не в понимании простой, он по своей организации простой

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

func main() {

  var i int = 1

  defer fmt.Println("result =>",func() int { return i * 2 }())

  i++ 

}

Здесь 2


А если сделать так:

	defer func() {
		fmt.Println("result =>", func() int { return i * 2 }())
	}()

то 4.

func main() {
  var data *byte
  var in interface{}  
 
  fmt.Println(in, in == nil)
  
  in = data

  fmt.Println(data, data == nil)
  fmt.Println(in, in == nil)
  fmt.Println("eq", data == in)
}

nil true

nil true

nil false

eq true

Я принят?)

хочется примеров

Так ещё в 2016 году примеров накидали:

https://habr.com/ru/companies/vk/articles/314804/

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

Go "простой" только в понимании организации самого языка, его компактности, размера языкового багажа. Он даже не простой, а лаконичный. Примерно как язык папуасов Новой Гвинеи очень прост в сравнении с русским языком.

А вот в применении он достаточно сложен. Комплексность решений вынесена за рамки собственно языка.

Добро пожаловать в Typescript ))

Я разрабатываю в том числе и на Го. То что нету фреймворков иногда мешает - приходится всю эту логику реализовывать самому от сервиса к сервису.

Мне показалось не очень удобным реализовывать бизнес логику - например, надо обработать сразу несколько сущностей из бд , приходится постоянно вставлять проверки на if err != nil. Мне кажется языку не хватает исключений.

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

Думаю что каждый пишет как может сервисы на Го, так как единых гайдов по организации пакетов, файлов нету.

ниразу мне за 7 лет в го не помешало то что нет фреймворков. единственное чего не хватает go - enum...все.

Думаю что каждый пишет как может сервисы на Го, так как единых гайдов {...} нету.

Есть самопровозглашённый гайд от Uber и во многих компаниях он, в принципе, считается стандартом.

То что нету фреймворков иногда мешает - приходится всю эту логику реализовывать самому от сервиса к сервису

Там не так много логики, чтобы это было проблемой. У фреймворков же есть оборотная сторона: они большие, монструозные и, в 90% случаев, излишние. Т.е. их надо знать, надо уметь применять, надо изучать. На выходе мы получаем специалиста в определенном фреймворке. Гоша тут гибче: сменить компактный роутер на порядки проще, чем перелезать целиком на новую архитектуру.

надо обработать сразу несколько сущностей из бд , приходится постоянно вставлять проверки на if err != nil. Мне кажется языку не хватает исключений

Не совсем понятно, как связаны сущности из БД и проверки на err != nil. А насчет исключений - ну, подвижки в сторону групповой обработки ошибок есть, но достаточно вялые, потому что не очевидно, зачем это надо. Просто в го концепция: обработай ошибку на месте. Право на жизнь имеет, т.к. исключения тоже сильно не идеальны.

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

Боб Мартин бы с вами не согласился. Возможно даже наругал бы... Откройте для себя композицию!

У фреймворков же есть оборотная сторона: они большие, монструозные и, в 90% случаев, излишние.

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

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

И чем это плохо? Эти знания лишними не будут.

Боб Мартин бы с вами не согласился. Возможно даже наругал бы... Откройте для себя композицию!

Так он и не абсолютный авторитет какой-нибудь (да и вроде он не прям категорично против наследований). А композиция - это не полная замена наследованию.

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

Однако это не отменяет вопроса о том, каким местом связан роутер и драйвер БД. Почему они часть одного фреймворка, и чем это лучше, чем роутер отдельной библиотекой + обвязка над БД - отдельной.

Заметьте, ни слова о велосипедах. Все библиотечное.

И чем это плохо? Эти знания лишними не будут.

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

Однако это не отменяет вопроса о том, каким местом связан роутер и драйвер БД. Почему они часть одного фреймворка, и чем это лучше, чем роутер отдельной библиотекой + обвязка над БД - отдельной.

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

Заметьте, ни слова о велосипедах. Все библиотечное.

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

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

Но хорошо продуманные, спроектированные и обкатанные. Знание их даёт полезный опыт. Некоторые решения можно потом применять и вне фреймворков.

там все компоненты хорошо интегрированы

Роутер хорошо интегрирован с драйвером БД? А зачем (и как)? А это точно хорошо, например, что для переползания на другую БД может понадобиться переписывать маршрутизацию? Может, ну его нафиг, такую интеграцию?

Но есть же фреймворки и без всего набора (только с роутером, например).

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

Если роутер (хорошо если пара процентов от суммарного размера приложения) является фреймворком (т.е. определяет то, как вам писать приложение) - может, ну его нафиг такой роутер.

А вот библиотечный роутер - это круто! Ничего против не имею, и их в Go имеется, в ассортименте, большинство хорошие.

Так библиотеки обычно от разных авторов (разного качества и разной степени совместимости между собой)

Ну какой совместимости мы хотим от роутера/контроллера и, допустим, ORM'а, драйвера БД или, например, брокера сообщений? Да они знать друг про друга ничего не должны!

велосипедом будет объединение этих библиотек

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

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

Да так же все, как у людей. Роутер -> слой контроллеров (их принято называть http.Handler) -> бизнесовый сервис. Внутри сервиса - ну тут да, демократия, доходящая до анархии.

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

Но хорошо продуманные, спроектированные и обкатанные.

Насчет хорошо продуманных и спроектированных - да вот по-разному бывает. Иногда автор решит, например, что в контроллере заголовки запроса не нужны, или urlPath не отдаст - гений, блин.

Пожалуй, самая хорошо продуманная и спроектированная штука, которую я видел - это http.HandlerFunc в Go. Просто один контракт func(w http.ResponseWirter, r *http.Request) , делающий, де-факто, не нужными веб-фреймворки на Go.

Знание их даёт полезный опыт. Некоторые решения можно потом применять и вне фреймворков.

Вот тут, скорее, соглашусь. На них стоит посмотреть, попробовать, понять, почему они нафиг не нужны и начинать применять 3-слойную архитектуру вне фреймворков. Так в Go и делают.

Роутер хорошо интегрирован с драйвером БД?

Что значит интегрирован с драйвером? Я ничего подобного не писал же.

Если роутер (хорошо если пара процентов от суммарного размера приложения) является фреймворком (т.е. определяет то, как вам писать приложение) - может, ну его нафиг такой роутер.

А вот библиотечный роутер - это круто!

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

Ну какой совместимости мы хотим от роутера/контроллера и, допустим, ORM'а, драйвера БД или, например, брокера сообщений? Да они знать друг про друга ничего не должны!

Так вы расширьте список библиотек, и узнаете. Посмотрите популярные full-stack фреймворки.

Что значит интегрирован с драйвером? Я ничего подобного не писал же.

Ну вот, вы же писали?

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

Окей, открываем доку Laravel: Роутинг, Цикл жизни запроса, Базы Данных, Eloquent ORM

Идем в Symphony: роутинг, бла-бла-бла, Базы Данных, Doctrine ORM

Доку Django смотреть будем? Какие там еще веб-фремворки в моде?

Каким образом и, самое главное, зачем все это интегрировано?

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

https://ru.wikipedia.org/wiki/%D0%A4%D1%80%D0%B5%D0%B9%D0%BC%D0%B2%D0%BE%D1%80%D0%BA

Фреймворк потому и фреймворк (дословно "каркас"), что он диктует архитектуру. Если вы взяли что-то, и в рамках взятого решения пишете код - это фрейворк. Если вы взяли что-то и заюзали как придумали внутри своего кода - это библиотека.

Так вы расширьте список библиотек, и узнаете

Что я узнаю? Что http-роутер не должен иметь в зависимостях ORM? Я и так это знаю.

Посмотрите популярные full-stack фреймворки.

А можно я этого делать не буду? Ну пожаааалуйста!

Я вам говорю, что даже в пределах бекенда "тесная интеграция роутера и ORM" - зло, а вы мне предлагаете всю цепочку от фронта до таблички в Postgres'е одним бандлом узреть... Что я вам плохого-то сделал?

В Symfony компоненты изолированны друг от друга. Вы можете вообще не загружать пакет с ORM или использовать любую другую реализацию.

В документации, естественно, описаны все доступные компоненты.

И архитектуру никто не диктует, вы можете спокойно подключать нужные компоненты из vendor, только те, которые ваше приложение использует. Тот же Zend Framework (мир его праху) в моей практике тоже так использовался: отдельные компоненты подключены (валидаторы, query builder), а всякие роутеры-контроллеры - свои.

В Symfony компоненты изолированны друг от друга. Вы можете вообще не загружать пакет с ORM или использовать любую другую реализацию.

Дык, верю. И именно по этой причине искренне недоумеваю:

  1. Если они изолированы, никак друг от друга не зависят, могут быть использованы по отдельности и не накладывают ограничений на архитектуру проекта, то фреймворк, получается - просто набор рандомных библиотек? А зачем их в кучу сложили? ZverCD же какой-то получается.

  2. Если во фреймворке есть какое-то удобное решение (пусть будет самописный ORM), почему он не поставляется отдельной библиотекой? А он точно за собой половину фреймворка в зависимости не притащит? Допустим, я использую Symphony, но ORM мне Laravel'овский нравится. Что делать? Оба фреймворка в проект тащить?

Тот же Zend Framework (мир его праху) в моей практике тоже так использовался: отдельные компоненты подключены (валидаторы, query builder), а всякие роутеры-контроллеры - свои.

Ну и зачем? Почему не взять нужный валидатор, нужный query-builder, роутер и контроллер по вкусу? Все равно же, получается, делаете то же самое, что гоферы (пользуете те части фреймворка, которые нужны), только не в виде библиотек, а в виде "подключаемых отдельно частей фреймворка". В чем смысл-то?

Как из "там [речь о фреймоврке] все компоненты хорошо интегрированы" получилось "Роутер хорошо интегрирован с драйвером БД"?

Что-то мало компонентов вы перечислили. Этого на полноценное приложение не хватит. А как страницы рендерить? А статику (js/css) как собирать? Где аутентификация/авторизация? А там и отправка писем понадобится. Работа с фоновыми задачами почти наверняка пригодится. Плюс там уже продуманы базовые средства безопасности от всяких xss, csrf, sql injection и т.д.; работа с куками, сессиями, http заголовками. И конечно же, средства тестирования.

Как из "там [речь о фреймоврке] все компоненты хорошо интегрированы" получилось "Роутер хорошо интегрирован с драйвером БД"?

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

А как страницы рендерить?

Любым из доступных способов: стандартным шаблонизатором, библиотечным, самописным, ручками собирать построчно - тысячи вариантов. Но, что важно, совершенно точно независимо от роутинга с одной стороны и работы с БД - с другой.

А статику (js/css) как собирать?

Хм, очевидно, вообще не средствами Go-шного бекенда. PHP для этой задачи тоже, впрочем, такой себе инструмент. JS хорошо бандлится, говорят, фронтовым тулингом. CSS - что-то мне шепчет про SCSS, SAAS, LESS. В любом случае кухня, к бекенду отношения не имеющая. Ответственность бекенда со стороны статики заканчивается на этапе "отдать статический файл".

Где аутентификация/авторизация?

В мидлваринах и библиотеках аутентификации/авторизации?

А там и отправка писем понадобится. 

Все верно. Когда понадобится, напишем import "some/lib/for/sending/email".

Думаю, у вас не сильно короче получится.

Работа с фоновыми задачами почти наверняка пригодится.

Вы на полном серьезе гоферу про фоновые задачи рассказываете?)

Плюс там уже продуманы базовые средства безопасности от всяких xss, csrf, sql injection и т.д.

Базовые средства безопасности - это иллюзия безопасности.

Мидлвари есть, CORS мы умеем. Побарывать SQL-injection в 2024 - уже даже не смешно. Из коробки не работает.

работа с куками, сессиями, http заголовками

Вы реально считаете, что Go не умеет работать с куками, сессиями и http-header'ами?) А вне фреймворка как-то с куками, сессиями и хедерами точно никак не поработаешь?

И конечно же, средства тестирования.

А с ними-то в Go что не так? Из коробки же!

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

Снова у вас какие-то непонятные выводы. Я ничего не писал про то, что go чего-то не умеет. Не сомневаюсь, что там есть куча разных библиотек на все случаи жизни. Я пишу о том, что входит в стандартный набор фреймворков, а то вы до пары библиотек всё сводите. Это просто готовый стандартный набор всех этих библиотек, мидлварей и тулингов, которым можно сразу быстро и легко пользоваться.

JS хорошо бандлится, говорят, фронтовым тулингом.

Не хочется ради пары скриптиков тащить этот нодовый тулинг.

Вы на полном серьезе гоферу про фоновые задачи рассказываете?)

Имел в виду отложенные, не параллельные (если вы об этом подумали).

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

https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D1%82%D0%B5%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D1%8F

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

Я пишу о том, что входит в стандартный набор фреймворков, а то вы до пары библиотек всё сводите.

Ну вот в "стандартный набор фреймворков" входит куча каких-то невзаимосвязанных библиотек, по какому-то неведомому принципу (типа желания левой пятки автора) объединенных в некую "единую сущность, в которой все отлично друг с другом интегрировано".

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

Болячка, стоит признать, постепенно отходит в прошлое (все же язык постабильней стал, хотя до сих пор вижу в резюме кандидатов строчки о достижениях на прошлых местах работы типа "переписал проект с php7.3 на php7.4"). Но привычка, мол, это типа круто и у подхода есть какие-то достоинства будет отмирать еще долго. Дольше PHP.

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

Стандартный - это когда в стандартной библиотеке языка. А тут - в каждом фреймворке свой набор велосипедов.

Не хочется ради пары скриптиков тащить этот нодовый тулинг.

Ну, положите эту пару скриптиков в одну папочку и одним статическим хендлером отдавайте наружу. PHP/Go тут вообще каким боком? Они ж бандлером JS работать все равно не умеют.

Имел в виду отложенные, не параллельные (если вы об этом подумали).

А вот это:

а) просто еще одна php-специфичная проблема, которую приходится решать сторонними средствами по типу cron'а (потому что процесс короткоживущий)

б) еще одна вещь, которая никакого отношения к остальным частям фреймворка не имеет, и включена в него ровно по той причине, что в каждом фреймворке свои хендлеры запросов на ровном месте придумывают (а потому хендлер отложенных задач, написанный одним автором, в другом фреймворке работать не будет)

Ну тоже мне, счастье великое нашли.

А давайте подождём. Есть куча языков, которые "побеждали простотой". И Java, и Python, и даже C#. А проходит лет 20 -- и уже можно смотреть, с какой скоростью разбухает официальная документация. Это какая-то прямо неизбежная эволюция: начинаем с "более лучшего Бейсика", а дальше начинается: как жить без лямбд, как жить без дженериков, как жить (подставьте своё). Ну и на выходе получаем ровно то же, а то и хуже, типа C++, который на этапе "C с классами" был достаточно тонкой надстройкой над C, а теперь вот оно как вышло.

Почти все серверы поддерживают php из коробки/ по умолчанию, хостинг для php гораздо дешевле любых других языков, достаточно проработанные системы разработки, сделать магазин проще чем на wordpress не встречал, множество готовый материалов. И есть много - много действительно готовых пакетов, тем, шаблонов и прочего и это бесплатно или почти бесплатно и попробуйте сделать магазин на golang хотя бы за 10 раз больше времени, чем на php.

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

И вроде бы php вовсе не сдаёт позиции уже много лет подрят и даже неплохо развивается и даже может излишне усложняется без внятных объяснений зачем так делают.

Ну так это сравнение тёплого с мягким.

Но по существу ваших тезисов:

Go везде будет работать. Хостинг для Go это вообще любая кофеварка. Да его на будильнике можно запустить. Ему не нужно ничего. Никаких nginx, php-fpm и прочего. Только голая ОС.

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

Вот в примере автора, никакая полезная нагрузка не приложена. Некая строка c JSON десериализуется в некий объект,.. чтобы что с ним сделать? А ничего. Оптимизатор может вообще эту инструкцию выкинуть, или использовать кеш. Сравнивать надо уметь, а неумеючи можно натянуть сову на глобус, что собственно автор и делает в статье. А ещё и без ссылок на код, из разряда "верьте мне".

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

Но напрашивается вопрос. Если пыха такая непобедимая и непотопляемая, зачем и откуда развиваются другие ЯП семимильными шагами и активно внедряются в процесс разработки ПО? Ведь от добра добра не ищут. Не задумывались об этом? Я не к тому, что пыха всё, а к тому, что надо трезво смотреть на вещи не с позиции "здесь и сейчас", а ретроспективно и перспективно.

Хостинг для Go это вообще любая кофеварка. Да его на будильнике можно запустить. Ему не нужно ничего. Никаких nginx, php-fpm и прочего. Только голая ОС.

Только виртуалка с голой ОС стоит дороже шаред-хостинга с nginx (на примере хетцнера более чем в 2 раза).

Ну и в каком-нить ISPmanager/Vesta тебе сразу и вордпресс, и почтовик, и днс, и летсенкрипт и все в 2 клика мышью.

Ну вы ещё с low-code хостингом сравните. Или с хостингом уже готового интернет-магазина, сайта, блога, который вообще на чём угодно может быть написан, просто покупайте и пользуйтесь. В два клика, без знаний какого-либо ЯП в принципе.

Сегодня уже интернет-магазины, которыми так любят хвастаться "за 3 минуты на коленке на Wordpress" уже давно уступили маркет-плейсам и готовым SaaS-решениям. Блоги тем более атавизм. Сайты-визитки? Я вас умоляю.

Весь этот дешёвый хостинг с ISP, почтовиком с каждым днём становится всё менее востребован. Даже забесплатно.

Разработку сложного ПО, если на внешнем хостинге, ведут в облаках (контейнеры), и там Go побеждает, безапелляционно. Ибо весит копейки, ресурсов ест минимально, не требует ничего, а с учётом тарифов на память и вычислительные ресурсы, это имеет серьёзный вес.

Вот пример базовый образ bitnami/php-fpm весит ~340Mb, а с далеко не самым сложным приложением легко доходит до 1Гб и выше. Базовый образ для приложения на Go, например alpine, весит... ~7Мб. Чувствуете разницу? И это не считая, что пыхе также нужен nginx, или ещё какой веб-сервер. На одном единственном экземпляре приложения может не так чувствуется, но когда идёт активная разработка CI/CD, а экземпляров много, уже ощущается неприличное давление и требовательность к ресурсам. И по времени развёртывания, и по хранению и прочее прочее.

И это не считая, что пыхе также нужен nginx, или ещё какой веб-сервер.

Формально — нет, не нужен. Можете запустить микроскопический socat и кидать соединения пыхе. Можете прямо на пыхе создать слушающий сокет и форкаться по мере поступления входящих соединений.

А чего тогда все возятся с php-fpm? Какой-то swoole или rr прикручивают, оказывается у пыхи есть свой встроенный продакшен-реди сервер? Или я что-то не понимаю? :)

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

Нет, это не так — PHP сам по себе может стать сервером, если при компиляции не вырезали поддержку POSIX-сокеточ.

А с php-fpm возятся потому, что люди любят использовать готовые решения, а не писать свои.

Когда я спросил у наших разработчиков PHP, почему вы не используете встроенный в PHP веб-сервер, они посмотрели на меня так, как будто я спросил почему вы не едите суп ботинком и на всякий случай отошли по-дальше :)

Никакого встроенного веб-сервера нет, его придётся написать.

Я нигде выше по ветви дискуссии не писал, что он есть в готовом виде. Но зато я выше по ветви дискуссии писал, что его можно написать на PHP и пришлось бы написать, если бы кто-то захотел так сделать.

Почему вы паясничаете и начинаете рассказывать про то, какие глупые вопросы вы задавали своим знакомым — мне непонятно.

Никакого встроенного веб-сервера нет, его придётся написать.

Ну строго говоря встроенный web-сервер в php всеже есть.
https://www.php.net/manual/ru/features.commandline.webserver.php

Но очевидное предупреждение как бы намекает

Внимание

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

Есть он, есть, php -S называется. Но да, использовать его - что есть суп ботинком, там правила маршрутизации определяет он сам, а не вы

$fp = fopen(__DIR__ . '/test.json', 'rb'); if ($fp === false) { echo 'Error' . PHP_EOL; return; } defer(function() use($fp) { fclose($fp); }); $result = ''; while ($line = fread($fp, 1024)) { $result .= $line; }

А вот это все разве нельзя по аналогии с golang заменить на:

$some_path_to_file = "....";

if (is_file($some_path_to_file)){

$line = file_get_contents($some_path_to_file);

}else{

throw new Exception('file not found...');

}

но я типа не сеньер... ))) Но на больших файлах и дисковых накопителей чтение по 1024 байта - может работать очень долго.

  • Показать, что PHP хороший язык программирования, на котором можно делать web-проекты

Можно и нужно - вещи разные. Это я оставлю, а то, что почти собрался писать про тесты выполняющие не написаное на тестируемых языках - опущу и перейду сразу к выводам. Для тех, кого подобное тестирование убедит что нужно выбрать PHP, PHP действительно лучший язык и его нужно выбрать. Всё хорошо.

Php убивает сам php. Самая важная фича языка - отсутствие типизации, толерантность к ошибкам в данных (сделать из числа строку или массив и работать с этим дальше - легко!) и простота языка позволяют писать то, что на других языках превращается в огромную портянку кода.

  • Разработчики взяли курс на принудительное внедрение строгой типизации.

  • Теперь нельзя вызвать count() на не-массиве потому что он, внезапно, not-countable - причем десятилетия это не было проблемой, но теперь всё падает с ошибкой. В итоге тот код, который раньше нормально переваривал любые входные данные теперь требует дополнительных приседаний, чтобы убедиться, что эти данные - корректны (т.е. получилось как во всех остальных языках).

  • Сделан очень большой упор на классы везде: причем все новые фичи языка завязаны именно на классы, при том что бОльшая часть старой кодовой базы - процедурно-ориентированная.

... и еще много всякого

В итоге php постепенно превращается в какой-то c++ на минималках, даже js уже выглядит лучше.

Ваш случай я буквально вчера описывал в соседнем топике.

Для подхода "фига-фигак и в продакшен" действительно совершенно неважно, какие там данные пришли. "У меня никаких ошибок не выдаёт".

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

$product = null;
$price = 10;
$cost = $price * $product['qty'];

До 7 версии РНР молча делал стоимость равной нулю, и это было ПОЗОРИЩЕ.

Если лопату применять для забивания гвоздей, то она почему-то гнется...

Эта фича нужна для обработки строковых данных разного формата. Причем когда формат заранее не определен и определять его, в общем-то, и нет смысла - задача в целом про другое. В итоге на каком-нибудь c# мы имеем портянку кода с кучей приседаний и объявлений переменных, чтобы не дай бог строка не полезла в int - а на php это одна-две примитивных строчки кода.

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

Чем отличаются все эти новомодные языки друг от друга?

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

Нескучным синтаксисом?

Как раз синтаксис в Go очень скучный. Зато можно прийти с другого языка и с ходу начать понимать написанное.

Go как раз отличается тем, что в нём стараются минимизировать количество таких "фич"

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

Зато можно прийти с другого языка и с ходу начать понимать написанное.

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

Так вот, только на php это делается более-менее простым кодом.

Ну так значит, для этой задачи PHP подходит хорошо) Никто же не говорит, что PHP со всех сторон ущербен. Но это таки вполне специфическая прикладная задача. Если в вашем приложении нужно её решать часто и в больших объёмах (есть наработанная кодовая база, опять же) - для вас PHP предпочтительнее. А если такой задачи нет и надо чаще считать, например, деньги, чем парсить текст, то это слабое преимущество. Многие, я думаю, предпочтут иметь портянку кода, но быть уверенными, что лишняя копеечка никуда не улетает.

Практически все языки сегодня имеют плюс-минус одинаковый си-подобный синтаксис.

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

Go, по сравнению с другими языками, пуст. Даже в каком-то смысле примитивен. Но благодаря этому вкатываться проще.

Проблема в том, что текущая 8ая ветка php сильно ограничивает эту свободу. А грядущие изменения ограничат её ещё сильнее. Т.е., имхо, в какой-то момент php просто станет таким же языком, как, например, c#. И зачем мне тогда его выбирать? ASP.Net, как язык со строгой типизацией, просто во всём лучше, чем такой переделанный php. И даже если они вдруг в этом будут равны - то Visual Studio с Resharper просто на несколько порядков лучше вообще любого известного мне редактора с поддержкой php.

Т.е., я повторюсь, разработчики php сейчас сами своими руками рубят уникальность php. И они выходят туда же, где тусуется куча других языков - у которых была куча времени для развития. В отличие от php. От этой конкуренции он и помрет...

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

Ну так они и выберут другой язык. Например, в котором есть специальный тип Price, умеющий корректно работать с долями копеек (с чем у обычных float/double есть проблемы).

Никто ж не призывал писать всё на php)

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

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

Намного важнее обвязка языка: библиотеки, документация, форумы. (По документации, имхо, дотнет вообще никто до сих пор не обошел. У многих просто плохо. У некоторых вообще такая беда, что не понятно как это стало популярным)

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

Недавно у нас при интеграции с PHP приложением возник казус. Получали в ответе JSON со словарём вида { "meta": { "prop1": "value1", "prop2": "value2" } } и всё было хорошо, пока всё внезапно не сломалось. По бизнесу словарь может быть пустой, в таких случаях ожидается вот такое { "meta": { } }, но пыха прислала { "meta": [ ] }. Стандартная сериализация при отсутствии явной типизации. Бизнес-процесс пострадали и был простой. Кто виноват? Глупые типизированные платформы, которые должны были догадаться, что [ ] это { }? :)

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

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

жидается вот такое { "meta": { } }, но пыха прислала { "meta": [ ] }

Так для php уже много лет массивы с [] и {} - одинаковое. Версии с седьмой, кажется (я по-прежнему предпочитаю старый синтаксис, поэтому не помню когда по-новому стали делать). Не, ну т.е. на самом-то деле это разное даже в php, но когда программируешь, разницу эту просто не чувствуешь - она слишком глубоко закопана.

С позиции enterprise разработки, когда бизнес-объектная модель состоит из десятков тысяч типов и множества слоёв логики, отсутствие максимально строгой типизации превращается просто в ад. Вплоть до того, что даже для таких значений как ИНН, ОГРН и т.п. создаётся отдельный доменный тип, на которым уже навешаны: валидация, парсинг, сериализация, куча расширений и нельзя вот так какую-то рандомную строку сунуть как параметр функции, т.е. перепутать ИНН и ОГРН физически компилятор не даст. Если честно, за всю практику работы в типизированной среде мне крайне редко доводилось кастовать строку к числу, чтобы с чем-нибудь сложить. Зачем? Число в виде строки просто тупо не появится в границах приложения. И такие случаи возникают чаще именно в интеграции с динамическими ЯП, которые без всяких Б могут прислать число строкой, вообще не запариваясь ни разу. Разработчики, получая такие "подарки" очень радуются, ну наконец-то! Расчехляем адаптивную десериализацию, обмазываем аспектами.

Для небольших микро-сервисов, и тем более прослоек, тупо гоняющих данные между БД и фронтом, особо типизация и не нужна наверное. Или я очень уж пристрастился к типизации )

Ну так поэтому все и стараются в энтерпрайзе использовать java или c#, но не php. Под задачу и инструмент.

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

Так для php уже много лет массивы с [] и {} - одинаковое

Оно разное, так же как и в JS. Первое массив. Второе объект.

То что json_decode в php можно (и в 90% случаев так и используется) включить associative режим, когда все JS объекты будут возвращаться как массивы - это немного другое

Оно разное

Я знаю, что оно разное, я про это написал. Но многие программисты на php их перестали различать вообще. И лепят {} всегда - благо, задачи такие, что там без разницы, на остальной код не влияет.

json_decode

А он причем, если писалось про результат encode? Тут-то вот эта дурная привычка некоторых php-шников лепить {} вместо [] к вам и вылезла боком.

Чтоб в PHP получить в json_encode {} надо налепить туда объект как-то так

// чтоб получить {} надо сговнякать объект
$a = new \stdClass();
$a->foo = 'foo';
$a->bar = 'bar';

// а "плоский" список даст []
$b = ['foo','bar'];

echo json_encode(['a'=>$a,'b'=>$b]);
{"a":{"foo":"foo","bar":"bar"},"b":["foo","bar"]}

Я, честно говоря, хз как это можно не различать и говнякать всегда {}?

// чтоб получить {} надо сговнякать объект $a = new \stdClass();

Не нужно. Достаточно написать $a={}; вместо $a=[];
И потом можно писать $b['foo']="foo"; $b['bar']="bar";

Такой синтаксис работает с 7ой версии - т.е. уже много лет.

Я, честно говоря, хз как это можно не различать и говнякать всегда {}?

Говнокодеры, сэр.

А ещё - всё хорошо, если у вас весь код перед глазами. А если этот массив/объект инициализировали в одном месте, а потом заполняют данными в совершенно других местах + код постепенно дополняли разные люди в разное время...

Совершенно не удивительно, что где-нибудь в дебрях foreach очередной программист по своей привычке влепил $data[]=[]; вместо $data[]={}; - вот у вас в json'e и получилась смесь объекта с массивом.

Самое прикольное, что после json_decode на php вы могли даже и не заметить, что там что-то не так было с форматом данных. Потому что с точки зрения синтаксиса языка foreach по объекту и массиву работают совершенно одинаково. И обращение по ключу к элементу - тоже.

Разница, повторюсь, есть - но для этой разницы нужен специальный код, который нужен ооочень редко и в проектах с очень хитрой спецификой. А остальным что там [], что {} - без разницы, на конечный код это не влияет.

Достаточно написать $a={};

Вы бредите

Действительно, я ошибся(

Проверил по-разному синтаксис и объект должен быть задан явно или через приведение типов или через new. Ну или объект незаметно вернет какая-то функция.

При этом по полям объекта тоже можно сделать цикл, как и по элементам массива:

$a=(object)['aaa'=>'bbb', 'xxx'=>array('yyy')];
foreach($a as $c)
var_dump ($c);

php.exe D:\x\x.php
string(3) "bbb"
array(1) {
[0]=>
string(3) "yyy"
}

И пока вы явно не полезете за полем через, например, $a['aaa'] - вы и не узнаете, что там объект, а не массив (или наоборот).

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

Соответственно, если у нас часть данных в том исходном json (с которого ветка комментариев началась), заполняется разными функциями или вообще через json_decode (а там всего лишь один необязательный параметр определяет, что будет на выходе: массив или объект), то получить в итоге смесь объектов с массивами вполне реально. И пока вы явно не полезете за данными по ключам - вы про это даже не узнаете.

Извините, но вы вообще ничего не поняли в этой ветке. И пишете какую-то чушь. Как получить непустой объект в джейсоне никто и не спрашивал. Причем никакой "объект говнякать" тут не нужно, массива будет вполне достаточно. Ассоциативного.

Речь шла про json_encode и пустые массивы. Которые действительно по умолчанию кодируются в список. То есть про случай, когда ни ни foo, ни bar в массиве нет. Но поскольку принимающая сторона ждёт объект, то действительно приходится ставить костыль, принудительно кастуя ассоциативный массив в объект перед вызовом json_encode (если есть вероятность, что массив может оказаться пустым). Либо заранее используя объект вместо массива.

Собеседник ваш, впрочем, недалеко от вас ушёл, воображая что в РНР можно определять массивы в фигурных скобках, получая массив какого-то "другого" вида.

Речь шла про json_encode и пустые массивы. Которые действительно по умолчанию кодируются в список.

А что, непустые массивы кодируются во что-то другое?
В json объекты {} кодируются по-умолчанию ассоциативные массивы, простые же массивы (ок, они же и есть списки) кодируются как json списки [], так же как и пустые массивы (которые из за своей «пустоты» не являются ассоциативными).

Если уж бизнес логика @posledam желает получить в json именно пустой объект, то почему кто-то туда пихает массив? Где логика? Хотите пустой объект - пихайте пустой объект. А уж как его приготовить в php, через new \stdClass() или кастинг (object) [] не важно. Мне лично понятнее первое.

$a  = []; // пустой массив
$b  = ['m1','m2']; // не пустой массив
$c  = ['k1'=>'v1','k2'=>'v2']; // не пустой ассоциативный массив
$o  = new \stdClass(); // новый объект
$of = (object) []; // объект из массива через кастинг
echo json_encode(['a'=>$a,'b'=>$b,'c'=>$c,'o'=>$o,'of'=>$of],JSON_UNESCAPED_UNICODE);
{"a":[],"b":["m1","m2"],"c":{"k1":"v1","k2":"v2"},"o":{},"of":{}}

А что, непустые массивы кодируются во что-то другое?

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

Да. В этом и заключается весь смысл этой ветки

Увы. Смысл этой ветки в том, что люди, которым требуется объект на выходе – пихают вместо него массив, а потом говорят, что php говно.

Логика вообще полезная штука. Или Вы хотите оспорить утверждение, что ['a','b'] это «непустой массив»? Вы считаете что он «пустой» или что он «не массив»?

мы с вами

Оглянулся, я тут вроде один, не надо ко мне во множественном числе обращаться :-)

Задам такой вопрос (вернее, три вопроса):

  • Во что кодируется пустой ассоциативный массив?

  • Во что кодируется непустой ассоциативный массив?

  • Как принимающей стороне писать код, чтобы декодировать оба случая?

Для начала задайте вопрос себе: в чем отличие ассоциативного массива от обычного с точки зрения логики, а не внутреннего устройства (которое в php, помнится, одинаковое)?

  1. Пустой массив не является ассоциативным. И поэтому кодируется как список [], так же как и непустой и не ассоциативный.

  2. Непустой ассоциативный кодируется как объект {}, потому что очевидно, по другому в js его не закодировать

  3. При чем тут принимающая сторона? Писать правильный код нужно кодирующей стороне

В этой ветке неправы вообще все (включая меня). Я таки победил лень, написал демо код и проверил:

$data1 = [];
$data2 = ['aaa'=>'bbb'];

echo json_encode($data1);
echo "\n";
echo json_encode($data2);
php.exe D:\x\x.php
[]
{"aaa":"bbb"}

Потом погуглил и выяснилось, что json_encode в php по умолчанию возвращает именно квадратные скобки вместо фигурных на пустом массиве. И надо делать так:

json_encode($data1, JSON_FORCE_OBJECT);

JSON_FORCE_OBJECT - тоже не решение, потому что в таком случае будут фигурные скобки вместо квадратных там, где по схеме должен быть массив.

Ну хотя бы всё будет единообразно, а не смесь из [] и {}.

Остается, правда, открытым вопрос, как к пустым {} отнесутся другие языки программирования, где будут делать json_decode... Боюсь, там тоже будет что-нибудь нетривиальное с параметрами вызова функции.

А это уже вопрос схемы как раз. Если в этом месте по схеме должен быть словарь (то бишь ассоциативный массив) - значит, {} - валидное значение и никакой специальной обработки не требует.

 Я таки победил лень, написал демо код и проверил:

Я вам написал все варианты выше. Ассоциативные массивы кодируются как js object, индексные, включая пустые - как js array

Если лопату применять для забивания гвоздей, то она почему-то гнется...

Так из этой лопаты при помощи костылей и скотча уже давно сделали экскаватор ;)

Я вот от части согласен с @Fahrain. Если правильно понял, то критика PHP в том, что он пытается конкурировать в той нише, к которой изначально не был предназначен. Его базовая архитектура рассчитана на интеграцию в простые HTML странички. Но теперь его используют совсем в другой среде. От этого и синтаксис, как по мне, получается нелепый (непривычным на фоне остальных языков) - с этими вездесущими тэгами в начале файла, обязательными $ для переменных и т.п.

Не "пытается", а успешно конкурирует. Средний энтерпрайз в вебе прекрасно пишется на РНР. Тот же екомм. Кроме парочки озон/вб, которые выросли ещё тогда, когда пхп действительно был убог, все остальные прекрасно живут (и растут) на пыхе. И те изменения в языке, о которых так убивается ваш Fahrain, как раз и позволили это. И "критикуют" его за это развитие либо хейтеры типа вас "язык для страничек", либо недотёпы типа Fahrain, которым не под силу писать нормальный код.

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

Не "пытается", а успешно конкурирует. Средний энтерпрайз в вебе прекрасно пишется на РНР.

Вот тут бы я поспорил. Не спешите ругаться, позвольте изложить поинт.

Проблема аргументации "за ПХП" в том, что она сводится к "в большинстве случаев, PHP справляется с задачей не хуже, чем язык X". Т.е. мы приходим к ситуации, когда есть некоторое количество задач, в 90% которых php не хуже (но и не лучше) других языков, и еще 10%, в которых хуже. Погодите-погодите! Но ведь это утверждение тождественно утверждению php хуже языка X!

Для ясности понимания: допустим, есть определенный проект, написанный на php.

Я могу достаточно внятно объяснить, в каких случаях есть смысл (спойлер: не во всех) переписать его на тот же, например, Go. Могу сказать, что мы получим от переписывания, могу навскидку определить, стоит ли оно того, и в каких местах будет выигрыш. У меня есть аргументы.

Точно так же я могу навскидку перечислить бенифиты от перехода на C#, Java или, простихоспади, Python (хоть это и совсем эзотерические случаи).

Хотелось бы понять, существует ли в принципе внятная аргументация миграции в обратную сторону. Вот, допустим, у меня есть проект на Go. В каких случаях есть смысл переписать его на PHP, и что я от этого получу.

Вы же понимаете, что пока нет таковой аргументации, PHP вполне обоснованно считается вымирающим языком?

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

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

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

И "умирающим" он является исключительно из-за иррационального хейта

Прям таки исключительно? Нет в языке недостатков? Просто так хейтят? И даже лицокнига отказалась от PHP исключительно потому, что "иррациональный хейт"? Или, все же, были какие-то реальные обоснованные причины?

с "интеграции в простые HTML странички" можно при наличии способностей перейти в средний энтерпрайз

"Средний" энтерпрайз, внезапно, копирует удачные решения у "большого" (у него нет денег на разработку собственых решений с нуля). Весь "большой" - от PHP отказался. Надолго у "среднего" хватит наработанного устаревающего багажа?

с соответствующими зарплатами

Ну да, с соответствующими: у среднего энтерпрайза зарплаты ниже, чем у большого. "Зато у нас программисты за еду работают" - ну, такой себе аргумент.

При переписывании крудов с Го на РНР вы получите очевидно сниженную стоимость владения

За счет чего, простите, я ее получу? За счет более дешевых разработчиков?

И время разработки MVP.

А это с чего? Сколько времени займет разрабтка crud-api на одну сущность на PHP? А что мы считаем MVP? "Вау, оно запустилось" или, все же "вот эту штуку можно продавать"?

Ту же Ларавель сложно превзойти в нише "фигак-фигак и в продакшен"

Wordpress, Joomla и nocode-редакторы ржут в голосину.

Пока вы будете писать в Го все эти проверки на каждый чих

Погодите, но строгая явная статическая типизация для того и нужна, чтобы НЕ писать проверки на каждый чих!

Вот я написал "у меня здесь число". Все. Мне, как раз, НЕ надо проверять, число мне пришло, или строка, или массив. У меня компилятор ГАРАНТИРУЕТ корректность из коробки.

приложение на Ларавле уже будут показывать заказчику

Ага, видел. "Ой, ну тут чота упало, тут в базу хрень записалась. Но половина-то запросов выполнилась"

При том что если выстрелит, то легко смасштабируемся на микросервисы и кубер. 

А вот тут, внезапно, проблемы. Го масштабируется на порядки дешевле (да, я опять про деньги).

И перепишем ключевые участки на Го.

Воооот, мы перепишем ключевые участки на Го (кстати, не особо тривиальная задача).

Я про то и говорю, что я вижу ситуацию, когда есть смысл переписать с пхп на го, но не вижу ни одного аргумента про переписывание в другую сторону (вы предложили только дешевизну разработчиков - ну, такое себе)

Не спешите ругаться

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

Недостатки есть в любом языке. Вопрос бессмысленный.

Хейтят просто так. Либо по привычке, либо за "значок доллара". Осмысленная критика, увы, встречается куда реже. И по её поводу см. рис. п.1.

Кейс лицокниги мы уже разбирали выше, вместе с озоном/вб. Вы передергиваете, подсовывая решение, принятое больше 10 лет назад, в обсуждение текущего состояния языка.

"Зато у нас программисты за еду работают"

Вы мараете себя своими репликами все сильнее.

"вот эту штуку можно продавать"?

Да.

Вот я написал "у меня здесь число". Все. Мне, как раз, НЕ надо проверять, число мне пришло, или строка, или массив. У меня компилятор ГАРАНТИРУЕТ корректность из коробки.

Вот видите, вы что-то знаете о плюсах пхп :)

А речь здесь шла про отсутствие исключений и бойлерплейт с проверками результата каждой функции в Го.

В целом мне не очень интересно продолжать. Полемические приёмы вида "объявить аргумент собеседника несостоятельным" мне наскучили ещё в фидо.

Кейс лицокниги мы уже разбирали выше, вместе с озоном/вб. Вы передергиваете, подсовывая решение, принятое больше 10 лет назад, в обсуждение текущего состояния языка.

На момент 10 лет назад лицокнига рапортовала, что в PHP их не устраивает: динамическая типизация, производительность, отстутствие состояния (php создан, чтобы умирать). Кое что, конечно, изменилось, но...

Но на данный момент, простите за откровенность, PHP выглядит догоняющим. У него нет киллер-фич, заради которых был бы смысл на него мигрировать. Он стоит на позиции "у нас можно не хуже" - этого мало.

Вы мараете себя своими репликами все сильнее.

Вы, все же, не ответили, за счет чего снижается "стоимость владения"? Я просто предположил, что за счет экономии на зарплатах - других вариантов не видно.

Вот я написал "у меня здесь число". Все. Мне, как раз, НЕ надо проверять, число мне пришло, или строка, или массив. У меня компилятор ГАРАНТИРУЕТ корректность из коробки.

Вот видите, вы что-то знаете о плюсах пхп :)

Давно в PHP компилятор, простите, появился? )

Вы постоянно, как мантру, повторяете, мол, на PHP разработка быстрее? У меня, в этом плане, есть вопрос: за счет чего? За счет динамической типизации? Дык, она не ускоряет разработку корректного кода (и даже, иногда, с точностью до наоборот)!

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

На Го дольше пишется код? Дык, зато дебаг быстрее, и тестов меньше писать! Я же потому и спрашивал, вы время до "о, оно запустилось" сравниваете, или работающее коммерческое решение?

"О, оно запустилось" - без вопросов, быстрее на PHP. MVP - одинаково. Долгое поддержание/развитие проекта, уж простите, выгоднее на Go (или Java/Kotlin/С#, например).

А речь здесь шла про отсутствие исключений и бойлерплейт с проверками результата каждой функции в Го.

Речь шла про "PHP успешно конкурирует с конкурентами". Что вы так и не продемонстрировали. Единственный весомый аргумент в пользу PHP (относительно конкурентов, не только Go), который вы привели - на PHP дешевле. Остальное - ну, такое. На всякий случай приведу здесь:

не видите трейтов, интерфейсов, исключений, енамов, замыканий, генераторов, производительности, типизации, мощных ORM и фреймворков, экосистемы с миллионом различных инструментов

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

В Go ровно одна киллер фича: M:N асинхронность, остальное, в лучшем случае, "на уровне". Заради нее все туда и идут (и ради нее, например, отказались от исключений)

Все остальное из указанного вами списка лучше реализовано в том же C# (или Java/Kotlin).

Какая киллер-фича у PHP?

Да я вроде ничего экспертного не писал. Я его не на столько хорошо знаю, что бы оценивать как там реализованы ООП, типизация, интерфейсы и т.д. Поэтому и оцениваю только синтаксис. Хотя попытки погружения были, но при прочих равных выбрал язык без "детских болячек". И никакого хейта, не имеют ничего против этого языка. У меня даже к пайтону больше претензий (грубо говоря). Просто отметил пару моментов, которые выбиваются в сравнении с другими языками.

Как недавно написали на Реддите в подобном обсуждении, "если ваши проблемы с РНР сводятся к открывающему тегу, то с языком всё хорошо".

Да нет, общепринятый camelCase мне не нравится больше) Хотя, я тут, глянул свежую доку, и там почти все методы в snake_case. Что меня малость запутало, там есть какие-то общепринятые нормы, или это просто переходный период? Но это всё мои личные мелкие придирки. Уверен, что с языком действительно всё хорошо и он развивается в нужном направлении.

Кроме парочки озон/вб, которые выросли ещё тогда, когда пхп действительно был убог

Вб сейчас активно переписывается на Go.

От этого и синтаксис, как по мне, получается нелепый (непривычным на фоне остальных языков) - с этими вездесущими тэгами в начале файла,

Этот «нелепый» синтаксис на 80% совпадает с синтаксисом Java и C++

обязательными $ для переменных и т.п.

Ну один символ для всех переменных это не так уж и страшно, ничего такого. Поглядите в Perl, там $ для скаляров, @ для списков и % для хешей. Никто не жаловался

Этот «нелепый» синтаксис на 80% совпадает с синтаксисом Java и C++

Так я про этот синтаксис ничего и не писал. И вот, кстати, эта мешанина объектного и процедурного стиля тоже смотрится так себе.

Поглядите в Perl, там $ для скаляров, @ для списков и % для хешей.

Ну так это заложено в архитектуру и имеет смысл. А в PHP $ имеет смысл только в контексте HTML (а этого кода минимум).

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

Еще раз: это преимущество php. Все сегодня буквально упарились в строгую типизацию. Но есть куча случаев, когда это просто не нужно. И то, что есть язык, который позволяет тебе писать код, которому плевать на тип входных данных - это хорошо и не надо такой язык делать очередным c++. Языков "а-ля c++" - много, а php такой один.

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

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

Есть задачи, где входные данные принципиально недетерминированы: тот же curl сегодня возвращает html с данными, а завтра там заглушка с капчёй. На php код легко поймет, что после парсинга данные кривые и не положит их в базу, а на c# он тупо умрет с exception еще на начальном этапе парсинга - просто потому, что невозможно написать алгоритм, который учтет все возможные виды страниц, которые может вернуть чужой сервер.

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

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

А на том же c# мне надо будет дополнительно ловить ошибку незаполнения этого массива, чтобы потом код не упал из-за выход за границы массива. И это еще ладно, но что если отсутствие какого-то элемента массива - не баг, а фича? Ну такие данные: сегодня там в табличке 3 колонки, завтра 2, послезавтра 5. На c# мне придется кодить отдельные приседания, чтобы это учитывать, а на php я одной строкой кода возьму именно нужные мне данные - и пофиг сколько там колонок. (Ну, понятно, что пример несколько утрирован, но всё же)

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

на php я одной строкой кода возьму именно нужные мне данные

Магия, не иначе.

Фишка в том, что в пространстве программного кода на строго типизированном ЯП просто не обитает чисел в строках, массивов непонятной размерности или непонятного толка, не бывает массивов-словарей, потому что все намерения выражены явно, они читаются и понимаются явным образом. И дело уже не только в защите компилятора, типизация позволяет проводить перспективный анализ кода, выявляя ошибки даже при соблюдении типов, например, недостижимый код, сравнения которые никогда не будут работать, бесконечное зацикливание, работа с переменными, которые по алгоритму не изменяются, попытка работать с объектами, которых не существует (null). И всё это не означает, что надо писать больше лишнего кода, благодаря той же типизации, современные IDE позволяют предсказывать и предугадывать намерения разработчика, генерируя стандартные проверки на лету.

Все эти бонусы просто меркнут на фоне его величества Рефакторинга. Проводить рефакторинг в типизированных языках, это удовольствие. Любые косяки исправляешь в одном месте, они исправляются в тысячи мест сразу, и ты знаешь что ничего не сломается, так как везде всё явно и строго.

Никакой чёрнокнижной магии, не нужно в голове работать компилятором и думать, как оно там скастит, и что будет если? Всё супер-очевидно и не надо играть в игру: угадай как это сработает в рантайме?

Как человек, изрядно посидевший на проектах с динамикой и статической типизацией, скажу, строгая типизация действительно величайшее благо. И в PHP её затаскивают именно по этой причине, а не потому что "модно", как кому-то по наивности может показаться.

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

В том же printf в c++ вы обязаны указывать, какой тип данных собрались вывести в консоль/файл - а echo в php выводит в виде строки вообще всё что угодно (кроме массивов).

И логично же, что для вывода, например, страницы с информацией из базы данных "как есть" вам вообще пофиг, что там за данные были - число или строка. Вы их на php просто выводите одной командой. А на типизированном языке вам надо сначала перегруженные функции преобразования типов накодить или еще какие проверки этих данных сделать (хотя бы на null) перед тем как вывести - потому что по-другому вам компилятор не даст и будет 100% прав.

Да основной наверное профит в строгой типизации это рефакторинг. Зарефачить с массовыми изменениями в сотнях и тысячах файлах в строго типизированном ЯП как за здрасьте :) Разделение, объединение типов, вывод интерфейсов, разнесение по модулям, смена списка аргументов, типа возврата, вообще без страха и сомнений.

А в динамике страшно до усрачки что-то трогать лишний раз, по крайней мере мне было страшно :)

Ну тут мы опять возвращаемся к инструментарию. После Visual Studio с Resharper и динамической отладкой в процессе выполнения, всё, в чём я пытался кодить на php, выглядит как запорожец против майбаха. А когда туда ещё и фреймворки суют... Массовый рефакторинг кода на php - это уже закат солнца вручную какой-то)

Да вот: Visual Studio Code сегодня умеет динамически обновлять изменения страницы в браузере при изменении в файле на диске. Ну типа круто же всё, да? Вот только если у нас типичный фреймворк или cms - мы правки вносим в один файл, а результаты смотри вообще в других местах. Т.е. вроде бы функционал в редакторе есть - а толку?

Или у нас есть css (scss) и php файлы. И тут и там упоминаются одни и те же html-классы. Только вот почему-то редактор php о том, что в css написано вообще не в курсе и автоподстановка названий классов там не работает (если сделать inline-тег style, то работает). Нет и возможности навести мышку на имя класса и подсмотреть какие там стили в нем прописаны.

Да много чего нет. После c# переключаться на кодинг на php очень больно)

Или у нас есть css (scss) и php файлы. И тут и там упоминаются одни и те же html-классы. Только вот почему-то редактор php о том, что в css написано вообще не в курсе и автоподстановка названий классов там не работает

По крайней мере PHPStorm/IntelliJ IDEA отлично умеют в рамках как минимум одного проекта видеть стили в css и автодополнять их в html/php коде. Или если css включен в этом html файле

Я их пробовал несколько лет назад и не увидел для себя ничего полезного. Поэтому с выходом VS Code ушел на него, так как он шустрее работает.

Или если css включен в этом html файле

Так VSCode тоже умеет. И автодополнять может, если стили текстом в файлике есть и если соотв. настройку автодополнения потюнить, чтобы в строках подстановку тоже делал (по-умолчанию не делает).

После перехода с полноценной студии все эти IDE настолько ущербны, что уже даже не важно, чего в них есть - на фоне того, чего сильно не хватает(

И логично же, что для вывода, например, страницы с информацией из базы данных "как есть" вам вообще пофиг, что там за данные были - число или строка. Вы их на php просто выводите одной командой. А на типизированном языке вам надо сначала перегруженные функции преобразования типов накодить или еще какие проверки этих данных сделать 

        foreach(DataRow row in thisTable.Rows)
        {
            foreach(DataColumn column in thisTable.Columns)
            {
                Console.WriteLine(row[column]);
            }
        }

В чем принципиальная разница с php?

В общем случае row[column] может быть кастомным типом без соответствующей функции преобразования типа к строке. Или Null. Или DataColumn выкатит вам исключение и всё упадет. Вы всегда вынуждены про всё это помнить - а на php в худшем случае вы получите пустоту или надпись null где-то, что особо ничего не сломает.

Не то, чтобы такой подход к выводу данных был хорош - данные всё же стоит как-то валидировать перед выводом. Но c# вас заставляет это делать даже если вам в данный конкретный момент это не нужно, а php - нет.

И я еще раз повторюсь: важнее даже не то, что вы выводите, а что вы вводите. Тут-то как раз все ограничения языков со строгой типизацией и вылезают. Не то чтобы это было плохо - в части задач. А в другой части задач валидировать входные данные вообще ни в каком виде не нужно (особенно если это одноразовый скрипт для первичной обработки сырых и кривых данных, например)

Т.е. просто у php специфичная ниша под специфичный класс задач. Не вижу в этом ничего плохого.

В общем случае row[column] может быть кастомным типом без соответствующей функции преобразования типа к строке. 

В шарпах метод ToString() определен у базового типа object, поэтому что-то да выведет.

Или Null.

Console.WriteLine нулла не боится.

Или DataColumn выкатит вам исключение и всё упадет. 

В PHP исключений не бывает, ничего упасть не может в принципе, потому что PHP? В чем разница?

Вы всегда вынуждены про всё это помнить

В том и дело что помнит про это компилятор, а в данном примере даже компилятору помнить нечего.

а на php в худшем случае вы получите пустоту или надпись null где-то, что особо ничего не сломает.

В данном примере (вывести значения из базы) в случае нулла будет просто "пустота". Никакой разницы вообще. Еще dynamic есть для любителей острых ощущений.

И я еще раз повторюсь: важнее даже не то, что вы выводите, а что вы вводите. Тут-то как раз все ограничения языков со строгой типизацией и вылезают.

Правильно, php может спокойно склеить строку с числом, неправильно распарсить плавучку или дату и кривые данные спокойно пойдут в базу. Это преимущество такое?

А в другой части задач валидировать входные данные вообще ни в каком виде не нужно

Не хотите валидировать, не валидируйте. База данных не начнет ругаться, если в числовое поле строки пихать? Для обработки данных все равно надо понимать, что это за данные, никуда от этого не денешься.

ToString() определен у базового типа object

Он не везде есть, у некоторых типов данных ToString() отсутствует, сталкивался с этим.

Console.WriteLine нулла не боится.

Но боится row[column] - там-то оно и вылезет. Или column за границу массива попадет потому что сегодня на входных данных так звёзды сошлись. Т.е. в вашем коде обязательно надо или try...catch или кучку if-ов. В прод я бы его точно в таком виде не пустил, он в любой момент может сломаться по куче причин

В PHP исключений не бывает, ничего упасть не может в принципе, потому что PHP? В чем разница?

Всё там есть - если нужно. Оно просто необязательное. Поэтому я могу написать if(a[c]=="") и оно не сломается даже если $c равно null или даже неопределено, а $a вообще не массив и даже не строка (или опять-таки null).

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

У типизированных же языков вам компилятор сразу же начнет по пальцам больно бить. В итоге вы потратите в разы больше времени на защиту от потенциальных ошибок (которые не дает пропустить компилятор), вместо основного алгоритма. Еще раз повторюсь - всё это в ситуации, когда вам в принципе плевать на тип данных в переменных и есть ли они там вообще.

В данном примере (вывести значения из базы) в случае нулла будет просто "пустота". Никакой разницы вообще.

Если там в переменой не null и приведение типов к строке не начнет вызывать ToString() на этом null - а его у null'а не существует.

И вы всё равно на c# не сможете положить строку в переменную, а потом использовать это (строковое!) значение как ключ в массиве, где все ключи - только числа. И при этом в результате получить правильный элемент из массива, а не exception. Еще раз повторюсь: этим надо пользоваться предельно аккуратно и лучше так не делать, если этого можно не делать.

Правильно, php может спокойно склеить строку с числом, неправильно распарсить плавучку или дату и кривые данные спокойно пойдут в базу. Это преимущество такое?

Да. Иногда именно так и надо: склеить строку с числом и положить в базу в одну ячейку. Например, у вас адрес: улица строкой, а дома числами хранятся (потому что валидация входных данных - и там везде toint() ). Но в базе-то вам по кусочкам адрес не нужен.

Не хотите валидировать, не валидируйте. База данных не начнет ругаться, если в числовое поле строки пихать?

Начнет же. Но зато я могу вызвать toint() на пустой строке или null (полученных из базы или от пользователя) и код на php не упадет и не выкинет exception. Еще раз повторюсь: это плохой подход и так в проде делать не надо. Но есть задачи, где именно такого сырого кода с кривыми данными вполне достаточно. Просто не надо лучше.

Он не везде есть, у некоторых типов данных ToString() отсутствует, сталкивался с этим.

Это у каких?

Но боится row[column] - там-то оно и вылезет. Или column за границу массива попадет потому что сегодня на входных данных так звёзды сошлись.

Вы код посмотрите, там foreach по колонкам идет, какие вообще границы массива?

Если там в переменой не null и приведение типов к строке не начнет вызывать ToString() на этом null - а его у null'а не существует.

Вот такой код выполните в фиддлере или где угодно

		Console.WriteLine("Null string");
		string s = null;
		Console.WriteLine(s);
		
		Console.WriteLine("Nullable int without value");
		int? d = null;
		Console.WriteLine(d);
		
		Console.WriteLine("New line");

Еще раз повторюсь: это плохой подход и так в проде делать не надо. Но есть задачи, где именно такого сырого кода с кривыми данными вполне достаточно.

И каков примерный процент таких задач в общем множестве? Не просто так типизацию в пхп тянут.

Это у каких?

Вы предлагаете мне перебрать всю документацию, чтобы их найти? Гугл, очевидно, ищет только тех, у кого этот метод есть)

В любом случае, это же может быть ваш собственный класс, у него может и не быть такого метода. И ладно если он ваш, а если этот класс из какой-то чужой библиотеки пришел через динамическую линковку dll?

Ну т.е. словить такую ошибку вполне реально. Лично я - ловил.

Вы код посмотрите, там foreach по колонкам идет, какие вообще границы массива?

  • Вы уверены, что thisTable.Rows - не null?

  • Вы уверены, что foreach(DataColumn column in thisTable.Columns) не вызывает свою версию IEnumerable и не упадет на этом?

  • Вы уверены, что row[column] не вызывает перегруженную версию [], которая не умрет по независящим от вас причинам или не вернет незаполненный кастомный объект, отчего неявный вызов ToString() внутри WriteLine не ляжет с exception?

В этом простеньком коде подводных камней на целый шалаш хватит)

Вот такой код выполните в фиддлере или где угодно

Ну ок, null оно обработает. Еще раз повторю - в общем случае нет гарантий, что row[column] не выкинет exception. А вот на php это гарантированно не выкинет ошибку. Иногда - это полезно, иногда - очень вредит.

И каков примерный процент таких задач в общем множестве? Не просто так типизацию в пхп тянут.

Ну я уже упоминал - очень сильно помогает при парсинге текстовых данных в разных не очень четко соблюдаемых форматах. Т.е. не чёткий xml или json, а какой-нибудь html где несколько тегов забыли закрыть, есть куча лишних кавычек, а внутри нужного куска текста лежит пара пустых . Или посреди чисел стоит невидимый неразрывный пробел - причем не во всех. И вообще не всегда пробел.

Ну т.е. у php это такая кривая фича для кривых данных в кривом окружении. Очень не всем такое нужно.

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

Вы предлагаете мне перебрать всю документацию, чтобы их найти? 

Это что-то типа летающего единорога, если бы такое было, это бы запомнилось.

В любом случае, это же может быть ваш собственный класс, у него может и не быть такого метода.

В шарпах любой класс является наследником object, поэтому в нем не может не быть ToString.

Вы уверены, что row[column] не вызывает перегруженную версию []

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

Просто на php именно такое делается проще и быстрее.

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

Это что-то типа летающего единорога, если бы такое было, это бы запомнилось.

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

Остальное поскипал, но так можно написать про любой метод вообще

Можно. Тут просто идеология. Php работает чтобы умереть. Т.е., в общем случае, чтобы там страшного в данных/коде не произошло - процесс умрет, запустится новый, который именно эту ошибку скорее всего просто не словит и отработает корректно.

Для классических приложений (именно там чаще всего и используют языки со строгой типизацией) такое невозможно же. Классические приложения требуют безошибочной работы любое длительное время без сбоев (т.е. как минимум с корректной обработкой ошибок) - то, чего не нужно коду на php вообще, просто по идеологии мест использования.

Как и работы с потоками и всего остального. Ну т.е. потоки тоже поддерживаются в php, но мало кто это использует. А остальные - извращенцы, забивающие гвозди стамеской. Им не запрещают это делать, но выглядят они всё равно странно)

Я парсил html для своих личных нужд, никаких специфических проблем там нет.

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

Еще раз повторюсь: я не рекомендую писать код в стиле, который я писал вам выше. Это - плохо. Делать так можно только если точно знаешь последствия. И если этот код потом не придется поддерживать/дорабатывать какое-то длительное время.

В остальных случаях всё же стоит обмазываться проверками данных со всех сторон. И не складывать число со строкой. Т.е. особой разницы в коде на php с кодом на c# уже не будет.

Поковырял я этот ваш PHP немного и вот пара примеров, как раз echo, версия PHP 7.4.33

<?php

class User
{
    private $helloString = 'Hi! I am';

    public $age;
    
    public $name;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

$foo = new User('Oleg', 32);
echo $foo;

Что-то как-то не работает "Fatal error: Uncaught Error: Object of class User could not be converted to string". Добавим магический метод "__toString()", даже название совпадает с шарпом

<?php

class User
{
    private $helloString = 'Hi! I am';

    public $age;
    
    public $name;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }

    public function __toString()
    {
        //return $this->helloString . ' ' . $this->name;
        return 132;
    }
}

$foo = new User('Oleg', 32);
echo $foo;

Опять не работает, почему-то не может переконвертить число в строку "Fatal error: Uncaught Error: Method User::__toString() must return a string value"

И только вот это заработало

<?php

class User
{
    private $helloString = 'Hi! I am';

    public $age;
    
    public $name;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }

    public function __toString()
    {
        return $this->helloString . ' ' . $this->name;
        //return 132;
    }
}

$foo = new User('Oleg', 32);
echo $foo;

А вот на php это гарантированно не выкинет ошибку

Что-то с гарантиями не очень. Банальное echo кастомного типа вызывает ошибку.

У вас еще есть варианты с print_r и var_dump. Можно попробовать приплюсовать объект к строке через точку. Можно попробовать явно вызвать приведение типов (string)$foo и уже потом echo.

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

Что-то с гарантиями не очень. Банальное echo кастомного типа вызывает ошибку.

Ой, ну умрет процесс с ошибкой и? Процесс перезапустится, скорее всего второй раз так звезды не сложатся и echo на объекте не будет пытаться вызваться. А если ошибка стабильно возникает, то будет это всё вам в логи сервера спамить. Вы это увидите и пофиксите кривое место. Никто ж вам панацеи от перехода на php и не обещал)

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

У вас еще есть варианты с print_r и var_dump. Можно попробовать приплюсовать объект к строке через точку. Можно попробовать явно вызвать приведение типов (string)$foo и уже потом echo.

Во-во. Эта штука еще и от версии php зависит, просто шикарно - работало так, стало работать сяк, а потом вообще перестало работать.

Result for 5.5.38, 5.4.45:
Catchable fatal error: Object of class User could not be converted to string in /home/user/scripts/code.php on line 19

Result for 5.3.29, 5.2.17:

Result for 5.0.5
Object id #1

Ну т.е. я ж не говорил, что php идеальный язык.

"на php в худшем случае вы получите пустоту или надпись null где-то, что особо ничего не сломает. "

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

Это какой-то PHP way? Ну типа упал процесс, да и черт с ним, поди заработает потом. А если не заработает? Не получится что кому-то заказали 100 билетов на самолет?

Никто ж вам панацеи от перехода на php и не обещал

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

Это какой-то PHP way? Ну типа упал процесс, да и черт с ним, поди заработает потом.

Ага. Традиционный php-way именно таков. Помер процесс - и фиг с ним. Большинство сайтов работает именно по такому принципу, отдельные перфекционисты погоды не делают

А если не заработает?

Сайт ляжет же. Найдут другого программиста и тот в мыле будет говнокод на php разгребать. Тут-то логи ошибок сервера он и посмотрит!

Не получится что кому-то заказали 100 билетов на самолет?

Вряд ли. Это же не десктопное приложение, где сбойнувший процесс в сбойном состоянии продолжает работать и писать сбойные данные куда не надо. Сбойнувший php-процесс все равно же быстро завершится, шансы, что он успеет кривыми данными серьезно навредить - минимальны. А глобально вредить так, чтобы прям это делали вообще все php-процессы сервера одновременно не позволяет тестирование (ну, вы же сначала тестируете код и только потом на прод, ведь так?).

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

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

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

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

Просто смысл переводить php на статическую типизацию? Кто от этого выиграет? Старые php-шники вас не поймут. Новые - просто выберут свежий и популярный язык программирования. Т.е. разработчики php сегодня просто стреляют себе в ногу.

Так вы

ну, вы же сначала тестируете код и только потом на прод, ведь так?

Или

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

?

Ну так одно другому не мешает. То, что код прошел тесты совершенно не мешает ему упасть на проде из-за уникальных входных данных, которые почему-то никто не подумал проверить на тесте.

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

Это вообще как?

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

Ну т.е. это совершенно другая идеология работы.

Погодите-погодите! Вы утверждаете, что я, допустим, заколбасил на сервер корявый запрос, из-за которого php крякнул и умер (например, из-за косяка приведения)... А потом я повторил тот же самый запрос, и php в этот раз не умер, и вернул нормальный результат? о_О

У вас там правила приведения генератором случайных чисел генерятся при каждом запуске, что ли?

Если у вас код на сервере падает от запроса стабильно - то вы его недостаточно протестировали и причем тут вообще php?) Эта ситуация достаточно редкая, она приводит к тому, что все допустившие такой код в мыле срочно его фиксят.

В большинстве же случаев падения - случайны, потому что причины, которые к ним приводят не-повторяемы. Условно, при первом клике у нас отвалился коннект к базе, а пока мы ждали и кликали второй раз база уже успела перезапуститься и всё отработало нормально. Вот мы и получаем "А потом я повторил тот же самый запрос, и php в этот раз не умер, и вернул нормальный результат?". М-магия!

У вас там правила приведения генератором случайных чисел генерятся при каждом запуске, что ли?

У нас там MySQL который просто иногда умирает. Причем не на всех проектах и не часто - один раз в год, например. И без логов из-за чего он умер.

Если у вас код на сервере падает от запроса стабильно - то вы его недостаточно протестировали и причем тут вообще php?)

Не понял, какое тестирование, если я "тяп-ляп и в продакшен"? Вы уж определитесь!

И ваще, мой код на сервере не от любого запроса падает, а только от тех, где я невалидный запрос передаю. Ему бы, конечно, вернуть мне что-то вроде "у вас тут в запросе говно", а он просто падает...

Вот допустим я принимаю заказ. В запросе должна прийти жсонка с полями user_id, product_ud и product_quantity (последнее поле - количество - целое положительное число в штуках). Что я делаю в Go?

type OrderRequest struct {
  UserID string `json:"user_id"`
  ProductID string `json:"product_id"`
  ProductQuantity int `json:"product_quantity"`
}

Какие жуткие проверки я выполняю?

var orderRequest OrderRequest
if err := json.NewDecoder(request.Body).Decode(&orderRequest); err != nil {
  // тут пишем в респонс ошибку как есть
  // отвечаем 400 - ваш запрос говно
}
// все, дальше проверки тривиальны
if orderRequest.ProductQuantity <= 0 { // это гарантированно не упадет, потому что здесь стопудово число
  // говорим вызывающему, что заказывать надо больше 0 штук
  // отвечаем тем же 400-м
}

Все, мне не надо "проверять, что поле заполнено, что оно правильного типа". И даже что оно не дробное - тоже проверять не надо, оно стопудово целое.

Если я суну в поле значение `"10 коробок по 100 штук"`, я не запишу в базу заказ на 10 карандашей, я просто верну ошибку "error can not parse string to int" или что-то вроде. Если напишу 4.5 в запросе, заказ ни на 4 карандаша, ни на 4-с-половиной так же не пройдет. Вся прелесть именно в том, что проверки на соответствие типу за меня уже сделал язык.

Цепочка проверок "если поле существует, и в нем лежит число, и это число целое" - это не в ту сторону камень, это к динамическим языкам претензия.

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

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

Если я суну в поле значение "10 коробок по 100 штук", я не запишу в базу заказ на 10 карандашей, я просто верну ошибку "error can not parse string to int" или что-то вроде.

Каким макаром в вашем json'е вообще оказалось "10 коробок по 100 штук"? Что за бред? У вас изначально кривой дизайн системы (как минимум на фронте), которая такую фигню допустила - и теперь вы это исправляете на бэке костылем с ошибкой. Я уже не говорю о том, что "error can not parse string to int" крайне информативен и очень полезен покупателям, да.

Цепочка проверок "если поле существует, и в нем лежит число, и это число целое" - это не в ту сторону камень, это к динамическим языкам претензия.

Ваш код кривой просто потому что завтра внезапно окажется, что у нас маркетинг запустил продажу премиальных карандашей и их таки, внезапно, 10,5 штук в упаковке! И им надо теперь продавать дробное количество - а у вас код к такому вообще ни в каком виде не готов. И все места, где упоминался int теперь надо не просто поменять на другой тип данных - а и проверить все взаимодействия, вдруг там где ещё что-то типизированное.

На php в лучшем случае вы просто intval уберете на входе. Если оно там вообще было: ибо количество можно и по-другому сравнивать с тем, что есть в базе и не заниматься двойной работой - у нас тут cms/фреймворк есть, там уже позаботились.

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

Ну да, проще. И? Теперь давайте запретим нетипизированные языки и будем сжигать их адептов на кострах? Я не понимаю, в чем претензии-то?

Каким макаром в вашем json'е вообще оказалось "10 коробок по 100 штук"? Что за бред? У вас изначально кривой дизайн системы (как минимум на фронте), которая такую фигню допустила - и теперь вы это исправляете на бэке костылем с ошибкой.

Бро, я пишу API, я бекендер. АПИ это может быть публичным, может использоваться другими системами, другими командами, короче, его дергают другие люди, не я. Swagger/OpenAPI - спеку я им отдал, конечно, но запретить им быть идиотами или косячить я не могу.

Вот я сказал, какую мне надо вот json-ку в запросе, даже пример приложил:

{
  "client_id": "user_128",
  "product_id": "karandash_tm_11",
  "qty": 10,
}

Кто запретит им прислать мне вот такое?

{
  "client_id": "user_128",
  "product_id": "karandash_tm_11",
  "qty": "10 коробок по 15 штук",
}

Это все еще валидный JSON. На Go я просто получу ошибку парсинга и со спокойной совестью верну 400BadRequest, PHP молча приведет "10 коробок по 15 штук" к числу 10, домножит и примет заказ (что, емнип, именно вы и выдавали за достоинство языка)... Ну и зачем, что хорошего то?

Я уже не говорю о том, что "error can not parse string to int" крайне информативен и очень полезен покупателям, да.

201Accepted "Ваш заказ принят", видимо, гораздо более информативно). К слову, там еще имя поля будет, на котором парсер поперхнулся в Goшной ошибке.

Ваш код кривой просто потому что завтра внезапно окажется, что у нас маркетинг запустил продажу премиальных карандашей и их таки, внезапно, 10,5 штук в упаковке! И им надо теперь продавать дробное количество - а у вас код к такому вообще ни в каком виде не готов.

Дык, он и не должен быть готов! С чего это софт, в бизнес-требованиях которого "продавать товар штуками" числится, должен быть готов к дробному количеству? о_О

На php в лучшем случае вы просто intval уберете на входе. Если оно там вообще было: ибо количество можно и по-другому сравнивать с тем, что есть в базе и не заниматься двойной работой 

Да-да, совершенно точно так и будет... И с клиента не попросят заплатить 416.5689102393 рубля за заказ, и округления боком не вылезут, и специфика флоатов нигде нам сумму заказа не сдвинет на 27 знаков вправо... Да-да, ничего делать не надо, все заработает само, вы ко всему готовы...

Ну да, проще. И? Теперь давайте запретим нетипизированные языки и будем сжигать их адептов на кострах? Я не понимаю, в чем претензии-то?

Где ж я предлагал кого-то на кострах сжигать? Я наоборот, понять пытаюсь, в чем выгода, какие плюшки у "соседнего лагеря"

На Go я просто получу ошибку парсинга и со спокойной совестью верну 400BadRequest, PHP молча приведет "10 коробок по 15 штук" к числу 10, домножит и примет заказ (что, емнип, именно вы и выдавали за достоинство языка)... Ну и зачем, что хорошего то?

Так кто вам мешает на php проверить, что там строка, а не чистый int и всё так же вернуть ошибку? Я вообще не вижу тут никакой проблемы. Более того, что на go, что на любом другом языке вы всё равно будете проверять эти входные данные на корректность - ну, если у вас нормальный код, а не тяп-ляп и в продакшн.

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

Т.е. я не понимаю, с чем вы спорите. Что молча приводить "10 коробок" к 10 неправильно? Ну да. Поэтому даже на php любой вменяемый программист сделает проверку, что там точно int, а не что-то иное. Что у вас за дурацкая претензия к php?

201Accepted "Ваш заказ принят", видимо, гораздо более информативно). К слову, там еще имя поля будет, на котором парсер поперхнулся в Goшной ошибке.

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

Дык, он и не должен быть готов! С чего это софт, в бизнес-требованиях которого "продавать товар штуками" числится, должен быть готов к дробному количеству? о_О

Потому что требования меняются с течением времени. Причем, такое дело, внезапно! И нужно всё поменять - причем срочно-срочно и ещё вчера. Практика...

Да-да, совершенно точно так и будет... И с клиента не попросят заплатить 416.5689102393 рубля за заказ, и округления боком не вылезут, и специфика флоатов нигде нам сумму заказа не сдвинет на 27 знаков вправо...

Не попросят. Чтобы такое вылезло - мы или пишем код с нуля сами и виноваты сами, либо берем cms, где уже есть нормальная работа с ценами, которая корректно их округляет.

Да блин, я вообще не представляю, что у вас должен быть за говнокод, чтобы в публичку 416.5689102393 рубля вылезло! Потому что есть скидки, наценки - и они, как правило, в процентах считают. Т.е. цены у вас всё равно хоть где-то, но выйдут дробными и вам так и так их надо округлять хотя бы до 2 знаков после запятой. Т.е. соответствующий код у вас полюбому есть - с какой стати он в оформлении заказа на дробное кол-во товара не будет выполнен, что за бред?

Где ж я предлагал кого-то на кострах сжигать? Я наоборот, понять пытаюсь, в чем выгода, какие плюшки у "соседнего лагеря"

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

Опять не работает, почему-то не может переконвертить число в строку "Fatal error: Uncaught Error: Method User::__toString() must return a string value"

И что не так? Вы же за строгую типизацию? Соверешенно очевидно, что магический __toString должен возвращать именно string, а не int или что-то ещё

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

Но это еще не все. Начиная с 8 версии даже return 132 работает. Так что "совершенно очевидно" не так уж и очевидно.

Вы предлагаете мне перебрать всю документацию, чтобы их найти? Гугл, очевидно, ищет только тех, у кого этот метод есть)

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

В любом случае, это же может быть ваш собственный класс, у него может и не быть такого метода.

Если у моего собственного класса нет такого метода, то он унаследует его от базового Object. Потому что по-другому никак, язык так устроен.

И ладно если он ваш, а если этот класс из какой-то чужой библиотеки пришел через динамическую линковку dll?

Проблема в том, что C# одинаковый что для прикладных разработчиков, что для разработчиков библиотек. Т.е. библиотечный класс, внезапно, тоже стопроцентно наследник базового object - таков путь.

Самое забавное в этом то, что вы противопоставляете логику C# логике PHP, совершенно не задумываясь о том, что под капотом в этом аспекте они работают абсолютно одинаково - php ровно те же мутки с to_string использует.

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

А можно реальный пример такой задачи?

https://habr.com/ru/articles/830354/comments/#comment_27072596
https://habr.com/ru/articles/830354/comments/#comment_27072746

Ну я уже упоминал - очень сильно помогает при парсинге текстовых данных в разных не очень четко соблюдаемых форматах. Т.е. не чёткий xml или json, а какой-нибудь html где несколько тегов забыли закрыть, есть куча лишних кавычек, а внутри нужного куска текста лежит пара пустых . Или посреди чисел стоит невидимый неразрывный пробел - причем не во всех. И вообще не всегда пробел.

Повторюсь - это всё можно сделать на любом языке программирования. Просто на php код получится короче.

Так вы парсите "какой-нибудь html где несколько тегов забыли закрыть" чтобы что? Наерное, чтобы данные извлечь и работать с ними дальше. Как тут поможет динамическая типизация? Мы либо получим нужные данные, либо нет. Как обработка невидимого неразрывного пробела, который по-факту, такой же символ, как и пробел, различается в пхп и условном c# и как статическая типизация шарпа вообще на это влияет? Я правда не понимаю

Как обработка невидимого неразрывного пробела, который по-факту, такой же символ, как и пробел, различается в пхп и условном c# и как статическая типизация шарпа вообще на это влияет?

Банальный вызов toint() в пхп на таком числе с лишними символами вернет вам число, а на c# - упадет с exception, что там неправильные символы.

Взять строку, вызвать на ней explode() по разделителю(ям) и потом обратиться к нужному элементу массива - легко. При этом процесс не сломается, если в массиве внезапно окажется меньше элементов, чем вы ожидали. Это всё равно потребует от вас проверить, что вы за значение из массива дёрнули перед тем как его использовать - но вам не нужно будет еще и проверять сам массив чтобы не выйти за его границы (вы конечно же можете это сделать, но вас не заставляют), как это придется делать в c#, например.

Можно написать для строки "ывораыв222dfgk" код вида:

if(intval($string)>0) {вызываем спец код для нужной вам обработки именно таких данных}

Распарсить какое-то значение, сунуть его в условный DateTime. Потом оказывается, что вместо даты там какая-то фигня - c# вам сложит лапки, а php - нет (я не проверял конкретно, пример из головы). И вам можно будет проверить только результат, а не проверять еще и сами входные данные в эту функцию на корректность.

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

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

Банальный вызов toint() в пхп на таком числе с лишними символами вернет вам число, а на c# - упадет с exception, что там неправильные символы.

Распарсить какое-то значение, сунуть его в условный DateTime. Потом оказывается, что вместо даты там какая-то фигня - c# вам сложит лапки

Тут дело не в языке, а в поведении конкретных функций. В C# есть int.TryParse и DateTime.TryParse с тучей перегрузок, которые исключения не бросают.

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

"Hello, World!"
    .Split(" ")
    .ElementAtOrDefault(42); // returns default(string) -> null

В C# есть int.TryParse и DateTime.TryParse с тучей перегрузок, которые исключения не бросают.

Да, но в итоге вы получаете дополнительный код, чтобы это всё прописать. А в php вы просто дергаете функцию - и в случае ошибочных данных она вам просто вернет 0. И вы можете в коде просто проверять на этот ноль и не думать, что там было в исходных данных. Т.е. просто код на php будет в одну строчку, а не в две, как в c#.

Понятно, что если значение 0 может быть в исходных данных - то такой грязный код уже нельзя писать и придется делать так же как в c#. Иначе вы перепутаете ошибку преобразования с данными и это может быть плохо.

.ElementAtOrDefault(42);

Серьезно? Использовать Linq для простейшей проверки доступа к элементу массива?? А чего не sql-запрос сразу?)

итоге вы получаете дополнительный код

Никакого дополнительного кода. Если дичь в результате вас устраивает, то ничего проверять не нужно, как в PHP.

код на php будет в одну строчку, а не в две

Экономия невероятных масштабов!)

Использовать Linq для простейшей проверки доступа к элементу массива?

А что вас смущает? Там тот же доступ по индексу под капотом.

Никакого дополнительного кода. Если дичь в результате вас устраивает, то ничего проверять не нужно, как в PHP.

Да где же нет дополнительного кода, если вы, фактически, вместо [] начинаете использовать спец.функции, которые внутри себя всю эту защиту и содержат?

Ну ок, в c# они есть - там вообще очень классная библиотека функций. A в c++ TryParse выглядит вон так https://stackoverflow.com/questions/8631327/is-there-a-tryparse-equivalent-in-c-gcc Да и проверка что элемент массива существует тоже не всегда тривиальна https://stackoverflow.com/questions/256807/check-if-array-index-exists или https://stackoverflow.com/questions/19215027/check-if-element-found-in-array-c

Т.е., повторюсь, именно МС вам дала удобные функции. Другие языки со строгой типизацией вам такие функции не предоставляют - и вы вынуждены писать портянку защитного кода самостоятельно.

На php же вы вольны эту портянку кода не писать вообще. Или написать. На выбор. Вам решать.

А что вас смущает?

Производительность? Дурной тон? Или возьмите c++ вместо c#, там-то linq нет - как там решать проблему будете?

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

Как можно сравнивать базовый [] оператор в php с расширенной библиотекой linq? Это же неправильно...

вместо [] начинаете использовать спец.функции

PHP - это не C, за границами буфера не пошариться, там такие же проверки есть внутри оператора скобок.

МС вам дала удобные функции. Другие языки со строгой типизацией вам такие функции не предоставляют

Ну дала и дала. Разве на других языках запрещено писать удобные функции и подключать библиотеки?

Производительность?

Что с ней не так? Вы видите там выделение динамической памяти или алгоритмическую сложность, отличную от О(1)?

Дурной тон?

Это: "Слышал звон, не знаю, где он". Linq2Objects повсеместно используется в шарпах.

Некоторый потенциал оптимизации у него есть зачастую, но не такой разительный, чтобы сходу обмазываться императивными циклами в I/O-bound приложениях.

ЗЫ Нужно понимать, что я показываю, что на C# можно писать такой же говнокод, как на PHP с не меньшим удобством)

PHP - это не C, за границами буфера не пошариться, там такие же проверки есть внутри оператора скобок.

Вы сильно ошибаетесь:

$x=15; var_dump($x[5]);
Warning: Trying to access array offset on int in D:\x\x.php on line 4
NULL

Ну дала и дала. Разве на других языках запрещено писать удобные функции и подключать библиотеки?

Ну так вы сравниваете теплое с мягким. Мы разве про удобство библиотек говорили? Какое отношение linq имеет к базовым массивам в языке?

Что с ней не так? Вы видите там выделение динамической памяти или алгоритмическую сложность, отличную от О(1)?

Привычка недоверять. Видишь linq - там потенциально могут быть проблемы производительности

Это: "Слышал звон, не знаю, где он". Linq2Objects повсеместно используется в шарпах.

Не люблю их. Вот просто - не нравится. Личное мнение, не навязываю.

ЗЫ Нужно понимать, что я показываю, что на C# можно писать такой же говнокод, как на PHP с не меньшим удобством)

Да нет. Вы вместо языка демонстрируете преимущество конкретной библиотеки, которой нет в php)

Вы сильно ошибаетесь

И как, прочли там что-то или записали на самом деле или просто null выплюнуло?) Появление предупреждения, кстати, подтверждает наличие проверки.

Какое отношение linq имеет к базовым массивам в языке?

Вы вместо языка демонстрируете преимущество конкретной библиотеки, которой нет в php

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

или просто null выплюнуло?)
Ну так вон же, NULL и выдало. На этом можно if завязать и пойти дальше. Иногда просто не важно, что в переменной вместо массива с нужным кол-вом элементов оказалось просто число, это не влияет на алгоритм - он в обоих случаях не должен с этим массивом работать. Т.е., грубо говоря, на php я могу сделать один if, а на c# мне понадобится два.

Появление предупреждения, кстати, подтверждает наличие проверки.

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

Окститесь, explode - это такая же часть стандартной библиотеки.

Мне не нужно делать доп. телодвижения, что explode включить. Оно есть сразу. Linq надо подключать через using. Ровно так же, как и Boost в с++: несмотря на то, что им все много лет пользуются, это всё-таки именно внешняя библиотека, а не часть языка.

Ну т.е. я всё же считаю, что это - разное.

Ну так вон же, NULL и выдало.

Вас не смущает, что это идеальный null, а не какие-то "мусорные" значения? Это рантайм выдал, чтения на самом деле не было.

Его можно отключить.

Если его вам не покажут, это не значит, что проверки не будет)

Мне не нужно делать доп. телодвижения, что explode включить.
Linq надо подключать через using.

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

Вас не смущает, что это идеальный null, а не какие-то "мусорные" значения? Это рантайм выдал, чтения на самом деле не было.

А что плохо-то? Дальше if(NULL) и побежали. И вам в принципе без разницы что за исходные данные там были: строка, число, массив. Результат или NULL или нет, его вы и проверяете.

Если его вам не покажут, это не значит, что проверки не будет)

Ну так скрипт-то же НЕ останавливают. На c# у вас в рантайме exception вылетит и скрипт ляжет до запланированного завершения по коду.

Грубо говоря, код вида:
a=explode(delim, data);
   if(a[5]=="...") ...;
на php выполнится всегда (правда, какие будут результаты - зависит от входных данных).

А на c# он с большой вероятностью упадет в рантайме, потому что вы проверки данных не делаете.

C# появился

Php - изначально грязный скриптовый язык для реализации работы с кривыми данными в кривом вёбе. Это его преимущество и недостаток. А C# задуман и реализован для гарантии того, что вы точно не будете стрелять себе в ногу.

Ну нельзя заменить одно другим. Это как картошку на майбахе возить. Т.е. вам не запрещают, но зачем так делать?

А что плохо-то?

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

так скрипт-то же НЕ останавливают.

Он свалится, но позже, когда ошибка уплывет куда-то дальше и поломает там данные)

Php - изначально грязный скриптовый язык для реализации работы с кривыми данными в кривом вёбе.

Да, так и есть. Это его ко дну и тянет.

Он свалится, но позже, когда ошибка уплывет куда-то дальше и поломает там данные)

ну дык! Поэтому в релиз такой код пускать и нельзя. А когда вы обмажете его всяческими проверками на всё подряд - код мало чем будет отличаться от такого же на любом другом языке программирования.

Да, так и есть. Это его ко дну и тянет.

Но если его переделать в "новый c++", то он с этого дна точно не всплывёт)

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

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

если его переделать в "новый c++", то он с этого дна точно не всплывёт

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

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

Там просто другие виды проверок. Разные задачи же, поэтому и упор на разное. Я не считаю, что это плохо. Просто ну надо же понимать ограничения и учитывать их.

Накачивать новыми фичами?

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

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

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

Поэтому, возможно, от таких изменений в языке и была бы польза, но т.к. в реальной жизни использовать это всё достаточно сложно (из-за примитивности инструментария), то мы получаем какое-то бессмысленное переусложнение на ровном месте. Причем его даже особо никто и не просил: старики уже привыкли к старым особенностям и им неудобно, а молодые не имеют достаточного опыта и не готовы с такими фичами кодить - т.е. им всё это еще не нужно.

Linq надо подключать через using.

using никак не связано с подключением библиотек, using всего-навсего подключает пространство имён.

Если я не путаю, то в старых версиях дотнета одного using вроде бы было недостаточно же? Вам надо было еще и dll прилинковать компилятором.

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

Ну т.е. я еще раз говорю: linq не часть языка. Это библиотека. Да, она стандартная и от разработчика языка - но это не базовые функции языка.

Как функции из библиотеки можно сравнивать с базовым оператором [] в языке? Если так делать, то тогда c# вообще лучший язык в мире - просто потому что МС накодило уже столько разных библиотек на все случаи жизни, что другим языкам даже соревноваться бессмысленно по кол-ву доступных функций, они гарантированно проиграют.

Если я не путаю, то в старых версиях дотнета одного using вроде бы было недостаточно же? Вам надо было еще и dll прилинковать компилятором.

Вот именно! Если вы знаете об этом - почему утверждаете, что необходимость using как-то связана с библиотеками?

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

"Сейчас в последних версиях" linq находится в System.Private.CoreLib.dll, которая именно что является стандартной библиотекой .NET

Как функции из библиотеки можно сравнивать с базовым оператором [] в языке? Если так делать, то тогда c# вообще лучший язык в мире [...]

Ну да, примерно так это и работает.

проверка что элемент массива существует тоже не всегда тривиальна

Функция size же. Приведенные вами ссылки - это C-проблемы.

Да тут в каждом языке будут свои приколы с массивами. И только php и js (может и еще где-то, сходу не вспомню) наплевать на попытку выхода за границы массива.

Если текст в определенном формате (xml к примеру), то пользуются готовой библиотекой. Если текст в черт знает какой формате, то работают просто со строками - ищут какие-то метки, теги, айдишники, регулярки в конце концов. В чем разница, причем тут типизация? Покажите уже код.

Я ж уже сто раз этот пример привел! Возьмем какую-нить csv - да, я знаю, что есть библиотека, но для примера нам подойдет.

заголовок1;заголовок2;заголовок3;
данные1;данные2;данные3;
... внезапная пустая строка...
данные1;данные2;данные3;

На c# вам обязательно нужно будет проверить что строка не пустая перед вызовом String.Split(). Потом убедиться, что получившийся массив имеет как минимум 2 элемента. И только потом можно взять данные второго элемента массива.

На php - нет. Вы можете проверять только наличие значения в этом втором элементе в получившемся после explode() массиве. И пропустить проверку исходной строки на корректность. Пропустить проверку длины массива.

Причем потом полученный элемент (несуществующий в массиве - т.к. исходная строка пуста) вы всё так же не проверяя можете сунуть, например, в функцию приведение к int-у и закончить код проверкой вида if($a>0) ...;

Т.е., фактически, сделать то, за что c# будет вас больно бить. И он, вообще-то прав. Просто если у вас одноразовый микроскрипт, который потом уйдет в мусорку - то все эти проверки в c# просто лишние.

Вообще, изначальный тэйк о том, что "есть куча ситуаций, где динамические языки лучше" (как минимум, я так понял) свелось к "одноразовый микроскрипт, который потом уйдет в мусорку".

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

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

который решается, на самом деле, просто на любом языке, который ты хорошо знаешь.

Я как бы много раз про это и писал. Никто не мешает вам всё то же самое реализовать на других языках. Просто из-за более жестких проверок эти языки заставят вас писать доп. код для защиты от ошибок в данных, а php - не заставляет. Это и хорошо, и плохо одновременно)

Ну вот раст возьмём, например. Система типов там посильнее чем в шарпах.

let a = "adsad,asdad";
let b = ""; // внезапная пустая строка,

let res_a = a.split(",").nth(1).unwrap_or_default();
let res_b = b.split(",").nth(1).unwrap_or_default();

println!("{:?}, {:?}", res_a, res_b); // -> "asdad", ""

По сравнению с пхп мы, условно, просто добавляем unwrap_or_default или его аналог, чтобы получить какой-то результат на месте невалидных данных. При этом, никаких ошибок не обрабатываем, никаких проверок строки на невалидность. Никто никого не бьёт.

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

А в php мы просто напишем $res_a = eplode(",", $a)[1]; - что явно короче.

и вместо println!("{:?}, {:?}", res_a, res_b); мы напишем echo res_a.res_b; - что опять-таки короче.

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

Php не требует проверять данные. Это позволяет кодить дичь - если именно ВАМ такая дичь нужна. При этом лучше дичь всё-таки не кодить)

Вот щас бы меряться, где код короче. За меня VSCode буквы пишет, ресурс клавиатуры расходуется так же. Если прям хочется однобуквенных штук - сделаю алиас один раз, split на p, nth заменю на h, unwrap_or_default заменю на p . Получится a.p(",").h(1).p(); - на 2 символа короче, чем пхп. Если написать достаточно скриптов, то в один момент я обгоню экономленными буквами затраты на мои алиасы, а потом только в плюс пойду!

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

Да не, могу не писать. Могу отправить эти данные дальше, куда надо, а там разберутся. Прям как... ну везде, в других языках, и в пхп тоже.

Например, я могу результат попробовать в БД записать. Если там None будет (это как null), то в бд будет пустое поле. Если там будет результат, то он и запишется.

Вот щас бы меряться, где код короче.

Так, собственно, почти все мои комменты тут были про то, что код на пхп можно сократить, убрав часть защиты)

Могу отправить эти данные дальше, куда надо, а там разберутся.

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

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

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

 закончить код проверкой вида if($a>0) ...;

Ваша проверка выкинет валидные строки в которых значение будет равно нулю ("oleg;0;bar").

А еще ваша проверка основывается на неявной информации о том, что вторая колонка должна содержать целое число. И вы все равно это проверяете, и в чем разница с шарпами?

На c# вам обязательно нужно будет проверить что строка не пустая перед вызовом String.Split().

Зачем? Вы явно путаете пустую строку и null, не говоря о том что в вашем примере вторая строка непустая, там перевод строки.

Ваша проверка выкинет валидные строки в которых значение будет равно нулю ("oleg;0;bar").

Да. Я про это в том же самом комменте написал: если у вас есть нули в данных, то такая проверка не будет корректной и надо писать другой код. Но если нулей гарантированной нет - то всё будет работать как надо.

А еще ваша проверка основывается на неявной информации о том, что вторая колонка должна содержать целое число.

Вообще нет. Если там будут только буквы - то тоже intval выдаст 0. Т.е. и для отсутствующего элемента и для не-числа мы гарантированно получаем 0.

При этом intval('111aa'); выдаст нам 111. Хотя, замечу, что intval('aa111'); выдает 0.

Ну т.е. на этом всё так же можно какую-то логику строить - зависит от того, какие вы ожидаете входные данные. Условно, вместо explode() двух колонок 'id;data' можно вызвать intval на строке и получить сразу значения id (; - буква и всё что идет после этого символа intval пропустит).

Зачем? Вы явно путаете пустую строку и null

Я не путаю. Просто в чем смысл вызывать String.Split() на заведомо пустой строке, чтобы потом проверить длину получившегося массива (чтобы дальше не вылететь с exception) и убедиться, что он меньше нужного? Это ж нелогично, я так код не пишу)

не говоря о том что в вашем примере вторая строка непустая, там перевод строки.

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

Я не путаю. Просто в чем смысл вызывать String.Split() на заведомо пустой строке, чтобы потом проверить длину получившегося массива (чтобы дальше не вылететь с exception) и убедиться, что он меньше нужного? Это ж нелогично, я так код не пишу)

Есть принципиальная разница между "вам обязательно нужно будет проверить " и "Просто в чем смысл вызывать String.Split() на заведомо пустой строке ". А что касается смысла, вы же вызываете explode на заведомо пустой строке и потом проверяете (криво) что int это int. И называете это преимуществом php. А когда вам показывают что в шарпе делается абсолютно тоже самое вы говорите - а это же нелогично, я код так не пишу.

чтобы потом проверить длину получившегося массива (чтобы дальше не вылететь с exception)

Вы понимаете что если хочется, то на шарпах поведение "как в пхп" делается за 5 минут кучей способов?

        public static string AsPhpIndex(this string[] array, int index)
        {
            if (array.Length <= index)
                return null;

            return array[index];
        }

Все, проверять выход за границы не надо, будет нулл, как в пхп. Расскажите что один раз написать 6 строчек кода это и сложно и долго и вообще караул.

А что касается смысла, вы же вызываете explode на заведомо пустой строке и потом проверяете (криво) что int это int. И называете это преимуществом php.

Я показывал, что я могу написать заведомо кривой код - и он не упадет по причине кривизны. Который нельзя написать на c#, потому что там-то он упадет. И это код - плохой, не надо так делать, просто иногда в строго ограниченном наборе ситуации удобнее именно как в php. Хотя и не рекомендуется.

В C++ же вы можете стрелять себе в ногу играясь с указателями, почему в php нельзя и надо бить по рукам?)

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

Еще раз, код вида:

$a=explode($delim, $data);
if($a[5]==15) {...}

в c# практически гарантированно упадет с ошибкой в рантайме (за редким исключением при 100% корректных входных данных).

На php он не упадет в рантайме. Он на выходе выдаст не то, что нужно - но выполнится без ошибок.

Естественно, что если писать код по-нормальному, то в обоих языках вы просто проверите $data на критичные вещи. Потом проверите $a на то, что получился массив, на кол-во элементов в нем, на то, что элементы там не null. И даже если вы будете писать на любом ином языке - вы всё равно все эти проверки там пропишете. Потому что они одинаковы для любого языка, ибо лучше лишнюю соломку подстелить лишней проверкой, чем потом словить случайный баг.

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

А в реальной жизни любого php-шника за такой код надо наказывать)

Вы понимаете что если хочется, то на шарпах поведение "как в пхп" делается за 5 минут кучей способов? ... Расскажите что один раз написать 6 строчек кода это и сложно и долго и вообще караул.

Ну, правильно. Напишете. Сами. А я на php это писать не буду - оно тут уже есть.

Это совершенно не значит, что php лучше c#. Он просто, ну, другой.

В C++ же вы можете стрелять себе в ногу играясь с указателями, почему в php нельзя и надо бить по рукам?)

Потому что в C++ это обосновывается возможностью низкоуровневой оптимизации? Да, можно стрелять по ногам, но только если быстро - разумный компромисс. Стрелять по ногам медленно - а зачем?

На php он не упадет в рантайме. Он на выходе выдаст не то, что нужно - но выполнится без ошибок.

Можно нескромный вопрос? А что в этом хорошего? Оно потрет вам данные в БД, сольёт базу пользователей, уничтожит труды многолетнего труда - зато молча, и не отвлекая сообщениями об ошибках? Ни разу не видел задачу, которая формулировалась бы "посчитай мне какие-нибудь данные, пофиг, что неправильно, главное - посчитать". Для чего оно надо-то?

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

Естественно, что если писать код по-нормальному, то в обоих языках вы просто проверите $data на критичные вещи. Потом проверите $a на то, что получился массив, на кол-во элементов в нем, на то, что элементы там не null.

Ну вот неправда же ваша. В Go, например, я не буду проверять, что a - массив, не буду считать кол-во элементов, и даже на null элементы проверять не буду. Я просто знаю, что вернется []string, а он по определению слайс, в нем стопроцентно строки лежат, и ни одного nil быть не может (классная штука - типизированные языки, правда?).

И на C# будет ровно то же самое - String::Split гарантированно вернет массив строк. И по нему даже законно можно будет итерироваться, и мы даже не упадем...

Потому что в C++ это обосновывается возможностью низкоуровневой оптимизации?

Не всегда ради оптимизации. Иногда просто удобнее код алгоритма. Насколько я помню, в нормальном С++ коде издеваться над указателями тоже не рекомендуют. Но ведь можно же! )

Стрелять по ногам медленно - а зачем?

Хочется.

Можно нескромный вопрос? А что в этом хорошего? Оно потрет вам данные в БД, сольёт базу пользователей, уничтожит труды многолетнего труда - зато молча, и не отвлекая сообщениями об ошибках?

Я уже десятки раз написал, что в нормальном коде такое никто и не пишет. А в одноразовых микроскриптах под конкретную задачу - почему нет-то? Если вы знаете, что вы написали заведомо кривой код, но запускаете его в контролируемом окружении и с бэкапами данных - что страшного в том, что ваш кривой код сломает данные? Ну откатите бэкап, подправите в этом месте код скрипта, прогоните его еще раз... Ровно так же вы поступите с кодом на любом языке - просто итоговый код будет немного разным.

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

Так я вроде бы про такие задачи и не писал... Я ж приводил пример: переложить в базу (или другие файлики) числа только из второй колонки csv-файлов. Причем файлов много и в некоторых просто нет второй колонки вообще. Или есть, но там не числа.

На каком-нибудь c# вы будете запускать свой скрипт, он будет падать на очередном уникальном csv-файле, вы будете добавлять очередную проверку данных в код и запускать заново...

Примечание: я надеюсь понятно, что пример утрирован - во всех языках есть готовые библиотеки для парсинга csv и сегодня не нужно такое кодить вручную ) Ну вместо csv можно какой-нибудь собственный формат представить. Или частично-побитые html, которые местами не до конца скачались с чужого сервера или вместо нужных данных содержат заглушку от роботов. Ну т.е. не надо упираться в то, что я упомянул csv, ага?

И на C# будет ровно то же самое - String::Split гарантированно вернет массив строк. И по нему даже законно можно будет итерироваться, и мы даже не упадем...

Ну т.е. вместо обращения к конкретному элементу массива, вам надо писать цикл, так? Я правильно понял комментарий? Т.е. вместо 3 букв - несколько строк кода. Я ж не спорю, что этот подход надежнее...

Не всегда ради оптимизации. Иногда просто удобнее код алгоритма. Насколько я помню, в нормальном С++ коде издеваться над указателями тоже не рекомендуют. Но ведь можно же! )

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

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

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

А в одноразовых микроскриптах под конкретную задачу - почему нет-то?

Одноразовые микроскрипты приятней пишутся на python'е, имхо. Там все прямо как вы любите, и еще вагон плюшек сверху. (А еще через него удобно с нейросетками работать, которые можно научить понимать, что означает "полкоробки этих ваших сникерсов" в поле "КоличествоТовара"). Мечта, а не язык!

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

Откатить БД, обычно, долгое мероприятие, сопряженное с пиздюлями от начальства.

переложить в базу (или другие файлики) числа только из второй колонки csv-файлов. Причем файлов много и в некоторых просто нет второй колонки вообще. Или есть, но там не числа.

Ну это же как раз просто решается. Мы говорим:

type CSVRowIWant struct {
  ID string `csv:"col_1"`
  Number float64 `csv:"col_2"`
} // теги взяты с потолка, конкретика от используемой библиотеки зависит

var data []CSVRowIWant
if err := csv.NewParser(csv.ParsingOpts{SkipInvalidRows: true}).ParseFile("file.csv", &data); err != nil {
  // если не распарсилось - файл говно, в нем, например, второй колонки нет
  // если в строке в колонке не число - она скипнется по директиве SkipInvalidRows
}

Это примерный код, конкретнее - надо конкретные либы искать.

В data будут лежать только те данные, которые вы умеете обрабатывать (с числом во второй колоне), остальное просто скипнется. Чудеса же?

... Ну т.е. не надо упираться в то, что я упомянул csv, ага?

Не вопрос, не упираемся. Просто подходим к задаче с другой стороны. У нас есть данные, которые мы хотим получить. У нас к ним, как правило, есть требование. Например, мы хотим получить 2 строки, одно число и 5 массивов.

Ну мы и описываем то, что (и как) мы хотим получить. Не получилось - ну и хрен с ним, скипаем, идем заниматься полезной работой. Мы же все равно не знаем, что делать, если данных не хватает?

вместо обращения к конкретному элементу массива, вам надо писать цикл, так ?

Нет. `if len(array) >= 3 && array[2] != "" {`. Ну, на 10-15 букв больше...

Издеваться над указателями - это очень неудобная и нечитаемая штука.

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

Поверьте, кроме как ради оптимизации, этим могут заниматься только отмороженные мазохисты.

Или любители экспериментов. Для домашнего использования. Вообще не планируя это где-то серьезно применять.

При этом чуть выше не вы ли писали, что если оно упало, то значит, что рукожопые программисты это плохо оттестировали?

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

Одноразовые микроскрипты приятней пишутся на python'е, имхо.

Мне не понравился. Я использую или c#, или php. Просто мне они более удобны.

Откатить БД, обычно, долгое мероприятие, сопряженное с пиздюлями от начальства.

Еще раз прочитайте абзац, на который вы отвечаете...

В data будут лежать только те данные, которые вы умеете обрабатывать (с числом во второй колоне), остальное просто скипнется. Чудеса же?

...обсуждаем пример как руками распарсить csv чтобы показать преимущества языка в отдельных случаях ... приходит qrKot, достает специализированную библиотеку и начинает рассказывать как она круто парсит csv. Л-логика!

Мы же все равно не знаем, что делать, если данных не хватает?

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

Или любители экспериментов. Для домашнего использования.

Т.е. мы PHP в языки для домашнего использования записываем?)

Если у вас упал код на php аж с ошибкой исполнения - то программисты таки рукожопые. Без вариантов.

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

приходит qrKot, достает специализированную библиотеку и начинает рассказывать как она круто парсит csv. Л-логика!

Ну, у нас задача - парсить CSV, мы берем библиотеку парсинга CSV и парсим. Что не так? Вы же сами ЗА то, чтобы не писать лишнего кода...

А иногда знаем.

Алгоритм может либо знать, как обработать данные (и тогда это учтенный кейс, happy-path), либо не знать (и тогда входные данные - мусор).

 А иногда данные в полях записаны не в том формате и требуют доп. обработки - но вы про это ещё не в курсе

пишем в лог: "в поле НазваниеПоля говно какое-то (тут пишем говно как есть)". Потом логи просматриваем, что-то игнорим, что-то не игнорим.

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

Дык, ваш же ровно то же самое сделает, он никак не гарантирует от false-negative сценария. При этом в false-positive он еще и в БД заведомым говном насрет, т.е. сделает вдвое хуже.

Т.е. мы PHP в языки для домашнего использования записываем?)

Еще раз повторюсь, что если вы пишете по-нормальному, то код на любом языке будет содержать практически один и тот же набор проверок - и общее их количество будет таким, что одна-две дополнительных из-за особенностей языка погоды вообще не делают (причем на разных языках будет свой набор таких языко-специфичных проверок. Может так оказаться, что там, где в php мы проверям php-специфичное - в go мы точно также проверяем уже go-специфичное и общее кол-во проверок у нас выйдет одинаковое)

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

Вы передергиваете. "Отработать и завершиться" совершенно отличается от "начать работать и умереть с exception"

Ну, у нас задача - парсить CSV, мы берем библиотеку парсинга CSV и парсим. Что не так?

То не так, что я csv взял как примитивный пример. О чем и написал. Поэтому ваше размахивание специализированной библиотекой тут ни к селу, ни к городу вообще.

И в php тоже есть куча средств работы с csv - и они тоже защищены от ошибок.

либо не знать (и тогда входные данные - мусор).

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

пишем в лог: "в поле НазваниеПоля говно какое-то (тут пишем говно как есть)". Потом логи просматриваем, что-то игнорим, что-то не игнорим.

... если подумали о такой ситуации - да, так и сделаем. А если не подумали?

Дык, ваш же ровно то же самое сделает, он никак не гарантирует от false-negative сценария. При этом в false-positive он еще и в БД заведомым говном насрет, т.е. сделает вдвое хуже.

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

Что пхп применяется совершенно для других задач, где высок уровень неопределенности в данных. Или вообще принципиально не важен тип этих данных.

PHP применятеся в веб-бекенде. Нет там таких задач.

Да и в принципе таких задач нет. "Мы хрен знаем, сколько там полей, но нас интересует только 3 из них, с которыми мы знаем что делать" в легкую редуцируется до "у нас совершенно определенно есть 3 поля, если их нет - падаем"

В том же printf в c++ вы обязаны

А в C#, Go, Java и прочих - не обязаны. Откуда вообще в этой ветке С++ взялся? Чтобы продемонстрировать что? Он вообще не в нише.

логично же, что для вывода, например, страницы с информацией из базы данных "как есть" вам вообще пофиг, что там за данные были - число или строка

Собственно, если такая задача стоит, то вы просто берете и выводите "как есть". Тем более, что изначально там именно строка (таков уж путь, SQL-запросы так устроены).

Вы их на php просто выводите одной командой. А на типизированном языке вам надо сначала перегруженные функции преобразования типов накодить или еще какие проверки этих данных сделать (хотя бы на null) перед тем как вывести

Да нет же! Ровно так же берете и выводите. fmt.Printf ровно так же умеет слово nil писать...

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

Вы ассоциативный массив имеете в виду? Дык, вот такая штука вас не устроит?

string value = "";
if !(ourAssociativeArray.TryGetValue("nonExistentKey", out value)) { return; }

Не надо даже с пустой строкой сравнивать! Огонь же!

На Go попишем?

if value, ok := ourAssociativeArray["nonExistentKey"]; ok {
    // здесь логика
}

И тут тоже ничего не пришлось проверять. На каких еще языках попишем?

Ну такие данные: сегодня там в табличке 3 колонки, завтра 2, послезавтра 5. На c# мне придется кодить отдельные приседания, чтобы это учитывать, а на php я одной строкой кода возьму именно нужные мне данные - и пофиг сколько там колонок. (Ну, понятно, что пример несколько утрирован, но всё же)

На C# и Go ровно так же пофиг на то, сколько там колонок. Мы на этапе парсинга заполняем только то, что нам нужно. И вопрос только в том, есть ли в полученном датасете нужные нам колонки, или нет. Если есть - на любом языке насрать, сколько их там всего. Если нету - у вас и пых не справится (с той только разницей, что C# отсутствие необходимых данных может прямо при парсинге выявить, и не доходить до того места, где все упадет с грохотом).

трудоемкость решения задачи на php и на языке со строгой типизацией сильно отличается.

Верно. В PHP делается так: "обращусь к несуществующему элементу массива, сравню его с пустой строкой (хотя там вообще-то должно быть число)". В строго-типизированных языках мы просто знаем, что элемент не существует.

Так я весь тред пишу о том, что

if($a['aaa']=="")

короче, чем

if !(ourAssociativeArray.TryGetValue("nonExistentKey", out value)) { return; }

Ну если вам нужна точность, то и на php никто не мешает вам написать

if(array_key_exists("nonExistentKey", $a)) {return;}

и это всё равно будет короче, чем на c#) Причем в вашем примере еще вторая проблема: вам надо еще где-то в коде заранее проверить, что ourAssociativeArray - не null. Иначе всё упадет при вызове TryGetValue. А коду на php в этой ситуации вообще плевать и он отработает ровно так же как если $a была массивом и там не было бы элемента (или был в виде пустой строки).

что C# отсутствие необходимых данных может прямо при парсинге выявить, и не доходить до того места, где все упадет с грохотом

Неа. Вы не правильно рассуждаете. Именно потому что c# падает с грохотом - вы сами, добровольно, напишете эту раннюю проверку данных при парсинге. У вас выбора нет.

я весь тред пишу о том, что

короче, чем

У вас буквы платные?)

Я вот в ответ пишу, что if($a['aaa']=="") вот это вот вообще не понятно, что тут произошло. Значения по ключу нет, или там просто пустая строка? Это нечитаемо, а удобство чтения важнее удобства написания (даже готов принимать камни в огород гоферов с if err != nil, которое таки вредит читаемости).

 вам надо еще где-то в коде заранее проверить, что ourAssociativeArray - не null

А я его откуда взял? Распарсил откуда-то (не, вряд ли, я исключение словил, именно потому что он null оказался)? Сам создал/заполнил (тогда почему он null)? Ну и, в конце концов, попытка дернуть метод у null в PHP приведет ровно к той же жопе.

А коду на php в этой ситуации вообще плевать и он отработает ровно так же как если $a была массивом и там не было бы элемента (или был в виде пустой строки)

Да я вообще гофер. И в Go if value, ok := ourAssociativeArray["nonExistentKey"]; ok гарантированно не упадет даже с неинициализированной мапой. Просто в ok будет false. А пустой строки там не будет, потому что у нас там числа - это мне и проверять не надо.

В чем проблема? )

 Вы не правильно рассуждаете. Именно потому что c# падает с грохотом - вы сами, добровольно, напишете эту раннюю проверку данных при парсинге

Не, это вы неправильно рассуждаете. Я не буду писать ни проверку при парсинге, ни сам парсинг (тем более на C#). Я просто опишу структуру того, что ожидаю получить. Если то, что пришло, ей не соответствует, оно просто не распарсится. Не упадет, а не распарсится. Я обработаю ситуацию "пришло какое-то говно", даже не проверяя, что за говно пришло (мне это не интересно), и продолжу жить дальше. Все, мне даже проверки никакие писать не придется.

Я вот в ответ пишу, что if($a['aaa']=="") вот это вот вообще не понятно, что тут произошло. Значения по ключу нет, или там просто пустая строка? Это нечитаемо, а удобство чтения важнее удобства написания

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

Причем тут в этих случаях вообще удобство и читаемость? Вы и любителям Brainfuck'a будете рассказывать, что их код нечитаем, да?)

А я его откуда взял? Распарсил откуда-то (не, вряд ли, я исключение словил, именно потому что он null оказался)? Сам создал/заполнил (тогда почему он null)?

Ну дык - о том и речь. У вас исполнение вообще в принципе не дойдет до проверок данных на кривизну, т.к. сработает защита от ошибок парсера еще на предыдущих этапах. И это - хорошо. Но, как я уже писал выше, это связывает вам руки: вы не можете эту раннюю защиту не писать. А на php - можете. И построить можете на этом факте какой-то алгоритм, который будет кривые данные особо кривым путем обрабатывать (аналог: та самая магия с указателями на c++, когда через указатели работают с частью данных переменной, например. Что крайне не рекомендуют делать).

Ну и, в конце концов, попытка дернуть метод у null в PHP приведет ровно к той же жопе.

Что тоже хорошо. Но если вы напишете код в процедурном стиле, то падать будет нечему - методов-то объектов у нас нет )

Вообще, отвлекаясь от темы, я всегда с подозрением смотрю на код вида var=SomeFunc1().SomeFunc2().data; - в любой функции потенциально может вернуться какая-то фигня или null и всё последующее тупо упадёт с exception. Причем иногда вообще не по вашей вине: могла база, например, отвалиться в процессе выполнения функции и код внутри функции это корректно обработал и вернул null, а снаружи всё умерло с exception вида "метод нельзя вызвать на null".

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

В чем проблема? )

Да нет проблемы же. Я вообще не понимаю, что вы мне пытаетесь доказать. Я во всех комментариях пишу, что те примеры кода, что я привел - не для прода. Они просто, ну, прикольные. Что сложить число со строкой - это тоже прикольно.

И я многократно везде предупреждал в серьезных проектах так не делать.

Теперь вы мне тоже пытаетесь объяснить, что такой код плохой и так делать не нужно. Ну так я в курсе - я об этом чуть ли не в каждом комментарии предупреждаю...

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

Что за магия? У вас полюбому есть код парсера, который все эти проверки и сделает. Вы этот парсер либо пишете сами - и тогда эти проверки вы кодите своими руками, либо берете готовую библиотеку - и там это всё тоже уже кто-то накодил. Иначе бы ваш парсер на кривых данных просто падал бы с exception.

Вы как-то упорно вместо языка пытаетесь сунуть в пример какие-то специальные библиотеки и на этом основании доказываете, что язык1 лучше языка2. Не надо так делать)

 У вас исполнение вообще в принципе не дойдет до проверок данных на кривизну, т.к. сработает защита от ошибок парсера еще на предыдущих этапах. И это - хорошо. Но, как я уже писал выше, это связывает вам руки: вы не можете эту раннюю защиту не писать.

Да почему не могу-то, если могу?)

я всегда с подозрением смотрю на код вида var=SomeFunc1().SomeFunc2().data; - в любой функции потенциально может вернуться какая-то фигня или null и всё последующее тупо упадёт с exception. 

Да я вообще гофер, если я такое смог написать, ни одна функция внутри цепочки не может вернуть какую-то фигню)

Что за магия? У вас полюбому есть код парсера, который все эти проверки и сделает. 

Не магия, просто явная типизация.

type DataINeed struct {
  Field1 int `json:"field_1"`
  Field2 string `json:"field_2"`

// поле с ХЗ-какими данными, сам разберусь, ручками
  Field3 interface{} `json:"field_3"`

// поле, в котором должно быть целое число, но могут написать через жопу
// тип кастомный - ниже описан
  Field4 ExpectToBeInt `json:"field_4"`
}

type ExpectToBeInt int
func (i *ExpectToBeInt) UnmarshalJSON(data []byte) error {
  // здесь набор байт анмаршаллим в число
  
  // предположим, мы хотим взять только цифры, остальное посчитать мусором
  number := 0
  str := string(data)
  for _, rune := range str {
    if !unicode.IsNumber(rune) {
      continue
    }
    number = number*10 + int(rune) - 48
  }
  return nil
}

Вуаля, у нас есть штука, в которую мы анмаршаллим, в ней 4 поля. Первые два - стандартно парсятся и валидируются на тип. Третье - парсится, но не валидируется, содержимое - сюрприз.

Четвертое - всегда число. Там может собственно число прийти, может строка, может массив, да по барабану что - мы просто все символы, кроме циферок, выкидываем, а циферки считаем числом.

Можно чот другое написать.

Еще раз, у меня с стандартной библиотеке есть стандартный парсер, который я могу стандартными методами кастомизировать в местах, которые должны себя вести нестандартно. Не полагаться на странную логику "10 бочек сельди по 25 рыбин" == 10, а кастомизировать так, как мне надо.

Да почему не могу-то, если могу?)

Потому что оно, блин, грохнется на следующей строчке кода из-за внезапного null!

Да я вообще гофер, если я такое смог написать, ни одна функция внутри цепочки не может вернуть какую-то фигню)

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

Не магия, просто явная типизация.

не явная типизация, а специализированная библиотека + json-специализированные фичи языка. Которыми вы теперь обосновываете, что проверки вам не нужны - ну ясен пень, что не нужны: за вас разработчики языка всю работу уже сделали.

Знаете как наш диалог выглядит?
А: смотрите, если изолентой прикрутить к велосипедному мотору вентилятор, то можно косить траву! Прикольно!
Б: что вы делаете? Вон же трактор-травокоситель есть! У вас тут даже кондиционера нет, вы как траву косить собрались?!

Потому что оно, блин, грохнется на следующей строчке кода из-за внезапного null!

null не будет внезапным, к нему в пару err придет же.

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

Допустим, у нас есть такое:someFunc1().someFunc2().someFunc3()

Это значит, что каждая из функций возвращает 1 (одно, ровно одно) значение, каждое из которых - некая сущность, у которой есть следующий в цепочке метод.

Все функции в цепочке не возвращают ошибок, проблем нет.

Делаем рефакторинг: someFunc2() начинает возвращать ошибку. Была func (i *Item) someFunc2() *Item, стала func (i *Item) someFunc2() (*Item, err). Все, код не собирается, в прод не пошло.

не явная типизация, а специализированная библиотека + json-специализированные фичи языка. Которыми вы теперь обосновываете, что проверки вам не нужны - ну ясен пень, что не нужны: за вас разработчики языка всю работу уже сделали.

Но ведь хорошо же, что сделали! Плохо, что ли?

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

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

Можно не следовать этому правилу, но вы тогда сам себе злобный буратино)

Ну вот вы сделали рефакторинг, потом еще и прошлись по всем местам где вызовы функций были - чтобы тоже переделать. А если бы изначально не писали паровозик из функций и обернули их результаты в if, то может и не надо было бы это делать.

Ну! В том и смысл же! Я не забыл пройтись по тем местам, где логика изменилась.

Ах да, типизированный язык же: if тоже переписать придется, а то типы значений в сравнении разные будут...

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

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

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

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

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

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

"Паровозик" из функций - это такая точка в коде, где вероятность, что код грохнется, стремится к 100%. Конкретно сегодня код может именно в этом месте и не падать. Но код живет и развивается - и эта вероятность растет. Причем уже через полгода особенность работы алгоритма именно в этом месте вы просто не вспомните. И компилятор вам не подскажет, а редактор все "паровозики" не подсветит.

И завтра, после 1001-ой правки кода вас ждет увлекательный процесс отладки рандомно возникающего исключения в коде, который выглядит как "да тут падать вообще нечему!" причем трейс-лог вас будет слать куда угодно, кроме места с тем паровозиком функций.

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

Дык, от бага в коде вас ничего не спасёт. Я же вам про ситуацию говорю, когда бага нет. Когда происходят валидные изменения контракта.

Был у вас метод какой-то, который локальные вычисления делал. Все было отлично, никаких ошибок он не возвращал, никаких исключений не кидал.

Поменяли логику метода (заметьте, не багу наворотили, а поменяли логику на валидную), он стал по дороге в БД ходить. А БД может быть недоступна, или еще чего. И вот у вас метод возвращает опциональную ошибку или бросает исключение.

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

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

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

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

"Паровозик" из функций - это такая точка в коде, где вероятность, что код грохнется, стремится к 100%.

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

онкретно сегодня код может именно в этом месте и не падать. Но код живет и развивается - и эта вероятность растет.

Изоляция, инкапсуляция... С чего растет-то?

Причем уже через полгода особенность работы алгоритма именно в этом месте вы просто не вспомните.

Зачем мне их вспоминать, если они у меня прямо в контракте метода описаны? func someAlogorithm() error. Вот там есть буквы error - это явное описание такой особенности алгоритма, как умение возвращать ошибку.

В Rust'е, говорят, еще список конкретных ошибок можно посмотреть, какие бывают у метода.

И завтра, после 1001-ой правки кода вас ждет увлекательный процесс отладки рандомно возникающего исключения в коде, который выглядит как "да тут падать вообще нечему!"

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

Я же вам про ситуацию говорю, когда бага нет.

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

Умница-компилятор предупредил уже вас, что контракт изменился, вот в этом конкретном месте "паровозик не смог".

У вас какой-то чудо-компилятор.

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

Ага. Говорит. И вот там, где он не сказал - оно всегда и падает )

Ужасный опыт. На моей памяти методы из стандартной библиотеки Go грохались по тысяче непредусмотренных причин... никогда.

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

Никому нельзя верить. Лучше добавить лишнюю проверку.

Изоляция, инкапсуляция... С чего растет-то?

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

Вот там есть буквы error - это явное описание такой особенности алгоритма, как умение возвращать ошибку.

Ну, возможно у вас там получше в этом смысле. Я всё-таки больше по c# и php/js - и тут надо по-другому.

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

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

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

Дык, может не будем плодить новые без надобности?

У вас какой-то чудо-компилятор.

Обычный компилятор. Не собираться при потере совместимости - обычная фича обычного компилятора.

Ага. Говорит. И вот там, где он не сказал - оно всегда и падает )

Не надо экстраполировать ваш опыт работы с мешаниной php-скриптов поверх каши из различных CRM-ок различной степени паршивости на все остальные языки. Не везде такая жесть, честное слово.

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

И с какой частотой это случается?

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

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

Ну, возможно у вас там получше в этом смысле

Так я про то и говорю, что получше.

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

Мы ВЫНУЖДЕНЫ полагаться на компилятор (точно так же, как и вы - на транслятор). Мы НЕ можем учесть в коде косяки компилятора, поэтому исходим из позиции, что с ним все ок. Если не ок - у нас лапки (так же, как и у вас). Проблема работы корректного кода в некорректной среде исполнения в общем случае неразрешима.

Ок, вы победили - код у вас всегда идеален и логических ошибок в нем никогда не бывает.

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

Решается удалением из языка null-значения и добавлением хорошей системы типов. Ну и конечно, этой системой типов надо пользоваться правильно.

Ну так в итоге мы и возвращаемся к исходному тезису: разные языки хороши разным и надо выбирать под особенности задачи, а не тупо хейтить за эти особенности )

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

Ничего из сказанного про якобы плюсы "пхп без типов" для меня не плюс, и попытки доказать на странных примерах, что в каких-то очень странных и ограниченных ситуациях пхп лучше, не принесли успеха. Если хорошо знаешь язык, все решения +- одинаковы будут по скорости написания (другое дело, что для того же пхп есть фреймворки для быстрой разработки условного интернет-магаза, а для другого языка их может и не быть). Вот только хорошая типизация добавит раннюю проверку на ошибки, а динамическая ничего не даст. Я тоже раньше на питоне писал и радовался, что типы не надо объявлять. Теперь вот попытки написать что-то на питоне вызывают боль, когнитивная нагрузка на тебя возрастает, а взамен ничего полезного.

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

Можно сколько угодно продвигать языки со строгой типизацией, но весь вёб взлетел не на них. И именно из-за особенностей этих языков они в вёбе и не использовались. Да и попытки протащить в вёб строгую типизацию до сих нельзя назвать успешными (особенно при сравнении масштабов использования). Тот же TypeScript до сих пор не получил заметного распространения - а уж сколько лет его пиарили!

Ничего из сказанного про якобы плюсы "пхп без типов" для меня не плюс

Ну а я считаю это плюсом. Все люди разные, у всех разное отношение к прекрасному) Я ж не заставлял вас менять язык...

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

Я когда на код на современном c++ смотрю, мне хочется плакать. Это ж не значит, что надо теперь запретить c++. Кто-то же этот код пишет, им, наверное, тоже прикольно (иначе бы они уже ушли на другие языки).

Можно сколько угодно продвигать языки со строгой типизацией, но весь вёб взлетел не на них. И именно из-за особенностей этих языков они в вёбе и не использовались. Да и попытки протащить в вёб строгую типизацию до сих нельзя назвать успешными (особенно при сравнении масштабов использования).

Фронтенд-то тут каким боком? Отделяйте уже котлеты от мух. PHP - язык бекенда, каким боком фронтенд-кухня к нему относится. Даже там, к слову, движ в сторону явной типизации заметен.

Тот же TypeScript до сих пор не получил заметного распространения - а уж сколько лет его пиарили!

Вот это поворот! А не назовете ли мне хотя бы парочку сколько-нибудь популярных фреймворков для фронта, которые на TS не переползли?

Просто начиная с определенного размера проекта динамическая типизация делает очень болезненным сопровождение/развитие. Причем этот размер настолько невелик (обычно не превышает одностраничного сайта), что даже в PHP стали приматывать скотчем всякие аннотации типов.

Языки без системы типов массово отмирают.

Фронтенд-то тут каким боком? Отделяйте уже котлеты от мух. PHP - язык бекенда, каким боком фронтенд-кухня к нему относится.

Да тем же самым. Php изначально был разработан, когда автор языка задолбался на предыдущих языках кодить https://www.php.net/manual/ru/history.php.php И взлетел php именно потому, что был простым. Как и js (и именно поэтому же все альтернативы js так и не взлетели - слишком сложно)

Даже там, к слову, движ в сторону явной типизации заметен.

Ну заметен, да. Только гляньте сколько на js пишут, а сколько на typescript и аналогах https://ninetailed.io/blog/typescript-vs-javascript/

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

Ага. Надо найти узкую нишу и там сравнивать - так точно получится победить. Куча сайтов без фреймворков вообще. Куча cms с чистым js или вообще на js+jquery (один вордпресс бОльшую часть всех cms занимает!). Typescript - требует квалификации. Правильной среды разработки. Его уже нельзя просто так взять и поправить через ftp в блокноте, если припёрло.

И в итоге побеждает js из-за бОльшей простоты.

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

Ну дык! Даже не собираюсь с этим спорить. Просто не всем нужны большие проекты, понимаете?

Причем этот размер настолько невелик (обычно не превышает одностраничного сайта), что даже в PHP стали приматывать скотчем всякие аннотации типов.

"В PHP творят фигню, которую никто не заказывал" - с этого моего утверждения вся ветка комментариев и началась. И вот мы снова здесь.

Языки без системы типов массово отмирают.

Ну вы, конечно, можете доказать это чем-то, да?

Php изначально был разработан, когда автор языка задолбался на предыдущих языках кодить

И взлетел php именно потому, что был простым

Ну да, так оно и было, я в курсе. Это были лихие 90-е, мы развлекались как могли Было это в 1994 году, из альтернатив были C/C++, Lisp и Perl. Даже Java появилась на 2 года позже.

С тех пор прошло 30 лет, а простоту PHP в веб-разработке до сих пор демонстрируют сравнением с C++. Ну за 30-то лет можно было выделить недельку на обзор того, что происходит у соседей?

Как и js (и именно поэтому же все альтернативы js так и не взлетели - слишком сложно)

Между "js - самый простой язык, работающий в браузере" и "js - единственный язык, работающий в браузере" - пропасть.

Ага. Надо найти узкую нишу и там сравнивать - так точно получится победить.

Веб-фронтенд - узкая ниша? Какая широкая?

Куча сайтов без фреймворков вообще. Куча cms с чистым js или вообще на js+jquery

Куча сайтов без фреймворков - одностраничники? CMS с чистым JS - это какие? Вордпресс/Joomla? Широкий рынок разработки - ага...

js+jquery... Кхм, вот за это вас и не любят (с) кто-то из мудрых

jQuery-то вам зачем? Все полезное, что в нем было, уже в стандарт JS занесли!

(один вордпресс бОльшую часть всех cms занимает!).

Ну, как я и думал, широкий рынок (противопоставляемый узкой нише веб-разработки) - это легаси-хоумпейджи на вордпрессе (и иногда на jooml'е), работающие на мешанине из древних версий php, приправленные кашей из рандомных версий jQuery на фронте? Широкий рынок, ага.

Typescript - требует квалификации.

Золотые слова! А PHP не требует??? Откровения!

Его уже нельзя просто так взять и поправить через ftp в блокноте, если припёрло.

Лучшие практики продуктовой разработки как они есть...

И в итоге побеждает js из-за бОльшей простоты.

Т.е. точно не из-за того, что браузер ничего, кроме js, запускать не умеет?

Просто не всем нужны большие проекты, понимаете?

Большие - это больше одной страницы? Ну да, не всем нужны. Но там и nocode-решения подойдут.

"В PHP творят фигню, которую никто не заказывал" - с этого моего утверждения вся ветка комментариев и началась. 

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

Ну вы, конечно, можете доказать это чем-то, да?

Доля языков с динамической типизацией падает, в Python и PHP завозят аннотации типов... Что еще доказывать надо?

Тот же TypeScript до сих пор не получил заметного распространения - а уж сколько лет его пиарили!

Смешно. Последние 9 лет не попадалось ни одной вакансии без требований тайпскрипта, всё хоть сколько-нибудь большое на нём. Есть исключения, но их очень мало (мне не попадалось).

Да не мало их - у вас своя привычная среда и вы их просто уже не замечаете. А старый веб с js/jquery вполне себе жив. Wordpress жив и всё ещё популярен.

Да, безусловно, это не принесет таких денег и есть ограничения на общий размер проектов. Но на таких проектах сегодня никакой речи о переходе на typescript или типизированный-php вообще не идет - это всё там просто нафик не нужно, не та сложность.

А старый веб с js/jquery вполне себе жив. Wordpress жив и всё ещё популярен.

Один только вопрос: зачем "старому" вебу новый PHP?

Если "старому вебу" все еще норм на jQuery, за который "в приличном обществе гоняли ссаными тряпками" еще лет 6-8 назад, когда я еще был как-то связан с фронтенд-разработкой (фулстечил). Если "старый веб" до сих пор работает на 4-5 версии PHP (т.е. на версиях языка 20-летней давности!), и ему норм...

Зачем слушать, что там хотят WordPress'еры и Jooml'еры, если они все равно на свежие версии языка не торопятся и, видимо, торопиться не собираются? PHP нужен вордпрессу, вордпресс PHP не нужен.

Видимо, разработчики языка и коммунити вокруг не хотят, чтобы php стал языком одного фреймворка (Ruby-On-Rails уже есть, зачем второй такой же?). Поэтому пытаются как-то выправить ситуацию, в которой оказался язык (и которая своими очертаниями подозрительно напоминает жопу).

Вы говорите, что язык у вас в какую-то не в ту сторону идет, но при этом сопровождаете легаси-говно, работающее на версиях языка 10-20 летней давности. И вы, может быть, и рады бы смигрировать, но не сможете. Ну потому что это легаси-говно, подпертое палками со всех сторон и обмотанное скотчем для солидности, работает только на эпмирически-подобранной комбинации из версии ОС, версии языка с точностью до x.x.x.patch145.build_vasyan2001 и билд-версии CRMки. Любая попытка что-то изменить/обновить приводит к неработоспособности системы вообще.

Ну и зачем оглядываться на ваше мнение? Ради чего? Ради того, чтобы вы побухтели, как язык изговняли/как язык похорошел и продолжили пользоваться старой версией?

По факту, язык похорошел. Сильно лучше стал, и даже движется в верном направлении. Одна беда - родовая травма "создан, чтобы умирать" делает все эти потуги с приведением языка в порядок сизифовым трудом. Нишу серьезной продуктовой разработки PHP уже конкурентам проиграл, нет его там. Дальше рынок применимости PHP сужается до короткоживущих небольших проектов (в которых пока еще бытует мнение, что php дает какие-то выгоды) и хоумпейджей - а это путь к вымиранию.

if(array_key_exists("nonExistentKey", $a)) {return;}

Еще короче

if ( isset($a['nonExistentKey']) ) {...}}

Ну тут уже думать надо. Если вы словите в массиве null-значение, то код с isset будет некорректным. Вот тут народ обсуждает https://stackoverflow.com/questions/3210935/whats-the-difference-between-isset-and-array-key-exists

Грубо говоря, разница в том, что array_key_exists точно говорит, есть ли ключ, а isset проверяет - есть ли значение по ключу и для него null - это отсутствие значения (оно же не установлено!)

Да, но для чего мы проверяем существование ключа? Если у вас бизнес логика подразумеват наличие null-значения, то да, придется использовать array_key_exists. У меня (подозреваю, что не только у меня) в 99% случаешь проверка isset($a['somekey']) служит для того, чтоб выяснить есть ли там валидное не null значение, которое как-то нужно обработать.

По факту isset($a['somekey']) заменяет if(array_key_exists("nonExistentKey", $a) && !is_null($a['somekey']))

Ну так в том и суть, что array_key_exists проверяет именно ключ. А isset - проверяет значение по ключу. Функции похожи, но цели - разные. Иногда это не важно, иногда у вас из-за этого логика алгоритма исказится. От конкретной ситуации зависит...

Именно по той же причине в c# есть ContainsKey и TryGetValue.

Впрочем, isset() можно приравнять к $a['aaa']=="" - результат-то будет одинаковым. Но здесь же ловушка: мы прекрасно знаем, что $a['aaa']=="" - плохой код и так писать нельзя, т.к. гарантирует нам проблемы в будущем. А вот if(isset()) уже так явно в глаза не бросается и код выглядит корректным, хотя это не так.

Ну так в том и суть, что array_key_exists проверяет именно ключ.

Наличие. Но читаем внимательно - я писал что в 98% случаев мне не нужно проверять наличие ключа, мне нужно проверить что в нем вообще что-то есть более осмысленное чем null

Не всегда. От задачи зависит, повторяю. Иногда невозможность различить значение-null и отсутствие значения - критично и ведет к ошибке в алгоритме. А иногда нет. И я не готов сказать, каких случаев больше - в моей практике как раз array_key_exists предпочтительнее. В вашей - наоборот, достаточно isset.

потому что данны�� кривые.

Самодемонстрирующая цитата?)

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

Для "целой кучи одноразовых задач" ничего не поменялось. Вы точно так же можете посчитать, сколько будет 5 * "25". Из языка убрали очевидный идиотизм, вида 5 * "25 рублей",count(null) или $a = ''; $a['foo'] = 'bar';, плюс добавили возможность использовать строгую типизацию на уровне параметров/возвращаемых значений в функциях.

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

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

Куча работы на ровном месте.

И проблема вообще не в том, что разработчики это ограничение ввели. А в том, что они его ввели после 20 лет разработки языка, фактически сломав весь старый код.

Для "целой кучи одноразовых задач" ничего не поменялось.

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

Вот я просто погуглил, что нас ждет в 9-ой версии: https://benjamincrozat.com/php-90

In order to make PHP more reliable, warnings for undefined variables and properties will now become errors.

//PHP 9.0: "Fatal error: Uncaught Error: Undefined variable '$foo'"
echo $foo;

Знаете сколько сайтов это убьет? ВСЕ.

Повторю: разработчики ломают вещи, которые копились 20+ лет с момента создания первой версии языка и существуют миллионы строк кода, которые теперь надо переписывать.

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

Я 15 лет занимаюсь мелкой поддержкой сайтов. Я не встречал "нормальных" сайтов вообще никогда - везде говнокод разной степени. Самое ужасное - сайты, которым 7-10 лет и они уже сменили версии php 5.4->5.6->7.0-7.2->8.0 и кучу программистов. Там поверх "родного" исторически накопленного говнокода добавляются костыли для совместимости с новыми версиями языка. Причем правки эти так же вносили разные программисты и по разному - т.е. с разным качеством кода. На выходе получается уже говнокод в кубе. И клиентов не заставить это всё переделать нормально - они не хотят, "сайт работает же!".

Возможно, где-то там в кровавом энтерпрайзе уровня Озона всё хорошо...

А всякая хрень как сидела на старой версии PHP, так и продолжит на ней сидеть, зачем о них думать?

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

ваши перс.данные куда-то утекли из-за уязвимостей в древней версии php

Вас, конечно, не затруднит привести пример такой утечки? :)

Просвещайтесь: https://www.ambionics.io/blog/iconv-cve-2024-2961-p1
php://filter/ + баг в iconv + немного магии и опа - с вашим сервером можно делать уже что угодно. Даже если дальше удастся получить root-права, то, имея возможность залить и исполнить любой php-код, слить базу уже не представляет сложностей.

сменили версии php 5.4->5.6->7.0-7.2->8.0

Мы сменили в одном проекте версии с 5.0 до 8.3 исправив только create_function в preg_replace_callback с выходом 8.0 в паре мест на замыкание.

Все зависит от того как изначально написан php-код. В соседнем проекте так просто не было, но там и кодили его изначально очень странные персонажи (со странными решениями и любители Go в настоящее время, кхе-кхе)

Мне регулярно попадаются Bitrix+Аспро. Причем код аспро - древний, в нем уже много раз покопались. Шаблон сайта, соответственно, тоже сильно переделан. Исходных версий - нет. Git отсутствует. Какие задачи решали правками - не помнит даже заказчик.

И всё это надо обновить с php 5.6 (ну, чаще все-таки с 7.0) до 8.3 - причем аспро обновлять нельзя из-за этих самых правок, а без обновления там не работает практически ни один из файликов: от проблем с синтаксисом, кривых вызовов не-public и не-static функций классов как-будто они static до банального "Parameter must be an array or an object that implements Countable".

Причем даже если код работает в одном месте, это вообще не значит, что он будет работать на соседней странице - потому что там другой набор товаров или иных данных, код идет по другим веткам if'ов и всё падает с новой уникальной проблемой)

Отдельные таланты ухитряются еще и в ядро битрикса правки внести - что еще сильнее помогает в его обновлении на свежую версию (опять-таки даже копий файлов "до правок" в наличии просто нет).

Причем, замечу, битрикс - это под 100к файлов, аспро - еще порядка 5к.

Все зависит от того как изначально написан php-код.

Всё зависит от того, сколько лет проекту и сколько там сменилось программистов (и их качество тоже важно). Большинство кода мелких сайтов - это просто адский-ад прям по советам из книг "так делать не надо!"...

Насколько я видел код битрикса N-летней наверное давности - там всё было очень криво

Ну они его начали еще до 2010го года писать - естественно там всё плохо. Уже лет 7 они к тому старому процедурно-ориентированному коду прикручивают ООП. Что сделало ситуацию еще хуже, чем было изначально.

Вообще, беда битрикса не в том, какой там внутри код. А в том, что документация или отсутствует вообще, или выглядит как "это функция, она функционирует". Да банально, про то, как писать оформление заказа и какие функции для этого надо использовать в новом ООП-api написано в сети на одной странице левого блога (чувак не имеет отношения к битриксу!). В официальной документации бОльшая часть этих (замечу, базовых!) функций вообще отсутствует.

Примеров кода, соответственно, обычно тоже нет. Советы на форумах содержат изначально-неработоспособный код (если ты знаешь битрикс - то видишь ошибки, если не знаешь - копипаст тебе никак не поможет). Да и советов тех - раз-два и кончились.

В общем, я когда-то встретил в сети фразу: "русский софт, бессмысленный и беспощадный". Вот битрикс под неё просто идеально подходит.

разработчики ломают вещи, которые копились 20+ лет с момента создания первой версии языка и существуют миллионы строк кода, которые теперь надо переписывать.

Это сейчас про С, верно?

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

И проблема вообще не в том, что разработчики это ограничение ввели. А в том, что они его ввели после 20 лет разработки языка, фактически сломав весь старый код

Какой прекрасный язык! )

Повторю: разработчики ломают вещи, которые копились 20+ лет с момента создания первой версии языка и существуют миллионы строк кода, которые теперь надо переписывать.

Отсутствие обратной совместимости - зло. Одна из причин, почему PHP "несправедливо хейтят". Но тут 2 стула: если сломать - хейтить будете вы, а если не ломать - языку конец. Главный косяк - это то, что название не поменяли. Так бы уже все спокойно php похоронили с песнями под баян, и жили бы дальше.

А в целом, ничего переписывать не надо. Просто не обновляйте "пых". Пусть эти проекты консервируются и постепенно вымирают. Лет на 10 запроса на "поддержку легаси" на хлеб с маслом еще хватит, а дальше - либо ишак, либо падишах.

Но тут 2 стула: если сломать - хейтить будете вы, а если не ломать - языку конец.

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

А в целом, ничего переписывать не надо. Просто не обновляйте "пых".

Уязвимости(
Старые версии пыха рано или поздно перестают патчить от дыр и дальше уже вопрос времени, когда всплывёт что-то серьезное и вас взломают. Впрочем, если сегодня сидеть на php2, то скорее всего вас сломать никто не сможет - настолько старые дыры уже не проверяют)

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

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

Дык платят же! Че бы и не посидеть, пока платят? ) Народ, вон, до сих пор на COBOL сидит - и ничего...

тот же curl сегодня возвращает html с данными, а завтра там заглушка с капчёй. На php код легко поймет, что после парсинга данные кривые и не положит их в базу, а на c# он тупо умрет с exception еще на начальном этапе парсинга

С чего C# умрёт-то? Он не умрет, он выкинет исключение. Мы исключение перехватим и точно так же не положим их в базу.

Вся разница в том, что C# не будет молча парсить до конца, в отличие от PHP. Да и "поймет, что после парсинга данные кривые" - это еще написать надо. Сомневаюсь, что алгоритм "понимания" будет короче, чем catch ExceptionNeSmogRasparsit.

А если в этот раз curl вместо ожидаемой странички получил VlastelinKoletsVse3ChastiRejisserskayaVersia8kHD.mp4? Будем качать до конца, прежде чем поймем, что пришло не то, что ожидалось? А, нет, по OOM раньше упадем...

просто потому, что невозможно написать алгоритм, который учтет все возможные виды страниц, которые может вернуть чужой сервер

Абсолютно верно. Именно поэтому, имеет смысл сразу прерывать работу в случае, если приходит не то, что мы ожидали. Мы же все равно не знаем, что с этим делать, зачем пытаться?

Мы исключение перехватим и точно так же не положим их в базу.

Ага. Перехватите. Если подумали о том, что это исключение может выкинуться. А если не подумали, то вы можете только вообще весь потенциально-уязвимый код в try...catch заранее обернуть. Ну, он тогда не упадет - просто не вернет вам никаких результатов.

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

Вся разница в том, что C# не будет молча парсить до конца, в отличие от PHP

Иногда именно это и мешает. Иногда наоборот - именно так лучше всего. Соответственно, в первом случае я возьму php, а во втором - c#.

Будем качать до конца, прежде чем поймем, что пришло не то, что ожидалось?

Ну если вы сами не подумали о том, что такое может произойти - то код на php выполнится до конца, вернув фигню. А на c# - упадет, да.

Теперь вопрос: если у вас условный скрипт выкачивает кучу страниц с внешнего сайта и на каких-то получает фигню, то что лучше - скачать эту фигню или чтобы процесс скачивания умер с exception и всё встало (потому что вы о такой ситуации заранее в коде не подумали и не прописали защиту)?

Ответ, в общем-то, простой: в разных ситуациях хорошо или так, или эдак. Зависит от ситуации.

Абсолютно верно. Именно поэтому, имеет смысл сразу прерывать работу в случае, если приходит не то, что мы ожидали. Мы же все равно не знаем, что с этим делать, зачем пытаться?

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

А на c# у вас всё упало прям сразу. Т.е. вам чтобы код скрипта чинить, надо сначала запустить скрипт в отладчике, чтобы отловить те данные, которые всё ломают. Причем видов таких необычных данных может быть несколько - поэтому вам придется к этой задаче возвращаться несколько раз, потому что скрипт будет продолжать падать и падать.

Повторюсь: всё очень сильно зависит от конкретной задачи. Где-то удобнее подход из php. А где-то он создает такие дикие проблемы, что лучше взять условный c#.

 Перехватите. Если подумали о том, что это исключение может выкинуться. А если не подумали, то...

... то он не скомпилируется! Все, проблема решена?

Или на Go посмотрим? Ну, там ошибка вернется, сложно забыть об ошибке, когда она вот она, у тебя перед глазами (и еще одно очко идет в копилку Гриффиндору в пользу позиции об обработке ошибок на месте)

А если не подумали, то вы можете только вообще весь потенциально-уязвимый код в try...catch заранее обернуть. Ну, он тогда не упадет - просто не вернет вам никаких результатов.

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

И код вернет какие-то результаты - не факт, что правильные, но они будут.

Один я искренне не понимаю, зачем нужны неправильные результаты?

если у вас условный скрипт выкачивает кучу страниц с внешнего сайта и на каких-то получает фигню, то что лучше - скачать эту фигню или чтобы процесс скачивания умер с exception и всё встало

Зачем он умер с exception? Он же его обработал! Берет первую страницу, вторую, третью, сотую, на сто первой ловит исключение, перехватывает его и просто пропускает страницу, начинает качать сто вторую.

Если мы видим, что качается говно - зачем принципиально докачивать до конца-то? Мы ее докачиваем, чтобы что? Чтобы выкинуть и перейти к следующей? А докачивать зачем?

потому что вы о такой ситуации заранее в коде не подумали и не прописали защиту

Мы не подумали о том, что скачивание внешней страницы может сфейлиться? А мы точно программисты? Ну ладно, допустим мы дурачки, но нам же компилятор напрямую скажет: "олееееени, у вас в 35-й строке исключение UnknownWebPageDownloadException может быть, обработайте!". Мы такие "Опачки!" и обработали. Кто красавчики? Мы красавчики!

Ответ, в общем-то, простой: в разных ситуациях хорошо или так, или эдак. Зависит от ситуации.

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

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

Жесть какая-то и приседания на месте. Процентов 50 таких вот косяков закрываются типизацией и валидацией входных данных.

Т.е. вам чтобы код скрипта чинить, надо сначала запустить скрипт в отладчике, чтобы отловить те данные, которые всё ломают

Или стектрейс почитать? Там может оказаться много полезного!

А в PHP нам, видимо, не надо отлавливать те данные, которые все ломают? Или ситуация, когда мы узнали, что в БД легло гогно, через пару дней - это значительно лучше, чем узнать сразу, при первом запуске?

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

Ага, так и будет. Зато нам данных откатывать меньше придется.

... то он не скомпилируется! Все, проблема решена?

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

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

И вот это, наконец, правильный вопрос! Ответ: зависит от того, что мне нужно. В каких-то задачах - выкинуть их в мусор, в каких-то - всё равно положить в базу, чтобы потом вторым скриптом дообработать.

Один я искренне не понимаю, зачем нужны неправильные результаты?

Ага. Я раз 5 уже русским языком написал зачем могут понадобиться неправильные результаты - чтобы посмотреть на них глазами и решить, что с ними сделать дальше: выкинуть или преобразовать по-другому. Или вообще исходный скрипт переделать на новую логику. Просто если исходных данных много, а кривые данные встречаются редко, то вы можете вообще даже не знать, что они там есть - и выкидывая данные ещё на парсере вы гарантированно и не узнаете (и так тоже можно - но не всегда это хорошо).

Мы не подумали о том, что скачивание внешней страницы может сфейлиться? А мы точно программисты? Ну ладно, допустим мы дурачки, но нам же компилятор напрямую скажет: "олееееени, у вас в 35-й строке исключение UnknownWebPageDownloadException может быть, обработайте!". Мы такие "Опачки!" и обработали. Кто красавчики? Мы красавчики!

А мы писали "срочно-срочно" скриптик для себя. И запустили его работать на всю ночь. А утром пришли - а там 2 файлика скачало и умерло с exception. Классно же! Мы - молодцы, мы взяли надежный язык, он нас спас от ошибки!

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

Ага. Если вы об этом подумали. Если у вас есть возможность отличить страницу "не так" от страницы "так". Банально: у вас скачивает один скрипт, а парсит другой.

Жесть какая-то и приседания на месте. Процентов 50 таких вот косяков закрываются типизацией и валидацией входных данных.

Ну так я в 100500-ый раз повторю, что в нормальном продашн-реди коде вы будете проверять ВСЁ и ещё чуть-чуть сверху - и вообще без разницы, какой у вас там был язык программирования. А если вы пишете для себя под конкретную личную задачку - то насрать. А где не насрать - там вы допишете код проверки.

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

А в PHP нам, видимо, не надо отлавливать те данные, которые все ломают? Или ситуация, когда мы узнали, что в БД легло гогно, через пару дней - это значительно лучше, чем узнать сразу, при первом запуске?

Еще раз (в третий только в этом комменте) повторю, что зависит от конкретной задачи: где-то да, кривые данные - не нужно класть в базу. А где-то - лучше пусть будут кривые, чем вообще без этих данных.

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

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

Тем более если на Go - у нас сигнатура функции поменялась. С чего оно сбилдится-то?

В каких-то задачах - выкинуть их в мусор, в каких-то - всё равно положить в базу

Данные, которые вы не смогли распарсить? В базу? Блобом, что ли? А зачем вам в базе куски данных произвольной размерности, которые вы не умеете парсить?

Просто если исходных данных много, а кривые данные встречаются редко, то вы можете...

... написать в лог "Вот эта штука сфейлилась", чтобы потом, если дойдут руки, воспроизвести.

Ага. Если вы об этом подумали. Если у вас есть возможность отличить страницу "не так" от страницы "так".

Как минимум, если я ожидаю html-страничку, если я в начале увижу не <html, а какой-нибудь 0xakljd832l, дальше можно не качать.

Банально: у вас скачивает один скрипт, а парсит другой.

А это, простите, зачем? Он не может качать и парсить sax-парсером?

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

Так откуда он его увидит, если не знает, какие данные вы будете обрабатывать этим кодом? Это вам только статический анализатор warning выкинуть сможет.

Данные, которые вы не смогли распарсить? В базу? Блобом, что ли? А зачем вам в базе куски данных произвольной размерности, которые вы не умеете парсить?

Ну или так. Или тупо строкой. Зачем? Чтобы посмотреть глазами - и решить что с ними потом делать дальше. Ну т.е. не всегда стоит обрабатывать данные в один шаг одним скриптом - можно и несколько раз разными скриптами пройтись по ним.

... написать в лог "Вот эта штука сфейлилась", чтобы потом, если дойдут руки, воспроизвести.

Если вы подумали, что именно тут оно сфейлится - о чем нет гарантии. Иначе бы все всегда писали 100% рабочий код)

Как минимум, если я ожидаю html-страничку, если я в начале увижу не <html, а какой-нибудь 0xakljd832l, дальше можно не качать.

Повторюсь - если вы о такой проверке подумали заранее. А могли и не подумать. Или конкретно 0xakljd832l выпадает один раз из ста запросов - а в остальное время там вообще другие ошибки, с которыми вы корректно работаете ибо соотв. код написали и отладили.

А это, простите, зачем? Он не может качать и парсить sax-парсером?

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

Так откуда он его увидит, если не знает, какие данные вы будете обрабатывать этим кодом? Это вам только статический анализатор warning выкинуть сможет.

Так в том и дело, что знает.

Если вы подумали, что именно тут оно сфейлится - о чем нет гарантии. Иначе бы все всегда писали 100% рабочий код)

Повторю: я гофер. У меня функция/метод возвращает ошибку (прямо в сигнатуре написано). Я не могу "подумать" или "не подумать", я прямо вижу.

Или конкретно 0xakljd832l выпадает один раз из ста запросов - а в остальное время там вообще другие ошибки, с которыми вы корректно работаете ибо соотв. код написали и отладили.

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

Но есть куча случаев, когда это просто не нужно

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

UPD Поясню, что хотел сказать.

Что вообще делает любая программа? По-сути, просто перекладывает байты из одного места в другое. То есть есть 3 шага - ввод данных, обработка и отправка. Как эти шаги будут выглядеть в разных типах языков?

Динамический язык.

  1. Вот есть у нас данные, которые как-то хранятся в процессе работы программы. Как они попали туда? Мы их прочитали из файла, или из базы, или сами заполнили переменную, или из канала получили - неважно. На динамическом языке мы можем записать их в переменную, не объявляя тип. Круто, малой ценой мы ввели данные в оборот.

  2. Далее, мы эти данные преобразовываем и отправляем. Может быть неоднократно. На динамическом языке мы не знаем, что там в переменной лежит. Может число, может строка или объект? Отправляем данные в функцию. Тут встаёт вопрос, а что мы получили в эту функцию? Функция же должна делать что-то конкретное. Абсурдно представить функцию, которая может принять вообще любой тип данных. Мы всё-таки ограничены типом входного параметра. Вот только тип мы не знаем. Ну ок, будем проверять поля на существование, или использовать всякие хаки языка. То есть, в самой функции мы тип знаем, просто он нигде не описан. И мы вынуждены его восстанавливать, путём валидации или ещё чего-то. Да и возвращаем мы из функции данные не со случайной структурой, а со вполне определённой. Ведь делаем мы осознанные операции со вполне осознанной структурой. Но увы, тип в динамическом языке не указывается, так что результат функции опять неопределён. И вот мы опять отправляем этот неопределённый (хотя вообще-то определённый буквально, но просто "забытый компилятором") тип, и всё повторяется: опять входной параметр функции надо как-то валидировать. То есть, мы определение типов просто размазываем по всей цепочке преобразования данных.

Как выгдядит ситуация со статическим языком?

  1. Описали типы данных, с которыми мы будем иметь дело. Типы с непостоянной структурой, кстати, прекрасно описываются и на статически типизированных языках, даже в хаскеле с растом.

  2. Ввели данные, провалидировав их. После валидации мы уверены, что данные соответствуют описанным типам.

  3. Преобразования данных существенно проще: каждая функция работает с определёнными данными, тип которых мы знаем заранее. На выходе тоже вполне определённый тип. Нет необходимости как-то валидировать входные данные в каждой функции (которая только преобразовывает данные), потому что на этапе ввода данных мы это уже сделали, а далее всё по цепочке.

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

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

Хабр

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

Мой самый крупный проект (за миллион строк кода, юзерская база 30М+) таки РНР - лютое легаси, писать начинали 15 лет назад, когда о строгой типизации в РНР никто и не слышал ещё. Если (когда) будем переписывать - несомненно, со строгой типизацией, ибо это автоматически предъявляет к программисту требование понимания, что у него на входе, что на выходе и как это правильно обрабатывать. А то иногда получается вот такая вот красота:

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

Golang интересный язык, но опишу пункты которые мне в нём не понравились

  1. Отсутствуют исключения, из-за этого код превращается в лапшу с постоянными проверками err != nil

  2. Отсутствует полноценное ООП, нет наследования, интерфейсы имплементируются не явно, а с помощью утиной типизации

  3. Нет полноценных фреймворков как Laravel иди Symfony

Если честно я не понимаю почему все говорят что PHP разработчикам легко переходить на Golang, я постоянно чувствовал отторжение, в тоже время когда я сел за проект на Kotlin, то я почувствовал себя как дома, все тоже самое что и в PHP, только с бОлшими возможностями

Отсутствуют исключения, из-за этого код превращается в лапшу с постоянными проверками err != nil

Зато не превращается в лотерею "из кишков какой библиотеки вылетело необработанное исключение и положило программу"

Отсутствует полноценное ООП, нет наследования, интерфейсы имплементируются не явно, а с помощью утиной типизации

Это проблема только если вы пытаетесь применять ООП. В го используется композиция, а не наследование.
Если не пытаться натянуть ООП и использовать то, что предлагает язык, то все вполне себе ок. Да, неудобно, если вы всю жизнь писали на ООП, но это проблема не в го. Никто же на хаскел не ругается, что там неправильный ООП.

Нет полноценных фреймворков как Laravel иди Symfony

Во-первых, есть. Во-вторых, они просто никому не нужны потому что стандартная библиотека позволяет сделать примерно всё, что надо, без фреймворков.
А если не позволяет, есть библиотеки, которые позволяют. При этом не возникает необходимости тянуть фреймворк.

Все ваши претензии - это не проблемы языка или экосистемы, это проблемы конкретно вас. И тут дело такое: программисту на Яве будет неудобен ПХП, программисту на Руби будет неудобна Ява, и т.д.
Из зоны комфорта выходить всегда неприятно, но если не хочется то и не надо

из кишков какой библиотеки

А разве имеет значение из какой? Вы наверное имели в виду просто "превращается в лотерею "вылетело необработанное исключение и положило программу"?

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

А разве имеет значение из какой?

Если отлаживать и исправлять это потом вам, то да. Если кому-то другому, то не имеет значения.
Пока исключение не вылетело на проде, о нём можно в принципе не узнать если оно выкидывается в зависимостях зависимостей. А если вы не обработали ошибку, это чаще всего осознанное решение, потому что в сигнатуре функции будет написано, что она возвращает <error type> вне зависимости от языка. Будь то error в Go или typedef int error_code_t

Если отлаживать и исправлять это потом вам, то да. Если кому-то другому, то не имеет значения.

Никак не возьму в толк, что вы имеете в виду. При чем здесь лотерея? И при чем здесь потом исправлять? Для исправлять есть стек трейс. Но вы явно имели в виду не это, а сам факт падения. Или всё-таки отладку и просто не в курсе про стек трейс?

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

Максимум - это обработка известных шаблонных исключений

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

А это уже не лотерея, а управляемое поведение, в отличие от исключений.
Потому что с исключениями будет либо try/catch на верхнем уровне и падение, либо try/catch везде, и тогда это будет уже ничем не отличаться от if err!= nil

Что такое "управляемое падение" и чем оно отличается от " try/catch на верхнем уровне и падение"?

Что такое управляемое падение - я не знаю, а поведение управляемое потому, что, внезапно, вы сами управляете поведением программы.

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

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

Зачем нужен try/catch везде, если его достаточно ставить только там где обработка исключительной ситуации имеет смысл?

В отличие от if err!= nil, который приходится писать действительно везде.

Так не пишите, если прямо желания нет. Игнорируйте ошибки и все:)

resp, _ := http.Get("https://example.com")

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

  1. либо try/catch на верхнем уровне и падение

  2. либо try/catch везде

Так вот, пункт 1 - это как раз и есть "ставить только там где обработка исключительной ситуации имеет смысл".
А пункт 2 - это "ничем не отличаться от if err!= nil".

Да с фига ли программа упадёт, если исключение поймано и обработано?

А что вы ещё сделаете, если вы поймали исключение на верхнем уровне? Это может быть любое исключение из любого места.
Кроме как попытаться ответить красиво (и/или кинуть стектрейс в лог) и откланяться вы ничего не сможете сделать.

Если вы ловите исключение не на верхнем уровне - см. пункт 2.
Если у вас try/catch не везде (где вы вызываете внешние функции), то программа падает, если в try/catch вдруг не будут обёрнуты функции, которые исключения бросают.

С каких пор ошибка HTTP 500 приравнена к падению сервера?

попытаться ответить красиво (и/или кинуть стектрейс в лог) и откланяться

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

По факту, есть 2 концепции обработки исключительных ситуаций: возврат ошибки и исключения.

Никто (ну, как минимум, я) не спорит, что исключения - достаточно мощный и крутой инструмент. Однако он не только мощный и крутой, но и сложный (как и любая мощная и крутая штука). В той же Java с иерархией исключений уже обосрались (и это не Вася какой-то на своем пет-проджекте обосрался, а прям реально разработчики языка, не последние люди в мире разработки). Вопрос в том, насколько нужна и оправдана такая сложность.

if err != nil действительно приходится писать везде. Только плохо ли это, на самом деле?

его достаточно ставить только там где обработка исключительной ситуации имеет смысл

Положа руку на сердце, скажите, насколько часто вы лично, как разработчик, действительно анализировали, в каком конкретно месте имеет смысл обрабатывать конкретное исключение? В каком проценте ваших решений все сводилось к throw Exception с перехватом всего зоопарка на самом верху и просто падению, если исключение возникло? А в каком проценте случаев вы обрабатываете исключение в месте возникновения (т.е. используете его как возвращаемую ошибку)?

Вся идея исключений сводится ровно к одной мысли: отделить флоу обработки исключительных ситуаций от флоу исполнения бизнес-логики. Получается, что у вас рождается параллельная БЛ иерархия обработки ошибок. Точно ли это хорошо и правильно? Ведь в самом примитивном случае вот это:

if err != nil {
  // TODO implement error handling here
}

несколько менее многословно, чем вот это:

try {
  // some business logic here
} catch (e Exception) {
  // TODO implement error handling here
}

Если же мы ловим отдельно разные Exception'ы (как и должны, по логике, делать), какова гарантия того, что при рефакторинге БЛ, удаляющей какой-то этап, мы не забудем удалить ставшую ненужной обработку исключения? Или, например, точно ли хорошо, что при изменении чего-то в зависимостях (допустим, добавилось новое выбрасываемое исключение), мы должны добавлять обработку новых исключений в вызывающем коде?

В общем, если честно, вопрос очень сильно дискуссионный, а правильно работать с исключениями вообще ни разу не просто.

Если же мы ловим отдельно разные Exception'ы (как и должны, по логике, делать),

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

какова гарантия того, что при рефакторинге БЛ, удаляющей какой-то этап, мы не забудем удалить ставшую ненужной обработку исключения?

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

if err := dec.Decode(&val); err != nil {
    if serr, ok := err.(*json.SyntaxError); ok {
        line, col := findLine(f, serr.Offset)
        return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
    }
    return err
}

точно ли хорошо, что при изменении чего-то в зависимостях (допустим, добавилось новое выбрасываемое исключение), мы должны добавлять обработку новых исключений в вызывающем коде?

Разве должны? Новые (под)типы исключений укладываются в имеющуюся иерархию типов исключений, которые так же перехватываются старыми обработчиками. Иначе это ломающее изменение, о таком пишут в релиз ноутс.

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

А вы точно уверены, что этот подход надо форсить? Весь смысл исключений в том, чтобы перехватывать наиболее узкую иерархию!

Ну добавите вы в каждый try/catch блок catch Exception { // ignore error here }. В вызываемом коде добавится новое исключение, а вы даже не узнаете.

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

В Go сделать неправильно синтаксически сложнее.

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

Выглядит так, что решение Go от этой проблемы не спасает

Не, я про другую проблему. Пример:
Ворнинг! Псевдокод!

public static void someFunc(int qty) {
  try {
    var price = someFuncFromDB() // потенциально бросает DBException
    var total = price * qty
    sendTotalToQueue(total) // потенциально бросает QueueException
  } catch QueueException {
    // чота сделали
  } catch DBException {
    // тут тоже чота сделали
  }
}

Допустим, мы перестали цену из БД читать, и заменили вызов в 3 строке на someFuncFromCache(), которая DBException не бросает. Кто удалит обработку исключения в 8-10 строке?

В го оно бы выглядело так:

price, err := someFuncFromDB()
if err != nil {
  // тут чота делаем
}
total := price * qty

Видно же, что 1-4 строка - это одна лексема. Удаляем 1, прицепом выпиливаем следующие строки.

Разве должны? Новые (под)типы исключений укладываются в имеющуюся иерархию типов исключений, которые так же перехватываются старыми обработчиками. Иначе это ломающее изменение, о таком пишут в релиз ноутс.

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

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

Весь смысл исключений в том, чтобы перехватывать наиболее узкую иерархию! Ну добавите вы в каждый try/catch блок catch Exception { // ignore error here }

Вы не поняли идею. Если у вас есть особый сценарий обработки для какого-то конкретного типа исключения - вы его ловите отдельным обработчиком. Фоллбек на базовый тип (рядом или выше) нужен затем, чтобы:

  • если вы разрабатываете конечную софтину, то любое исключение нужно залоггировать, как минимум. Как максимум - восстановится и продолжить работу;

  • если вы разрабатываете библиотеку и не хотите, чтобы типы ваших зависимостей/других слоев протекали к вашему клиенту, нарушая абстракцию - обернуть BaseThirdPartyLibException в свой тип MyLibException с указанием контекстной информации и перевыбросить.

А игнорить исключения - порочная практика, достойная гонения мокрыми тряпками.

В вызываемом коде добавится новое исключение, а вы даже не узнаете.

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

Не, я про другую проблему. Пример:

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

вы при обновлении зависимости всегда очень внимательно релиз-ноутсы читаете?

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

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

Если вы не собираетесь обрабатывать конкретное исключение каким-то особым образом и вам в общем-то не важно, что именно сломалось, то перехватывать конкретный тип смысла нет - смело вписывайте базовый.

Вы не поняли идею. Если у вас есть особый сценарий обработки для какого-то конкретного типа исключения - вы его ловите отдельным обработчиком. Фоллбек на базовый тип (рядом или выше) нужен затем, чтобы...

Не, это вы не поняли идею).

Давайте разберем пример.

Допустим, у нас есть некий юнит Storage с такой иерархией исключений:

StorageException
  - StorageItemNotFoundException
  - StorageItemNotUniqueException

Вы дергаете метод CreateItem у этого Storage, и он, согласно контракту, может кинуть StorageItemNotUniqueException в случае, если, например, запись уже существует.

Мой подход:

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

Ваш подход:

Вы делаете то же, что я, но еще с перехватом StorageException и выбросом наружу, чтобы навалить в лог.

Отлично, мы оба молодцы! Теперь ситуация: этот самый 3rdParty Storage обновился, и, допустим, появилось еще StorageWeJustKilledAllDataException, которое теперь так же выкидывает наш метод CreateItem.

Смотрим на последствия.

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

Мой подход: код не сбилдился. Я пошел в доку, посмотрел, что за новое исключение, написал соответствующий обработчик, бага не пошла на прод.

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

А игнорить исключения - порочная практика, достойная гонения мокрыми тряпками.

А вот это совершенно точно. И фолбек на более общее исключение, чем реально требуется - это один из способов игнора.

Логи почитаю и все узнаю.

А можно было прямо на этапе компиляции узнать...

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

Почему предсказуемо? У нас как раз появилась новая исключительная ситуация, которая не обработана корректно. Это точно подходит под определение "предсказуемо"?

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

А, ну если по соглашению, то это в корне меняет дело! )

Только тут нет breaking change. Брейкинг чендж - это то, что ломает совместимость. Выпиливание методов, изменение сигнатур существующих методов, выпиливание исключений и классов - все то, что потребует изменений в вызывающем коде, чтобы оно скомпилировалось и запустилось.

Добавление новых сущностей breaking change'м, обычно, не считается. Тот же semver напрямую разрешает расширять контракт в минорных версиях.

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

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

Если вы не собираетесь обрабатывать конкретное исключение каким-то особым образом и вам в общем-то не важно, что именно сломалось, то перехватывать конкретный тип смысла нет - смело вписывайте базовый.

Вам бы книгу написать! "Лучшие практики программирования: catch Exception - универсальный способ обработки исключений" - я бы даже почитал)

Мой подход: код не сбилдился.

Ага, то есть вы топите за checked exceptions. Это крайне спорная фича, от которой индустрия таки планомерно отказывается и в современных языках ее нет. При желании вы можете сдуть пыль с миллиона холливаров и насладиться ими. Я во всем треде исхожу из того, что компилятор обработку исключений не проверяет.

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

Ну это уже от логики приложения зависит, ожидаемое оно или нет. Я для примера предположил, что оно не ожидаемое - пусть у нас кеш есть, и оно сначала в кеше проверяет, а потом в базу пишется. Если вдруг на констреинт БД напоролась, видимо кеш разъехался, или что-то вроде.

Ага, то есть вы топите за checked exceptions. Это крайне спорная фича, от которой индустрия таки планомерно отказывается.

Зря, имхо. Сэкономили на буквах, получили по лбу в проде...

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

Ну т.е. таки падаем в проде?)

Просто если компилятор не проверяет обработку исключений, то нафига вообще эта возня с исключениями как таковыми?

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

Go со своей обработкой ошибок идет в сторону упорядочивания работы с ними, типизации и т.д.

Допустим.

(т.е. в сторону исключений)

А вот и не "т.е.". Haskell, Rust и прочие языки с алгебраическими типами идут "в сторону исключений" или "к тупому error-хендлингу"?

А вот и не "т.е."

Не не "т.е.", а не все). Куда они идут - зависит от того, как принято в них обрабатывать исключительную ситуацию типа недоступности БД.

Вы же сейчас вот про это?

enum Result<T, E> {
    Ok(T),
    Err(E),
}

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {error:?}"),
    };
}

Чем это, простите, так уж принципиально отличается от гошного error-хендлинга?

file, err := os.Open("hello.txt")
if err != nil {
  panic(fmt.Errorf("Problem opening the file: %s", err))
}

таки падаем в проде?

Читаем книжку советы выше)

нафига вообще эта возня с исключениями как таковыми?

Чистый happy-path без бестолкового бойлерплейта с пробросом ошибок выше.

Go со своей обработкой ошибок идет в сторону упорядочивания работы с ними, типизации и т.д

Пока у него обработка ошибок на примитивном уровне и вряд ли ее сделают сильно сложнее, ибо это прямой конфликт с философией примитивного кода. Как могло бы быть - это реализовано в Rust, его оператором ? и трейтом From, match с контролем обработки всех ветвей enum.

Пока у него обработка ошибок на примитивном уровне и вряд ли ее сделают сильно сложнее

А вы про какую обработку ошибок? В версии 1.13, например, дали возможность сделать сильно сложнее.

Иерархию ошибок хотим? Кастомные расширяемые ошибки? Типизированные ошибки? - все, лишь бы вы улыбались.

Как могло бы быть - это реализовано в Rust, его оператором ? и трейтом Frommatch с контролем обработки всех ветвей enum.

match при наличии кортежей не особенно нужен.

? - было бы приятно, но, при наличии кортежей, работать не будет

А вы про какую обработку ошибок?

Для начала - не писать и не читать штуки, вроде:

if err != nil {
    return nil, err
}

match при наличии кортежей не особенно нужен

Если функция возвращает разные типы ошибок, то кортежи едва ли помогут. Там скорее будет кастинг через ifс As/Is, но компилятор не будет проверять ни полноту, ни вообще возможность функции возвращать ожидаемые типы. Поправьте, если не так.

А match (анализирующий ErrorKind) - это буквально ваша мечта про checked exceptions, требующая обработать все возможные варианты или сделать явный discard и удалить все лишнее, что к перечислению не относится.

Для начала - не писать и не читать штуки, вроде

Дык, не особенно длиннее matсh'а же)

Там скорее будет кастинг через ifс As/Is

Не скорее, а совершенно точно, только if не обязательно, switch/case можно

но компилятор не будет проверять ни полноту, ни вообще возможность функции возвращать ожидаемые типы

В смысле, полноту кортежа? Будет - это его прямая обязанность, проверять на соответствие контракту.

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

А match (анализирующий ErrorKind) - это буквально ваша мечта про checked exceptions, требующая обработать все возможные варианты или сделать явный discard и удалить все лишнее, что к перечислению не относится.

А вот тут респект! Возможно, даже взгляну на этот ваш раст одним глазком (писать на нем я, конечно, не буду). Если еще иерархия ошибок поддерживается (типа чтобы PackageError -> ReadError -> [NotFoundError,DuplicateError] позволяло ReadError проверить, и наследников проигнорить) - тогда точно-точно взгляну, может даже 2-мя глазами)

не особенно длиннее matсh'а же

Сильно длиннее ?. И чем длиннее цепочка вызовов, тем веселее:
foo()?.bar()?.baz()?

В смысле, полноту кортежа?
Полноту по всем видам/типам/экземплярам возвращаемых ошибок, к сожалению, не проверит.

Вот про это.

Если еще иерархия ошибок поддерживается (типа чтобы PackageError -> ReadError -> [NotFoundError,DuplicateError] позволяло ReadError проверить, и наследников проигнорить)

Никакие иерархии "из коробки" не поддерживаются - как напишите, так и будет)

В вашем примере в соседней ветке io::Error и num::ParseIntError оборачиваются в CliError - благодаря реализациям трейта From для CliError, которые неявно вызываются оператором ? если он видит несоответствие типу возврата open_and_parse_file. В процессе обработки ошибку разворачиваете и обрабатываете так, как посчитаете нужным.

Благо, всю эту машинерию руками писать не нужно, в языке есть макросы и сообщество напилило уже всяких инструментов автоматизации.

Сильно длиннее ?. И чем длиннее цепочка вызовов, тем веселее:foo()?.bar()?.baz()?

Ради такой штуки от кортежей же отказаться придется. На это я не готов.

Т.е. либо foo()?.bar()?.baz()? , либо result, err := someFunc()

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

Да и штука, в целом, ситуативная (ну ладно, в большинстве случаев ок, но не всегда). Есть же, допустим, такое:

publishRestirction = restrictions.Get(uderID)

if publishRestriction != null {
// то есть ограничения,
// у нас не happy-path, 
// мы тут обработать должны
}

Такая штука в логику не ляжет. Да и логика тут - только в страховке от null-ов. Если метод скаляр возвращает, оно же не сработает, цепочку рвать будем

Чтобы оно работало, вот это:

valueINeed, err := someMethod() 
// отсутствие err гарантирует, что valueINeed не nil
// (или тот, кто писал someMethod() - редиска и нехороший человек

легким движением руки превращается вот в это:

var valueINeed SomeMethodResult
var err = someMethod(ref valueINeed) 
// а еще тут исключение шарахнуть может

// ===== ну либо так =======
var err SomeMethodError
var valueINeed = someMethod(ref err)

// ====== или, не приведи Г-дь, вот так =======
var valueINeed SomeMethodResult
var err SomeMethodError
int resultCode = someMethod(ref valueINeed, ref err)

В итоге - вкусовщина же, кому как приседать нравится.

Метод с одним возвращаемым значением в Go может вернуть nil только если это happy-path и так и задумано (ну либо автор рукожоп). В первом случае ? не работает, во втором - не спасет)

Если nil - результат ошибки, рядом с ним ошибка возвращаться должна, и ? опять не сработает.

Благо, всю эту машинерию руками писать не нужно, в языке есть макросы и сообщество напилило уже всяких инструментов автоматизации.

Да ладно, ладно, уговорил, взгляну. Но только одним глазком!

Ради такой штуки от кортежей же отказаться придется

А зачем они? При желании, кортеж все так же может быть альтернативным (успешным) значением результата, например - Result<(u8, String, MyType), std::io::Error>

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

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

publishRestirction = restrictions.Get(uderID)
if publishRestriction != null {...
Такая штука в логику не ляжет

Эта штука (?) нужна только для проброса ошибки выше. Если нужно обработать на месте, то это и надо делать. Но касаемо примера, опциональные (нуллабельные) значения в Rust выражаются через отдельный enum - Option<T>, который может оборачивать значение Some(value) или же быть пустым - None. Эквивалент вашему примеру выглядел бы так:

if let Some(publish_restriction) = restrictions.get(user_id) {
    //... existing publish_restriction usage
}

Если метод скаляр возвращает, оно же не сработает, цепочку рвать будем

Ничего не надо рвать, цепочка foo()?.bar().baz()? будет работать, с учетом, что функция bar никогда не возвращает ошибок (что можно понять по отсутствию ? после вызова). Оператор ? разворачивает значение так, что следующий за ним код ни сном, ни духом ни про какие ошибки, он "видит" только значение по успешному сценарию.

Если nil - результат ошибки, рядом с ним ошибка возвращаться должна

В каком смысле "результат ошибки"? Операция обычно либо удалась, либо нет. Если у ошибки есть какие-то свои вспомогательные данные, пусть даже опциональные, то их можно банально положить внутрь структуры самой ошибки.

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

Ну, когда ? уже написан, много о чем догадаться можно. Вопрос в том, как догадаться, нужно ли этот самый ? писать.

И, к слову, а разве ? не признак того, что функция может вернуть null?

Но касаемо примера, опциональные (нуллабельные) значения в Rust выражаются через отдельный enum - Option<T>, который может оборачивать значение Some(value) или же быть пустым - None. Эквивалент вашему примеру выглядел бы так:

Optional и в Go отлично реализуем. https://pkg.go.dev/github.com/markphelps/optional

Оператор ? разворачивает значение так, что следующий за ним код ни сном, ни духом ни про какие ошибки, он "видит" только значение по успешному сценарию.

Ну, т.е., вся разница в том, в каком месте кода мы обработаем ошибку? На месте, или в конце блока?

Операция обычно либо удалась, либо нет. Если у ошибки есть какие-то свои вспомогательные данные, пусть даже опциональные, то их можно банально положить внутрь структуры самой ошибки.

Ровно то же самое же в Go можно сделать...

Вопрос в том, как догадаться, нужно ли этот самый ? писать.

По возвращаемому типу. Ну и в Rust компилятор подскажет если забыли обработать ошибку.

И, к слову, а разве ? не признак того, что функция может вернуть null?

Вы путаете C# (и его последователей вроде Javascript) и Rust. В Rust нет никакого null, и оператор используется для проброса ошибки наверх.

Optional и в Go отлично реализуем.

Он много где отлично реализуем, но не каждый язык следит за тотальностью (т.е. обязательной обработкой всех случаев).

Ну, т.е., вся разница в том, в каком месте кода мы обработаем ошибку? На месте, или в конце блока?

Разница в том, что использованием оператора ? можно вытягивать в цепочку.

Там, где в Rust пишется в одну строчку foo()?.bar()?.baz()? - в Go будет аж три разных блока, каждый с проверкой err.

Ровно то же самое же в Go можно сделать...

Пожалуйста, хватит терять контекст разговора. Сначала вы привели пример на Go, и спросили как что-то аналогичное сделать на Rust. А теперь в ответ на уточняющий вопрос утверждаете что это можно и на Go сделать. Ну разумеется можно, с этого же и начиналось! Вопрос-то в чём?

Вопрос в том, как догадаться, нужно ли этот самый ? писать.

Зачем догадываться, если можно знать наверняка?
Почитать доку, навести курсор на функцию. Если функция возвращает Result<>, то это значит, что она может возвращать ошибку такого-то типа. Если программист вдруг ошибется - компилятор напомнит.

разве ? не признак того, что функция может вернуть null?

В Rust нет null, а с Option<T> этот оператор не работает. Если Option<T>::None нужно сконвертировать в ошибку, то необходимо сделать это явно.

Optional и в Go отлично реализуем.
https://pkg.go.dev/github.com/markphelps/optional

(Сейчас с дженериками это должно элегантнее выглядеть, чем эти костыли с кодогенеторами)

Отлично - это слишком громко сказано) По сути, это оборачивание потенциального нулла паттерном ошибки, чтобы пользователя принудить проверить значение несоответствием типов. То есть в Go, в терминах Rust, нет Option, есть лишь корявая реализация Result. Почему корявая? Невалидная переменная значения даже в случае такой "ошибки" будет доступна. Типы-суммы в Rust не позволяют получить невалидное значение, ибо enum-переменная может иметь лишь одно значение: либо результат, либо ошибка. Мухи от котлет будут отделены на этапе компиляции.

Да, можно сказать, что в здравом уме в Go никто не будет использовать это значение, но гарантии от компилятора - все равно прикольнее, чем сырая надежда на здравый смысл)

На месте, или в конце блока?

На месте или выше по стеку вызовов.

Ровно то же самое же в Go можно сделать.

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

Ну блин, try-оператор обрастает все новыми подробностями. Дальше надо помнить, что try-оператор обрабатывает null, Optional, еще что-то, еще что-то важное и т.д. Сложная штука, которая обрастает все новыми и новыми семантиками.

Это точно хорошо?

Конечно) Один раз выучил - радуешься все остальное время.

Да, можно сказать, что в здравом уме в Go никто не будет использовать это значение, но гарантии от компилятора - все равно прикольнее, чем сырая надежда на здравый смысл)

Ну, тут соглашусь.

На месте или выше по стеку вызовов.

Выше по стеку вызовов может не быть. Мы же в Go.

Выше по стеку вызовов может не быть. Мы же в Go.

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

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

В Го любая функция может быть запущена в горутине. Т.е. это равносильно тому, чтобы никогда не передавать ошибку выше по стеку. А в случае использования исключений - код всех функций оборачивать в try {} catch e Exception {}

Но даже в Go не любая функция предназначена для запуска в горутине.

Как минимум, функции, возвращающие err, лучше в горутине напрямую не запускать.

Но даже в Go не любая функция предназначена для запуска в горутине.

В том и дело, что все функции в Go одинаковы. И все могут быть запущены в горутине, а значит неизбежно будут.

Понимаете, в C# есть правило "любая функция может выбросить исключение", в го - "любая функция может быть запущена в горутине". Вы живете с одним ограничением, я - с другим.

У вас всегда есть стектрейс от любой точки приложения до точки входа (до main'а), у меня - совершенно не обязательно. Зато у меня есть горутины (M:N-асинхронность), у вас - нет.

Как минимум, функции, возвращающие err, лучше в горутине напрямую не запускать.

Почему нет-то? Если меня не волнует возвращаемый результат функции, кто мне запретит?

Исключений в Go нет. Вернулась ошибка - ну ок, вернулась, дальше что? Приложение не упадет, т.к. ошибка - это не исключение, я не обязан ее перехватывать.

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

go _, _ = someFunc() - посмотрите внимательно, мы и не собирались работать с этим результатом. Нельзя уронить программу по обращению к nil, если ты к нему не обращаешься!

Почему нет-то? Если меня не волнует возвращаемый результат функции, кто мне запретит?

Потому что в большинстве случаев ошибки следует обрабатывать, а не игнорировать.

Выше по стеку вызовов может не быть. Мы же в Go.

Вы таки хотите сказать, что в Go нет функций, способных вызывать другие функции?

Вы таки хотите сказать, что в Go нет функций, способных вызывать другие функции?

Я хочу сказать, что в Го вызывающая функция не обязана дожидаться выполнения функции, которую вызвала.

func doSomeWork() {
  // что-то важное делается
  // предположим, что в Го появились исключения
  throw SomeWorkException
}

func handleSomeJob() {
  go doSomeWork()
}

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

Внимание, вопрос: кто перехватит исключение? handleSomeJob? А как, если она завершилась?

А теперь вам встречный вопрос:

func doSomeWork() (int, string) {
    return 0, "some error"
}

func handleSomeJob() {
  go doSomeWork()
}

Кто обработает some error? Никто, эта ошибка была потеряна.

Почему потерянная ошибка - нормально, а потерянное исключение - страшно-страшно?

Кто обработает some error? Никто, эта ошибка была потеряна.

Все верно, только возвращаем мы таки не строку, а ошибку. Но в данном контек.

Пофиг, в текущем контексте все верно.

Почему потерянная ошибка - нормально, а потерянное исключение - страшно-страшно?

Потому что потерянная ошибка, в отличие от потерянного (не обработанного) исключения, не приводит к крашу приложения?

Потому что потерянная ошибка, в отличие от потерянного (не обработанного) исключения, не приводит к крашу приложения?

Вы же понимаете, что приводит ли "потерянное" асинхронное исключение к крашу приложения - определяется исключительно авторами языка?

Кстати, в C# - не приводит, если запускать через Task.Run.

Ради такой штуки от кортежей же отказаться придется. На это я не готов.

Но почему эти глупые кортежи вам так важны? С ними же слишком просто ошибиться. Забыл записать err != nil и всё.

Почему вам так не нравится когда компилятор за наличием этой проверки следит?

Но почему эти глупые кортежи вам так важны? С ними же слишком просто ошибиться. Забыл записать err != nil и всё.

Не глупые они, классная штука!

Вот тут посмотрел:

https://metanit.com/rust/tutorial/10.5.php

Мож, конечно, чего поменялось, но разве эта штука не макрос?

fn create_person(username: &str, userage: u8) -> Result<Person, String>

Какбэ из сигнатуры вообще не понятно, что тут какая-то ошибка возникает. Это просто враппер по типу Optional.
С одной стороны не вижу проблем в Go боксинг/анбоксинг намутить, с другой - не вижу кто меня обяжет в Rust'е юзать Result<Success,Error>

Мож, конечно, чего поменялось, но разве эта штука не макрос?

Конечно же нет. Где вы вообще там макрос увидели?

Какбэ из сигнатуры вообще не понятно, что тут какая-то ошибка возникает. Это просто враппер по типу Optional.

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

с другой - не вижу кто меня обяжет в Rust'е юзать Result<Success,Error>

А почему вы вообще ставите вопрос таким образом? С чего бы вообще выбор способ возврата ошибок был чьей-то ответственностью кроме как автора кода?

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

и трейтом From

Это про вот это?

use std::fs;
use std::io;
use std::num;

enum CliError {
    IoError(io::Error),
    ParseError(num::ParseIntError),
}

impl From<io::Error> for CliError {
    fn from(error: io::Error) -> Self {
        CliError::IoError(error)
    }
}

impl From<num::ParseIntError> for CliError {
    fn from(error: num::ParseIntError) -> Self {
        CliError::ParseError(error)
    }
}

fn open_and_parse_file(file_name: &str) -> Result<i32, CliError> {
    let mut contents = fs::read_to_string(&file_name)?;
    let num: i32 = contents.trim().parse()?;
    Ok(num)
}

Ну, примерно такое и на Go набросать можно: https://pkg.go.dev/errors

Про все вместе.
Что ж вы тогда портянку-сравнение на Go не написали в этот раз?

Ну давайте напишу...

package demo

import (
  "errors"
  "fmt"
)

var ErrDemoError = errors.New("demo error")
var ErrDemoRead = fmt.Errorf("%w: error reading", ErrDemoError)
var ErrDemoWrite = fmt.Errorf("%w: error writing", ErrDemoError)
var ErrDemoFileWrite = fmt.Errorf("%w: error writing file", ErrDemoWrite)

// обрабатываем ошибку записи файла
if err := demo.WriteSomeShit(); err != nil {
  if errors.Is(err, demo.ErrDemoWriteFile) {
    // сюда попадет ErrDemoWriteFile
  } else {
    panic(err)
  }
}

// Допустим, нас интересуют любые ошибки записи:
if err := demo.WriteSomeShit(); err != nil {
  if errors.Is(err, demo.ErrDemoWrite) {
    // в этот блок попадут ErrDemoWrite И ErrDemoWriteFile
  }
}

// Мониторим все ошибки из пакета:
if errors.Is(err, demo.ErrDemoError) {}

Можем даже поштучно интересующие вещи кейсом разбирать:
switch {
  case errors.Is(err, demo.ErrDemoFileWrite):
    // тут ошибку записи файла хендлим
  case errors.Is(err, demo.ErrDemoRead):
    // а тут - ошибки чтения
}

А Ok(num) - это если никакой ошибки не возникло же?

но это же буквально if err != nil {} ?

У вас в коде не показано оборачивание ошибок от библиотек.

Ну и компилятор не проверяет полноту проверок errors.Is.

но это же буквально if err != nil {} ?

Только его писать не нужно.

У вас в коде не показано оборачивание ошибок от библиотек.

Дык, оно ж ничем не отличается от оборачивания обычных ошибок!

package mylibrary

import (
  "fmt"
  "errors"

  lib "url/to/some/3rdparty/library"
)

var ErrMyLibError = errors.New("my library error")

func DoSomething() error {
  if libError := lib.SomeFunc(); libError != nil {
    return fmt.Errorf("%w: %w", ErrMyLibError, libError)
  }
}

Все.

Это же не эквивалентный код. Для Rust код с пробрасываем ошибок выше, а для Go вы уже снаружи - делаете обработку.

Дополню ваш пример вызовом open_and_parse_file:

enum CliError {
    IoError(io::Error),
    ParseError(num::ParseIntError),
}
// ...
fn main() {
    match open_and_parse_file("some file") {
        Ok(_value) => todo!(),
        Err(err) => match err {
            CliError::IoError(_io_err) => todo!(),
            
            // non-exhaustive patterns: `CliError::ParseError(_)` not covered
            // CliError::ParseError(_parse_err) => todo!(), 
            
            // no variant or associated item named `SomeExtraError` 
            // found for enum `CliError` in the current scope
            CliError::SomeExtraError(_extErr) => todo!(),
        }
    }
}

Здесь, благодаря enum и match, будут ошибки компиляции в случаях:

  • если не обработан какой-то тип возможной ошибки (CliError::ParseError),

  • если из CliError удалили ожидаемую ошибку, а обработчик остался висеть (CliError::SomeExtraError).

Получится ли в Go эти ошибки поймать с его if-Is? Наверняка только второй случай и то без учета скоупа - если перенесли в другого "родителя", то ошибки не будет.

А Ok(num) - это если никакой ошибки не возникло же?
но это же буквально if err != nil {} ?

Это буквально return num, nil

Это же не эквивалентный код. Для Rust код с пробрасываем ошибок выше, а для Go вы уже снаружи - делаете обработку.

Краткая запись для if err != nil { return nil } ) Все понял)

В Go подход: возникла ошибка => сделай с этим что-то. На месте, пока понимаешь, что произошло, и что с этим делать. Хочешь бросить наверх - бросай, но ручками.

В целом, наверное, может бесить, но право подхода на жизнь стоит признать)

Получится ли в Go эти ошибки поймать с его if-Is? Наверняка только второй случай и то без учета скоупа - если перенесли в другого "родителя", то ошибки не будет.

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

Это буквально return num, nil

Только return num, err оно, видимо.

Краткая запись для if err != nil { return nil } ) Все понял)

Нет, для if err != nil { return nil, from(err) } ), где from - полиморфная функция, преобразующая тип ошибки к заявленному.

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

Иными словами, ничем не лучше исключений, но многословнее.

Только return num, err оно, видимо.

Нет, Ok(num) - аналог именно return num, nil. Точнее, даже просто num, nil - return сюда не входит. Никакого err на этой строчке не существует и существовать не может.

Иными словами, ничем не лучше исключений, но многословнее.

Не лучше, согласен. Но и не хуже. По совокупности плюсов/минусов я бы сказал "те же яйца, только в профиль". Исключения - меньше букв (в 99% случаев), ошибки - больше контроля (в 99% случаев).

Нет, Ok(num) - аналог именно return num, nil. Точнее, даже просто num, nil - return сюда не входит. Никакого err на этой строчке не существует и существовать не может.

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

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

И мы должны этот списочек написать (ну ладно-ладно, есть инструменты, видимо, которые его сгенерят?).

Итого, у нас есть метод и список ошибок, которые он может вернуть. И мы должны (и даже обязаны) этот список обработать... Я точно этого нигде не видел?

public static void someMethod() throws IOException, FileReadException...

Выглядит знакомо. Checked Exceptions, вроде, называется. И даже беда была, с ними связанная. Я так понимаю, в Rust принципиальное отличие в том, что макрос Ok() можно не писать? Или, все же, checked exceptions в Rust'е переизобрели?

Мы же этот списочек для каждого метода ведем?

Как напишите, так и будет. Но обычно ошибки шарятся на модуль, в котором может быть больше одной публичной функции.

у нас есть метод и список ошибок, которые он может вернуть. И мы должны (и даже обязаны) этот список обработать...

Обязательство писать отдельные обработчики по каждому элементу списка с себя можно снять через discard pattern (_).

Checked Exceptions, вроде, называется.

Я ж обещал, что match - это ваша мечта про checked exceptions)

Я ж обещал, что match - это ваша мечта про checked exceptions)

И вы же мне рассказывали, что checked exceptions зло и не модные.

Ok - это не макрос, это конструктор Ok-значения в типе Result.

Я так понимаю, в Rust принципиальное отличие в том, что макрос Ok() можно не писать?

Принципиальное отличие в том, что механизм в Rust более общий и не основан на исключениях. Вместо этого используются типы-суммы.

Один и тот же механизм используется чтобы вернуть либо результат, либо ошибку (Result), чтобы получить аналог checked exceptions, чтобы вернуть несколько принципиально разных альтернативных результатов (см. fork), или просто чтобы хранить разные данные в одном месте (см. serde_json::Value)

Только return num, err оно, видимо.

Если мы знаем, что err - это nil, почему бы не написать явно?

Пример устарел: errors.Is, errors.As уже есть.

Можно заменить на Is, только обозначенную проблему это не исправит.

Я чуть-чуть другую проблему обозначал.

Смотрите, у вас есть штука, которая делает, допустим, 10 проверок, и если ни одна не сфейлилась, что-то пишет в базу.

HappyPath-красота:

try {
  userRepo.EnsureExists(user) // может выкинуть users.UserNotExistsException
  currencyRepo.EnsureExists(currency) // Может выкинуть currency.CurrencyNotExistsException
  scheduleRepo.EnsureWeReTrading(now) // Может выкинуть schedules.ScheduleNotAvailableException
  ...
  ...
  тут еще 30 строк всяких манипуляций
  ordersRepo.Write(order)
}
catch u users.UserNotExistsException { // тут что-то делаем }
catch c currency.CurrencyNotExistsException { тут что-то делаем }
catch t schedules.ScheduleNotAvailableException { тут тоже какой-то сценарий }
catch ...
catch ...
catch e Exception { фолбек-исключение }

красивое, окей. Go-шный "ужас":

if err := userRepo.EnsureExists(user); err != nil {
  // что-то делаем
}
if err := currencyRepo.EnsureExists(currency); err != nil {
  // тоже что-то делаем
}
if err := scheduleRepo.EnsureWeReTrading(now); err != nil {
  // и тут тоже что-то делаем
}
  ...
  тут еще 30 строк всяких манипуляций
  if err := ordersRepo.Write(order); err != nil {
    // не смогли записать, беда-беда
  }

Даже готов согласиться, что прекрасный HappyPath-мир симпатичней (за исключением того факта, что я по коду не вижу, в каком месте может что-то пойти не так. Я же вообще не в курсе, что scheduleRepo.EnsureWeReTrading(now) может мне какое-то исключение выкинуть. Сюрпризом будет, потом, когда запущу.

Но ладно, мы перешли к сути того, о чем я говорил. Допустим, мы отказались от использования currencyRepo в этом методе. Валюты стали валидировать на входе, до этого кода невалидные валюты не доходят. Надо проверку выпилить.

Идем в "Гошный ужас", удаляем 4 строку, за которой, очевидно, тянутся 5-я и 6-я. У меня операция и проверка ошибки, которую операция может выкинуть рядом лежат.

Хорошо, идем в HappyPath-рай. Удалили 3-ю строку. Откуда мы узнаем, что 11-ю тоже выкинуть надо? На catch же не написано, в какой строке исключение выстрелить может. Оно, может быть, и выглядит не особо критично, но, с другой стороны, представим, что у вас ровно для этого сценария currency-репозиторий импортировался. Проверку -то удалили, а репозиторий в зависимостях модуля остается, чтобы перехватывать исключение, которое в принципе выстрелить не может.

Я тут топил за checked exceptions, которые реально проблему решают. У вас компилятор скажет "олень ты плюшевый, у тебя обрабатывается исключение, которое не может выстрелить". Но меня в ответ ссаными тряпками гоняют, мол, это уже давно не круто и вообще моветон...

Я же вообще не в курсе, что scheduleRepo.EnsureWeReTrading(now) может мне какое-то исключение выкинуть. Сюрпризом будет, потом, когда запущу.

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

Во-вторых, префикс Ensure явно указывает на возможность исключения.

Но ладно, мы перешли к сути того, о чем я говорил. Допустим, мы отказались от использования currencyRepo в этом методе. Валюты стали валидировать на входе, до этого кода невалидные валюты не доходят. Надо проверку выпилить.

Прежде всего не надо было отдельную проверку делать. Все исключительные ситуации из вашего кода - совершенно однотипные, и не требуют индивидуальной обработки. Нет никакой реальной причины разделять UserNotExistsException и CurrencyNotExistsException.

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

Жесть! Надеюсь, хоть не любое исключение?)

Ну, в смысле, странно же будет, если какой-нибудь метод calculateOrderTotals начинает кидать исключение KafkaConsumerStreamUnavailableException?

Приятно же было бы знать наверняка, какая строчка может кидать исключения, а какая - нет? Если еще быть в курсе, какие конкретные исключения могут быть - вообще бы песня была? Тем более, что задача вполне математически сводимая?

Во-вторых, префикс Ensure явно указывает на возможность исключения.

Вот это совсем не очевидно, например. В спецификации языка ни про какие "префиксы" не говорится. Вот когда в сигнатуре написано "возвращает опциональную ошибку" - это да, "явно указывает". Когда "префикс" - это не "явно", а "по соглашению". По соглашению, которое никто не подписывал, и за соблюдением не следит. Автор модуля, из которого вы метод дергаете, мог быть, например, не в курсе, что он на что-то там соглашался.

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

Ну это же пример! Реальные названия - можете подставить свои.

Нет никакой реальной причины разделять UserNotExistsException и CurrencyNotExistsException.

Почему не было? У вас есть 2 разных модуля (неймспейса, библиотеки, пакета - назовите по вкусу). Каждый из них имеет свою иерархию исключений (имеет право, и даже "все правильно сделали"). И вот какбэ NotExists из модуля currency и аналог из модуля users - это два разных исключения.

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

Жесть! Надеюсь, хоть не любое исключение?)

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

Кстати, одно время вообще любая строчка могла выкинуть ThreadAbortException, которое к тому же невозможно было обработать окончательно. К счастью, в .NET Core от этого безумия удалось отказаться.

Приятно же было бы знать наверняка, какая строчка может кидать исключения, а какая - нет?

В целом приятно, чаще всего не требуется.

Вот это совсем не очевидно, например. В спецификации языка ни про какие "префиксы" не говорится. [...]

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

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

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

Ну это же пример! Реальные названия - можете подставить свои.

Названия названиями, но у вас все методы отвечают за ту или иную разновидность валидации. А значит, общего ValidationException им всем достаточно. Если оставить за скобками тот факт, что валидацию лучше делать вообще не исключениями (и не другими механизмами обработки ошибок).

Но всегда надо быть готовым к выбрасыванию неизвестного заранее исключения.

Гоферы, обычно, готовы! if err != nil { return err } не даст соврать)

В целом приятно

Вот видите, приятно, и даже полезно.

А еще есть паттерны всякие типа Optional (вроде уже даже в стандартной библиотеке есть). Да и дженерики подвезли, можно теперь всякую занятную дичь пилить:

type ValueOrError[T any] struct {
  value T
  err error
}

func NewValue[T any](value T) ValueOrError {
  return ValueOrError{value: value}
}
func NewError[T any](err error) ValueOrError {
  return ValueOrError{err: err}
}

Потом на это коллбеки повесить. Какой-нибудь Resolve() прикрутить и всякое делать.

Да, я заранее сказал, что это дичь). Но валидацию на то, что на любую ошибку есть хендлер - вполне запилить можно.

Эдак можно и вовсе дойти до того, что название метода ни на что не влияет

Справедливости ради, в том же C# действительно не влияет. В Go влияет (если начинается с заглавной буквы - публичный, нет - приватный).

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

Вот тут лучше таки иметь штуку, которая проверяется компилятором, чем надеяться на соглашения.

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

Дык, ensure - это не "проверить", ensure - это "гарантировать". Видите, как просто соглашения ломаются...

EnsureCurrencyIsValidOrThrowException - вот такое соглашение гораздо более похоже на работающую схему, правда ведь?

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

Если метод одновременно и результат, и ошибку вернет, ничего страшного не случится. Если вернулась ошибка, в остальные элементы кортежа никто и смотреть не будет, пока вы явным образом их как-то не заставите (да, в общем-то, тоже соглашение, только уровня EnsureTheWorkMayBeDoneOrThrowException)

А если ошибки в сигнатуре функции нет, то она ее и вернуть не сможет - это прям с гарантией.

Названия названиями, но у вас все методы отвечают за ту или иную разновидность валидации. А значит, общего ValidationException им всем достаточно

Да уперлись вы в эту захардкоженную иерархию! Говорю же:

  1. Это разные модули, изолированные, не знающие друг про друга, я каждое утро начинаю с того, чтобы сформулировать новый пример, демонстрирующий каждую буковку из SOLID. У них своя иерархия исключений.

  2. Чтобы было общее ValidationException, я должен либо юзать иерархию исключений из стандартной библиотеки, либо создать отдельный модуль, в котором будут храниться все исключения (либо только общие). Допустим, у меня 2 модуля, в каждом по 10 возможных исключений, из них 2 общих. Итого, я либо делаю модуль, содержащий 18 исключений (и каждый из нужных модулей тащит себе в зависимости по 8 ненужных ему эксепшенов), либо делаю отдельный модуль, в котором будет ровно 2 исключения и ничего больше, нужный ровно в 2 других модулях. Что то фигня, что то фигня.

  3. Даже если это валидация, валидацию разных вещей нам может понадобиться обрабатывать по-разному. Мы интернет-магазин, мы принимаем заказ, валидируем товар в списке товаров, валидируем цену в реестре цен, валидируем наличие товара в складском регистре. Допустим, мы умеем дозаказывать. Т.е. если валидация по складу упала, мы все еще можем валидировать возможность дозаказа нужного количества и выполнить заказ. Ну ок, все выкинули ValidationException - мы его по цене получили, по списку товаров, или по складу?

Если оставить за скобками тот факт, что валидацию лучше делать вообще не исключениями (и не другими механизмами обработки ошибок).

ну, сделаете вы ее так:

var isTradeable = warehouse.CheckTheItemIsTradeable(itemID)
if (!isTradeable) {
  где-то я это уже видел
}

А вот так вы сделать уже не сможете:

quantityAvailable, isTradeable, err := warehouse.CheckTheItemIsTradeable(itemID)
if err != nil || !isTradeable {
  return fmt.Errorf("you are not allowed to khow why the item %s isn't tradeable", itemID) 
}

А вот с этим вообще не понятно, что делать:

func ensureAllChecksAreOk(itemID string, itemQty int) err {
  c := context.WithTimeout(context.Background(), time.Second * 10)
  eg, ctx := errgroup.WithContext(context.Background())
  eg.Go(func() error {
    return billingSystem.Ping(ctx)
  })
  eg.Go(func() error{
    if qty, isTradeable, err := warehouse.CheckItem(ctx, itemID); err != nil || !isTradeable || qty < itemQty {
      return fmt.Errorf("%s isn't tradeable", itemID)
    }
  })
  // ... тут мы еще накидали
  return eg.Wait()  
}

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

Вот видите, приятно, и даже полезно.

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

Вот тут лучше таки иметь штуку, которая проверяется компилятором, чем надеяться на соглашения.

Прежде всего нужно чтобы компилятор проверял что ошибка не была случайно проигнорирована. В Go это не проверяется.

Да уперлись вы в эту захардкоженную иерархию! Говорю же

А вы упёрлись в какой-то странный пример, где одна функция вызывает 10 разных независимых модулей. Не видел я таких функций в хорошем коде.

А вот так вы сделать уже не сможете: [...]

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

А вот с этим вообще не понятно, что делать: [...]

Выглядит как задача для Task.WhenAll

Прежде всего нужно чтобы компилятор проверял что ошибка не была случайно проигнорирована. В Go это не проверяется.

Это нигде не проверяется. Проигнорить ошибку же и в Rust'е можно.

А вы упёрлись в какой-то странный пример, где одна функция вызывает 10 разных независимых модулей. Не видел я таких функций в хорошем коде.

Ну, либо у нас задачи разные, либо я плохой код пишу.

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

Выглядит как задача для Task.WhenAll

Вот тут вы суть не уловили. Асинхронщина - это хорошо. А M:N - еще лучше.

В Go нормально делать вот так:

func RunHandlers(ctx context.Context) {
  go handleItems(ctx)
  go hanldeSomethingElse(ctx)
  go doSomeAnotherHandler(ctx)
}

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

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

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

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

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

Если кода выше нет, то перехватывать надо. Всё. Точка.

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

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

Очевидно, что этим должен заниматься не вызывающий код, а кто-то ещё.

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

Это нигде не проверяется. Проигнорить ошибку же и в Rust'е можно.

Намеренно - да, случайно - нет. Компилятор предупредит.

Если кода выше нет, то перехватывать надо. Всё. Точка.

Т.е. перехватывать надо везде, вне зависимости от того, может ли исключение возникнуть? Круто!

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

Какие ужасные вещи вы говорите. Т.е. любой нетривиальный алгоритм, даже математически-доказанно надежный, может кинуть исключение?

тривиальный код никто не выносит в фоновые задачи

Тривиальный но ресурсоемкий - почему нет, собственно?

Очевидно, что этим должен заниматься не вызывающий код, а кто-то ещё.

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

Как обработать ошибку - вопрос к фантазии и опыту разработчика. Обработать исключение концепции green thread'ов невозможно. Поэтому их в Go и нет.

Однако, у такого кода много проблем и помимо обработки ошибок: возобновление работы при сбоях, упущенное обратное давление и т.п.

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

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

А, точно. M:N не нужно, вполне хватит асинхронности на одном потоке! Зачем нам 60к RPS, 10к - тоже хорошо!

Вот в этом месте можно и перехват исключений добавить.

В шедулере? Свят-свят-свят!

Намеренно - да, случайно - нет. Компилятор предупредит.

Дык, и в Go предупредит.

Как обработать ошибку - вопрос к фантазии и опыту разработчика. Обработать исключение концепции green thread'ов невозможно.

Хватит нести чушь, нет ни единой причины, которая бы помешала добавить try/catch в green thread (при наличии языка, который бы поддерживал их одновременно).

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

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

И после решения код перестаёт выглядеть так, как вы его написали.

А, точно. M:N не нужно, вполне хватит асинхронности на одном потоке! Зачем нам 60к RPS, 10к - тоже хорошо!

Я этого не говорил.

В шедулере? Свят-свят-свят!

Ну, если у вас шедулер занимается чтением сообщений из MQ - то да, исключения надо обрабатывать в нём же. Однако, насколько я знаю, шедулеры этим не занимаются.

Дык, и в Go предупредит.

Вот специально проверил: нет, не предупредил.

	a, err := foo()
	// if err != 0 { … } - забытая строчка
	b, err := foo()
	if err != 0 { … }

Хватит нести чушь, нет ни единой причины, которая бы помешала добавить try/catch в green thread (при наличии языка, который бы поддерживал их одновременно).

Это вам хватит нести чушь, не разобравшись в вопросе.

Я понимаю, вы привыкли к асинхронной модели того же C# или Rust. async/await - отличная штука, даже вопросов не имею. Но... async для вас - атрибут функции, для меня - вызова.

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

При этом асинхронная модель в Go достаточно ощутимо отличается от async/await. Это не то же самое. У функции, запущенной в отдельной горутине по определению не может быть "предка", которому она может отдать исключение на обработку. Это отдельный процесс, управляемый шедулером.

Если кода выше нет, то перехватывать надо. Всё. Точка.

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

Ну, если у вас шедулер занимается чтением сообщений из MQ - то да, исключения надо обрабатывать в нём же. Однако, насколько я знаю, шедулеры этим не занимаются.

go func() - прямое указание отдать функцию в очередь выполнения шедулера. Функция в этом месте даже не вызывается, ее вызов передается планировщику.

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

Оно может вам не нравиться, но оно позволяет писать синхронный код и запускать нужные куски асинхронно. Это - цена удобоваримой M:N асинхронности.

Она не везде нужна, да и в принципе нужна ровно в одном сценарии - максимально быстрая обработка потока сообщений/событий, не связанных друг с другом. Но ведь буквально весь веб ровно этим и занят!

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

Посмотрел я тут на ваш код ещё раз и увидел одну странность...

Функция RunHandlers спавнит кучу горутин и спокойно завершает свою работу. Допустим, одна их этих горутин исключение кидает.

А что вы делаете в этом случае на go? Ну вот вы вызвали go handleItems(ctx), а handleItems вернула ошибку. Кто её обработает? Никто.

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

А что вы делаете в этом случае на go? Ну вот вы вызвали go handleItems(ctx), а handleItems вернула ошибку. Кто её обработает? Никто.

Ну, для начала, я увижу в сигнатуре handleItems сам факт того, что она ошибку может вернуть. Если мне на ошибку по барабану , я сделаю go _ = handleItems(ctx) . Если не по барабану, то буду делать что-то такое:

go func() {
  if err := hanldeItems(ctx); err != nil {
    // обработаю здесь
  }
}()

В чём разница с исключениями? Будь у меня исключения, я бы это делал каждый раз, а сейчас - когда мне это надо.

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

Посмотрите внимательно, у меня нет ключевого слова async в сигнатуре функции. У меня его в языке нет.

В Go буквально любая функция может быть запущена в горутине. Что превращает концепцию "асинхронная функция обязана обработать исключение сама" в концепцию "любая функция обязана сама обрабатывать все исключения".

Ну и зачем они тогда нужны?

В чём разница с исключениями? Будь у меня исключения, я бы это делал каждый раз, а сейчас - когда мне это надо.

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

В Go буквально любая функция может быть запущена в горутине.

То, что она может быть запущена в горутине - не означает, что её следует там запускать!

И так-то в C# есть Task.Run, для которого я всё ещё не вижу никаких фундаментальных отличий от запуска горутины.

В Go буквально любая функция может быть запущена в горутине. Что превращает концепцию "асинхронная функция обязана обработать исключение сама" в концепцию "любая функция обязана сама обрабатывать все исключения".

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

fn try_something(n: i32) -> Result<String, String> {
    if n > 0 {
        Ok("Ok".to_string())
    } else {
        Err("Error".to_string())
    }
}

fn do_work() -> Result<(), String> {
    // Запускаем параллелльно
    thread::spawn(|| {
        println!("hi thread");
        try_something(1)?; // Не, пробросить нельзя, давай обработай
        // без этой ^ строчки тред стартанёт
    });

    // Запускаем асинхронно
    tokio::spawn(async {
        println!("hi async");
        try_something(1)?; // Не, пробросить нельзя, давай обработай
        // без этой ^ строчки таска стартанёт
    });
    
    try_something(1)? // А тут ошибку пробросили
}

// Запускаем параллелльно

1:N

// Запускаем асинхронно

M:1

M:N покажете?

thread::spawn

Системный тред? Вот тот самый, который 12МБ памяти на старте просит? С отдельным коллстеком? А тысячу сможем запустить? А 10 тысяч?

Я не покажу, я в расте так себе. Но скорее всего, оно M:N, потому что токио - мультитредовый рантайм.

Но вообще, при чём тут это? Как типизация влияет на 1:N/M:1/M:N? Разговор был о том, что в го так себе реализованы ошибки. И, не смотря на то, что вы считаете, что в других языках так же, то нет, не так же, а получше.

Или вам просто надо хоть что-то ответить?)

Я не покажу, я в расте так себе. Но скорее всего, оно M:N, потому что токио - мультитредовый рантайм.

Да, токио - мультитредовый рантайм, и нет, оно не M:N. Оно 1:N.

async/await - M:1, tokyo - 1:N. M:N, кроме Go, из коробки, вроде, Haskell только (или Erlang, не помню, не большой специалист в хаскелях и эрлангах) и умеют. Собственно, весь язык ровно из плясок над "M:N с человеческим лицом" и состоит. Все, что мешает этой концепции - совершенно обоснованно идет лесом. Исключения, например, одна из вещей, которая мешает.

Как типизация влияет на 1:N/M:1/M:N? Разговор был о том, что в го так себе реализованы ошибки.

Типизация, в общем смысле, не мешает. А try/catch, ? и прочие примитивы обработки исключительных/ошибочных ситуаций - мешает. Я вот про это говорю.

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

Дык, там и M:N нету. Другие языки решают другие проблемы.

Поясните тогда, что вы подразумеваете под M:N? Я поискал, но, видимо, нашёл что-то не то, потому что под то описание M:N, которое я видел, tokio подходит.

// Запускаем асинхронно

M:1

M:N - задачи, закинутые в spawn, распределяются по всем доступным ядрам, если не сказать прямо "работаем только в текущем потоке".

M:N - задачи, закинутые в spawn, распределяются по всем доступным ядрам, если не сказать прямо "работаем только в текущем потоке".

async при этом явно говорит "работаем в текущем потоке".

tokio::spawn(async {

Присмотритесь внимательно:

tokio::spawn - 1:N

async {} - M:1

Это не M:N, это, по сути, от thread::spawn отличается только наличием "легковесного потока".

То, что вы написали - это "запустить поток, внутри которого выполнить асинхронный код". Т.е. это будет N раз по M:1. Это не то же самое, что M:N.

Просто для ясности ситуации:

Допустим, вы спавните (tokio::spawn) 2 потока, должны дождаться выполнения обоих и с полученными данными дальше что-то делать. В первом вы асинхронно запускаете 10 задач, во втором - 2. Допустим, время выполнения асинхронной задачи константно, берем за единицу.

За сколько выполнится ваш код? Очевидно, за 10 (потому что 10 задач на одном потоке выполнялись, а второй выполнил 2 и благополучно сдох).

Теперь представим, что у нас 2 потока доступно гошному рантайму (на самом деле мне, как разработчику, откровенно насрать, сколько их там, я могу и имею право даже не знать). Мы запустили 10 горутин в одном блоке и 2 - в другом. Итого 12. Делим на 2 потока - сделали за 6.

Все, на этом "фишки" языка Go окончены. Он создан ровно ради этой фичи. Во всех остальных сценариях Rust круче, C# круче, Java кручке, все круче (кроме, простихоспади, PHP, Basic'а и "Языка программирования 1С" ).

async при этом явно говорит "работаем в текущем потоке".
tokio::spawn(async {

Неа. tokio::spawn создаёт task-у, которая может быть запущена в любом потоке из имеющегося пула.

Неа. tokio::spawn создаёт task-у, которая может быть запущена в любом потоке из имеющегося пула.

Да в смысле "неа"?

У вас async внутри tokyo::spawn! Буквально M асинхронных задач внутри одного потока, M:1 внутри 1:N.

Асинхронные задачи, которые будут выполняться внутри этой таски, исполнятся ровно в том потоке, в котором таска запущена!

В Go - любая асинхронная задача может выполниться на произвольном потоке.

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

  1. async { ... } - это синтаксический сахар для создания пары "структура + метод" (реализации трейта, выражаясь в терминах Rust). Структура хранит в себе всю информацию о текущем состоянии блока (точка последнего await, значения, которые переносятся через этот await, etc.), метод содержит, собственно, код, который изначально находился внутри блока, плюс логику возврата в вызывающий код по await.

  2. tokio::spawn принимает эту пару "структура + метод", сохраняет структуру где-то внутри текущего executor-а и вызывает её метод в произвольном потоке из своего пула.

  3. Метод вызывается до тех пор, пока не дойдёт до конца (т.е. пока не завершится чем-либо, кроме await); при этом каждый вызов независим от предыдущих, в частности, независимо выбирает поток, на котором код будет работать.

  4. Внутри метода может быть снова вызван tokio::spawn, который сохранит внутри executor-а ещё одну структуру, независимую от текущей, и будет вызывать её метод опять-таки независимо от текущей.

По идее, если отстраниться от различия между кооперативной и преемптивной асинхронностью (т.е. наличием/отсутствием явного await), tokio::spawn работает точно так же, как ключевое слово go. Я не замечаю какой-то принципиальной разницы?

По идее, если отстраниться от различия между кооперативной и преемптивной асинхронностью (т.е. наличием/отсутствием явного await), tokio::spawn работает точно так же, как ключевое слово go

Не совсем. tokio::spawn - паттерн actor, go - csp.

Вот тут есть сравнение подходов: https://en.wikipedia.org/wiki/Communicating_sequential_processes

Прям раздел есть Actor vs CSP

Вкратце:

  • актор - управляемый, CSP - нет.

  • csp - обмен сообщениями эксклюзивно через каналы

  • actor позволяет отправку сообщения с отложенной обработкой, csp - блокирует отправку, пока читатель не готов к получению.

Из побочек: Rust, видимо, стектрейс ломать не дает. Т.е., фактически, мы не можем завершить выполнение вызывающего метода (как минимум стек вызова чистить нельзя), пока заспавненный не завершился + мы иерархию задач имеем. В Go - ипанул и забыл.

tokio::spawn работает точно так же, как ключевое слово go.

tokio:spawn сложнее, чем go . Горутины легче.

Не совсем. tokio::spawn - паттерн actor

С какого перепугу? За акторами вам в actix, а не в tokio.

Кстати, в go одни-единственные горутины тоже не реализуют csp, им ещё каналы нужны. И да, в Rust каналы тоже есть.

Из побочек: Rust, видимо, стектрейс ломать не дает. Т.е., фактически, мы не можем завершить выполнение вызывающего метода (как минимум стек вызова чистить нельзя), пока заспавненный не завершился

С какого перепугу?

tokio:spawn сложнее, чем go . Горутины легче.

С какого перепугу?

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

Вот тут есть сравнение подходов: https://en.wikipedia.org/wiki/Communicating_sequential_processes

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

Rust, видимо, стектрейс ломать не дает.

async-блок ничего не знает о том, где именно его создали и за-spawn-или (если ему об этом специально не сообщить), весь код работает на уровне шедулера, то есть, выше по стеку, чем точка, где вызван spawn.

Асинхронные задачи, которые будут выполняться внутри этой таски, исполнятся ровно в том потоке, в котором таска запущена!

Ну хз.

Можно потыкать тут

Вон там 3 уровня тасок запускаются. Можно видеть, то Id треда дочерней таски не всегда совпадает с корневой, хотя и часто равно ему. PS Ну там вообще вроде только 2 треда в песочнице, поэтому часто совпадает. Если запустить локально, будет больше непопаданий.

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

Чота большой пост написал, боюсь, до конца не дочитаете. Важное из конца поста сюда принесу (хотя и остальное там тоже интересное):

func ensureAllChecksAreOk(itemID string, itemQty int) err {
  c := context.WithTimeout(context.Background(), time.Second * 10)
  eg, ctx := errgroup.WithContext(context.Background())
  eg.Go(func() error {
    return billingSystem.Ping(ctx)
  })
  eg.Go(func() error{
    if qty, isTradeable, err := warehouse.CheckItem(ctx, itemID); err != nil || !isTradeable || qty < itemQty {
      return fmt.Errorf("%s isn't tradeable", itemID)
    }
  })
  // ... тут мы еще накидали проверок
  return eg.Wait()  
}

Решительно не понятно, куда и как здесь исключения протащить. Это же жесть будет.

Да-да, суть вашей претензии я в очередной раз понимаю и соглашаюсь.

Но если добавить уровень абстракции, унеся обработчики ошибок выше - в вызывающую функцию, то модель обработки ошибок с кастингом типов ошибок/Is Go садится в ту же лужу. Я об этом.

по коду не вижу, в каком месте может что-то пойти не так

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

В Go такого разделения нет, поэтому ошибки валидации перемешиваются с разного рода критичными проблемами, вроде проблем сетевого взаимодействия, которые к бизнес-логике прямого отношения не имеют и обработка которых в большинстве случаев ограничивается логгированием и отображением юзеру сообщения, вроде: "Упс, что-то пошло не так! Повторите позже" или падения процесса насмерть. Читающему, желающему понять бизнес-логику, этот код не нужен, он мешает.

меня в ответ ссаными тряпками гоняют, мол, это уже давно не круто и вообще моветон...

Никто вас не гоняет, но так уж сложилось, что концепция checked exceptions оказалась недостаточно гибкой и больше вредит, чем помогает на практике. Я верю, что со временем рынок созреет к необходимости повышать качество софта и какой-то компромисс найдет отражение в синтаксисе.

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

если добавить уровень абстракции, унеся обработчики ошибок выше - в вызывающую функцию, то модель обработки ошибок с кастингом типов ошибок/Is Go садится в ту же лужу

Да, так и есть. Поэтому в Go и рекомендуется ошибки обрабатывать в месте возникновения. Другое дело, что запретить пробрасывать наверх не могут - утилитарность языка медным тазиком покроется. В целом все свелось к следующему:

"Мы не знаем, как на самом деле надо [удобно] обрабатывать ошибки и исключительные ситуации. Вам, ленивым сволочам, хоть ошибки дай (вы их if err != nil { return err }), хоть исключения (вы их catch e Exception { throw e } ). Вот вам if err != nil в качестве мотиватора, чтобы вам стыдно стало. Вот вам инструмент (Wrap, Is, As), может, у вас получится что-то придумать. Если получится - затащим в стандартную библиотеку, а то, может, и в стандарт языка. Не получится - ну и хрен на вас."

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

Так, например? )))

bool TryToDoOperations(Request req, out Result res) {
  try {
    var operation1Result = someRepo.Operation1())
    if operation1Result.Ok() { // вау, метод!
      return false
    }
    res.Qty = operation1Result.Qty

    var operation2Result = someRepo.Operation2()
    if operation2Result.Success { // вау, анбоксинг!
      return false
    }
    res.Price = operation2Result.Price

    var operation3Result Operation3Result
    if !someRepo.Operation3(ref operation3Result) { // вау, передача по ссылке!
      return false
    }
    res.Sum = res.Price * res.Qty * operation3Result.Coefficient

    var operation4Result Operation4Result
    if !someRepo.Operation4(ref opeartion4Result) != 0 { // вау, код возврата!)
      ...
    }

    var operation5Result = someRepo.Operation5())
    if operationResultIsOk(operation5Result) { // вау, функция проверки!
      return false
    }
    
  } catch e DBException {
    throw CriticalException(e)
  } catch u ExceptionToBeIgnored {
    return true
  } catch e Exception {
    throw e
  }

  return true
}

концепция checked exceptions оказалась недостаточно гибкой и больше вредит, чем помогает на практике

Это да, в Java с type erasure, вроде, из-за этого жёппа получилась.

Но без checked exceptions весь смысл исключений (которые задумывались как мощный инструмент контроля обработки исключительных ситуаций) сводится к тому, чтобы написать тот же самый if err != nil, только не в месте возникновения ошибки, а в конце try/catch блока. К этому пришли, получается?

Ну, не уверен, что игра прямо стоит свеч.

Поэтому в Go и рекомендуется ошибки обрабатывать в месте возникновения.

Но не всегда это возможно и разумно. В частности - в публичном API, под капотом которого может быть куча логики с разными ошибками, которые пользователю может быть полезно отличать.

Так, например?

В целом - идея такая. Псевдокод подозрительно напоминает C#, удивляет, что вы не использовали кортежи привычным образом)

var (res, err) = FooCanFail(142);
if (err != null) {
	// ...
}

(int result, Error err) FooCanFail(int input)
	=> input > 10 
		? (42, null) 
		: (default, new Error($"Argument {nameof(input)} is out of range"));

Но не всегда это возможно и разумно. В частности - в публичном API, под капотом которого может быть куча логики с разными ошибками, которые пользователю может быть полезно отличать.

Но ведь это же как раз тот случай, когда ошибку надо обработать на месте.

user, err := users.GetUser(currentUserID)
if err != nil {
  if errors.Is(err, ErrUserUnknown) {
    return StatusNotAuthorized, fmt.Errorf("user unknown")
  }
  if errors.Is(err, ErrSessionExpired) {
    return StatusNotAuthorized, fmt.Errorf("session expored")
  }
  return StatusBadRequest, fmt.Errorf("something went wrong")
}

commentBlockRecord, exists, err := badUsersCache.Get(user.ID)
if err != nil {
  log.Printf("comment blocker unavailable")
}
if exists && commendBlockRecord.BlockedTill.After(time.Now) {
  return StatusBadRequest, fmt.Errorf("you are to comment after %v", commendBlockRecord.BlockedTill)
}

if user.IsBadGuy() {
  defer blockCommentsFor(user, time.Now().Add(5*time.Minute))
}

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

Псевдокод подозрительно напоминает C#, удивляет, что вы не использовали кортежи привычным образом)

Ну я специально написал, что псевдокод) А привычный образ для меня value, err := someFunc()

это же как раз тот случай, когда ошибку надо обработать на месте.

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

привычный образ для меня

То есть семантически то же самое, только синтаксисом другого языка.
Однако, цель здесь - демонстрация того, что условный C#, несмотря на наличие исключений, при обработке ошибок может запросто "деградировать" до подхода Go там, где нужна наглядность обработки ошибок и раскрутка стека вызовов не нужна. (Впрочем, это может и Java, и большинство прочих языков с исключениями, если отказаться от кортежей, которые при наличии дженериков могут быть запросто заменены на обобщенную структуру-пару)

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

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

Фишка в том, что я знаю, какая из проверок мне ErrNotFound какой-нибудь вернула. Если у вас две проверки подряд могут выкинуть одно и то же исключение, вы же вообще не в курсе в блоке catch, какая из них это сделала. Тоже ведь не идеальный подход получается.

Однако, цель здесь - демонстрация того, что условный C#, несмотря на наличие исключений, при обработке ошибок может запросто "деградировать" до подхода Go там, где нужна наглядность обработки ошибок

Деградировать - это в легкую! Можно и до return-codes деграднуть, чо останавливаться-то?)

Только он становится еще более многословным и неудобным, чем Go, на таких кейсах.

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

А, ну да, забыл, можно же еще боксинг-анбоксинг накрутить!

раскрутка стека вызовов не нужна

Так в том и прикол, что раскрутка стека вызовов в Go работает ровно до первого go func() в коде.

Т.е., по факту, ее нет. И перехватить исключение выше вы не можете. А "исключения" в виде синтаксического сахара для группового сравнения err с nil... А надо оно вообще?

Специально же и привел

Я напоминаю, что речь изначально была о том недостатке, что эти хэндлеры можно забыть удалить. Вы вынуждены можете пробежаться по цепочке вызовов в поисках подходящих return.

Только он становится еще более многословным и неудобным, чем Go

Это все слова, выше я приводил практически идентичный код, где вся разница - в наличии скобок вокруг кортежа и new перед ошибкой.

можно же еще боксинг-анбоксинг накрутить

Без него в Java не обойтись. В Go при оборачивании ошибки будет боксинг исходной ошибки, к слову.

А надо оно вообще?

Сообщество говорит, что надо.

Я напоминаю, что речь изначально была о том недостатке, что эти хэндлеры можно забыть удалить. Вы вынуждены можете пробежаться по цепочке вызовов в поисках подходящих return.

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

Флоу Go:

// удаляем блок целиком
if err := someFuncToDelete(); err != nil {
  if error.Is(err, ErrSomeError)
  ... // пофиг, что внутри
}
// вот прям досюда. 
// Мы знаем, что эта ошибка - результат конкретного вызова, она не аффектит соседей.

// тоже, вроде, видно, где удалять
val, err := someAnotherFuncToDelete()
if err != nil {
  if errors.Is(err, ErrSomeError)
  ... аналогично пофиг, сколько проверок
}

// и даже так не особо спутаешь
val, err := oneMoreFuncToDelete()
if errors.Is(err, ErrSomeError) {
  // обработали
}
...
if err != nil {
  // пофиг какая ошибка
}
// ну видно же, что после этой строки уже можно не проверять?

value, err := someFuncToNotDelete()

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

Флоу с исключениями:

try {
  someMethodNotToDelete()
  someMethodToDelete() // вот это удаляем
  someAnotherMethodNotToDelete()
} catch e1 SomeException {
  // вот этот блок удаляем?
} catch e2 SomeAnotherException {
  // а этот? он все еще нужен, или уже нет?
} catch e Exception {

}

Вы, видимо, как-то вычленяете список исключений, которые потенциально может конкретный вызов выкинуть, а потом - проверяете по остальным инструкциям, не могут ли они выкинуть аналогичное?

Квадратичная сложность алгоритма поиска того, что можно удалить?

Это все слова, выше я приводил практически идентичный код, где вся разница - в наличии скобок вокруг кортежа и new перед ошибкой.

Код аналогичный, только обернутый в try/catch? И в конце обработка известных исключений?

В Go при оборачивании ошибки будет боксинг исходной ошибки, к слову.

Зато при возврате кортежа из значения + потенциальной ошибки - не будет.

Сообщество говорит, что надо.

Не увидел там требований вкорячить исключения, если честно. То, что чуть больше 10% опрошенных считают error-handling в Го сложным - вижу. Даже пару proposal'ов видел про групповую обработку ошибок. Все забритые (по вполне объективным причинам). Уверен, что прямо в текущий момент кем-то пилятся штук 5 новых.

Только там синтаксический сахар для того, чтобы не писать if err != nil , просят. Не исключения!

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

Это не решение проблемы, это вынужденный отказ от удобства.

Код аналогичный, только обернутый в try/catch?

Кортеж с ошибкой на C#

Не увидел там требований вкорячить исключения

Там обозначена проблема, а за решением - ищите пропозалы.

Только там синтаксический сахар .... Не исключения!

Лол. Вы сначала этот сахар нарекаете "исключениями", а потом говорите с такой экспрессией, будто я предлагал сделать настоящие исключения в Go. Не надо придумывать мне позицию)

Это не решение проблемы, это вынужденный отказ от удобства.

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

Кортеж с ошибкой на C#

Ну, и что следует из того, что в C# можно сделать такую же обработку ошибок, как в Go? Удобство-то где?

Лол. Вы сначала этот сахар нарекаете "исключениями", а потом говорите с такой экспрессией, будто я предлагал сделать настоящие исключения в Go.

Дык, вы мне всю дорогу демонстрируете красоту try/catch. В каком языке try/catch используется не для обработки исключений? Исключение и ошибка - это ведь не одно и то же. Исключение - это альтернативный флоу обработки исключительных ситуаций.

Если ошибка остается в скоупе возвращаемых значений и нет механики перехвата исключений выше по стеку (а мы уже выяснили, что в Go она нереализуема) - смысл городить альтернативный флоу обработки ошибок? Просто заради "чтобы было красиво"? Ну, оно не всегда красиво. В большинстве случаев красиво, но не всегда.

Придумают удобный синтаксис обработки (пока нигде не придумали) - затащат в язык. Не придумают - ну, будем страдать вместе со всеми.

Удобство-то где?

Удобство - в выборе подходящего инструмента.

вы мне всю дорогу демонстрируете красоту try/catch

Вы всю дорогу говорите про уродство, я вам оппонирую) Но недостатки исключений заключаются не в синтаксисе, они никуда не исчезли.

Исключение - это альтернативный флоу обработки исключительных ситуаций.

Не каждая ошибка - это исключительная ситуация. Поэтому для них отдельный флоу, который обладает супер-способностями, но не очень подходит для прочих ошибок.

смысл городить альтернативный флоу обработки ошибок? Просто заради "чтобы было красиво"?

Так нужна не альтернатива, а допиливание имеющегося. Чтобы в Go тоже можно было пробрасывать ошибки выше без боли.

Не каждая ошибка - это исключительная ситуация. Поэтому для них отдельный флоу, который обладает супер-способностями, но не очень подходит для прочих ошибок.

... а флоу обработки "прочих ошибок" в других языках не отличается от Go-шного... В C# ситуация, получается, ровно такая же, как в Go, но там - красиво, а здесь - нет...

Альтернативы-то какие?

Так нужна не альтернатива, а допиливание имеющегося. Чтобы в Go тоже можно было пробрасывать ошибки выше без боли.

Это Go, "выше" может не быть. Куда пробрасывать-то? И, самое главное, зачем? Нигде не пробрасывают, а в Go всенепременнейше надо!

флоу обработки "прочих ошибок" в других языках не отличается от Go-шного

Выше я приводил в пример Rust, где система обработки ошибок не идеальная, но самые больные болячки Go там побеждены.

В C# ситуация, получается, ровно такая же, как в Go, но там - красиво, а здесь - нет

В C# есть исключения, что уже добавляет гибкости. Но обработка ожидаемых ошибок там тоже не вызывает восхищения. Хотя есть некоторые приятные фичи, вроде кастомных implicit cast'ов, которые могут действовать, как трейт From<T> в Rust.

Нигде не пробрасывают, а в Go всенепременнейше надо!

Везде пробрасывают. И даже в Go пытаются.

Rust, где система обработки ошибок не идеальная

Ну вот как станет идеальная, так сразу и скопипастим целиком!)

В C# есть исключения

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

Но обработка ожидаемых ошибок там тоже не вызывает восхищения.

Вот и я говорю, что ровно тот же хрен, просто в другой руке.

Хотя есть некоторые приятные фичи, вроде кастомных implicit cast'ов, которые могут действовать, как трейт From<T> в Rust.

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

Везде пробрасывают. И даже в Go пытаются.

Каким образом в Go пытаются, если в языке нет возможности проброса? Вернуть значение - пожалуйста, что-то пробросить - ваще без вариантов.

А в Go - нету, потому что они сломают горутины.

Погуглите пропозалы, что ж вы так вцепились в исключения?

Да и в чем спор-то? Вы пытаетесь запретить мне называть проблему проблемой, пока у нее не появится решения?

вот как станет идеальная, так сразу и скопипастим

ровно тот же хрен

Все пойдут с крыши прыгать - ты тоже пойдешь? Достойный довод, чтобы не язык не развивался)

Каким образом в Go пытаются, если в языке нет возможности проброса?

Все необходимое там есть и активно используется.

Погуглите пропозалы

Да читал я эти пропозалы! Про групповую обработку ошибок - штук 5, наверное, читал. Во всех есть рациональное зерно, и все, в конечом итоге, не работающие в совокупности с другими элементами языка.

что ж вы так вцепились в исключения?

Вы мне их активно сватаете, я отбиваюсь.

Тем более вы кроме if err != nil в языке других проблем не видите. А они там есть, причем в ассортименте, и гораздо более важные, чем "среднее количество букв, уходящее на обработку ошибок".

Достойный довод, чтобы не язык не развивался)

Дык, в том и дело, что развивается.

Кастомные ошибки - завезли (возможность однозначной идентификации ошибки и матчинга ошибок по стеку с целью более гибкой обработки - гораздо более важная штука, чем уменьшение количества if err != nil )

Дженерики - завезли (тоже фича сильно более интересная, чем неработающий try/catch).

Итераторы вкорячили, конверсии расширили, неудобные инициализации починили. Не, я понимаю, что списочек покороче, чем в том же Python, куда тащат вообще все, что блестит. Только они и не надо в таких количествах.

Решаются те проблемы, которые а) можно решить, б) важно решить.

Все необходимое там есть и активно используется.

Ключевое слово return - это возврат, а не проброс. Вы же понимаете, что возврат и проброс чем-то семантически различаться должны? Не только ключевое слово throw вместо return, но и причина, по которой это слово появилось. Оно должно делать что-то другое, отличное от того, что return делает.

throw Exception - выбросить исключение, не обработать исключение в текущем методе - пробросить исключение.

if err != nil { return err } - это не проброс, а закат солнца вручную какой-то. Нету в Go механики выброса, проброса, переброса, пофигчегоброса, есть только возврат.

И, самое главное, зачем?

Потому что ошибка может возникнуть в какой-то вспомогательной функции. Например, есть иерархия вызовов функций:
fn1 -> parse_file -> parse_string_to_smth
fn2 -> parse_file -> parse_string_to_smth

Ошибка возникает в parse_string_to_smth. Что должен делать parse_file? На месте обработать? Кинуть панику или вернуть что-то пустое? Так это почти всегда (если нормально делать) не зона ответственности этой функции. Что делать должны решать fn1 и fn2 (или что-то повыше). И в одном случае там может быть просто варнинг, потому что данные из файла вспомогательные и у нас есть какое-то дефолтное значение. А во втором случае надо паниковать, потому что данные из файла необходимы. Так что обычно как-раз пробрасывают ошибку от "утилитарной" до какой-то более "сведущей" функции. Я хз, может в го не так, там свой way, надо всегда паниковать или nil вместо значений возвращать, но почему тогда я в го-коде вижу if err != nil { return err } ?

Ошибка возникает в parse_string_to_smth. Что должен делать parse_file? На месте обработать? Кинуть панику или вернуть что-то пустое? 

Если знает, что с ошибкой делать - обработать на месте. Если не знает - обернуть своей ошибкой и вернуть. Панику - а зачем? Ну, если нужна паника - кинуть панику. В чем проблема-то?

Так это почти всегда (если нормально делать) не зона ответственности этой функции.

Ну, я и говорю:
если не зона ответственности

  • if err != nil { return nil, err }

  • if err != nil { return nil, fmt.Errorf("%w: some package error", err) }

  • if err != nil { return nil, errors.New("error parsing string") }

если зона ответственности: if err != nil { // do something }

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

А вот тут вы, внезапно, не очень умную вещь сказали.

"потому что данные из файла" - тут, как минимум, "из файла" - лишнее.

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

А дальше, собственно, от требований пляшем.

Если, допустим, вызывающей функции надо четко отличать ситуацию "все пропало" от "говно пришло какое-то (например, не парсится, или еще какая беда)" - ну так у вас функция, которую мы вызываем, должна вернуть соответствующую информацию. Если не возвращает - функция не подходит, ищем/пишем другую.

var ErrShitHappened = errors.New("shit happens")
var ErrWeCanIgnore = errors.New("ignore this")

func readData(someParamsHere...) (any, err) {
  if err := openResource(); err != nil {
    return nil, fmt.Errorf("%w: can't open resourse %w", ErrShitHappens, err)
  }
  if err := parser.ParseResource(); err != nil {
    if errors.Is(err, parser.ErrCritical) {
      return nil, fmt.Errorf("%w: parser failed with %w", ErrShitHappens, err)
    }
    return nil, ErrWeCanIgnore
  }
  if len(result.Rows) == 0 {
    return nil, ErrWeCanIgnore
  }
  .....
  return value, nil
}

func business() {
  data, err := readData(someParams)
  if err != nil && errors.Is(ErrShitHappened) {
    panic(err)
  }
    ...
}

Так что обычно как-раз пробрасывают ошибку от "утилитарной" до какой-то более "сведущей" функции.

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

Я хз, может в го не так, там свой way, надо всегда паниковать или nil вместо значений возвращать

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

, но почему тогда я в го-коде вижу if err != nil { return err } ?

Потому что таков путь, и в 99% случаев этого достаточно. Можно бы было забить, но все еще есть 1%, где этой конструкции не хватает. Ну да, такова жизнь, более удачных решений не наблюдается (и не надо опять поднимать песню про исключения. Они а) сами далеки от идеала, б) полноценно нереализуемы в парадигме Go (а эрзац - нафиг не нужен).

С исключениями будет гораздо хуже выглядеть

Будет выглядеть лучше
var banTime = TimeSpan::FromMinutes(5);

try {
    var user = users.GetUser(currentUserId);
 
    try {    
        if (badUsersCache.TryGet(user.Id, out var record) && record.After(DateTime.Now)) {
            return new Response(Status.BadRequest, $"you are to comment after {record.BlockedTill}");
        }
    }
    catch (CacheException ex) {
        _logger.Log("comment blocker unavailable");
    }
 
    if (user.IsBadGuy()) {
        blockCommentsFor(user, DateTime.Now.Add(banTime));
    }
  
    return new Response(Status.OK);
}
catch (ErrUserUnknown ex) {
    return new Response(Status.NotAuthorized, "user unknown");
}
catch (ErrSessionExpired ex) {
    return new Response(Status.NotAuthorized, "session expored");
}
catch (Exception ex) {
    return new Response(Status.BadRequest, "something went wrong");
}
Или еще лучше
var banTime = TimeSpan::FromMinutes(5);

try {
    var user = users.GetUser(currentUserId);

    if (!TryCheckCanComment(user.Id, out var badRequestResponse)) {
      return badRequestResponse;
    }
 
    if (user.IsBadGuy()) {
        blockCommentsFor(user, DateTime.Now.Add(banTime));
    }
  
    return new Response(Status.OK);
}
catch (ErrUserUnknown ex) {
    return new Response(Status.NotAuthorized, "user unknown");
}
catch (ErrSessionExpired ex) {
    return new Response(Status.NotAuthorized, "session expored");
}
catch (Exception ex) {
    return new Response(Status.BadRequest, "something went wrong");
}

bool TryCheckCanComment(long userId, out Response response) {
  response = default;

  try {    
      if (badUsersCache.TryGet(user.Id, out var record) && record.After(DateTime.Now)) {
          response = new Response(Status.BadRequest, $"you are to comment after {record.BlockedTill}");
          return false;
      }
  }
  catch (CacheException ex) {
      _logger.Log("comment blocker unavailable");
  }
  return true;
}

...за счет того, что:

  • обработка ошибок уезжает ниже;

  • блок try{...} задает границы, выделяющие зависимые части алгоритма.

В итоге код бизнес-логики читается гораздо легче:

var user = users.GetUser(currentUserId);

if (!TryCheckCanComment(user.Id, out var badRequestResponse)) {
  return badRequestResponse;
}

if (user.IsBadGuy()) {
    blockCommentsFor(user, DateTime.Now.Add(banTime));
}

return new Response(Status.OK);

Будет лучше

Или еще лучше

Вкусовщина же, чесслово. И даже не короче получилось...

  • обработка ошибок уезжает ниже;

А оно настолько круто, чтобы заради этой "фишки" целые исключения городить?

А не вы ли говорили, что бизнесовые проверки, типа UserExists() или user.CanPostComments() лучше во флоу исключений не ссыпать? Да еще и продемонстрировали (!TryCheckCanComment(user.Id, out var badRequestResponse))

Если так, то ведь оно вообще страшненько получится:

// чем, по сути, вот это:
if (!TryCheckCanComment(user.Id, out var badRequestResponse)) {
  return badRequestResponse;
}

// отличается от вот этого:
c

// тем, что дальше еще обязательно надо exception обработать?
try {
  if (!TryCheckCanComment(user.Id, out var badRequestResponse)) {
    return badRequestResponse;
  }
} catch e ErrorException {
  // здесь тоже надо что-то сделать
}
  • блок try{...} задает границы, выделяющие зависимые части алгоритма.

// блок, даже прям с ограниченной областью видимости
if ok, err := TryCheckCanComment(user.Id); err != nil || !ok {
  return badRequestResponse(err);
}

// не знаю, тоже не вижу проблем с идентификацией, к чему проверка относится
ok, err := TryCheckCanComment(user.Id)
if err != nil || !ok {
  return badRequestResponse(err);
}

В итоге код бизнес-логики читается гораздо легче:

Дело привычки же. Мне вот ваш вариант не нравится - мозг менее приучен такое парсить.

А оно настолько круто, чтобы заради этой "фишки" целые исключения городить?

Не "ради", исключения в C# - это родной инструмент и в данном случае их использование оправдано. В случае использования try-паттерна была бы такая же грязь, как в Go.

Фантазии на тему отделения обработки ошибок от бизнес-кода

По идее, ничто не мешает задизайнить в языках такую штуку для легковесных типов: аналог try-оператора, который позволял бы типу "всплывать", но не из функции, а в до ближайшего catch внутри вызывающей функции. То есть чисто синтаксический сахар над легковесными типами ошибок в стиле исключений, но без их затрат на раскрутку стека и собирание бэктрейсов.

бизнесовые проверки, типа UserExists() или user.CanPostComments() лучше во флоу исключений не ссыпать

Не понимаю, о чем вы. Бизнес-логика выше, обработка исключений - отдельно ниже.

// чем, по сути, вот это:
// отличается от вот этого:

Разделение между ожидаемыми ошибками (валидацией) и обработкой исключительных ситуаций. Исключения можно безболезненно ловить выше, а try-функции или аналогичные конструкции - для ситуативного, что необходимо обрабатывать тут же.

Не "ради", исключения в C# - это родной инструмент

В C# - родной. Вы же сетуете, что такой штуки в Go нет. Ну вот в Go, окромя обязательного перехвата всех исключений в конце каждой функции, вариантов нет. Полезная нагрузка - возможность обработать исключительные ситуации пачкой. Геморрой - обязанность обработать все исключительные ситуации в конце каждой функции.

Т.е. вы бесячий if err != nil {} предлагаете на ровно такой же try/catch Exception {} поменять.

По идее, ничто не мешает задизайнить в языках такую штуку для легковесных типов: аналог try-оператора, который позволял бы типу "всплывать", но не из функции, а в до ближайшего catch внутри вызывающей функции. То есть чисто синтаксический сахар над легковесными типами ошибок в стиле исключений, но без их затрат на раскрутку стека и собирание бэктрейсов.

При этом, ни в одном языке такого не сделали... Может, единогласно решили, что оно не надо?

Например, потому что ошибка NotFoundInDatabase , полученная из какой-нибудь CheckUserValid и CheckUserBalance должны, все равно, быть обработано по-разному?

Не понимаю, о чем вы. Бизнес-логика выше, обработка исключений - отдельно ниже.

Тут вопрос в том, что в довесок - обязанность обязательно обработать исключение в конце функции.

Исключения можно безболезненно ловить выше

Дак я вам еще раз говорю: нельзя, нету никакого "выше". Мы в Go.

а try-функции или аналогичные конструкции - для ситуативного, что необходимо обрабатывать тут же.

А это в Go есть. Он именно так и делает: обрабатывает "тут же".

Понимаете, 2 флоу: исключения и ошибки. Ошибки везде (включая Go) обрабатываются на месте, а исключения в Go не работают, потому что горутины.

Ошибки везде (включая Go) обрабатываются на месте

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

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

То есть в любом языке с легковесными тредами нет исключений? А почему в ерланге они тогда есть?

Нет, в расте могу их поднимать наверх насколько хочу

Ну вот только в Расте и можете.

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

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

То есть в любом языке с легковесными тредами нет исключений?

В любом языке с горутинами нет исключений. Потому что концепция горутин напрямую противоречит концепции исключений.

Да и "любых языков с легковесными тредами" не так много. Для статистики откровенно маловато. Где там CSP из коробки еще в языке есть, кроме Go? В Limbo, разве что. Но там тоже с исключениями не богато, вроде?

А почему в ерланге они тогда есть?

Потому что Erlang предоставляет модель акторов из коробки, а Golang - CSP? Потому что не перехваченное исключение в эрланге уронит актора, а в Go - все приложение? Потому что в эрланге легковесные потоки не шарят стейт между собой, а гошные горутины напрямую шарят память? Каких еще аргументов надо?

Вы же сетуете, что такой штуки в Go нет.

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

А сетую я на отсутствие в Go try-оператора, кастомных implicit cast'ов и типов-сумм.

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

Почему? Что мешает пробрасывать ошибки в вызывающую функцию?

При этом, ни в одном языке такого не сделали... Может, единогласно решили, что оно не надо?

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

потому что ошибка NotFoundInDatabase , полученная из какой-нибудь CheckUserValid и CheckUserBalance должны, все равно, быть обработано по-разному?

Если их важно отличать, то это будут разные (вложенные) try-блоки, аналогично работе с исключениями.

Вы говорите, что исключения - ерунда неудобная

Я говорю, что в контексте Go роль исключений сводится к синтаксическому сахару над if err != nil {} (т.е. полезность в контексте применимости в Go - очень спорна)

отсутствие в Go try-оператора

Не надо он там, только читаемость роняет.

кастомных implicit cast'ов

Явное лучше неявного, имхо. Палка о двух концах, которая а) может больно ударить (особенно в контексте достаточно куцей системы типов в Go), б) шаг в сторону слабой (нестрогой) типизации. Очень дискуссионно, причем вы - на позиции "я так привык".

типов-сумм

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

Что мешает пробрасывать ошибки в вызывающую функцию?

Да блин, сколько повторять-то? ОТСУТСТВИЕ ВЫЗЫВАЮЩЕЙ ФУНКЦИИ очень сильно мешает пробрасывать в нее ошибки! Ну хоть одним глазком посмотрите, как горутины устроены, и что такое CSP. Не обязательно все читать, хотя бы просто тезисно пройтись.

или крайности: либо суровая явная явность

Дык, прямо лозунг Go "Суровая Явная Явность". С самого начала так декларировалось.

полностью неявный флоу исключений

Полностью явный флоу исключений называется Checked Exceptions. Его критикуют, сильно и небезосновательно.

то это будут разные (вложенные) try-блоки, аналогично работе с исключениями

Но ведь именно в этом (пусть и вырожденном) случае try/catch более многословен и даже местами некрасив.

if err := someFunc(); err != nil {
  return err
}
try {
  someFunc();
} catch e Exception {
  throw e;
}

И кто тут более многословный?

в контексте Go роль исключений сводится к синтаксическому сахару над if err != nil {}

В Go нет исключений и они вряд ли там к месту. Забейте уже на них)

Не надо он там, только читаемость роняет

Читаемость роняет мусорный бойлерплейт.

Явное лучше неявного.

Явно указывается (оператором), что ошибка пробрасывается выше. Если сборка не падает, то очевидно, что тип совместим. Тут пространства для ошибки нет, да и негативный сценарий нужен гораздо реже, чем позитивный. Поэтому последний стараются расчищать по возможности.

ОТСУТСТВИЕ ВЫЗЫВАЮЩЕЙ ФУНКЦИИ очень сильно мешает пробрасывать в нее ошибки!

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

С самого начала так декларировалось.

А потом поняли, что так жить грустно и начали фичи завозить.

ведь именно в этом (пусть и вырожденном) случае try/catch более многословен и даже местами некрасив.

В вырожденном случае можно и по-старому. Богатый синтаксис - это ведь круто.

В Go нет исключений и они вряд ли там к месту. Забейте уже на них)

Я бы уже давно забил, если бы вы мне в каждом первом комментарии не рассказывали, какие они классные в C#, и почему в Go они прямо необходимы.

Читаемость роняет мусорный бойлерплейт.

Все же, еще раз про исключения: в го они этим самым мусорным бойлерплейтом и станут, без вариантов.

А растаманский подход с Result/Optional и т.д... ну, в виде дженериков - хоть сейчас пиши. Полноценная реализация на уровне языка - кортежи мешают (они слишком дофига свободы дают)

Еще раз приведу примеры из стандартной библиотеки, где это делается.

Еще раз говорю, это не проброс ошибки, а возврат значения.

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

И это не хорошо и не плохо, это просто цена 100-тысячных RPS.

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

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

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

На выходе мы имеем 2 сценария групповой обработки ошибок/исключительных ситуаций:

  1. обработать те, с которыми мы знаем, что делать, остальные уйдут по стеку наверх (exception-style) с падением приложения при выходе за границы стека - неприменимо в Go, т.к. наличие "наверх" никто не гарантирует. Мы вырождаемся в обязанность никогда не возвращать ошибки, что выглядит как бред.

  2. гарантировать обработку всех ошибок (назовем это rust-style) - ну, какбэ, бойлерплейт по обработке рискует превратиться в портянку такого размера, что if err != nil {} покажется цветочками.

А потом поняли, что так жить грустно и начали фичи завозить.

Фичи завозят, стоит признать, аккуратно, дозированно, и только те, которые признаны удачными.

Богатый синтаксис - это ведь круто.

А вот тут - не всегда. Смотрел списки фич в Python'е, чейнджлоги того же PHP... А работать когда?

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

Вообще-то часто.

В каком проценте ваших решений все сводилось к throw Exception с перехватом всего зоопарка на самом верху и просто падению, если исключение возникло?

Опять "падение". Не падение, а обработка ошибки в общем виде - логи, Sentry, 500й ответ. И для огромного количества ошибок этого достаточно.

Если же мы ловим отдельно разные Exception'ы (как и должны, по логике, делать), какова гарантия того, что при рефакторинге БЛ, удаляющей какой-то этап, мы не забудем удалить ставшую ненужной обработку исключения?

Зачем это "по логике" делать? Нет никакой разницы для бизнес-логики по какой причине не удалось сохранить запись в базу данных - потому что хост СУБД не резолвится, потому что СУБД не отвечает, или потому что схема БД изменилась.

Вообще-то часто.

Вы молодец!) Просто статистика, которую приводили авторы языка, утверждает, что вы - редкое исключение. Возможно даже вы такой один!

обработка ошибки в общем виде - логи, Sentry, 500й ответ. И для огромного количества ошибок этого достаточно

Для огромного количества - достаточно. Для всех кейсов - увы и ах - нет.

Как минимум, с детализацией логов могут быть проблемы. "Не смогли записать что-то в какую-то БД" сильно отличается от "Ошибка записи баланса для аккаунта 517286: превышение лимита".

Зачем это "по логике" делать? Нет никакой разницы для бизнес-логики по какой причине не удалось сохранить запись в базу данных - потому что хост СУБД не резолвится, потому что СУБД не отвечает, или потому что схема БД изменилась.

Но все эти случаи отличаются от NotFound или AlreadyExists. Если БД отвалилась - можно со спокойной совестью с грохотом падать, а если мы по какой-то причине пытаемся дублирующую запись завести в БД, или еще в какие констрейнты наступили... Ну, мне кажется, падать тут несколько преждевременно, не?

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

Я их и на Go наблюдал во всей красе, особенно при поддержке чужого кода, это вообще не от способа обработки ошибок зависит.

если мы по какой-то причине пытаемся дублирующую запись завести в БД, или еще в какие констрейнты наступили... Ну, мне кажется, падать тут несколько преждевременно, не?

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

Я их и на Go наблюдал во всей красе, особенно при поддержке чужого кода, это вообще не от способа обработки ошибок зависит.

Ну т.е. исключения - тот же хрен, только вид сбоку? Там тоже catch e Exception через раз.

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

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

Как мы это будем делать. Очевидно, у нас какой-то механизм должен быть, который проверяет, насколько давно я комментил. Навскидку, отлично подойдет распределенный кеш с моим userId и временем последнего коммента (чтобы табличку показать, мол, "тебе можно комментить раз в 5 минут, подожди еще 4 минуты 58 секунд", с протуханием в 5 минут.

Отлично, запилили, прям, вероятно, редис подняли и сидим довольные. Как теперь выглядит флоу коммента:

  • пошли в кеш, посмотрели, нет ли там пользователя

  • если нету - даем комментить

Теперь представим, что редис упал. Явное исключение же? Прям без вариантов не happy-path.

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

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

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

try {
  userIsValid = userRepo.CheckUser(comment.userID)
} catch e Exception {
  handleHappyPathErrors()
  return
}
try {
  userCommentRestrictions = cache.CheckComments(comment.userID)
  if !userCommentRestrictions.CanPublishAfter == null && userCommentRestrictions.CanPublishAfter.After(now) {
    showPublishAvailableAfter(userCommentRestrictions.CanPublishAfter - now)
    return
  }
}
try {
  publish(comment)
} catch e Exception {
  handleHappyPathErrors()
}

За это, получается, боролись?

Ну т.е. исключения - тот же хрен, только вид сбоку? Там тоже catch e Exception через раз.

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

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

Это у вас гоферский if err != nil { return nil, err; } получился. Но он не нужен, его можно убрать:

userRepo.CheckUser(comment.userID);

Restrictions? userCommentRestrictions = null;
try {
  userCommentRestrictions = cache.CheckComments(comment.userID);
}
catch (Exception e) {
  // …
}

if (userCommentRestrictions?.CanPublishAfter > now)
    throw new RestrictionException("…");

publish(comment)

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

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

Ну, я ее тоже вижу (трассировку стека). Не всегда, конечно, только когда она есть. Ее может не быть, если вызывающий метод заспавнил горутину и завершился. Но это цена производительности - отказываться от green threads заради стектрейса... А зачем тогда вообще Go?

Не самый красивый код, но всё равно лаконичнее чем на Go.

Вот вы, прямо положа руку на сердце, совершенно не покривя душой, мне сейчас скажете, что это лаконичнее и более читаемо, чем гошный код?)

Проблема лишь с необходимостью объявлять переменную userCommentRestrictions отдельно - но тут не исключения виноваты, а устаревшее разделение операторов и выражений. А, и ещё реальный код будет изуродован автоматическим форматированием по стайл-гайдам, но тут тоже не исключения виноваты.

Ну т.е. будет некрасивое объявление переменных, форматирование разъедется, будет нечитаемо и вообще плохо, но точно-точно лучше чем в Go?

О-о-о-ок)

Вот вы, прямо положа руку на сердце, совершенно не покривя душой, мне сейчас скажете, что это лаконичнее и более читаемо, чем гошный код?)

Да.

Ну т.е. будет некрасивое объявление переменных, форматирование разъедется, будет нечитаемо и вообще плохо, но точно-точно лучше чем в Go?

Это не имеет отношения к исключениям против кортежей.

Вот это:

userRepo.CheckUser(comment.userID);

Restrictions? userCommentRestrictions = null;
try {
  userCommentRestrictions = cache.CheckComments(comment.userID);
}
catch (Exception e) {
  // …
}

if (userCommentRestrictions?.CanPublishAfter > now)
    throw new RestrictionException("…");

publish(comment)

более читаемо, чем вот это?

// валидируем пользователя
errValidation := userRepo.CheckUser(comment.userID)
if errValidation != nil {
  return fmt.Errorf("%w: user validation failed", ErrValidation)
}

// проверяем блокировку комментов
restrictions, err := cache.CheckComments(comment.userID)
if err == nil && restrictions.SilentTill.After(time.Now()) {
  return fmt.Errorf("%w: you're not allowed to publish comments till %v", ErrCommentsBlocked, restrictions.SilentTill)
}

return publish(comment)

Полно вам!

Это не имеет отношения к исключениям против кортежей.

try/catch, смещающие значащие блоки кода вправо. Бойлерплейт для отдельной обработки единичных исключений в пользу удобства групповой обработки. Отсутствие вложенности try/catch блоков.

Ну конечно, это не имеет отношения к исключениям.

Добавьте во второй блок кода обработку ошибок CheckComments (их надо хотя бы в лог записать!) и publish, и сравните снова.

Добавьте во второй блок кода...

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

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

Единичная ошибка в Go обрабатывается проще ценой бойлерплейта if err != nil , без которого можно бы было обойтись при наличии групповой обработки (пара RFC, кстати, есть на эту тему).

Групповая обработка исключений в try/catch в разы удобнее, но ценой меньшей гибкости в обработке единичных ситуаций.

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

Я кажется нашел дырку в вашей логике. В РНР, если ошибку проигнорировать, то программа упадёт. В Го, если вы её решили проигнорировать, то программа продолжит работу, но уже по непредсказуемому сценарию.

С одной стороны, я сторонник подхода "явное лучше неявного". Но с другой, если это явное требует раздувать код в два раза, то предпочту явное соглашение c fail early.

если вы её решили проигнорировать

Причина, по которой "программа продолжит работать по непредсказумому сценарию" уже в вашем утверждении написана. Если не хотите - обрабатывайте. Язык-то тут причём?

Может быть для вас конкретно в этом месте ошибка - это ожидаемое поведение и вместо падения программы вы хотите чтобы ошибка опускалась и дальше уходила пустая строка или nil. Откуда языку знать, что вы хотите этим сказать?

И часто ли такая ситуация возникает?

Можете собрать статистику с github, если вам интересно.

Всё равно я пока не понял как красиво писать код на go, у меня получается что большАя часть кода - это постоянные проверки if err != nil

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

Да, везде говорят что если функция возвращает error, то мы обязаны его обработать, но по факту имеем что имеем, и как по мне в этом случае исключения дают бОльшую стабильность, потому что позволяют уровнем выше перехватить условную PDOException или родительскую Throwable, а потом выполнить какую то логику, например залогировать ошибку, или в случае PDOException восстановить соединение и с базой данных и выполнить метод ещё раз. При этом не будет дублирования кода в каждом if err != nil

А по поводу того что фреймворки не нужны, это странно, все приходят к фреймворкам, они ускоряют разработку, позволяют стандартизировать код в своих проектах. Вам всё равно придётся реализовывать одно и тоже из проекта в проект, если это backend, то вам придётся делать роутинг, логирование, работу с БД, работу с кешами и очередями и т.д., и не важно это opensource фреймворк или самописный.
Да и сама фраза что фреймворки есть, но они не нужны содержит в себе противоречие

Да и сама фраза что фреймворки есть, но они не нужны содержит в себе противоречие

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

Просто возврат ошибок приводит вот к такому игнорированию ошибок

Какому такому? Во-первых, последний аргумент - это не всегда ошибка.
Часто пишут конструкции типа val, _ := xxx.Read(), и игнорируется не ошибка а длина, допустим.

Во вторых, в подавляющем большинстве случаев, аргументов больше одного (т.е. значение + ошибка).
В таких случаях, если первый аргумент имеет нулевое значение ("", nil, 0, и т.д.), можно использовать его для проверки, потому что вызывающему коду чаще всего фиолетово почему результат нулевой. В этом нет никакого криминала, если нулевой ответ - это ожидаемый ответ.

Дискуссия об исключениях в Go напоминает мне дискуссию о дженериках, тех времен, когда их в Go ещё не было, тогда фанаты тоже активно всех убеждали, что дженерики не нужны или даже вредны.

Намерения замечательные, заставим программиста проверять каждую ошибку. В итоге, паник на панике, и тупая копипаста с пробрасыванием ошибки. Или ещё тупее, бойлерплейт код if(err) printf(ой что-то сломалось). Это всё хуже исключений.

Если уж хотелось очень заставить программиста чекать ошибки там, где это требуется, можно было сделать как в Java и заявлять о выбрасываемых исключениях в контракте метода, чтоб программист либо пробросил это исключение в свой контракт, или try/catch-ил.

В C++ есть исключения и всё там хорошо.

Или ещё тупее, бойлерплейт код if(err) printf(ой что-то сломалось).

Просто надо было в этом случае использовать goto! /s

В C++ есть исключения и всё там хорошо.

А я могу там глядя на сигнатуру функции узнать, будет ли у меня исключение или нет?

Пока самый здравый для меня подход в расте - хочешь лепи везде .unwrap и лови паники, хочешь - сделай все ошибки "общими", хочешь - делай свою систему ошибок. Можешь ловить точечно, там где надо, можешь только на верхнем уровне. И в отличие от Го, для пробрасывания ошибки достаточно просто добавить ? куда надо и немного поменять типы у функций. При этом, всё наглядно и без боли.

А я могу там глядя на сигнатуру функции узнать, будет ли у меня исключение или нет?

noexcept

заявлять о выбрасываемых исключениях в контракте метода, чтоб программист либо пробросил это исключение в свой контракт, или try/catch-ил

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

И насколько грамотно ошибка обработается, всё ещё зависит от программиста. Можно всё подряд оборачивать в try/catch и никогда не узнать о том, почему код не работает в половине случаев.

В C++ есть исключения и всё там хорошо.

В C++ много чего хорошо, а много чего нехорошо. Притом одновременно. Им надо владеть на очень глубоком уровне, чтобы знать, какой способ подходит для конкретного случая. И то бородатые программисты из разных поколений будут бесконечно спорить друг с другом как правильно. Это не то, что требуется от универсального языка.

В C++ есть исключения и всё там хорошо.

  1. Там прямо сейчас нет кроссплатформенных стектрейсов без внешних библиотек

  2. Если исключения включены, то вы не знаете что и когда выбрасывается, пока не прочитаете документацию и/или код

  3. Включены они не всегда, в embedded почти никогда

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

Насколько они не кроссплатформенные? Когда разрабатывал на C++, это было очень-очень давно, на Visual Studio, нормально всё было со стектрейсами. Неужели всё сломали?

Тут речь не про детали реализации, а скорее парадигма разработки. Очень хорошо, когда у исключения есть строгий базовый тип (как в C#, почти). Тут про такое понятие как отказ + возможность что с этим нормально сделать. Или восстановиться до изначального состояния, а не просто молча выплюнуть в консоль "что-то пошло не опять, а снова не так" + стектрейс. Много этим пользовался и на большие портянки кода на Go мне порой больно смотреть. Почти 30-40% это проверки err. Да ещё и часто просто в лог плюют ошибку и.. всё. И делать ещё это надо по месту. А пробрасывать err так вообще на грани преступления.

на Visual Studio

Visual Studio и кроссплатформа - это две разные вещи.

Насколько они не кроссплатформенные?

Если не использовать библиотеки типа backtrace или libunwind - то их в принципе нет.

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

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

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

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

Истинно так. Уже год, собственно, живём с этими дженериками. После релиза сам думал, куда бы их воткнуть в бизнес-логику - не воткнулось. Да и в целом в production-коде видел их всего один раз.

Потому что реализовали их так что и использовать почти нигде нельзя.

Вот сегодня хотел сделать

func DoSomething[T any](data T) {
  type Request struct {
     T
     MoreData int
  }
  ///...
}

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

И что бы вы думали - нельзя так делать.

Нельзя, но не возможно

Видимо, и вам тоже недостаточно надо было, потому что

If this is considered important it would be good to see compelling use cases which cannot be written reasonably in any other way.
цитата

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

решать реальные проблемы

Какую реальную проблему вы решаете? Ваш пример этой проблемы не показывает, он показывает конкретное решение, которое вам показалось правильным, но которое не работает. Может быть это можно решить и без копипасты, просто не именно через встроенную структуру.

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

type A struct {
  X int
}

type B struct {
  S string
}

data = `{"X": 10, "total":100}`

parsedA, total, err := ParseData[A](data)

data = `{"S": "test", "total":5}`
parsedB, total, err := ParseData[B](data)

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

https://go.dev/play/p/ve-fOI53GF_o

main.A: {X:10} 100
main.B: {S:test} 5

Куда мне подойти за вашей зарплатой?

Где же вы были года два назад, мы как раз гошников искали =(

Если нужно ещё, можете в личку написать (:

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

Поэтому можно сделать так:
https://go.dev/play/p/3KQ81WhZaTA

Результат абсолютно такой же, копипасты всё так же нету.

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

Здесь от контекста всё зависит.

Если вы сериализуете в json внутри запроса - можно сериализовать в два поля, возвращать 2 json и парсить в A/B + Request{MoreData}, тогда будет тоже самое без дженериков. Но и в один json в принципе тоже нормально.

Можно StructScan просто два раза на разные структуры вызвать. Зависит от драйвера, но скорее всего вся строка на момент вызова уже на клиенте. Второй раз точно.

Всё равно я пока не понял как красиво писать код на go

Никак)
Go - это не про красоту. Это про утилитарность и get shit done. Но когда попривыкнешь, понимаешь, что в этом и есть своего рода красота.
Ну плюс модные IDE часто красиво отображают всю это порнографию с if err != nil

Вам всё равно придётся реализовывать одно и тоже из проекта в проект

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

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

Ну вот мы на примере Го-экосистемы видим, что не все приходят к фреймворкам.

Вам всё равно придётся реализовывать одно и тоже из проекта в проект, если это backend, то вам придётся делать роутинг, логирование, работу с БД, работу с кешами и очередями и т.д., и не важно это opensource фреймворк или самописный.

Ну, собственно, берем один из полусотни роутеров на выбор, логгер по вкусу, подключаем БД (библиотеки в ассортименте + queryBuilder'ы или requestHandler'ы по вкусу), подключаем кеш (какой захотим, а не тот, который разработчики фреймворка поддерживают), подписываемся на очереди (тоже библиотечкой, их есть у нас)...

Почему обязательно фреймворк-то? Назовите хоть одну причину, делающую необходимостью бандлинг http-роутера и драйвера БД одной сущностью. Я лично вообще не вижу причин так поступать.

Go надо воспринимать как чуть более удобный С. Исключений нет, потому что их нет на самом деле на "железном" уровне, эта абстракция создается языками высокого уровня, а Golang - низкого. На C их тоже нет. По вопросу ООП - так же. Ну и да, это неудобно для человеческого мозга. Но если golang развивать до уровня удобства пайтона, мы пайтон в итоге и получим, со всеми его плюсами и минусами.

Go надо воспринимать как чуть более удобный С.

Это вы конечно сильно упростили.
В C нет ни корутин, ни сборки мусора, ни стандартной библиотеки со всеми "батарейками".

Исключений нет, потому что их нет на самом деле на "железном" уровне

Это заблуждение. В Go нет исключений не потому что их нет в C:

The reason we didn't include exceptions in Go is not because of expense. It's because exceptions thread an invisible second control flow through your programs making them less readable and harder to reason about.
In Go the code does what it says. The error is handled or it is not. You may find Go's error handling verbose, but a lot of programmers find this a great relief.
In short, we didn't include exceptions because we don't need them. Why add all that complexity for such contentious gains?
— Andrew Gerrand

Хинт: на уровне железа исключения тоже есть.

О как? Мне кажется, вы говорите тут про совсем другие исключения (которые связаны с кольцами защиты в CPU), а не про исключение NoMoreFreeRoomsExceptions в методе ReserveRoom класса Hotel.

Целочисленное деление на 0 - это кольца защиты?

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

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

Ну у нас уже спор про "этость", мне кажется, что как раз ЭТИХ механизмов - нет. Есть другие механизмы, с похожим названием, но для другого предназначения, которые мы не можем использовать в golang (или другом ЯП) для обработки высокоуровневых исключений вродеFileNotFoundError.

Машина-то у нас есть, но на речку мы на ней не уедем - она стиральная. :-)

но для другого предназначения,

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

Исключений нет, потому что их нет на самом деле на "железном" уровне,

Исключения на железном уровне тоже есть, только называются они прерываниями.

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

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

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

После прерываний выполнение возвращается в тот код, откуда в прерывание ввалились

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

Мне кажется, что между NoMoreFreeRoomsExceptions и INT 21h все таки существенная разница. И полагаю, что механизм эксепшнов в ЯП высокого уровня работает НЕ через прерывания. Или через него?

И полагаю, что механизм эксепшнов в ЯП высокого уровня работает НЕ через прерывания

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

это понятно, а минусы какие?

Отсутствуют исключения

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

`if err` может и выглядят некрасиво но обладают достоинством предсказуемости и видимости. Небольшая "лишняя" нагрузка зато огромный выразительный пласт, который сразу говорит "здесь что-то может пойти не так". И каждый раз вы принимаете решение, что-то вроде

  1. не могу дальше работать - выбрасываем ошибку наружу

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

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

Я не так много кода на Go прям изучал, но то что из прикладухи видел похоже на 30-40% "мусора" в виде проверок err после чуть ли не каждой функции. Да ещё и обработка такая, как пыль протёр для видимости, чтоб мама не ругалась :)

30-40% "мусора" в виде проверок err после чуть ли не каждой функции

Более того, аж целый errgroup есть, чтобы проверять ошибки из параллельных потоков)

Всё так: очевидность в ущерб краткости.

Да ещё и обработка такая, как пыль протёр для видимости, чтоб мама не ругалась

Так обработка по большому счёту нужна только на верхнем слое, а задача всех остальных - прокинуть ошибку выше. Можно добавить что-то от себя, чтобы по тексту ошибки можно было сразу стек определить, а можно и не добавлять, чтобы не мусорить - всё равно есть stack trace.

Это в go-то есть стектрейс? А где его найти, если не секрет?

Kotlin + spring? Где работаете, если не секрет?

Kotlin + Apache Camel, но это было аж 2 работы назад

Есть golaravel фреймворк

Человечество постоянно переписывает софт с одного языка на другой. В этом нет ничего сверхестественного. Однско в этом переписывании есть несколько проблем:

  • Потеря знаний массами программистов (существовало огромное число библиотек, написанных на языках Algol, FORTRAN, и теперь они не могут служить в качестве учебников для современнях программистов, а вся математика теперь в таких вещах, как Mathematica, Mathlab, ...)

  • Чрезмерные финансовые затраты (корпорации мечтают о создании языков, удешевляющих работу программистов, однако, затраты на портирование старых программ огромны. Пример: COBOL, программы на котором уже, 10 лет как назад, пора портировать из-за естественного вымирания программистов)

    А в остальном прогресс - это хорошо. Но совершать его надо с умом и осторожно.

существовало огромное число библиотек, написанных на языках Algol,
FORTRAN, и теперь они не могут служить в качестве учебников для
современнях программистов

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

Это хорошо, но линейной алгеброй жизнь не заканчивается.

линейной алгеброй жизнь не заканчивается.

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

Что-то тут не чисто. Я решил из любопытства исполнить приведённый код на Go на своей локальной машине (i7-13700, 64gb ram, win11). Так как автор не удосужился представить полных исходников и пример JSON файла, я руками сделал самый простейший.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"sync"
	"time"
)

type Test struct {
	Value1 int    `json:"Value1"`
	Value2 string `json:"Value2"`
}

func main() {
	// var data []byte
	// var err error
	data, err := os.ReadFile("test.json")
	if err != nil {
		log.Fatalln("Error reading file ", err)
		return
	}
	start := time.Now().UnixMicro()

	wg := &sync.WaitGroup{}
	for i := 0; i < 1000000000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			var test *Test
			err = json.Unmarshal(data, &test)

			if err != nil {
				fmt.Println("Error reading file ", err)
				return
			}
		}()
	}

	wg.Wait()
	end := time.Now().UnixMicro()

	fmt.Printf("result = %d microseconds \n", (end-start))
}

test.json

{
  "Value1": 123,
  "Value2": "test2"
}

И у меня получилось вот так

result = 271338709 microseconds

~271 секунда, Карл!

В результате выполнения этих команд получил:
Golang скомпилированный под локальную машину выполнял команду - 452.430ms

Откуда тут пол секунды получилось?

И для сравнения, сделал подобный тест на dotnet 8.0

using System.Diagnostics;
using System.Text.Json;

var file = File.ReadAllText("D:\\Sources\\TestVsPhp\\test.json");
var sw = Stopwatch.StartNew();
var last = Enumerable.Range(0, 1000000000)
    .AsParallel()
    .Select<int, Test>(_ => JsonSerializer.Deserialize<Test>(file))
    .LastOrDefault();
Console.WriteLine($"result: {sw.ElapsedMilliseconds} milliseconds");

public record Test(int Value1, string Value2);

с результатом

result: 25600 milliseconds

25.6 сек, быстрее в 10 раз. Если брать байты, а не строку, то ~22 сек.

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

Зачем делать 1000000000 горутин? Это ж там только на шедулинг сколько оверхеда будет.

Вот-вот. На лицо непонимание зачем нужны горутины, тут даже задача не асинхронная, а чисто cpu bound.

а сколько надо?) может еще пул создать?) смысл горутин и прочих всяких виртуальных потоков в том, что ты создаешь из столько - сколько надо. можно конечно возразить что в реальности придет не 1000000000 сразу а частями, это валидное замечание.

автору комментария плюс, тест хоть и был довольно синтетический, но он захватывал сразу несколько объемных областей и лично мне было интересно что там не так с go (хотя я в курсе, что он совсем не c++ и даже java может проиграть). те редкие тесты php что попадались никогда не показывали что-то сравнимого по скорости, а то что появился jit не значит что он сразу станет быстрым как все, потому что всегда есть нюансы

смысл горутин и ... в том, что ты создаешь иx столько - сколько надо

Почитайте еще про горутины и требуемые им ресурсы, различия между io- и cpu-bound задачами.

У горутин бывает интересное поведение в зависимости от их кол-ва. Была в свое время классная статья с анимашками, по ней можно было грубо прикидывать сколько и как запускать для достижения нужного результата не в ущерб производительности. https://divan.dev/posts/go_concurrency_visualize/

а если не сложно - выпили запуск в go-рутинах

оставь последовательный, там явно должно быть меньше по времени

а с "github.com/json-iterator/go" будет еще быстрее

я выпилил, в полтора раза примерно дольше получилось. Шедулер - молодец

Правильно, как заметили выше у вас слишком большое кол-во горутин, шедулинг между горутинами/потоками не бесплатный, плюс ещё в go unmarshall использует рефлексию, а это медленно (поправьте если ошибаюсь)

лучше сравнивать через time.Since(started) без преобразования к UnixMicro() потому что в вашем случае вы сравниваете абсолютное время, а в случае time.Since() будет использован монотонный таймер.

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

У вас принципиально разные алгоритмы. Один создает миллион горутин, по 2 килобайта каждая, это 2 гига памяти и выполняет на M:N шедулере, а другой выполняет стрим задач из генератора на пуле потоков размером в количество ядер, так что затраты на память и оркестрацию минимальны. В принципе из-за разной асинхроной модели сранить перфоманс го и .net очень сложно, в зависимости от вида бенчмарка будет выигрывать то один, то другой.

Очевидно так! Это была ирония, ибо создавать миллионы Task для выполнения cpu-bound операций, даже писать такой код мучительно больно :) А в .NET таки есть экспериментально грин-треды, но производительность у них похуже async/await.

Даже если создать миллион таких тасок, они займут минимальное количество памяти(чуть больше 24 мегабайт), так что в таком варианте теста .net всеравно будет выигрывать. Есть даже статья как раз с таким замером, но несовсем корретным кодом

https://pkolaczk.github.io/memory-consumption-of-async/

Это о чем статья? О скорости работы дефолтных json парсеров?

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

Фигасе у тебя перерыв: до этого последний раз публиковался в 2011. Чего раньше на связь не выходил, опытом не делился ...?)

Вроде пользователи никаких обязательств при регистрации не дают по регулярному развлечению друг друга.

Во-первых, php очень помогает jit(скорее всего) в данной задаче. Во-вторых, тест немного не честный, потому что в пример с Go json мапится на объект, а php выплевывает массив. Неплохо было бы протестировать кейс с Go и map. И дефолтный парсер в Go - это не самое быстрое решение, так что о скорости языка тут мало что сказать можно. Плюс библиотека для парсинга на Go написана на нем же, а у php - это C модуль, что не совсем честно

php выплевывает массив

Мягко говоря, это не совсем так.

К тому же когда мы десериализуем объект в структуру, используется reflect, а он весьма медленный

Да, так и есть, и это ещё один недостаток go.

В чем недостаток? Это известное поведение стандартной бибилиотеки, и в подавляющем большинстве случаев оно вполне приемлемо.
Если я пишу какой-нибудь REST, то понимаю, что работа с БД все равно намного медленнее, ну так и какой мне смысл придумывать супербыструю обработку JSONов, если я на этом выиграю в лучшем случае около 1%, зато потеряю в читаемости кода и увеличу возможность сделать ошибку?
Вот если мне нужен сервис, который перелопачивает многомегабайтные JSONы и это его основная задача, то тут да, имеет смысл поискать альтернативы стандартной библиотеке и пожертвовать простотой кода. Но как часто такое нужно?

Вот именно в том недостаток и заключается, что вам приходится выбирать: красивая у вас будет работа с json или быстрая.

в смысле, наличие выбора - недостаток?

В смысле что нет варианта, удовлетворяющего обоим требованиям.

Категоричненько...

easyjson, go-json и пара десятков альтернатив, конечно, идут нафиг(

Golang не убивает PHP, а делает его сильнее.

Да, так и есть, и это ещё один недостаток go.

Недавно тоже делал замеры программы на Go. Написал простенький алгоритм, который преобразует числа в слова (например "15" в "пятнадцать" с поддержкой двух языков). Консольное приложение.

Замерял через баш скрипт. Сразу получил какие-то дикие числа в 200 мс в среднем за один прогон. Потом выяснилось, что баш скрипт даёт оверхед на виндовсе. Переписал скрипт за мера да Go. Число упало до 60 мс в среднем за прогон.

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

Убрал учёт первого прогона. Получил уже более приемлемые 5-6 мс за прогон в среднем.

Но и тут я был очень не доволен. "быстрый язык, говорили они". Какой же он быстрый, если на таких простых операциях расходиет 5 мс? - подумал я.

Скоприрол код на Ubuntu сервер, скомпилировал и запустил. Средний прогон выполнялся за 1 мс, даже с учётом инициализации, которая заняла 5 мс. Вероятно даже алгоритм работал быстрее, но у меня заканчивалось свободное время и я не стал адаптировать замерочный скрипт под микросекунды.

Загуглил, и да, на форумах пишут, что Go больше заточен под *nix системы.

Отсюда, возникает вопрос к автору: если все говорят, что Go быстрее PHP, но у вас по вашим замерам выходит наоборот, что вы сделали, что бы понять почему ваш результат отличается от общего мнения?

Что-то вы смешали в кучу коней, людей и горящие избы.
Swoole-корутины не работают для json_decode. Они помогают асинхронно выполнять блокирующие вызовы.

Если взять ваш код и убрать все, что касается swoole, он будет работать на четверть быстрее. В примере я поставил 10кк вместо 1ккк, чтобы сильно долго не ждать.

<?php

// ...
// тут пхп-код из статьи
// ...

$start = microtime(true);

$cnt = 10_000_000;

$fp = fopen(__DIR__ . '/test.json', 'rb');
if ($fp === false) {
    echo 'Error' . PHP_EOL;
    return;
}

$result = '';
while ($line = fread($fp, 1024)) {
    $result .= $line;
}

for ($i = 0; $i < $cnt; $i++) {
    $decoded = json_decode($result);
    unset($decoded);
}
fclose($fp);

$end = microtime(true);

file_put_contents('php://stdout', sprintf("%.3fms \n", ($end - $start)));

и получил:

41.331ms 
31.497ms 

Ускорить json_decode, по идее, можно через pcntl_fork. Но, это в любом языке так можно.

Ну и в целом, итоги статьи бессмысленны чуть более, чем полностью. Взяли рандомную функцию, прогнали ее 1kkk раз и сделали выводы. А почему не сравнили с ffjson или easyjson в golang?

CPU-bound операции надо параллелить на физическое количество процессоров. Только тогда будет толк. Выше приводил пример, толк есть, более чем в 10 раз быстрее извращений с горутинами.

Основная беда php (на мой взгляд) не в php, а в овощной грядке, которая его использует. Я не понимаю в чём феномен, но разработчик на php обычно не то что решить проблемы не может, он их даже не пытается решать. Какие там кубернетесы, докеры, господи. Слава яйцам если он сам себе IDE настроить сможет.

Хотя может мне по жизни не везло на пхп-шников.

Какой отвратительный снобизм. Как же хорошо, что карма у вас соответвует)

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

НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь

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

Насчет Golang. Язык хороший, но для своих локальных целей. Если вам нужен скрипт который на сайте получает несколько переменных, и на их основе делает запрос к базе данных, то берите просто PHP и все. Плюс на любом виртуальном сервере провайдеров php из коробки идет.

Проблема PHP в том, что в его фонде люди в плане маркетинга слабые работают.

Ну Go продвигает аж сам Google. Неиронично тяжело с таким состязаться в маркетинге. Практически невозможно)

А часто вы такие вещи из примера на Пыхе пишите?

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

Когда на PHP проект начинается с того, что берём Симфони и ставим пару бандлов и побежали на го уже надо будет дофига всего писать руками.

Согласен, что этот пример не доказывает, что PHP быстрее чем Golang, но при этом, он развеивает миф, что golang однозначно лучше PHP.

Вот за это вас и не любят (с) какой-то известный анекдот.

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

Теперь к собственно коду: мы наспавнили миллиард (буквально) горутин и ожидаем... что мы, собственно, ожидаем? Что аллокации памяти убьют всю производительность, а планировщик не вывезет, и мы получим производительность, сравнимую с линейной? Ну, собственно, так и есть:

package main

import (
  "encoding/json"
   "fmt"
   "log"
   "os"
   "sync"
   "time"
)

type Test struct {
   Test string `json:"test"`
}

func main() {
   // var data []byte
   // var err error
   data, err := os.ReadFile("test.json")
   if err != nil {
      log.Fatalln("Error reading file ", err)
      return
   }
   start := time.Now()
   wg := &sync.WaitGroup{}
   for i := 0; i < 1000000000; i++ {
      wg.Add(1)
      go func() {
         defer wg.Done()
         var test *Test
         err = json.Unmarshal(data, &test)         if err != nil {
            fmt.Println("Error reading file ", err)
            return
         }
      }()
   }
   wg.Wait()
   end := time.Now()
   fmt.Printf("%dms \n", end.Sub(start)/time.Millisecond)
}


Поправил, теперь запускается. На моей машинке - 911345мс (видимо, она примерно вдвое медленнее вашей.

Хорошо, пробуем написать по-человечески:

wg := sync.WaitGroup{}

for k := 0; k < runtime.NumCPU(); k++ {

   wg.Add(1)

   go func() {

      defer wg.Done()

      for i := 0; i < 1000000000/k; i++ {

         var test Test

         if err = json.Unmarshal(data, &test); err != nil {

            log.Fatal(err)

         }

      }

   }()

}

184634ms. Ускорились в 4,5 раза. Если поднапрячься и подумать, еще в пару раз, думаю, ускориться можно.

Классика, доказываем "превосходство" языка, сравнивая производительность на максимально кривой реализации оппонента (надеюсь, хоть не специально?).

Если уж хочется более-менее адекватного сравнения производительности: https://www.toptal.com/back-end/server-side-io-performance-node-php-java-go

Ну либо перевод тут: https://habr.com/ru/companies/vk/articles/329258/

Класс.

А почему тогда взяли стороннее решение swoole, а в go не сделали нормальный воркер пул и какой-нибудь сторонний производительный пакет для json Unmarshal?

Теперь жду следующую статью — тест на регулярки в go и php)

 тест на регулярки в go и php)

А что в go какие-то свой регулярки? КМК смысла сравнивать особо нет, в PHP libpcre/libpcre2 как и в 90% остального софта

Меня больше всего заинтересовал два последний пункт:

Показать пример unit-экономики, разделения продуктовых фич, расчета стоимости разработки и содержания проекта на разных языках программирования. (Опираться буду на PHP и Golang)

Мне будет интересно почитать.

А вы не пробовали оптимизировать ваш код на го?

wg.Add(1000000000) за пределы цикла, не использовать стандартный json.Unmarshal, например

Тяжелая судьба у PHP, мало того что язык постоянно "умирает", так еще кто только не грозился его убить, всякие питоны, руби и спринги, потом ноджз, а теперь вот го

По факту вы использовали в PHP вызовы функций которые под капотом написаны на C/C++. И это одно из достоинств PHP.

Однако тест не совсем честен:

  1. В Go используется самая медленная библиотека для работы с json. Возьмите jsoniter. jsoniter.ConfigFastest.Unmarshal(data, &response) .

  2. Ограничьте пулл.

  3. И я бы все же тестировал синхронный код.

Мдя, автор запустил 1000000000 горутин -- это кратно замедлило go, поэтому (нужно запускать столько же горутин, сколько ядер в процессоре). Выбрал самый медленный сериализатор json, который идёт из коробки. Для php выбрал один из самых быстрых асинхронных фреймворков(тогда уж и в php использовали инструменты, только из коробки) и потом такой: ой, чет go медленнее php...

По мне го хорош тем что на нём просто больше возможностей. Можно писать программы под линукс, можно писать микросервисы. Но порог вхождения в профессию просто невозможен сейчас. Это либо идёшь джуном куда-то, либо начинаешь писать в своей компании и уже с опытом идёшь куда-то. Сам пытаюсь вкатиться в го, но попросту не рассматривают из-за отсутствия коммерческого опыта. Везде нужно минимум 2-3 года опыта. Хотя я занимаюсь разработкой больше 8 лет и перейти на другой стэк займёт минимум времени.

PS если кто-то ищет го разработчика, пишите)

Публикации

Истории