Создатель Node.js: «Для серверов я не могу представить другой язык кроме Go»

Автор оригинала: Pramod HS
  • Перевод
Перевод отрывка из интервью с создателем Node.js Раяном Далом (Ryan Dahl) в котором обсуждается модель программирования и язык Go.

— Расскажи нам, как проходила начальная разработка Node? Это ведь уже было достаточно давно, ты создал Node в 2009.

Райан: — Я думаю для себя лично, что нет лучшего момента в жизни, когда ты, как это говорится, «в потоке» и у тебя есть идея, в которую ты сильно веришь. И при этом есть время, чтобы сесть и на самом деле работать над ней. И я думаю, Node была именно такой идеей, которая ждала, чтобы её кто-то ухватил, и если бы не я, то это бы сделал кто-то другой. Но так случалось, что я был тогда достаточно свободен от работы и имел время, и мог работать нон-стоп несколько месяцев, которых как раз были нужны, чтобы выкатить начальную версию. Так что да, это был отличный период.

— Отлично, супер. Node построена на идее «полностью асинхронной» модели программирования. Удачна ли она была для Node?

Райан: — Да, это очень интересный вопрос. Уже вот несколько лет, как я не работаю над Node, ну, где-то, примерно с 2012 или 2013. И Node, конечно же, большой проект сегодня. Поэтому да, я думаю… когда она только вышла, я разъезжал и выступал на конференциях, пытаясь убедить людей, что они должны… что может быть мы совершенно неправильно делаем I/O подсистему и что, может быть, если бы всё начали делать в неблокирующем стиле, мы бы смогли решить огромное количество сложностей в программировании на Node. Ну, как, например, мы могли бы забыть про потоки полностью и использовать только абстракции процессов и сериализованную коммуникацию между ними. Но в одном процессе мы могли бы обрабатывать сразу много запросов, будучи полностью асинхронными. Я был ярым сторонником этой идеи в то время, но через пару лет, я понял, что это была не самая лучшая идея, решающая все проблемы в программировании. В частности, когда вышел язык Go… точнее, Go вышел достаточно давно, но когда я впервые начал о нём слышать, это где-то около 2012-го, у них, на самом деле, был очень приятный runtime, в которых были нормальные «зеленые потоки» и они реально строили абстракции вокруг них, и я начал думать про блокирующий ввод/вывод опять — ну, «блокирующий» в кавычках, — потому что это опять же все в «зеленых потоках»… между Go и операционной системой, так что по сути, я думаю, это всё же неблокирующий ввод-вывод.

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

— Да, мне тоже нравится модель программирования в Go. Использование горутин намного легче и fun. Мы, кстати, тоже Go используем на работе для распределённых систем.

Райан: — Да, я думаю, что для определённого класса приложений, как например, серверы — если вы пишете сервер, я не могу представить другой язык кроме Go. В общем, неблокирующая парадигма в Node работала очень неплохо для JavaScript, там где у вас нету потоков. И я думаю, что многие из тех проблем с callback hell, когда вам нужно прыгать в кучу разных функций чтобы закончить то, что вы делаете, в эти дни достаточно неплохо решены, с помощью async, например, который сейчас есть в JavaScript. То есть, как бы, новые версии Javascript делают жизнь немного проще. Учитывая всё это, я бы сказал, Node не лучшая система для массивных веб-серверов, я бы использовал Go для этого. И, если честно, это вобщем-то причина, почему я ушел из Node. Это было осознание: ох, ну реально, это далеко не лучшая система для серверного софта.

Да, я думаю, что Node на самом деле реально себя показала, как это ни странно, на клиентской стороне. Вроде скриптинга для построения веб-сайтов, или browserify, или bundles для клиентского Javascript кода. Ну или что можно делать всю эту серверную часть обработки клиентского Javastipt-кода. А потом, ну знаете, может быть небольшой сервер, чисто для девелопмента, там и тут, а потом может и настоящий сервер в продакшн, который будет получать реальный трафик. Node может быть полезной, или это может быть просто ваш выбор. Но если вы пишете распределённый DNS сервер, я бы не выбирал Node.
Поделиться публикацией

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

    0
    Зачем в заголовке запятая после слова «серверов»?
      +1

      Спасибо, это рефакторинг ) Оригинальная фраза была "Если вы пишете сервер, ...", но для краткости заголовка была заменена на "Для серверов", а запятая осталась.

      –26
      Статья супер, спасибо. Буду тыкать носом в этот пост упоротых асинхронщиков и адептов js на сервере.
        +27
        я упоротый асинхронщик, куда именно в этой статье вы предлагаете меня ткнуть?
          –2
          В статью целиком, очевидно. Автор nodejs признает, что go намного круче его школьной поделки
            +2
            А статью почитать?
            >Да, я думаю, что Node на самом деле реально себя показала, как это ни странно, на клиентской стороне. Вроде скриптинга для построения веб-сайтов, или browserify, или bundles для клиентского Javascript кода. Ну или что можно делать всю эту серверную часть обработки клиентского Javastipt-кода. А потом, ну знаете, может быть небольшой сервер, чисто для девелопмента, там и тут, а потом может и настоящий сервер в продакшн, который будет получать реальный трафик. Node может быть полезной, или это может быть просто ваш выбор. Но если вы пишете распределённый DNS сервер, я бы не выбирал Node.

            То есть конкретно ОН не выбрал бы ноду для распределенки. Ну ок. От его школьной поделки в современной ноде уже ничего не осталось. Странно вообще видеть среди программистов — оплот логики и здравого смысла — такую тягу к авторитаризму.
            Вот тут списочек, сами выберите по вкусу:
            Подчинение авторитету
            Предвзятость подтверждения
            Субъективное придание значимости
            Эффект знакомства с объектом
            Рационализация после покупки
              0
              От его школьной поделки в современной ноде уже ничего не осталось.

              Чего конкретно не осталось? Обёртки над V8 для применения JS в обычном окружении современной ОС, а не в песочнице браузера?

                +3
                Вот репа ноды github.com/nodejs/node, Раян отошел от дел в 2012, качайте репу, делайте что то вроде git whatchanged --since=«5 years ago» и посмотрите как много там осталось нетронутого.
                На гитхабе — полторы тысячи контрибуторов, вот тут nodejs.org/en/foundation/members мемберы Node.js Foundation. Обертка для v8 — это просто идея, и как идея она простая. Реализация посложнее будет.
        +10
        В транскрипции интервью нужно было избавиться от бесконечных «то есть», «в общем», «как бы» и прочих фраз-паразитов. С ними очень неприятно читать.
          +5
          да и сам перевод как будто через гугл транслейт
            +5

            Там текст достаточно сумбурный сам по себе, прямая речь.

            +3

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

              +3

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

              +11
              C async/await жизнь реально наладилась, а тут на тебе, переходи на go говорят!
              Ну фреймворк на ноде есть хороший уже, это я про koa2, еще бы cms уровня wordpress и вообще круто было бы)
                –6
                То, что вы купились на рекламу Node.JS — ваши проблемы. А советская модель «отучился в универе, больше учиться не надо, я теперь специалист» больше вообще нигде не работает, особенно в IT.
                  +5
                  Ну у меня то выбор был простой, один язык и на клиенте и на сервере, плюс пакетный менеджер (npm), который теперь тоже и на клиенте и на сервере (это я про смерь bower-а)
                    +7
                    Откуда вы взяли такую «советскую модель»? Даже Ленин призывал в своих публикациях непрерывно учиться.
                      –6
                      Учиться, учиться и еще раз учиться — это лучше, чем работать, работать и еще раз работать. Ленин.
                        +2
                        Это Стейтем сказал, а не Ленин
                          +2
                          Вы все неправы.

                          Всегда говорит Аллах: «Надо учиться, учиться, надо учиться». Мы День знаний не отменили, потому что хотели побольше рассказывать про наш праздник религиозный, и чтобы они [школьники] пришли 1 сентября в школу, которую они ждали, и чтобы побольше был баракат, побольше было всего лучшего для наших детей.

                          Рамзан Кадыров, глава Чечни
                            0
                            Не знаю почему вы так думаете, мне лично Ленин это говорил.
                    +7
                    Да, я думаю, что Node на самом деле реально себя показала, как это ни странно, на клиентской стороне.


                    Буквально год назад задумался о том, а к чему идёт вообще нода и правильно ли её использовать на сервере? Пришёл к выводу, что нода показывает себя сильнее всего на данный момент в фронте и перешёл на го. Как же я, оказывается, был прав.
                      +11
                      То, что ваше мнение совпало с мнением другого разработчика не свидетельствует о правильности мнения. Тут вообще нельзя быть правым или не правым в таком субъективном вопросе.
                      +16

                      Ну все, пойду переписывать все свои Node.js бэки на Go. А если серьезно, как-же достала это выдуманная проблема с языками и платформами (особенное с очень близкими по производительности). Т.е. выбор языка по бенчмаркам куда предпочтительнее, чем скорость разработки функционала / экосистема / синтаксис / высокоуровневость?


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

                      async function thingA() {
                        // magic
                      }
                      
                      async function thingB() {
                        // magic
                      }
                      
                      (async function main() {
                        await thingA(); // И подождет ошибку, и сам упадет
                      
                        try {
                          await thingB(); // Сделает штуку Б
                        } catch (err) {
                          // Проверит ошибку
                        }
                      })()
                        .catch((err) => {
                          console.error(err);
                      
                          process.exit(1);
                        });

                      Действительно, это гораздо сложнее и меннее читаемо чем постоянные if err != nil.


                      «зеленые потоки»

                      Event-loop не сравляется? Ок, fibjs, node-fibers, они должны быть чуть побыстрее. (нам ведь это реально нужно).


                      Но если вы пишете распределённый DNS сервер, я бы не выбирал Node.

                      А я бы выбрал C, нам ведь важны только бенчмарки, да?

                        0
                        А может нам важна золатая середина? Не Node и не C, а Go?
                          +16

                          Вы не правильно поняли мой посыл. Я не агитирую ни за Node, ни за Go. Я говорил, что есть вещи куда важнее, чем мериться бенчмарками и бесконечно переписывать с языка на язык (не забывайте, что скоро Rust станет меинстримом, и как заживем, как начнем рефакторить-переписывать :)).

                            +9

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

                          +10

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

                            +2

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


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


                            Knowledge debt сам себя не погасит с таким подходом. :)

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

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

                                –1

                                И опять же, полностью согласен с вами в общем случае. Я тут как-то делал перевод статьи одного хаскелиста, который (как это принято) высмеивает Go, но, который, при этом признает, что он может попросить коллегу выучить Go за выходные, но не может это же сделать с Haskell или Purescript.
                                https://habrahabr.ru/post/270707/


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


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

                                  +6
                                  Большинство программистов могут освоить язык и самые основные вещи из стандартной библиотеки за выходные (благодаря Go Tour, например). Как правило через неделю новый разработчик, никогда не писавший на Go, уже смело может контрибьютить в проект. Конечно, бывает и дольше, но бывает и быстрее.

                                  Не вижу проблем сделать это же, например, с python, ruby, java, nodejs и кучей других языков. В свое время, когда я только был студентом я осилил python за 15 минут. А вот с go у меня такое не получилось. Например, в силу того, что в отличии от python и golang странные для меня архитектурные решения в плане GOPATH (я тогда еще не знал, как нормально завести GOPATH везде и уж очень намучался с аналогичной проблеме в CUDA) и зависимостей, ну это такое.


                                  Ну и дополнительно:


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

                                  Эта штука работает только если проект на pure Golang, но в современной разработке очень популярна тенденция фрейворков и как только golang окончательно придет массы (например, с go 2.0), то оно все обернется кучей фрейворков и получится такая же проблема как и с другими языками.

                                    –1
                                    Не вижу проблем сделать это же, например, с python, ruby, java, nodejs и кучей других языков.

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


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

                                    Ага, некоторые люди прямо так и говорят — "я считаю, что Go ещё сырой, потому что под него мало фрейморков для микросервисов, в отличие от Scala, например".
                                    Но ничего не может быть дальше от истины, чем это утверждение. Это как говорить "я считаю Tesla еще сырая, потому что там нет отверстия для бензобака".


                                    Вы никогда не задумывались, почему создаются фреймворки? По-сути, фреймворк это "язык в языке", для решения специфической проблемы. Прелесть Go в том, что он зародился в 2007-м, в отличие от остальных мейнстримовых языков, которые уже по 20-30+ лет. Он родился тогда и в том месте, где такие вещи как микросервисы/RPC/JSON/криптография и т.д. были жизненной ежедневной необходимостью, поэтому практически всё это есть в стандартной библиотеке.


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

                                      +7
                                      Почему? Вам кажется, что у всех языков одинаковая сложность и порог входа?
                                      Если да, то это странная точка зрения.

                                      Нет, у c++, scala, clojure, haskell значительно выше порог. Но это я к тому, что golang не пионер по низкому порогу входа.


                                      Ага, некоторые люди прямо так и говорят — "я считаю, что Go ещё сырой, потому что под него мало фрейморков для микросервисов, в отличие от Scala, например".
                                      Но ничего не может быть дальше от истины, чем это утверждение. Это как говорить "я считаю Tesla еще сырая, потому что там нет отверстия для бензобака".

                                      Вы никогда не задумывались, почему создаются фреймворки? По-сути, фреймворк это "язык в языке", для решения специфической проблемы. Прелесть Go в том, что он зародился в 2007-м, в отличие от остальных мейнстримовых языков, которые уже по 20-30+ лет. Он родился тогда и в том месте, где такие вещи как микросервисы/RPC/JSON/криптография и т.д. были жизненной ежедневной необходимостью, поэтому практически всё это есть в стандартной библиотеке.

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

                                      Верно. И где же в golang таки фичи, как:


                                      1. ORM
                                      2. Построение web приложений при помощи MVC
                                      3. Traceback и умная работа с ошибками.

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


                                      Да, для этих вещей уже написали отдельные framework на golang, но проблема в том, что как и для каждой новой технологии необходимо, что бы они поддерживались не только энтузиастами, которые могут в любой момент просто сказать "я устал, я мухожух" и оставить проект в непонятном состоянии, но и какими-то компаниями. Есть ли что-то из первого или второго пункта с такой поддержкой? Я вот не нашел, может вы подскажите.


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

                                        –9
                                        И где же в golang таки фичи, как:

                                        ORM
                                        Построение web приложений при помощи MVC
                                        Traceback и умная работа с ошибками.



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


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

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

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

                                          А вы предлагаете работать с базой при помощи чистых sql запросов? Это мало того, что не удобно, это еще делает ваш код базо-ориентированным и приводит к куче проблем с производительностью, безопасностью и прочему. Да, программисты не любят orm за странные вещи и то, что некоторые части таки приходится переписывать на чистый sql. Но писать все приложение на sql — это безумие.


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

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


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

                                            +3
                                            В последнем проекте я пишу чистые SQL запросы. Неудобно? Пока не вникнешь в основы SQL, а они элементарные, особенно если полистать параллельно книгу Кристофера Дейта «SQL и реляционная теория».
                                            это еще делает ваш код базо-ориентированным

                                            А каким он ещё должен быть? Если вы используете базу, значит работаете с данными.
                                            Да, программисты не любят orm за странные вещи и то, что некоторые части таки приходится переписывать на чистый sql. Но писать все приложение на sql — это безумие.

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

                                            Вот это уже полная ерунда. Все нормальные библиотеки для работы с базами предохраняют от SQL-injection на 100%, а грамотно написанный SQL ВСЕГДА быстрее работает, чем запросы от ORM.
                                              +3
                                              Понимаете, какая дилемма: ORM не лажает в простых случаях, в которых написать на чистом SQL тоже не так сложно (хоть и дольше), а в сложных случаях всё сложно: хоть на SQL, хоть на ORM. В общем, как всегда: магии не случилось и ты получаешь то, за что платишь (в данном случае знаниями и трудом).

                                              Вы говорите так, как будто ORM работает как LINQ, вы упускаете вот такие штуки:


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

                                              И это все на чистом SQL делается только через страдания и шаблонный код. Ну или вы реализуете свою ORM.


                                              Вот это уже полная ерунда. Все нормальные библиотеки для работы с базами предохраняют от SQL-injection на 100%, а грамотно написанный SQL ВСЕГДА быстрее работает, чем запросы от ORM.

                                              1. Для сложных случаев грамотно написать SQL на проекте может, ну, 20% девов, так что лучше ORM.
                                              2. Подскажите, как выбора по id для всех полей может быть написана быстрее, чем select * from table where id=<id>? Однострочники, которые генерирует ORM вряд ли будут сильно быстрее. Разве что у вас большая таблица и вы заранее знаете, какие поля будете использовать. А потом будете страдать и каждый раз дописывать поле в запрос.
                                              3. psycopg2 нормальная? Для передачи параметров там нужно использовать специальную функцию, потому что простое cur.execute(query) не делает экранирования (что логично), а значит, если вы будете формировать запрос в обход предоставленного интерфейса, то у вас будет дыра.
                                                +1
                                                Универсального решения нет — где-то лучше ОРМ, где-то простые запросы или хранимые процедуры. По поводу же ОРМ — добавлю еще несколько проблем (решаемых, но все же):
                                                1. В ОРМ как правило есть только одна модель сущности — из-за этого запросы возвращают все поля, даже если реально нужно одно-два
                                                2. Бутстрап ОРМ в базе с большим числом обьектов может занять приличное время из-за чтения метаданных
                                                3. Если вам нужны hint-ы, тем более разные у разных клиентов — это проблема
                                                4. В ОРМ как правило есть кэш которым надо управлять если базой пользуется кто-то еще
                                                5. Управление транзакциями в ОРМ может быть довольно нетривиальным
                                                  0

                                                  К сожалению, вы правы. В защиту orm могу сказать, что значительная часть ORM позволяет вам взять только отдельные поля, если это вам сильно нужно, ну и не везде есть кеш.


                                                  Но транзакции это да(

                                                  +2
                                                  Для сложных случаев грамотно написать SQL на проекте может, ну, 20% девов, так что лучше ORM.

                                                  Одного решения для всех случаев нет.

                                                  В сложных случаях на больших проектах программист идёт к DBA и рыдает в жилетку, а тот его выслушивает, делает чашку кофе, и пишет километровое CREATE VIEW (или, в особо сложных случаях, хранимую процедуру, возвращающую датасет). После чего получившееся уже можно использовать как угодно, хоть напрямую, хоть через ORM.

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

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

                                                  P. S. psycopg2 абсолютно нормальная, просто не надо давать её использовать напрямую. Откройте к ней фиксированный интерфейс и принимайте только параметризованные запросы.
                                                    0
                                                    Для подавляющего большинства таблиц эти ваши ID (a.k.a. суррогатные первичные ключи) нафиг не нужны и жрут ресурсы, вынуждая делать множество JOIN по любому поводу. А всякие тупые ORM (типа Django ORM) просто вынуждают так делать.
                                                    Яркий повод — вспомогательные таблицы для отношений Many-To-Many, где ID не нужен, а первичным ключом может служить комбинация foreign key
                                                      0

                                                      Спорный вопрос, ведь есть же нормальные формы (если я вас правильно понял), зачем-то их придумали.


                                                      Если у вас такой проект, то может стоит выкинуть реляционку и добавить что-то документно-ориентированное?

                                                        0

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

                                                          0
                                                          Один пример с Many-To-Many я уже привёл.
                                                          Натуральные (иногда композитные ключи) могут заменить суррогагные почти всегда. Исключение — личные данные людей (даже комбинация фамилия, имя, отчество, дата_рождения не может по-хорошему считаться уникальной) или когда натуральный ключ получается совсем уж развесистым, что скажется на производительности.

                                                          Например, есть таблица authors и таблицы books.
                                                          Если таблица authors однозначно сопоставима с таблицей persons (где хранятся имена/фамилии людей и уже есть суррогатный ID по причинам, описанным выше), то первичным ключом для authors может быть foreign key на persons.
                                                          Для таблицы books можно взять в качестве первичного ключа ISBN (он сюда так и просится) или комбинация дата_публикации, название или что-то ещё.

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

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

                                                            "Для изданий, выходящих малым тиражом (в издательской практике 2003 года — до 1 000 экземпляров), либо для «личного» использования присваивать номер ISBN необязательно."


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


                                                            "Международная стандартная нумерация книг не распространяется на" и там длинный список.


                                                            https://ru.wikipedia.org/wiki/Международный_стандартный_книжный_номер


                                                            Давайте ещё пример.

                                                              0
                                                              Не проблема. Значит будет другой ключ.
                                                                0

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

                                                        +1
                                                        Для сложных случаев грамотно написать SQL на проекте может, ну, 20% девов, так что лучше ORM.

                                                        я бы сказал так: 20% разработчиков понимают, какой ужас делает ORM и могут написать в разы быстрее. Остальные — пишут примерно на уровне ORM.

                                                        Хотите скорости — пишите на SQL, хотите гибкости в ущерб скорости — используйте ORM. Ну и отсылаю к замечательной книжке бывшего коллег "Дефрагментация мозга".
                                                          0

                                                          А можно получить и скорость и гибкость, если не цепляться за РСУБД, а взять графовую СУБД.

                                                            –1

                                                            Вы как-то противопоставляете ORM и вручную написанный SQL. ORM ничего не говорит о том как получается SQL при работе с объектами. Как-то. Хотите пишите вручную, хотите — генерируйте каким-нибудь квери-билдером на основе метаданных из ОРМ.

                                                          0
                                                          это еще делает ваш код базо-ориентированным

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



                                                          Проблема в том, что код становится ориентированным на конкретную базу данных. Захотел сменить MySQL на Postgres? Переписывай большинство запросов.


                                                          грамотно написанный SQL ВСЕГДА быстрее работает

                                                          … только если его пишет специалист с опытом работы с конкретной базой данных, разбирается в планах и индексах и вообще полу-DBA. Запрос, работающий быстро в Firebird, тормозит как черепаха в MSSQL, например. И наоборот.


                                                          Все нормальные библиотеки для работы с базами предохраняют от SQL-injection на 100%

                                                          Только до тех пор, пока программист не пытается программно сгенерировать сам SQL.

                                                            +1
                                                            Захотел сменить MySQL на Postgres? Переписывай большинство запросов.

                                                            И как часто вы меняли базу?
                                                              0
                                                              За последние 10 лет трижды менял и раз 5 отговаривал тех, кто слушая моё «нытьё» про мускуль и мскуль, говорили «так давай поменяем — пары недель хватит без отрыва на текущие задачи?». В двух случаях была ОРМ для, как минимум, бизнес-логики. После третьего стал отговаривать, если её не было.
                                                                0
                                                                Interbase->Firebird, MySQL->Postgres, Firebird -> MSSQL… Слишком часто. Веб-проекты вообще часто вынуждены были подстраиваться под то, что даёт провайдер, или под объём памяти на инстансе.
                                                              +1
                                                              Если я использую базу данных в качестве хранилища данных, это ещё не значит, что моё приложение ориентируется на неё. Стараюсь писать так, чтобы базу данных можно было «моментально» заменить на файловое или веб-хранилище, на хранилище в памяти, на что угодно, что можно свести к коллекции объектов или подобных им структур данных, тесно связанных с поведением системы.
                                                              –2
                                                              А вы предлагаете работать с базой при помощи чистых sql запросов?

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


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

                                                              И что? У вас тоже нет гарантии, что компании, которые вы так тут хвалите (мы все знаем, на какую компанию вы намекаете, но опустим этот момент), не обанкротятся и не забросят библиотеки. Абсолютных гарантий нет, но open-source коммьюнити оказывается часто гораздо более живучи и адекватны многих "компаний". Если следовать вашей логике, то Linux как явление вообще не могло существовать — нет же гарантий, что разработчики-энтузиасты не забросят его. А он есть.


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

                                                              Вы, скорее, пытаетесь подтвердить свой confirmation bias, а не разобраться и судить о проблеме объективно.

                                                                –1
                                                                Именно. Попробуйте, это не так сложно, как кажется, но в разы более надежно, особенно когда приходится отлаживать проблемы. ORM очень спорная штука — далеко не всегда базу можно красиво "замаппить" на объектную модель языка. SQL не так страшен, как вам может казаться.

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


                                                                И что? У вас тоже нет гарантии, что компании, которые вы так тут хвалите (мы все знаем, на какую компанию вы намекаете, но опустим этот момент), не обанкротятся и не забросят библиотеки. Абсолютных гарантий нет, но open-source коммьюнити оказывается часто гораздо более живучи и адекватны многих "компаний". Если следовать вашей логике, то Linux как явление вообще не могло существовать — нет же гарантий, что разработчики-энтузиасты не забросят его. А он есть.

                                                                Компанию дают хоть какую-то гарантию. Например, spring и django курируются компаниями. Linux для бизнеса и не существовало, пока не появились такие штуки как Linux Fundation, Canonical, RedHat. А гарантом того, что они не обанкротятся служат другие компании, которые их спонсируют и используют их продукты.


                                                                Open-source часто оказывается, а часто и не оказывается. И играть в такую лотерею вроде бизнесу не хочется?


                                                                Вы, скорее, пытаетесь подтвердить свой confirmation bias, а не разобраться и судить о проблеме объективно.

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


                                                                Вот есть шикарная библиотека bumpversion, которая отлично работает и решает некоторые проблемы версионности и которая… не поддерживается с 2015 года. И много людей ее использует, даже делают форки, но в силу того, что pypi пакет никто не может отозвать ее развитие остановилось навсегда. И таких примеров можно найти достаточно.

                                                                  –6
                                                                  И таких примеров можно найти достаточно.

                                                                  Вы просто демонстрируете мой поинт. Вместо того, чтобы разобраться в теме, вы типичным cherry-picking-гом (это форма confirmation bias-а) находите пример, подтверждающий вашу точку зрения, и выдаете его за "доказательство". Если бы вы хотели разобраться объективно в теме — вы бы поискали библиотеки, которые не были заброшены, посчитали бы статистику и сделали выводы.
                                                                  Скучно.

                                                                    +3

                                                                    Когда у вас нет хватает аргументов, вы начинаете применять не очень честные приемы риторики?
                                                                    Чем мой confirmation bias хуже вашего? У вас так же нет статистики, данных или еще чего-то.


                                                                    Причем у меня это даже не confirmation bias, так как мой поинт в том, что полезные, крутые и удобные open-source проекты, которые не пользуются поддержкой спонсоров могут быть запрошены (а есть ли пример того же, но с поддержкой спонсоров, который пропал не по схеме "денег становилось все меньше и меньше?). А это дополнительные риски для бизнеса, так как тогда нужно будет выделять людей, которые будут поддерживать это решение и исправлять в нем недочеты.

                                                                      –5
                                                                      Чем мой confirmation bias хуже вашего?

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


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

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

                                                                        +1

                                                                        Ну окей, давайте на конкретном примере.


                                                                        Вот вы CTO крупной компании и начинаете новый проект. Это будет еще один web проект, с кучей интересных штук. Проект планируется на 5 лет.


                                                                        Вы можете взять известный вам уже язык (например, python) и знакомый стек технологий django + celery, который точно знаете, что еще довольно долго будут на плаву хотя бы по тому, что их используют и спонсирую крупные компании.


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


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


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

                                                                          –2

                                                                          Если вы как CTO свели выбор платформы к вашим субъективным оценкам гарантий риска смертности авторов ORM библиотеки (которую вы сами нагуглили и решили, что она единственная), то такой из вас себе CTO. :)

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

                                                                            1. Я указал, что этот вопрос не единственный.
                                                                            2. Я нашел где-то штук 7. Можно глянуть тут. Одна перешла к фреймворк (которых, как вы считали, go не нужно), еще одна вроде как не orm вовсе. А остальные поддерживаются одним человеком, я написал, что сделал выбора исходя из этого.
                                                                            3. Это не субъективные оценки. Это называется bus factor. И очень грустно, когда он внезапно не зависит от компании вовсе.
                                                                            4. Ну и самое главное. Можно было еще заметить, что я упомянул еще task queue, положение которых в golang еще хуже. Сказали бы хотя бы, что я свел выбор платформы к двум факторам, а это получается какой-то confirmation bias.
                                                                              –5
                                                                              Вы вправду не видите логической ошибки в вашей логике? Или специально накручиваете количество комментариев? :)
                                                                                +3

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


                                                                                В чем тут ошибка?

                                                                                  –5
                                                                                  • язык это не ORM
                                                                                  • гарантий абсолютных нет нигде
                                                                                  • нужные экосистеме библиотеки, которые забрасываются — это исключение
                                                                                  • это одинаково справедливо для любых языков
                                                                                  • и даже эти проблемы решаемы

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


                                                                                  Это же азбука логики.

                                                                                    +2
                                                                                    гарантий абсолютных нет нигде

                                                                                    Да? Вот я знаю, что Linux не загнется, потому что есть linux foundation, я знаю, что django тоже останется на плаву, потому что есть Django Software Foundation, я знаю, что с golang будет все в порядке, потому что есть Google, который его использует.


                                                                                    А кто работает с task queue на golang? Как много компаний используют orm? Большинство проектов вообще не используют sql, а всякие kv базы, как kubernetes, docker и так далее.


                                                                                    язык это не ORM

                                                                                    Язык это еще и инфраструктура, выработанные решения и фреймворки.

                                                                                      –5
                                                                                      Вот я знаю, что Linux не загнется, потому что есть linux foundation

                                                                                      Представляю как бы вы разглагольствовали до 2000, пока не было Linux Foundation :)


                                                                                      А кто работает с task queue на golang?

                                                                                      Все. А в ваших привычных языках для этого разве нужны отдельные библиотеки? :)


                                                                                      Большинство проектов вообще не используют sql, а всякие kv базы

                                                                                      Right tool for the right job. Или вы все пишете через ORM, там где даже не нужна реляционная модель данных?


                                                                                      Язык это еще и инфраструктура, выработанные решения и фреймворки.

                                                                                      Ага. Проблема-то в чём? Угадайте сколько за 4 года использования Go тысячами компаний в мире библиотек перестало поддерживаться и похоронило проект?

                                                                                        +1
                                                                                        Все. А в ваших привычных языках для этого разве нужны отдельные библиотеки? :)

                                                                                        А в golang отложенная очередь идет из коробки? зачем тогда вот этот проект? Ну и несколько таких же.


                                                                                        Right tool for the right job. Или вы все пишете через ORM, там где даже не нужна реляционная модель данных?

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


                                                                                        Ага. Проблема-то в чём? Угадайте сколько за 4 года использования Go тысячами компаний в мире библиотек перестало поддерживаться и похоронило проект?

                                                                                        Думаю, тысячи. И это только не известных. Загляните, например, вот сюда и посмотрите на количество проектов, которые уже не поддерживаются год или два. А это 9 из 17 проектов, если я правильно посчитал. Проекты, которые не получают спонсоров довольно часто пропадают, когда у автора пропадает желание им заниматься. И к сожалению, это естественно.


                                                                                        Представляю как бы вы разглагольствовали до 2000, пока не было Linux Foundation :)

                                                                                        А до Linux Foundation был redhat. Думаю, два года люди как-то пожили без linux)

                                                                                          –1
                                                                                          зачем тогда вот этот проект?

                                                                                          Это распределенный task queue c кешем и стораджем.


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

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

                                                                                            +3
                                                                                            что почти нереально в других языках

                                                                                            Простите, что? Вы когда-то использовали какие-то языки помимо go или только страшные статейки читали?


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

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

                                                                                              +1
                                                                                              Или каждый проект написанный на Golang отливается в граните и является идеальным с самого начала?

                                                                                              Конечно, это ж фича языка: "никаких новых фич после версии 1.0" :-D

                                                                                                0

                                                                                                У них там gc менялся вроде)

                                                                                                  +1

                                                                                                  Так это фича рантайма — её апгрейдить можно ;-)

                                                                                        0
                                                                                        Никакие фаундейшены не спасут, если что-то случится с Линусом Торвальдсом или Грегом Кроа-Хартманном. Они лично значат так много, что это будет сильным ударом по самому процессу разработки, не говоря уже о стратегическом развитии и пр.
                                                                                          0

                                                                                          Уверены? Судя вот по этой статье, весьма спасут. Да, это будет сложное время для Linux, но я думаю, они вполне справятся.

                                                                                      +1
                                                                                      Мне вот кажется, что все логично. Строить долгосрочные проекты на продуктах, поддержки которых вам никто не гарантирует довольно печальное занятие.

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

                                                                                      А для Go идеология Google — развивать и затягивать из открытых исходников к себе. А не развивать у себя, и открывать уже вовне.
                                                                                        0
                                                                                        А для Go идеология Google — развивать и затягивать из открытых исходников к себе. А не развивать у себя, и открывать уже вовне.

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

                                                                                          –1
                                                                                          У них там gc менялся вроде)

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

                                                                                            0

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


                                                                                            Если этой простой проект, который делает одну функцию, то его код может не меняется годами. Но если это сложный проект, например, orm, то тут все становится хуже.

                                                                                              –1

                                                                                              Вы когда нибудь работаете, или всё ещё ждёте, пока загрузится IDE?

                                                                                                0

                                                                                                Я использую Sublime, он быстро грузится.

                                                                                              0

                                                                                              Он может начать дико тормозить или падать с нехваткой памяти.

                                                                                              +1
                                                                                              И это совершенно отвратительно. Потому что тебе потом хочется предложить человеку новый фунционал, а у него библиотека docker-api отстает на несколько версий, как вот тут.


                                                                                              Что конкретно отвратительно? Вести разработку библиотек в открытом виде?

                                                                                              Если нет готового, сделайте форк этой утилиты — допишите API. Мне понадобился gowsdl, я просто поправил под себя.
                                                                                                0

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

                                                                          0
                                                                          Дело не в том, как работать с базой, дело именно в маппинге её на объектную модель. Вы, кажется, один из немногих, кто понимает, что ОРМ — это не инструмент, генерирующий под капотом скуль на основе аннотаций или конфигов рядом с основным кодом, а то и с помощью квери-билдера, малоотличимого от скуля. Мало кто понимает, что ОРМ может быть не универсальным, может быть написан на «чистом скуле», максимально оптимизированном, что если есть полноценные объекты и реляционная БД как средство обеспечения их персистентности, то ОРМ уже есть, даже если каждый запрос захардкожен, а поля объектов заполняются по числовому индексу.
                                                                    0
                                                                    Вы никогда не задумывались, почему создаются фреймворки? По-сути, фреймворк это «язык в языке», для решения специфической проблемы.

                                                                    Фреймворк — это как IDE, он объединяет несколько/множество инструментов для решения разнообразных задач. И совсем не обязательно, чтобы эти инструменты были необходимой частью фреймворка, напротив, если он не содержит этих инструментов, а служит лишь интерфейсом, то это вообще замечательно.
                                                                    Тоесть фреймворки в других языках существуют, потому что самого языка и его стандарного набора не хватает. В Go хватает.
                                                                    Фреймворки существуют, потому что делают процесс разработки быстрее и удобнее (Осторожно, вызывает привыкание). Если вам хватает стандартного набора Go, то зачем использовать «левые» пакеты?
                                                          –2

                                                          Кстати, по вашему примеру кода — чисто субьективные впечатления после Go:


                                                          • async/await просто лишний когнитивный балласт (в сравнении с Go, опять же)
                                                          • без комментария не понятно, что произойдет при ошибке в thingA(), пока не проскроллишь глазами в самый низ (и это на микроскопическом примере уже не очень приятно)
                                                          • try { } catch (err) { } конструкция гораздо более громоздка, голословна, и при этом менее ясна, чем if err := thingB(); err != nil {... }. 5 строк против 3, при нулевых бенефитах в данном случае, и еще и спрятанной магией передачи ошибки.
                                                          • снова же, приходится бегать глазами от начала main до конца, чтобы понять, точно ли не упустил ничего из этого flow-а программы, раскиданным в нескольких местах.

                                                          Ну это я так, набросил. Когнитивная нагрузка у JS таки выше.

                                                            +5
                                                            try { } catch (err) { } конструкция гораздо более громоздка, голословна, и при этом менее ясна, чем if err := thingB(); err != nil {… }. 5 строк против 3, при нулевых бенефитах в данном случае, и еще и спрятанной магией передачи ошибки.

                                                            Справедливости ради есть вот такая штука, которая значительно удобнее err !=nil. Более того, вы упускаете тот факт, что err!=nil вам нужно будет пихать после каждого вызова, который вам нужно обрабатывать, а в try блок можно завернуть большое количество строк кода и отловить ошибку в самом конце.


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

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

                                                              –2
                                                              Более того, вы упускаете тот факт, что err!=nil вам нужно будет пихать после каждого вызова, который вам нужно обрабатывать, а в try блок можно завернуть большое количество строк кода и отловить ошибку в самом конце.

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


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


                                                              • уменьшает локальность кода — вам нужно прыгать глазами вниз функции, чтобы вообще узнать, как обрабатывается ошибка и обрабатывается вообще, при этом держать в памяти вызов, пока вы прыгаете (когнитивная нагрузка)
                                                              • прячет от вас ясность того, что происходит и какой оверхед от передачи ошибки (она ж таки где-то там передается сама при throw, но у вас нет шанса это узнать, кроме как изучить внутренности устройства exceptions в JS)
                                                              • прячет от вас понимание того, вообще возвращает функция ошибку или нет
                                                              • создает лишнюю нагрузку на производительность и расход памяти (вам всего-то надо передать одну переменную, а рантайм там целые стектрейсы в фоне гоняет, выделяя память, и никакой видимости этого нет)
                                                              • ну и менее очевидное, но очень важное (и мое любимое), заставляет вас относится к ошибкам, как к "не сильно важному коду, который нужно засунуть подальше".

                                                              И вот в этом двухстрочном примере у вас же и выбора нет. Вы действительно должны использовать этот 5 строчный громоздкий блок с кучей проблем, потому что "удобный вам язык" предоставляет только такую модель. А в Go при желании можно сделать что угодно — "ошибки" это просто интерфейсные переменные. Можно даже try..catch… реализовать для тех кейсов где это нужно, но оно правда лишнее.

                                                                +4
                                                                Такие ситуации, действительно, имеют быть, но они чаще исключение, чем норма, да и для них в Go можно сделать красивые решения.

                                                                Но почему же тогда так не поступили вот тут, например? Или вот тут?


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

                                                                Зато увеличивает бессмысленность кода. Это отлично, когда каждая ошибка обрабатывается умно и отдельно, но мне кажется, что 99% ошибок в golang просто выкидывают в лог или прокидывают дальше.


                                                                прячет от вас ясность того, что происходит и какой оверхед от передачи ошибки (она ж таки где-то там передается сама при throw, но у вас нет шанса это узнать, кроме как изучить внутренности устройства exceptions в JS)

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


                                                                прячет от вас понимание того, вообще возвращает функция ошибку или нет

                                                                Спасает документация или такие штуки как в Java. Более того, документация вида raises в python мне нравится больше, так как она дает понимание какого рода ошибка вернется, чего не дает простой err в golang. Ну и да. Как вообще вот эта функция работает, если там в конце возвращает одна переменная, а не две? иногда ошибка, иногда нет?


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

                                                                Ну, а так вы пробрасываете ошибку и теряете весь traceback. На мой взгляд информация про ошибку значительно важнее мифических 4-8МБ.


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

                                                                Эм… а вы уверены? У вас так же 99% ошибок выкидывается. Как видите, это не помогло.


                                                                И вот в этом двухстрочном примере у вас же и выбора нет. Вы действительно должны использовать этот 5 строчный громоздкий блок с кучей проблем, потому что "удобный вам язык" предоставляет только такую модель. А в Go при желании можно сделать что угодно — "ошибки" это просто интерфейсные переменные. Можно даже try..catch… реализовать для тех кейсов где это нужно, но оно правда лишнее.

                                                                В других языках так можно делать тоже. В python их можно просто игнорировать, в java можно сделать метод для экранирования. А golang заставляет вас эмулировать traceback без самого traceback своими силами. Возможно идеология за этим стояла другая, но получилось у людей так. И какой тогда в этом смысл?

                                                                  –3
                                                                  Но почему же тогда так не поступили вот тут, например?

                                                                  Там вполне ок код — три возврата это не проблема. Мне бы было интересно, кстати, как бы вы переписали второй пример на эксепшенах :)
                                                                  Я как-то слышал аргумент про возвращаемые значения от людей, которые не сильно поняли разницу между возвращаемыми ошибками в Go и С: "Тот, кто работал с возвращаемыми кодами ошибок в ядре Linux, больше никогда такого не захочет". А потом попытался представить как бы ядро Linux выглядело, если было бы написано с помощью эксепшенов. Думаю, не было бы у нас Linux.


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

                                                                  Именно об этом и речь.


                                                                  но мне кажется, что 99% ошибок в golang просто выкидывают в лог или прокидывают дальше.

                                                                  И супер. А что еще делать с ошибками? Это самые популярные варианты, разумеется. Но это не просто "прокинуть дальше" — это логичный и понятный flow программы для программиста, который читает код. Не забыайте, что язык программирования это не столько язык для общения человека с компьютером, сколько человека с человеком.


                                                                  А так вам нужно изучать оверхед блока if

                                                                  И какой-же там оверхед? :) Одна CMP инструкция?


                                                                  Спасает документация

                                                                  Безусловно, это не была нерешаемая проблема. Можно написать специальные IDE для языка, которые будут это делать, можно даже автору кода позвонить и спросить, что функция должна вернуть. Только это высокая стоимость для такой обыденной операции, которую делаю миллионы программистов много раз за день. Фред Брукс это называет "добавленной сложностью" — которую мы сами приносим с новыми языками/технологиями. Её нужно уменьшать как только можно.


                                                                  Ну, а так вы пробрасываете ошибку и теряете весь traceback.

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


                                                                  У вас так же 99% ошибок выкидывается.

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


                                                                  А golang заставляет вас эмулировать traceback без самого traceback своими силами.

                                                                  Опять же, если нужен стектрейс, используют либо panic() (fail fast), либо debug.PrintStack(). Можно и просто в переменную error стек сохранять, есть удобные либы для этого. В Go есть выбор, гибкость и очень разумные дефолтные значения.

                                                                    +2
                                                                    Там вполне ок код — три возврата это не проблема. Мне бы было интересно, кстати, как бы вы переписали второй пример на эксепшенах :)

                                                                    Мне довольно сложно понять, что именно там происходит, но, например, на python это будет просто цикл через os.walk и raise из цикла.


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

                                                                    Я думаю, надо писать статью "Ваше веб-приложение не ядро Linux".


                                                                    И супер. А что еще делать с ошибками? Это самые популярные варианты, разумеется. Но это не просто "прокинуть дальше" — это логичный и понятный flow программы для программиста, который читает код. Не забыайте, что язык программирования это не столько язык для общения человека с компьютером, сколько человека с человеком.

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


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

                                                                    1. А в каких еще случаях должны выводится ошибки, если не в тех, когда разработчику нужен traceback?
                                                                    2. Вложенность функций все равно остается. И не совсем представляю, как можно оставить маленькие читаемые функции, маленький уровень их вложенности и при этом писать сложные приложения.
                                                                    3. Java тоже используется в таких случаях, и ей почему-то все "кчему".

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

                                                                    Нет, я считаю, что ради 1% случаев не имеет смысла не делать этот проброс автоматическим.


                                                                    Опять же, если нужен стектрейс, используют либо panic() (fail fast), либо debug.PrintStack(). Можно и просто в переменную error стек сохранять, есть удобные либы для этого. В Go есть выбор, гибкость и очень разумные дефолтные значения.

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


                                                                    очень разумные дефолтные значения

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

                                                                      –5
                                                                      А в каких еще случаях должны выводится ошибки, если не в тех, когда разработчику нужен traceback?
                                                                      Например "порт занят" или "db/users.go:45: ошибка в SQL: такая-то такая то".

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


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

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


                                                                      Я думаю, надо писать статью "Ваше веб-приложение не ядро Linux".

                                                                      Go это не веб-фреймворк.

                                                                        +4
                                                                        Если не задумываясь считать, что стектрейсы нужны везде — это печально.

                                                                        Мне кажется, вариантов где они нужны, все-таки больше. Хотя тут нужна какая-то статистика.


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

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


                                                                        Go это не веб-фреймворк.

                                                                        А для чего он тогда нужен? Драйвера все равно будут писать на C, десктопные штуки на нем писать точно не будут, остаются только системные утилиты, которые пишутся на том, что получится и web-сервисы. Ну и embedded в котором я не разбираюсь.


                                                                        embedded это отлично, но потеснить там C так же будет сложно

                                                                          0

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

                                                                            0

                                                                            Я, кстати, добрался до google, и, например, для python такая возможность есть.


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


                                                                            Например, у вас где-то отваливается коннект, вы знаете, что в конкретном месте, но вместо traceback у вас в логах просто куча сообщений вида "socket connection reset:49". Такие ситуации обычно куда хуже, чем небольшое излишество от traceback.

                                                                              –3

                                                                              У меня тут непопулярная точка зрения, но у меня во многих проектах вместо трейсбеков стоят просто в начале строки в логгере путь к файлу и номер линии:
                                                                              - [timestamp] path/file.go:42: Failed to connect to server: connection reset
                                                                              и этого не просто с головой хватает, но еще и красиво, удобно и очень ускоряет процесс использования логов в дебаг-сессиях.


                                                                              Трейсбек тут понадобится только тогда, когда структура программы станет запутанной и страшной, и ошибка будет исходит из неверных параметров, и родительских функций может быть 20 разных вариантов и тд. Тогда да. Но пока программы не гигантские и не спагетти — file:num хватает с головой.

                                                                                0

                                                                                Как я понял, у вас кроме микросервисов и embedded проектов, получается, ничего и нет?

                                                                                  –3

                                                                                  Как всегда, неправильно поняли.

                                                                            0
                                                                            Пишем мобильные приложения на go + демонов-обработчиков данных.
                                                                              –1

                                                                              Хм, круто! Подскажите, как там с дизайном? Я просто как-то раз попробовал похожую штуку на python, столкнулся с прискорбным фактом того, что не хватает кучи необходимых компонентов из material design и загрустил.

                                                                        +2
                                                                        И какой-же там оверхед? :) Одна CMP инструкция?

                                                                        Как минимум копирование error, которое по факту может быть большим. Вот вам
                                                                        бенчмарк
                                                                        package main
                                                                        
                                                                        import (
                                                                        	"testing"
                                                                        )
                                                                        
                                                                        type errStr struct {
                                                                        	message string
                                                                        }
                                                                        
                                                                        type errStruct struct {
                                                                        	payload [4096]byte
                                                                        	message string
                                                                        }
                                                                        
                                                                        func (e errStr) Error() string {
                                                                        	return e.message
                                                                        }
                                                                        
                                                                        func (e *errStruct) Error() string {
                                                                        	return e.message
                                                                        }
                                                                        
                                                                        type FailFn func() error
                                                                        
                                                                        func DoSomething(k int, ff FailFn) error {
                                                                        	if k == 0 {
                                                                        		return ff()
                                                                        	}
                                                                        	return DoSomething(k-1, ff)
                                                                        }
                                                                        
                                                                        func RunBenchmarkFib(b *testing.B, k int, ff FailFn) {
                                                                        	errn := 0
                                                                        	for n := 0; n < b.N; n++ {
                                                                        		err := DoSomething(k, ff)
                                                                        		if err != nil {
                                                                        			errn++
                                                                        		}
                                                                        	}
                                                                        }
                                                                        
                                                                        func BenchmarkStrErr(b *testing.B) {
                                                                        	RunBenchmarkFib(b, 10, func() error { return errStr{message: "fail"} })
                                                                        }
                                                                        
                                                                        func BenchmarkBigErr(b *testing.B) {
                                                                        	RunBenchmarkFib(b, 10, func() error { return &errStruct{message: "fail"} })
                                                                        }
                                                                        
                                                                        func BenchmarkNilErr(b *testing.B) {
                                                                        	RunBenchmarkFib(b, 10, func() error { return nil })
                                                                        }


                                                                        А вот результаты
                                                                        /t/goben $ go test -bench=.
                                                                        BenchmarkStrErr-12 20000000 115 ns/op
                                                                        BenchmarkBigErr-12 1000000 1436 ns/op
                                                                        BenchmarkNilErr-12 50000000 27.5 ns/op
                                                                        PASS
                                                                        ok _/tmp/goben 5.962s

                                                                          –2

                                                                          Ну вот это другое дело — с бенчмарками интересно общаться.


                                                                          Только бенчмарк у вас а) переусложнен (зачем рекурсия и фибоначчи для бенчмарка возврата ошибки?) б) бенчмаркает не только возврат ошибки, но и аллокацию ее. Отсюда и неверное трактование результата.


                                                                          Дело в том, что интерфейс устроен так — это, по сути, структура, которая в себе держит два поинтера. Я вот тут про это писал — https://habrahabr.ru/post/325468/#interfeysy


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


                                                                          Вот измененный бенчмарк, который трекает только возврат ошибки и ничего более. Кстати, это довольно частый паттерн, когда ошибка определяется один раз, например типа такого — var ErrNotFound = errors.New("not found") — и дальше возвращается где нужно. Тоесть пример не надуманный, если что.


                                                                          https://play.golang.org/p/WSiHxIU-o2


                                                                          Результаты:


                                                                          $ go test -bench .
                                                                          BenchmarkString-4       2000000000           1.95 ns/op
                                                                          BenchmarkStruct-4       1000000000           1.93 ns/op
                                                                          BenchmarkNil-4          1000000000           2.38 ns/op
                                                                          PASS
                                                                          ok      test/habr   8.882s
                                                                            +1

                                                                            Да, вы правы — виновата аллокация ошибки.


                                                                            Может тогда, как знающий человек, поясните результаты такого бенчмарка
                                                                            https://play.golang.org/p/WM1wgiUb1y


                                                                            Там мы вычисляем чисола фиббоначи (10е и 30е). Иногда падаем с ошибкой (всегда, никогда, в 1% и 20% случаев).


                                                                            Результаты:
                                                                            ~/w/goben $ go test -bench=. Benchmark/err___small_k_without_errors-12 3000000 465 ns/op Benchmark/panic_small_k_without_error-12 3000000 426 ns/op Benchmark/err___big_k_without_errors-12 200 6801365 ns/op Benchmark/panic_big_k_without_error-12 300 5701481 ns/op Benchmark/err___small_k,_rare_errors-12 3000000 455 ns/op Benchmark/panic_small_k,_rare_error-12 3000000 430 ns/op Benchmark/err___big_k,_rare_errors-12 200 6833296 ns/op Benchmark/panic_big_k,_rare_error-12 300 5713887 ns/op Benchmark/err___small_k,_often_errors-12 3000000 463 ns/op Benchmark/panic_small_k,_often_error-12 3000000 437 ns/op Benchmark/err___big_k,_often_errors-12 200 6795496 ns/op Benchmark/panic_big_k,_often_error-12 300 5704373 ns/op PASS


                                                                            Неужели panic быстрее проверок if err != nil? Или в бенчмарк опять закралась ошибка?

                                                                              –2
                                                                              Вообще, вы очень запутанные бенчмарки пишете. Если вы используете бенчмарки для сравнения двух решений, то лучше их уменьшить как можно сильнее. В вашем примере вроде как мы хотим сравнить error return vs panic, а бенчмаркаем вообще всё. Плюс много запутанного кода увеличивает риск ошибок.

                                                                              Собственно, в вашем бенчмарке ни возврат ошибки, ни паника ни разу не происходят. Я бы рекомендовал упростить бенчмарк до двух простых функций — вовзрат ошибки и перехват паники (опять же, тут мы бенчмаркамем не только panic(), но и recover()).

                                                                                +2
                                                                                В вашем примере вроде как мы хотим сравнить error return vs panic, а бенчмаркаем вообще всё.
                                                                                Ну так мне интересно узнать общую разницу в производительности, а не просто единичный return и единичный panic. Приблизить ситацию к реальности, так сказать.

                                                                                Собственно, в вашем бенчмарке ни возврат ошибки, ни паника ни разу не происходят.
                                                                                Виноват, вот исправленные результаты
                                                                                play.golang.org/p/yppTDM0w2j
                                                                                Benchmark/err___small_k_without_errors-12 3000000 492 ns/op
                                                                                Benchmark/panic_small_k_without_error-12 3000000 437 ns/op
                                                                                Benchmark/err___big_k_without_errors-12 200 7175016 ns/op
                                                                                Benchmark/panic_big_k_without_error-12 200 5873139 ns/op
                                                                                Benchmark/err___small_k,_rare_errors-12 3000000 476 ns/op
                                                                                Benchmark/panic_small_k,_rare_error-12 3000000 482 ns/op
                                                                                Benchmark/err___big_k,_rare_errors-12 200 7134222 ns/op
                                                                                Benchmark/panic_big_k,_rare_error-12 300 5766522 ns/op
                                                                                Benchmark/err___small_k,_often_errors-12 5000000 391 ns/op
                                                                                Benchmark/panic_small_k,_often_error-12 5000000 371 ns/op
                                                                                Benchmark/err___big_k,_often_errors-12 300 5732042 ns/op
                                                                                Benchmark/panic_big_k,_often_error-12 300 4775898 ns/op
                                                                                Benchmark/err___small_k,_always_errors-12 50000000 28.9 ns/op
                                                                                Benchmark/panic_small_k,_always_error-12 20000000 106 ns/op
                                                                                Benchmark/err___big_k,_always_errors-12 20000000 62.8 ns/op
                                                                                Benchmark/panic_big_k,_always_error-12 10000000 124 ns/op


                                                                                Я бы рекомендовал упростить бенчмарк до двух простых функций — вовзрат ошибки и перехват паники (опять же, тут мы бенчмаркамем не только panic(), но и recover()).
                                                                                Ну вы же понимаете, что количество вызовов `recover` будет куда меньше, чем проверок `if err != nil`. В случае с проверками их нужно вставлять во все функции (даже если у нас 10 функций рекурсивно вызывают друг друга). Если это panic/exception — recover вставляется только в верхнюю (где и просиходит обработка), а `if err != nil { return err }` уже не нужна. Ясно что в случае неглубокого стека `if err != nil` выигрывает. Но судя по бенчмарку `panic` быстрее уже при глубине рекурсии 10, а это всего 108 рекурсивных вызовов, в реальных приложения может быть куда больше. При глубине 8-9 наступает паритет (это примерно 30 рекурсивных вызовов).

                                                                              +1
                                                                              BenchmarkStruct-4 1000000000 1.93 ns/op
                                                                              BenchmarkNil-4 1000000000 2.38 ns/op

                                                                              Интересно, а почему результат с `nil` на 20% медленнее?
                                                                              Как-то совсем не ясно, что же тут происходит и какой оверхед от передачи ошибки. Неужели нет шанса это узнать, кроме как изучить внутренности устройства компилятора Go?
                                                                                –3

                                                                                Ну почему же. добавив флаг cpuprofile и memprofile можно попрофайлить и разобраться.


                                                                                Мне кажется, в случае с nil там какая-то внутренняя магия по заворачиванию nil в интерфейс error? Если определить nil-ошибку заранее, как в примере со struct, то выравнивается:
                                                                                https://play.golang.org/p/rHSgVR4zDs


                                                                                $ go test -bench . -benchmem .
                                                                                BenchmarkString-4           2000000000           2.00 ns/op        0 B/op          0 allocs/op
                                                                                BenchmarkStruct-4           1000000000           1.97 ns/op        0 B/op          0 allocs/op
                                                                                BenchmarkNil-4              1000000000           2.27 ns/op        0 B/op          0 allocs/op
                                                                                BenchmarkNilAllocated-4     1000000000           1.93 ns/op        0 B/op          0 allocs/op
                                                                                PASS
                                                                                ok      test/habr   11.032s
                                                                                  +2
                                                                                  Мне кажется, в случае с nil там какая-то внутренняя магия по заворачиванию nil в интерфейс error?
                                                                                  Т.е. вы не знаете ответа и не может это объяснить?
                                                                                    –1
                                                                                    вы не знаете ответа и не может это объяснить?

                                                                                    Неа, я же не автор Go :) Если бы мне эти 0.30 наносекунд были актуальны, я бы докопался до ответа, но пока надобности не было.

                                                                          +1

                                                                          Я не автор статьи (и не переводчик), но всё-таки хочу заметить, что ваши рассуждения не универсальны. Оверхед блока if виден невооружённым взгядом — вы в общем-то можете заранее сказать, какого вида машинный код в этом месте будет сгенерирован и как будут возвращаться результаты. В случае с исключением всё уже сложнее, вам придётся его именно что изучать. (Да, по поводу того, какого рода ошибка — в Go вроде бы можно именовать возвращаемые значения.) По памяти оверхед тоже важен — в embedded, например (реализация исключений в gcc использует чудовищный размер стека при его раскрутке, и этот размер вам приходится давать каждой задаче, если у вас RTOS).


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

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

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

                                                                              0

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


                                                                              Я вот апокрифичный пример из C++ Annotations вставлю:


                                                                              void fun() {
                                                                                X x;
                                                                                cout << x;
                                                                                X *xp = new X{ x };
                                                                                cout << (x + *xp);
                                                                                delete xp;
                                                                              }

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


                                                                              Можно долго не думать

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

                                                                                +1

                                                                                А теперь представьте, как это выглядело бы с другим подходом. 5 строчек существенного кода и 39(!) строчек вида if err!=nil {}.


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

                                                                                  –2
                                                                                  и 39(!) строчек вида if err!=nil {}.
                                                                                  Мне кажется, вы не поняли, почему в примере выше взялось 13 мест, и просто умножили 13 на 3, чтобы подпитать свой confirmation bias и самого же себя убедить в том, что вы правы. :)
                                                                                    0

                                                                                    Да, вы правы.
                                                                                    Давайте умножим 5 на 3 и получилим — 15 строчек. 75% всего кода — это шаблонный код, который не несет смысловой нагрузки.


                                                                                    Стала ли ситуация лучше?


                                                                                    Или я неправильно понял, и не каждая строчка этого кода может выбросить исключение?

                                                                                      –6
                                                                                      который не несет смысловой нагрузки.

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

                                                                                        0

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


                                                                                        if err != nil {
                                                                                            return nil, err
                                                                                        }

                                                                                        Это не делает ваш код чище, это не заставляет вас прорабатывать исключительные случаи старательнее.


                                                                                        Более того, как отметили ниже, вопрос перформанса тоже может быть спорный, потому что в какой-то момент количество if проверок станет настолько велико, что по замедлению производительности побьет плату, которую мы несем за возможность raise в python, хотя вряд ли ее можно оценить отдельно от всего runtime в python.

                                                                                          –4
                                                                                          Нет никакого реального профита, исключая вопросы перформанса, вставлять

                                                                                          Вам же уже 100 раз написали, что главный профит в том, что следующий программист, читающий код, мгновенно будет понимать, что происходит в случае ошибки, со всеми вытекающими.
                                                                                          Уже 101 раз.

                                                                                            +3

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


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


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


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

                                                                                              –2
                                                                                              Вы поймете это только тогда, когда перестанете мыслить категориями «я пишу», «я и мой код» и т.д. и начнете смотреть на код чужими глазами, глазами людей, которые впервые ваш код видят. Программирование это социальное занятие.
                                                                                                +1

                                                                                                Ну да. И я смотрю проекты на python и отлично понимаю, как обрабатываются ошибки.


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

                                                                                                  –2
                                                                                                  Я вот возьмите эту строчку и скажите, что произойдет с ошибкой, которую тут вернули. Без знания стека вызова, пожалуйста.

                                                                                                  Вы не понимаете и оперируете какими-то концепциями в вакууме. Эта строчка вам даёт самое важное:


                                                                                                  • ошибка в этом месте будет обработана
                                                                                                  • эта ошибка останавливает дальнейшую работу функции
                                                                                                  • ошибка передается наверх как есть

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


                                                                                                  Я пойду писать бот, который будет автоматически на ваши комментарии отвечать одним и тем же :)

                                                                                                    +2

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


                                                                                                    Эта строчка не дает вам самого важного. Понимания того, как именно программа поведет себя, когда вы вернете ошибку. А raise Exception дает.


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

                                                                                                      –2
                                                                                                      у вас процедурная деформация

                                                                                                      Понятно.


                                                                                                      А если какой-то парень забыл

                                                                                                      Пусть девушка значит пишет.

                                                                                                        0

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

                                                                                                          +1

                                                                                                          Да? А у меня собралось, и никаких усилий я не приложил.

                                                                                                            0

                                                                                                            После того, как вы сознательно проигнорировали ошибку. А у меня проект упал, потому что где-то в глубине фреймворка пробрасывалось исключение и разработчик не знал про это и забыл сделать try/except. Это и называется явная обработка ошибок, в отличии от неявной. Хотя мне больше нравится Option тип в расте, чем обработка в Го. Но там и там это явно

                                                                                                              +1

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


                                                                                                              Ну и да, монада Option тащит :)

                                                                                                              –2
                                                                                                              у меня собралось, и никаких усилий я не приложил.

                                                                                                              А '_' сам себя написал, да? :) Вы фанат своего мнения, я смотрю.

                                                                                                                0

                                                                                                                Я сделаю код реальнее, что бы вы мне поверили.

                                                                                                                  –2
                                                                                                                  Я сделаю код реальнее, что бы вы мне поверили.

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


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

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

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


                                                                                                                    Хотя мне кажется, что такой mute ошибки куда серьезнее, чем неиспользуемый импорт.

                                                                                                                      –1
                                                                                                                      потому что удивительно строгий, но такой же удивительно непоследовательный компилятор golang это спокойно схавал.

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

                                                                                                                        0

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


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

                                                                                                                          –1
                                                                                                                          Подскажите, как компилятору помочь игнорировать неиспользуемый импорт?

                                                                                                                          Просто перестаньте писать бессмысленные комментарии на Хабре и сразу начнёт игнорировать. Это же в спецификации Go написано.

                                                                                                                            +2
                                                                                                                            В языках с исключениями вы точно так же можете заглушить их. обернув в трай/кэтч с пустым кэтч.
                                                                                                                              0

                                                                                                                              А потом оторвать тому, кто так делает руки.
                                                                                                                              Ну и на самом деле, найти такое место довольно просто, по grep "catch Exception" в python, например.


                                                                                                                              А как найти описанный выше пример на golang?

                                                                                                                              0
                                                                                                                              Или дополнительные пару мегабайт веса приложения гораздо важнее потерянной ошибки?

                                                                                                                              Это от неиспользуемого импорта? Там что, линкер не умеет удалять неиспользуемые символы?

                                                                                                                      –2

                                                                                                                      Ни в коем случае, не умаляя преимуществ Go в сфере применения системных утилит, но...


                                                                                                                      В Go можно написать что-то похожее на


                                                                                                                      func doSmth() {
                                                                                                                        db.Exec("important select")
                                                                                                                      }

                                                                                                                      и никто никогда никак не узнает, где произошла ошибка.


                                                                                                                      Вышеописанный случай может произойти, к примеру, при смене минорной/патч версии какой-либо зависимости.


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


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


                                                                                                                      Раз в треде говорят о JS, то в ноде поведение по-умолчанию — кричать в error log, что кто-то не обработал ошибку (если говорим о промисах). В будущем — будет падать. Отлавливать проигнорированные ошибки тоже просто — process.on('error', cb);


                                                                                                                      В других языках нередко оборачивают пользовательский код в один большой try {} catch () {}

                                                                                                                        –1
                                                                                                                        Кто-то может написать в таком виде рабочий вариант, только личь чтобы проверить концепт и забыть отрефакторить до продакшн-реди кода.

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


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


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

                                                                                                                        Это в продакшене уже получится узнать только :)

                                                                                                                          0
                                                                                                                          Поэтому то что вы написали, чисто теоретически в вакууме возможно, но на самом деле нет, так никто не пишет, а если какой совсем новичок (пришедший из языков с эксепшенами) и пишет, то ему/ей быстро вправляют мозги и бьют по рукам коллеги, код ревью или линтер errcheck.

                                                                                                                          Можно подумать, в других языках не так же?


                                                                                                                          Это в продакшене уже получится узнать только :)
                                                                                                                          Разве что у вас не какой-то язык с жестким требованиям для отлова ошибок, например, java.
                                                                                                                +4

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

                                                                                                              0

                                                                                                              Да какая разница. Всё, что здесь написано — если функция вернула ошибку, прекратить вычисления и вернуть ошибку вызывающему. Всё. Здесь нет никакой обработки.


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


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

                                                                                                  +2

                                                                                                  Вы меня превратно поняли; я не говорю, что исключения надо повсеместно похоронить и заменить тонной boilerplate. :) Я просто привёл пример того, что исключения — это не волшебное средство от сложности и "просто писать код" всё равно не получится. Они просто переносят сложность в другую плоскость — пишут за вас невидимые if (err != nil) { return err }. В каких-то задачах это приемлемо и желательно, в каких-то совсем наоборот, но я не знаю способа решить раз и навсегда, какие из задач какие. Думаю, что никто не знает, иначе бы мы тут не спорили.

                                                                                                    0

                                                                                                    Я плохо знаю C++, так что можете, пожалуйста, для меня подтвердить — там большинство ошибок же будет связано с невозможностью выделить или освободить память, а так же с отсутствием stdout?

                                                                                                      +1

                                                                                                      stdout, нет, такого нет… Управление памятью — да, добавляет рисков, но на мой взгляд это не самая большая их доля, плюс современный идиоматичный код с умными указателями этому не так подвержен. Посмотрите внизу, где мы с vintage дискутируем — там пример неприятностей, которые не связаны с управлением ресурсами. Исключение может застать вас в процессе изменения данных, которые вы должны изменить консистентно (или не изменить вообще, если происходит ошибка).

                                                                                                  +1
                                                                                                  void doMyJob() {
                                                                                                      auto file = fopen( ... );
                                                                                                      scope( exit ) file.close();
                                                                                                      // работаем с файлом и не боимся исключений
                                                                                                  }

                                                                                                  Или даже так:


                                                                                                  void doMyJob() {
                                                                                                      scoped file = File( ... );
                                                                                                      // работаем с файлом и не боимся исключений ведь файл будет закрыт при выходе из скоупа
                                                                                                  }

                                                                                                  Или вообще так:


                                                                                                  void doMyJob() {
                                                                                                      auto file = File( ... );
                                                                                                      // работаем с файлом и не боимся исключений ведь файл будет закрыт, когда на него не останется ссылок
                                                                                                  }
                                                                                                    +3

                                                                                                    Да, RAII — замечательная штука, не спорю. Но управление ресурсами — это далеко не единственное место, где исключения привносят сложность. Давайте возьмём ваш пример и добавим работу с файлом:


                                                                                                    void doMyJob() {
                                                                                                        auto file = File(...);
                                                                                                        // работаем с файлом и не боимся исключений ведь файл будет закрыт, когда на него не останется ссылок
                                                                                                        auto data = readSomeData(file);
                                                                                                        writeRecordHeader(file);
                                                                                                        auto record = processData(data);  // Бросает исключение.
                                                                                                        writeRecordBody(file, record);
                                                                                                    }

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


                                                                                                    Пример с файлом утрированный и частный, поэтому давайте абстрактнее:


                                                                                                    void UpdateRecord(Record* r, Data d) {
                                                                                                      r->x = CalculateNewX(d);
                                                                                                      r->y = CalculateNewY(d);
                                                                                                    }

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


                                                                                                    Ресурсы — это не самая большая проблема с исключениями; RAII поможет освободить память или закрыть файл. Проблема в том, что исключения — это невидимые глазу if (...) { return; } в вашем коде, и если вы их не учитываете, то запросто можете оставить мир в некоем фатально видоизменённом состоянии после ошибки.

                                                                                                      +1
                                                                                                      Но управление ресурсами — это далеко не единственное место, где исключения привносят сложность.

                                                                                                      Нет там никакой сложности в современном языке.


                                                                                                      И всё потому, что функцию processData() писали не вы, и кто знал, что она бросает исключение.

                                                                                                      В языке с исключениями все функции их могут бросать. Это аксиома. В любом случае ваш код должен выглядеть так:


                                                                                                      void doMyJob() {
                                                                                                          auto file = File(...);
                                                                                                          auto data = readSomeData(file);
                                                                                                          auto record = processData(data);  // Бросает исключение или взрывает компьютер.
                                                                                                      
                                                                                                          auto newFile = tmpfile();
                                                                                                          newFile.writeRecordHeader();
                                                                                                          newFile.writeRecordBody(record);
                                                                                                      
                                                                                                          file.remove();
                                                                                                          newFile.rename( file.name );
                                                                                                      }

                                                                                                      Пример с файлом утрированный и частный, поэтому давайте абстрактнее:

                                                                                                      Ваш код при многопоточном доступе точно так же огребёт проблем. Кроме того, как вы будете восстанавливать консистентность, если уже стёрли x?


                                                                                                      исключения — это невидимые глазу if (...) { return; } в вашем коде

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

                                                                                                        +1
                                                                                                        Нет там никакой сложности в современном языке.

                                                                                                        Возможно. Но вокруг тонны legacy кода, не готового к RAII, и с ним приходится как-то жить.


                                                                                                        В языке с исключениями все функции их могут бросать. Это аксиома. В любом случае ваш код должен выглядеть так:

                                                                                                        Именно. :) Выше я писал о том, что исключения предполагают определённый стиль мышления, и вы избавили меня от необходимости это иллюстрировать. Да, в языке с исключениями все функции под подозрением, и это вырабатывает у вас навык писать в таком стиле — группировать работу функций в собственно работу, и commit фазу, где закрепляются результаты этой работы. Это как раз и есть способ обеспечить strong exception guarantee. Но сам по себе этот навык не вырабатывается, это и есть та сложность и та когнитивная нагрузка, которую несут исключения.


                                                                                                        Ваш код при многопоточном доступе точно так же огребёт проблем. Кроме того, как вы будете восстанавливать консистентность, если уже стёрли x?

                                                                                                        При чём здесь многопоточность? В этой функции нет глобального состояния. (Если что, запись у каждого потока своя, мы это не видим, да и не про то речь.) А консистентность — вы ведь уже сами продемонстрировали выше, в коде. Консистентность приходится не восстанавливать, а просто не нарушать — строить функцию так, что сначала идёт работа, а потом commit phase, где результаты работы (причём, если нужно strong exception guarantee, то там всё должно быть nothrow) присваиваются x. Конечно, то же самое вы будете проделывать с кодами ошибок, но в том-то вся и суть, что в отсутствие исключений вы видите явно, какая функция может упасть, а вот с исключениями они все под подозрением, и вам приходится программировать защитно, вот в таком стиле.


                                                                                                        Нет там никаких if-return. Прелесть исключений в том, что вы за них платите лишь при установке обработчика и раскрутке стека.

                                                                                                        Оставляя в сторону ту самую когнитивную нагрузку, которую вы платите за кучу скрытых нелокальных goto в коде… Да, нагрузка времени исполнения выше у if'ов. Но откуда мнение, что это везде настолько критично? Плюс я уже приводил выше пример — за исключения я плачу ещё и памятью во время раскрутки стека. В embedded, если вы используете RTOS, это здорово портит жизнь и (в моей практике) заставляет (именно в embedded) отказаться от исключений — при раскрутке одномоментно используется очень много стека, и стек каждой задачи должен быть достаточно большим, чтобы быть к этому готовым, а задач может быть много. Прелести исключений не универсальны, и за них приходится платить.

                                                                                                          +1
                                                                                                          это вырабатывает у вас навык писать в таком стиле — группировать работу функций в собственно работу, и commit фазу, где закрепляются результаты этой работы

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


                                                                                                          В этой функции нет глобального состояния.

                                                                                                          Есть внешнее состояние, которое может оказаться и глобальным.


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

                                                                                                          В Go уже завезли Thread Local Storage? Впрочем, если CalculateNewX снимет горутину с ядра, то следующая горутина увидит запись в неконсистентном состоянии.


                                                                                                          в отсутствие исключений вы видите явно, какая функция может упасть

                                                                                                          Вы мне покажите лучше функцию, которая гарантированно не может кинуть панику :-)


                                                                                                          а вот с исключениями они все под подозрением, и вам приходится программировать защитно, вот в таком стиле.

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


                                                                                                          за исключения я плачу ещё и памятью во время раскрутки стека

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

                                                                                                            +1
                                                                                                            В Go уже завезли Thread Local Storage? Впрочем, если CalculateNewX снимет горутину…
                                                                                                            … которая гарантированно не может кинуть панику :-)

                                                                                                            Извините если я у вас случайно создал впечатление, что мы обсуждаем Go. :) Мы обсуждаем исключения. Я на Go в общем и целом не пишу (так, потыкал для общего развития), поэтому не буду развивать тему паники (а чем она не исключение?) и TLS (что вам мешает просто не передавать эти данные во много горутин/потоков? если передаёте, то уж наверное это делаете осознанно и не каждую минуту). В общем, мы не про Go и уж точно не про shared state. Для простоты представьте что у нас один поток, проблема exception safety от этого никуда не денется.


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

                                                                                                            Не хочу вас расстраивать — но, нет, не показали. Вот кстати, вы сейчас клеймили if'ы за то, что они сбрасывают конвейер (я бы не согласился, кстати — во-первых есть предсказание ветвления, во-вторых ему ещё и помочь можно, указав наиболее вероятную ветвь). Но возьмите такой пример:


                                                                                                            void Update(Record* r) {
                                                                                                               GetNewDataX(r->x, ...);  // x - это массив.
                                                                                                               GetNewDataY(r->y, ...);  // y - тоже.
                                                                                                            }

                                                                                                            Если у вас язык без исключений, то вы можете увидеть, что GetNewDataX() не возвращает кода ошибки (и исключений нет, поэтому неявно вернуть тоже не может) и, если она, конечно, реализована правильно и соблюдает контракт, то она — подразумевается — всегда успешна, там нет ничего, что может упасть. (Да, всегда есть залётный нейтрон или глючащее оборудование, но только не говорите, что вы и в расчёте на это код пишете.) Поэтому функцию использовать безопасно. Но вот представьте теперь, что у вас есть исключения, и, поскольку вы сами сказали, что все функции теперь под подозрением, то придётся переписать так:


                                                                                                            void Update(Record* r) {
                                                                                                               // Тут выделяем массив temp.
                                                                                                               GetNewDataX(temp, ...);
                                                                                                               GetNewDataY(r->y, ...);
                                                                                                               Copy(r->x, temp);  // nothrow
                                                                                                            }

                                                                                                            Представьте, что x и y — массивы размером в мегабайт каждый. Внезапно, необходимость учитывать возможность выброса исключений вынудила вас выделять мегабайт памяти и тратить время на его копирование. Вы только что спустили в трубу гораздо больше тактов процессора, чем вы сэкономили на if'ах. Функция GetNewDataX() на самом деле не бросает исключения, но поскольку все в списке подозреваемых, пришлось написать неэффективный код.


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

                                                                                                            Вы меня, кажется, не поняли. Под защитным стилем я имел (что и написал) не то, где вы размещаете try-catch, а то, как вы организуете работу функции. Если у вас вылетело исключение при частично модифицированных данных, то это не имеет отношения к тому, что вы ловите и на каком уровне — вызывающая процедура получит неконсистентный результат. И поэтому (как вы сами и продемонстрировали) вы переписываете свой код определённым методом. (И ещё раз — извините, я не "делаю в Go", не переходите на личности.)


                                                                                                            Ну это уже особенности конкретной кривой реализации исключений. В любом случае вполне нормально повышенное потребление ресурсов...

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

                                                                                                              0
                                                                                                              Для простоты представьте что у нас один поток, проблема exception safety от этого никуда не денется.

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


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

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


                                                                                                              GetNewDataX() не возвращает кода ошибки (и исключений нет, поэтому неявно вернуть тоже не может). Поэтому функцию использовать безопасно.

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


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

                                                                                                              Специфическим условиям могут быть специфические решения. Не идиомы ту пинать надо.


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

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

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

                                                                                                                А вы всегда пишете thread-safe код, вне зависимости от? То есть вы всегда исходите из предположения, что ваши входные данные всегда может кто-то щупать из другого потока? Извините, но… мне очень сложно заставить себя в это поверить. Учтите, что в таком случае просто сделать работу а потом обновить результат недостаточно. Даже простое копирование может наложиться на доступ из другого потока. Вы каждый кусок данных окружаете защёлками, каждый флаг делаете атомарным? В яве, например, пожалели что сделали старые Vector и Hashtable synchronized, потому что оверхед большой, а потокобезопасность нужна редко. Вы же только что превозносили исключения за то, что они позволяют вам не платить за то, что не используете, а теперь сами говорите, что хотите заплатить за потокобезопасность (которая вам пока не нужна). Я удивляюсь.


                                                                                                                Вообще, строго говоря, я не знаю, откуда тут вообще аргумент про многопоточность. Организация кода для exception safety не даёт потокобезопасность каким-то магическим образом, и наоборот — код может быть потокобезопасным, но не exception safe.


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

                                                                                                                Вы тратите сотни (или тысячи, если в куче) тактов на выделение и копирование памяти, когда вам надо сделать временное хранилище результата и обеспечить exception safety, и для вас это приемлемо, а промах предсказателя ветвлений — трагедия. Про защиту от конкурентного доступа я уже молчу.


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

                                                                                                                Функция принимает типизированный массив фиксированного размера, и компилятор бы её просто не скомпилировал, если бы был выход за границы? (И да, я не просто так упомянул "если она правильно реализована". Вы можете вернуть мусор и при этом не выбросить исключение.) Я не думаю, что это относится к теме обсуждения.


                                                                                                                Специфическим условиям могут быть специфические решения. Не идиомы ту пинать надо.

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


                                                                                                                За меня об этом думают разработчики компиляторов. <...> Компилятор не то что условия, а даже собственно вызова может не вставить, если посчитает нужным.

                                                                                                                Тогда о чём говорить? Компилятор и ветвления умеет оптимизировать — вы же не проверяете, сколько ветвлений он выбрасывает, сколько переставляет, угадывая, какая из ветвей более вероятна, что ему profile-guided optimization нашёптывает. Вообще, мир, в котором ошибки предсказания ветвления были бы самым значимым фактором в оптимизации программ, был бы поистину прекрасен.


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

                                                                                                                  0
                                                                                                                  Функция принимает типизированный массив фиксированного размера, и компилятор бы её просто не скомпилировал, если бы был выход за границы?

                                                                                                                  Как вы предлагаете это (проверку) реализовать?

                                                                                                                    0

                                                                                                                    Передать в функцию не голый указатель, а что-то наподобие std::array<T, N>? Если вы попытаетесь передать массив другого размера, это будет уже другой тип.


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

                                                                                                                      0

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

                                                                                                                        0

                                                                                                                        Во-первых, вы можете упереться в ulimit. Во-вторых, массив может выделяться в куче, а в самом классе будет только указатель. (Да, std::array<> выделяет статически, но в общем случае вы этого не знаете.)

                                                                                                                          0

                                                                                                                          А, ну если в куче, то да.


                                                                                                                          Но тогда можно и про дешёвую nursery area, пулы и прочие вещи пофантазировать.

                                                                                                                            +1

                                                                                                                            Мы тут всё обсуждаем в контексте фантастического выигрыша в скорости от того, что мы избавились от if (err == nil) в коде. :) То есть хорошо бы что-нибудь дешевле нескольких cmpq $0, N(%rsp); jne on_error;. :)

                                                                                                                              –1

                                                                                                                              Выигрыш в скорости скорее будет оттого, что я сэкономлю порядком времени на набивании и поддержании всех этих if (err == nil) и быстрее перейду к профилированию.

                                                                                                                      –1

                                                                                                                      Есть языки с зависимыми типами, где это можно закодировать, собственно, в типе, но обсуждать завтипы в треде про Go, JS и C++ несколько смешно, как по мне.

                                                                                                                        0
                                                                                                                        Как вы предлагаете это (проверку) реализовать?

                                                                                                                        Шаблоны в C++ умеют делать такие страшные вещи, если не ошибаюсь.

                                                                                                                          0
                                                                                                                          auto doMyJob( int[3] data ) {
                                                                                                                              return data[4]; // Compilation Error: array index 4 is out of bounds data[0 .. 3]
                                                                                                                          }
                                                                                                                            0
                                                                                                                            void do(int[3] data){
                                                                                                                                 int x = system("node -c 'process.exit(5)'");
                                                                                                                                 data[x]
                                                                                                                            }
                                                                                                                          0

                                                                                                                          Я стараюсь вносить изменения атомарно — это хорошая практика из которой уже вытекает и "exception safety" и "thread-safety" и "debug-friendly". D по умолчанию хранит все данные в TLS, а чтобы расшарить данные между потоками — нужно сделать их синхронизированными, что включается добавлением всего одного ключевого слова. Данные я не копирую без нужны, ведь есть COW и move.


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


                                                                                                                          Вообще, по теме могу предложить почитать: https://habrahabr.ru/post/280378/

                                                                                                                            0

                                                                                                                            Я не совсем вас понял, и я недостаточно хорошо знаю D, чтобы понять специфику. Вот мы исходим из того, что ваш аргумент — структуру — может щупать другой поток. Вы сначала сказали, что многопоточность может вкрасться в любой точке функции, и надо её делать thread safe, а теперь вы говорите что в D всё в TLS и никаких общих данных нет, если вы сами явно не скажете. И как атомарность изменений (в контексте защиты от конкурентного доступа) помогает с exception safety, если исключения поднимаются вашим же потоком, то есть никак не затрагивают многопоточность? В середине изменения synchronized объекта у вас точно так же может вылететь исключение. Тут вам нужна уже другая атомарность — не в плане защиты от одновременного доступа, а в транзакционном плане.


                                                                                                                            Ну и про функцию — а как вы вообще собираетесь учитывать ошибки разработчика? Мы сейчас говорим об исключениях, заметьте. Вы мне возразили — как же можно, функцию вызвали, кода ошибки она не возвращает, говорит что хорошая, но внутри баг и она делает страшные вещи. Какая в таком случае разница с исключением? Да, в функции баг, но она сама об этом не знает, и потому не вернёт ни исключение ни код ошибки. Вы думали, что сливание паролей злоумышленникам обязательно сопровождается исключениями? Она просто вылезет за пределы, и тогда может произойти всё что угодно, но сама функция об этом не догадается и не вставит ни throw ни if (...). Мы можем судить о функции только по контракту.


                                                                                                                            За ссылку спасибо — почитаю, хотя я D никогда не ковырял, да и в Go в общем-то не силён.

                                                                                                                              0
                                                                                                                              Вы сначала сказали, что многопоточность может вкрасться в любой точке функции, и надо её делать thread safe

                                                                                                                              Ну, это в C и Go так, если функция работает с внешней структурой.


                                                                                                                              И как атомарность изменений (в контексте защиты от конкурентного доступа) помогает с exception safety

                                                                                                                              Если возникло исключение, то до атомарного изменения (одного машинного слова, например, ссылки на данные) дело не дойдёт.


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

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


                                                                                                                              Ну и про функцию — а как вы вообще собираетесь учитывать ошибки разработчика?

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


                                                                                                                              Она просто вылезет за пределы, и тогда может произойти всё что угодно

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

                                                                                                                                0
                                                                                                                                Ну, это в C и Go так, если функция работает с внешней структурой.
                                                                                                                                Если возникло исключение, то до атомарного изменения (одного машинного слова, например, ссылки на данные) дело не дойдёт.

                                                                                                                                То есть вы для любых входных данных делаете так, что изменение их происходит атомарно, инструкцией с барьером. Вопрос — как? Вот предположим, что вам передали объект. У него есть метод SetX() и метод SetY(). Или даже просто два разных поля, которые вам предстоит изменить. Вы не можете записать эти два поля атомарно (в значении слова "атомарно", которое вы используете, которое вас защищает и от исключений, и от других потоков). Какие ваши действия в таком случае?


                                                                                                                                Так она только такая и есть. Атомарное изменение для любого наблюдателя происходит либо целиком, либо не происходит вовсе.

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


                                                                                                                                void Update(Record* r) {
                                                                                                                                   EnterCriticalSection();
                                                                                                                                   GetNewDataX(r->x, ...);  // x - это массив.
                                                                                                                                   GetNewDataY(r->y, ...);  // y - тоже.
                                                                                                                                   LeaveCriticalSection();
                                                                                                                                }

                                                                                                                                Отлично, с точки зрения других потоков r изменяется атомарно. Но этот код всё равно не exception safe, потому что GetNewDataY() может бросить исключение.


                                                                                                                                void Update(Record* r) {
                                                                                                                                   // Тут выделяем массив temp.
                                                                                                                                   GetNewDataX(temp, ...);
                                                                                                                                   GetNewDataY(r->y, ...);
                                                                                                                                   Copy(r->x, temp);  // nothrow
                                                                                                                                }

                                                                                                                                Этот код даёт strong exception guarantee. Но он ни разу не защищён от изменения данных со стороны. Да даже невинная Copy() может наложиться на операцию записи другим потоком, и у вас будут смешанные данные.


                                                                                                                                Видите? Мы защищаемся от двух разных вещей двумя разными методами, это разная атомарность. (И кстати, первый вариант всё равно не даёт вам защиту от многопоточности, потому что некий поток изменит вашу запись после критической секции и результат работы функции просто потеряется. Какой физический смысл вообще у того, чтобы защищать r от параллельного доступа в этой функции? Можете объяснить, зачем это делать?)


                                                                                                                                В том-то и дело, что никак. <...> Исключения дают единый механизм реакции на исключительные ситуации. Будь то, пользовательские исключения или системные.

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

                                                                                                                                Я не понимаю. При чём здесь исключения? Как поддержка исключений (сама по себе) магически даст вам возможность отловить выход за границы массива или доступ по нулевому указателю? Исключения должны где-то выбрасываться; коды ошибок — где-то возвращаться. В случае ошибки разработчика этих мест там нет. Вы, возможно, привыкли к языкам с bounds checking, но во-первых это совершенно ортогонально исключениям (вы в принципе можете иметь проверку границ и без исключений), а во-вторых это не панацея от багов.


                                                                                                                                Возьмите код на С++ — там есть исключения, но при этом функция, которую вы вызываете, может радостно пройтись по чужой памяти, получить доступ к нулевому указателю (или указателю с мусором), и при этом никаких исключений выброшено не будет. Вы или получите мусор в памяти, или программа упадёт (и, да, грохнет весь сервер), но исключения вам не будет. Вы можете посоветовать стать ёжиком и использовать язык с bounds checking, но и это не защитит вас от ошибок разработчика — во-первых они не сводятся к ошибкам памяти, а во-вторых, и в этих языках (возьмём Java для примера) вы не можете гарантировать, что охочий до оптимизации программист не сделал реализацию через JNI или не использовал Unsafe, и теперь функция может пройтись по любой памяти, а исключения вы всё равно не получите. Исключения — это всего лишь один из способов пробросить ошибку наверх, но у вас нет гарантии, что все ошибки будут сопровождаться исключениями. Они не дают вам защиты от багов. Язык — может некоторую защиту дать, но это ортогонально способу доставки сообщений о тех багах, которые он поймал.

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

                                                                                                                                  Барьеры-то тут при чём? Они лишь упорядочивают операции.


                                                                                                                                  У него есть метод SetX() и метод SetY()

                                                                                                                                  У него будет метод Update() или я просто создам новый объект с нужным состоянием.


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

                                                                                                                                    0
                                                                                                                                    У него будет метод Update() или я просто создам новый объект с нужным состоянием.

                                                                                                                                    Это не ваш объект. Вы не можете его менять, он вам даден сверху. Или опять "мышки, станьте ёжиками"? А в методе Update() не надо будет заботиться об атомарности? Вообще, вы не ответили ни на один из моих вопросов по сути. Я так и не узнал, что вы в действительности хотели сказать. Притянули за уши многопоточность — но не смогли объяснить, зачем она вам нужна. Заявили, что накопление результатов работы и атомарное изменение выходных данных — всегда хорошая привычка, но на контраргументы не ответили. Заявили, что ветвления портят вам производительность — и тут же сказали, что вы в общем-то никогда и не измеряли, пусть, мол, разработчики компиляторов заботятся. То есть в общем-то ничего и не сказали.


                                                                                                                                    Ну, я не прессую — бог с ним, останемся при своих. Спасибо за беседу; пардон если где-то горячился.

                                                                                                                                    0

                                                                                                                                    Жалко, что тут как-то совсем не вспомнили про такую замечательную вещь, как STM. Но это так, если об идеальных мирах говорить.

                                                                                                                                      0

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

                                                                                                            0

                                                                                                            Явный return явно показывает, что и где из функции может возвращаться, и по сигнатуре функции видно, какие у неё ошибки.


                                                                                                            Правда, опять же, это не случай Go. Где-то тут монады, конечно, п