Pull to refresh

Comments 618

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

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

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

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

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

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

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

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

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

UFO just landed and posted this here
То, что вы купились на рекламу Node.JS — ваши проблемы. А советская модель «отучился в универе, больше учиться не надо, я теперь специалист» больше вообще нигде не работает, особенно в IT.
UFO just landed and posted this here
Откуда вы взяли такую «советскую модель»? Даже Ленин призывал в своих публикациях непрерывно учиться.
Учиться, учиться и еще раз учиться — это лучше, чем работать, работать и еще раз работать. Ленин.
UFO just landed and posted this here
Вы все неправы.

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

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


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

Ну все, пойду переписывать все свои 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, нам ведь важны только бенчмарки, да?

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

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

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

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

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


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


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

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

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

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


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


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

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

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


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


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

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

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

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


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

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


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


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

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

Нет, у 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 еще сырая, потому что создается энтузиастами в качестве хобби". И это очень похоже на правду.

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

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



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


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

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

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

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


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

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


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

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

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

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

Вот это уже полная ерунда. Все нормальные библиотеки для работы с базами предохраняют от SQL-injection на 100%, а грамотно написанный SQL ВСЕГДА быстрее работает, чем запросы от ORM.
Понимаете, какая дилемма: 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. В ОРМ как правило есть только одна модель сущности — из-за этого запросы возвращают все поля, даже если реально нужно одно-два
  2. Бутстрап ОРМ в базе с большим числом обьектов может занять приличное время из-за чтения метаданных
  3. Если вам нужны hint-ы, тем более разные у разных клиентов — это проблема
  4. В ОРМ как правило есть кэш которым надо управлять если базой пользуется кто-то еще
  5. Управление транзакциями в ОРМ может быть довольно нетривиальным

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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


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


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


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


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

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

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

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

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

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

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

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

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

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



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


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

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


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

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

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

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

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


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

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


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

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

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

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


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

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


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


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

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


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

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

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

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


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

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

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


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

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

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


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


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


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


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


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

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

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

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

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


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

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

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


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

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

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


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


язык это не ORM

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

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

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


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

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


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

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


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

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

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

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


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

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


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

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


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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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


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

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

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

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


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

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

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

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


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


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

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

Такие ситуации, действительно, имеют быть, но они чаще исключение, чем норма, да и для них в 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 своими силами. Возможно идеология за этим стояла другая, но получилось у людей так. И какой тогда в этом смысл?

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

Там вполне ок код — три возврата это не проблема. Мне бы было интересно, кстати, как бы вы переписали второй пример на эксепшенах :)
Я как-то слышал аргумент про возвращаемые значения от людей, которые не сильно поняли разницу между возвращаемыми ошибками в 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 есть выбор, гибкость и очень разумные дефолтные значения.

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

Мне довольно сложно понять, что именно там происходит, но, например, на 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 кладет всю программу, что довольно странно, например, для веб-сервера. Ну и понятно, что своих костылей можно накрутить много, но хотелось бы это все из коробки.


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

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

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

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


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

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


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

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

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

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


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

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


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

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


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

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

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


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


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

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


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

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

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

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

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

И какой-же там оверхед? :) Одна 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

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


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


Дело в том, что интерфейс устроен так — это, по сути, структура, которая в себе держит два поинтера. Я вот тут про это писал — 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

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


Может тогда, как знающий человек, поясните результаты такого бенчмарка
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? Или в бенчмарк опять закралась ошибка?

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

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

В вашем примере вроде как мы хотим сравнить 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 рекурсивных вызовов).

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

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

Ну почему же. добавив флаг 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
Мне кажется, в случае с nil там какая-то внутренняя магия по заворачиванию nil в интерфейс error?
Т.е. вы не знаете ответа и не может это объяснить?
вы не знаете ответа и не может это объяснить?

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

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


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

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

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

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


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


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

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


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

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

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


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

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

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


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


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

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

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

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


if err != nil {
    return nil, err
}

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


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

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

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

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


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


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


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

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

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


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

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

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


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

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


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

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


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


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

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

Понятно.


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

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

UFO just landed and posted this here
UFO just landed and posted this here

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


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

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

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

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

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


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

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

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


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

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

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

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


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

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

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

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

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


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

UFO just landed and posted this here

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


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


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

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


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


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


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


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


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

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

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


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


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

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

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

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


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

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

UFO just landed and posted this here

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

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

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

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

Или даже так:


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

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


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

Да, 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; } в вашем коде, и если вы их не учитываете, то запросто можете оставить мир в некоем фатально видоизменённом состоянии после ошибки.

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

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


И всё потому, что функцию 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. Прелесть исключений в том, что вы за них платите лишь при установке обработчика и раскрутке стека. А вот ваши ветвления больно бьют по процессорному конвееру всегда.

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

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


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

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


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

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


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

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

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

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


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

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


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

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


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

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


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

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


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

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

В 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", не переходите на личности.)


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

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

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

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


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

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


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

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


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

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


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

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

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

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


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


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

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


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

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


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

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


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

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


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

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

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

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


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

UFO just landed and posted this here

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

UFO just landed and posted this here

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

UFO just landed and posted this here
UFO just landed and posted this here
Как вы предлагаете это (проверку) реализовать?

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

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

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


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


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

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


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


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

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

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


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

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


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

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


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

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


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

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

Ну, это в 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, и теперь функция может пройтись по любой памяти, а исключения вы всё равно не получите. Исключения — это всего лишь один из способов пробросить ошибку наверх, но у вас нет гарантии, что все ошибки будут сопровождаться исключениями. Они не дают вам защиты от багов. Язык — может некоторую защиту дать, но это ортогонально способу доставки сообщений о тех багах, которые он поймал.

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

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


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

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


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

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

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


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

UFO just landed and posted this here

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

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

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

UFO just landed and posted this here
без комментария не понятно, что произойдет при ошибке в thingA(), пока не проскроллишь глазами в самый низ (и это на микроскопическом примере уже не очень приятно)

Вероятнее всего, для вас это непонятно потому-что вы, возможно, не знакомы с async / await API и промисами в JS. Если произойдет ошибка в асинхронной функции, она свалится в ближайший .catch() или в unhandledRejection


Собственно сниппет


(async function () {
})()
  .catch((err) => {
    console.error(err);

    process.exit(1);
  });

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


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

А если я не хочу вообще перехватывать эту ошибку, и другие на каждый чих, если я за fail-fast?


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

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

А если я не хочу вообще перехватывать эту ошибку, и другие на каждый чих, если я за fail-fast?

А если хотите?


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

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


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

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

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

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


* кроме пары стандартных.

Уж простите, я не евангелист всех остальных языков, так что текстом:


Go: сидит дитетка, лепит куличики из песка: "Да я какой хош небоскрёб так сделаю, нужно только побольше куличиков".


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

Хорошая фантазия :) Далека от истины, но хороша.
Особенно понравилось про «любой другой язык». Прямо все хороши, кроме Go, да.

Ну почему, про куличики прям в яблочко.

Расмус Лердорф и Брендон Айк (из самых известных создателей %любых других продуманных языков%) внимательно тебя слушают. :)

Детство у них тяжёлое, но сейчас они уже ничего.

Ну… да.
Вы посмотрите, какие они либы открыли. Куличик для обработки ошибок, куличик для баз данных, куличик для простого кеширования, куличик для расчета хеша.


То есть взяли, и написали кучу надстроек вместо того, что бы сразу приступить к решению задачи. Хотя тот же divan0 утверждал, что все же в go есть, видимо, эти ребята из Dropbox не постигли дзен.

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

Эта ниша уже занята вам, мне вас не одолеть :(

Я не о том, что они открывали в свободный доступ. Процитирую:

About a year ago, we decided to migrate our performance-critical backends from Python to Go to leverage better concurrency support and faster execution speed.

Потому что раньше медленные места с Python переписывали на C/C++, а теперь взяли Golang?
Ну да, я уже говорил, что golang отличная замена C.

Дропбокс — яркий пример успешного бизнеса построенного на довольно простой в реализации идее. Вы посмотрите сколько конкурентов у дропбокса, а сколько у, например, SAP ERP.

И?..

Предлагаете обсудить ABAP? Знакомы с ним?

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

Ничего, я его не курил.

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

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

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

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

То есть вот этот человек не прав?

Он написал совершенно не то, что вы написали. Вам не надоело еще людям морочить голову?
Прочел обе ссылки и там действительно написано что усложнит
Таким образом ввод generics сильно усложнит язык — как реализацию, так и использование.

Дженерики невероятно сложны как по своей семантике, так и по реализации

или не так понял? как надо было понять?
То что усложнит, это любому понятно, надеюсь. Но:
а) дженерики это концепт, а шаблонные типы — это реализация
б) в Go нет дженериков, потому что авторы Go не видят способа их реализовать без того, чтобы не усложнить язык (не только код)
в) авторы Go не отказывались от дженериков, наоборот только за, если все специалисты Хабра расскажут, как и правильно сделать чтобы потом запрос в гугл «language X generics sucks» при X=Golang не выигрывал конкурс Generics Sucks Contest.
Чет я не понял в каком месте то что написал SirEdvin противоречит тому что вы.
Он свёл это к «шаблоны усложняют код, поэтому в Go нет дженериков». Усложнение языка это не «усложнить код» — это усложнение рантайма, сборщика мусора, процесса компиляции, когнитивной нагрузки на программиста, тулинга, грамматики и много чего ещё. Шаблоны != дженерики, это лишь один из вариантов. Ну и если уж на то пошло, в Go есть дженерики (типонезависимые функции), но пользователи создавать свои не могут. Плюс, целый класс тех задач, которые решаются «шаблонами» в Go решаются интерфейсами.

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

Касательно шаблонов..


усложнение рантайма,

Нет.


сборщика мусора

Нет.


процесса компиляции

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


когнитивной нагрузки на программиста

Она уменьшится, ибо ему придётся меньше писать и читать generic кода.


тулинга

Незначительно.


грамматики

Незначительно.


и много чего ещё

Чего ещё?

Касательно шаблонов.
Нет.
Нет
Незначительно.
Незначительно.

Спасибо за доступное объяснение и хорошую аргументацию, всегда приятно послушать мысли умных людей. Теперь понятно.

Вы сказали глупость, от незнания как работают шаблоны. Я вас поправил. Не благодарите. Но в следующий раз такие глупости не говорите.


Вы выдвинули тезис о каком-то значительном усложнении всего — вам его и доказывать.

в Go есть дженерики (типонезависимые функции

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

Слово trade-off слышали? Или у вас всё бинарно через "нужен/не нужен"?

Хм, и почему то что сделали авторы go это trade-off, а то что пишу я это бинарное мышление?
Ладно проехали, я остался при первоначальном впечатлении, никакого противоречия не увидел.
Ещё чего нет:
— классов
— эксепшенов
— наследования
— тернарного оператора
— многих способов сделать одно и то же
— алгебраических типов
— арифметики поинтеров
— ручного освобождения памяти

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

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


Я терпеть не могу эксепшены, и, может быть, в следующей жизни готов помириться с фантастическими if err != nil по всему коду, но я вас прошу, не пытайтесь тут продать язык, в котором якобы "специально" отказались от нереально удобных вещей, использующихся десятилетиями, в угоду мнимому субъективному удобству и понижению порогу входа. Последнее вообще рассмешило, вон в пхп тоже низкий порог входа.

Ха-ха, прощаю, знаете сколько я такого наслушался за последние 4 года? Вот этих гневных бравад про «я знаю точно, что удобно в языках программирования, а эти неучи из Го, пусть даже создавшие Unix, ничего не понимают в жизни, и не пытайтесь мне доказать, что они не идиоты!». Прям обнять хочется и успокоить. Вот уж где стокгольмский синдром, это точно. :)
знаете сколько я такого наслушался за последние 4 года

Ну а я бы сделал выводы.


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


Про неучей я ничего не говорил, видимо вам уже мерещится. И я вам ничего не доказываю, ни что они неучи, ни что идиоты, это вы всё сами.


Почитайте про этот чудесный синдром — вас чего-то лишают, а вы считаете, что это хорошо.

Ну а я бы сделал выводы.

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


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

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


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


Почитайте про этот чудесный синдром — вас чего-то лишают, а вы считаете, что это хорошо.

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

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

Что? Вы вообще когда-то видели другие языки помимо golang? Или для вас interface{} образец читабельности? Дженерики можно обвинить в чем угодно, но в ухудшении читабельности их обвинить никак нельзя. У меня такое чувство, что вы пришли из С99 и теперь рассказываете, как все в go отлично. А тем временем на дворе 2к17, однако.


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

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


Но черт возьми, от этого не стоит писать "дженериков в golang нет, потому что они не нужны". Они объективно нужны, потому что открывается любой более менее большой проект на go, делается grep и открывается прекрасный мир interface{}. И то, что создатели golang не смогли в дженерики является единственной причиной того, что их в языке нет. Никто не говорит "ахаха, лалки не смогли", потому что задача в самом деле сложная. Но делать из этого религию в духе "дженерики не нужны", глупо.

Вы вообще когда-то видели другие языки помимо golang?

Да.


Или для вас interface{} образец читабельности?

interface{} это empty interface, мы говорим про интерфейсы в целом. Вы сначала разберитесь в теме, а потом пишите свой 200-й бессмысленный комментарий.


Но делать из этого религию в духе "дженерики не нужны", глупо.

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


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

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


И еще раз — если вы настолько свято верите, что без привычных вам дженериков жизни нет и быть не может и это святое и ежедневная необходимость — напишите Experience Report, объясните конкретную проблему, которую вы решали, и как отсутствие дженериков стал вам на пути. Объясните насколько это серьезная проблема, чтобы авторы Go вообще могли оценить, какое решение могло бы подойти лучше.


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

interface{} это empty interface, мы говорим про интерфейсы в целом. Вы сначала разберитесь в теме, а потом пишите свой 200-й бессмысленный комментарий.

А я думал вы знаете Go. interface{} это еще способ эмулирования дженериков, как это делает Java, но вручную. Как и большинство вещей в go.


дженерики в Go есть, кроме определенных видов

Ну да. Generic в go — это interface{}. И да, это конечно же отличный дизайн, как и все в go.


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

Потому что он заменил C? Ну да, это все знают.


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

Невозможного ничего нет, пока есть приведение к interface{} и обратно, но попробуйте написать какой-то универсальный контейнер, например, монаду Maybe. И поиспользовать ее на go.

interface{} это еще способ эмулирования дженериков,
Ну да. Generic в go — это interface{}

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


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

Попробуйте решить реальную задачу на реальном проекте. Go для них создан.

Docker решает? Там вот используются функции, которые принимают interface{}.
Или вам нужно еще десяток проектов накидать, что бы вы поняли, то, что дошло до создателей языка?

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

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


Вот такой у нас тут был pipeline:


  • Дженерики не нужны
  • Еще как нужны, просто вместо них используется ущербный вариант через interface{}
  • Нет, это ничего не нужно и вообще без него все отлично
  • Ну вот нет, напишите такую монаду
  • Ох нет, это выдуманная проблема, а вот вы напишите реальный проект
  • Docker реальный проект?
  • И вот мы тут.
Попробуйте решить реальную задачу на реальном проекте. Go для них создан.

А можете привести пример такой реальной задачи на Go? Ну там бухгалтерия и склад какой-нибудь или АвтоКАДа? Может CRM, ERP? Игрушка полноценная? Что-то, что решает реальные задачи, а не служит инфраструктурой?

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


И еще одна, которая попытаеться повторить удачу odoo (раньше openerp), но с таким синтаксисом они далеко не уедут.

Ну там бухгалтерия и склад какой-нибудь или АвтоКАДа? Может CRM, ERP? Игрушка полноценная? Что-то, что решает реальные задачи, а не служит инфраструктурой?

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

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


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

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

Спасибо, это смешно.

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

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

Почти все реализации блокчейна пишут на Go, например (btcd, geth, hyperledger, etc). Это конечно, не так круто, как «программы бухучета» из вашего «реального мира», но «проценты там тоже можно начислять» :D

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


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

Ну и да.


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

Вам не стыдно так нагло врать то?) OpenStack вот почти и не слышал про go. Куча бекенда все еще на Java, C#, python и прочих.

все еще

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

Вам люди пишут про реальные проблемы языка — вы говорите, что это виноваты люди, что они не поняли go-way, что фича X не нужна, что они ничего не понимают.


Что бы слезть с удобных технологий нужна причина. Бенчмарки не решают для большинства проектов, решает инфрастуктура и полезность языка. И тут golang как бы где-то в середине. Те, кто переходят с него на C готовы его целовать, те, кому приходится переходить в более гибких языков на него не из-за проблем с производительностью только плюются и правильно делают. Потому что переход не решит никаких проблем, кроме проблем производительности и то, возможно. Потому что i/o bound задачи такой переход не пофиксит.

вы говорите, что это виноваты люди, что они не поняли go-way

Сами придумали, и сами меня в этом обвинили. Покажите, где я такое говорю.


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

Зачем же переходят, если и языки более гибкие, и производительность не нужна, и инфраструктура и полезность куда лучше, чем Go, и дженерики, и эксепшены — рай же, зачем переходить?

Зачем же переходят, если и языки более гибкие, и производительность не нужна, и инфраструктура и полезность куда лучше, чем Go, и дженерики, и эксепшены — рай же, зачем переходить?

Ну так вы же форсите go, вы и отвечайте.


Сами придумали, и сами меня в этом обвинили. Покажите, где я такое говорю.

https://habrahabr.ru/post/337098/?reply_to=10402372#comment_10398648
Ну так вы же форсите go, вы и отвечайте.

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


Сами придумали, и сами меня в этом обвинили. Покажите, где я такое говорю.
https://habrahabr.ru/post/337098/?reply_to=10402372#comment_10398648

И где там слова "люди виноваты" и "go-way". Зачем вы каждый свой комментарий перекручиваете и пишете заведомо ложные вещи, которые порождают опровергающие комментарии, на которые вы отвечаете таким же образом?

UFO just landed and posted this here
Иногда проще, ну, не пользоваться языком.

Проще, чем что? Чем написать понятным языком о том, как вы решали реальную задачу, и отсутствие пользовательских дженериков привело к тому, что код стал неэффективным, нечитабельным, глючным или некорректным? Да ладно, если бы хотя бы 1% свято верящих в абсолют дженериков понимал вопрос, таких бы репортов было бы уже тысячи, понятно и доступно написанных, без этих надменных бравад о том, какие авторы Go недалекие. Но что-то пока ни один не осилил.

UFO just landed and posted this here
чтобы за меня для простых типов генерил всякие селекты-инсерты.

Генерьте на здоровье. Есть go generate, встроенные библиотеки для работы с AST — такое не сложно написать и генерить себе что хотите для своих типов. Думаю, что если кому-то такое подход нужен был, то уже написано.


символьное дифференцирование на этапе компиляции.

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


некий алгоритм машинного обучения.

Очень конкретно, да.


4, 5, 6

Вы делаете ту же ошибку, что и комментатор выше — вы рассказываете не проблему, а уже готовый способ решать в вашем удобном языке. Я потому и приводил пример про вопрос "горутину в Хаскеле", потому что ваш пример "монадка для кодогенерации" это именно такой же нонсенс.


Неужели сложно так описывать проблему, если она реальна?

UFO just landed and posted this here
Ну, мы же всё-таки решаем проблемы, а не создаём их? :)
У вас в категорию реальных проблем входят только опердени и перекидывающиеся джсонами микросервисы, что ли?

Ну неужели такие вещи нужно объяснять. Это как прийти к водителю Теслы и спрашивать его, куда заливать бензин, вместо того, чтобы объяснить проблему "добраться из пункта А в пункта Б".

UFO just landed and posted this here
Вам приводят реальные примеры, вы говорите, что это нереальные примеры и вообще уже имеющися способы.

Вы вправду не видите разницу между "описать проблему" и "описать придуманное решение"? Будь вы продакт менеджером вы прямо так бы и писали в беклог — "сегодня мы должны написать монадку" и все такие — "да, да, пишем монадку, ведь наша бизнес модель построена на решении проблемы отсутствия монадок".

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


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

UFO just landed and posted this here
Именно к нему я вас и веду. Вот «провести вычислительный эксперимент» — у вас есть какая-то функция, какие-то входные данные, и вы пишете код, который даст вам данные на выходе. Зачем вам реализовывать эту функцию «для всех возможных типов»? Вам нужно взять вот эти флоаты, посчитать их и получить ответ тоже во флоате. Зачем вам делать так, чтобы эта функция могла работать со строками, например?
UFO just landed and posted this here

Да нет там ничего сложного.


Парсим шаблон:


T sum( T )( T[] list ) {
    T result;
    foreach( item ; list ) result += item;
    return result;
}

Видим использование:


sum!int([ 1 , 2 , 3 ]);

Подставляем тип в шаблон, генерируем код и компилируем:


int sum( int[] list ) {
    int result;
    foreach( item ; list ) result += item;
    return result;
}

Видим другое использование:


sum!float([ 1.1 , 2.2 , 3.2 ]);

Подставляем тип в шаблон, генерируем код и компилируем:


float sum( float[] list ) {
    float result;
    foreach( item ; list ) result += item;
    return result;
}

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

Да нет там ничего сложного.

Это так мило. Ну не умеют люди из Bell Labs и Google парсить шаблоны. Давайте я им ваше резюме скину, научите их?

Ага, а люди из никому не известной Digital Mars почему-то умеют.

Ага, а люди из никому не известной Digital Mars почему-то умеют.

Что именно они умеют? И почему они неизвестные, если они так много умеют.

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

Я вам рассказал, как они работают в языке D. А вы так и не рассказали какие же там великие сложности.

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

Я не увидел там никаких сложностей

Может законтрибьютете в Go, добавите дженериков, если не увидели никаких сложностей? :)

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

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

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

У меня в профиле всё написано так-то.


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

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

Тут проблема в великом и ужастном legacy. Как только вы так сделаете, то потом вам же нужно будет тащить за собой этот не очень продуманный синтаксис веками, как это сейчас делает Java. И это очень грустно для нее, посмотрите только на иерархую классов Property там. Там просто ад из-за того, что примитивы не могут в generic.

Я плохо знаю D, так что подскажите мне пожалуйста, если у вас будет время:


  • Как там решается проблема, когда в шаблон нужно засунуть объект, который должен удовлетворять двум интерфейсам?
  • Как обстоит дело с тем, что объект может быть наследником класса любой вложености?
  • Как обстоит дело с тем, что объект должен быть предком класса любой вложенности?
  • Как обстоит дело с тем, что объект должен быть только нескольких классов и не их предков/наследников?
Как там решается проблема, когда в шаблон нужно засунуть объект, который должен удовлетворять двум интерфейсам?

interface A {}
interface B {}

void foo( T )( T o )
if ( is( T : A ) && is( T : B ) )
{
    // ...
}

Как обстоит дело с тем, что объект может быть наследником класса любой вложености?

class A {}

void foo( T )( T o )
if( is( T : A ) )
{
    // ...
}

Или просто:


void foo( A o )
{
    // ...
}

Как обстоит дело с тем, что объект должен быть предком класса любой вложенности?

class A {}
class B : A {}

void foo( T )( T o )
if( is( B : T ) )
{
    // ...
}

Как обстоит дело с тем, что объект должен быть только нескольких классов и не их предков/наследников?

class A {}
class B {}

void foo( T )( T o )
if( is( T == A ) || is( T == B ) )
{
    // ...
}

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

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


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


Это я к тому, что
t -> t*t
и
(DoubleUnaryOperator & Serializable)t -> t*t
внезапно, порождает разные типы одной и той же функции, что довольно удобно в редких случаях.

Вот этот кейс я уже не понял. Что это и зачем?

Оно работает только через bytecode, а жаль.


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

Понятней не стало. Пример бы.

Выглядит как-то так. Вторая лябмда удовлетворяет интерфейсу Serializable, а первая нет.

Эм, и во что оно сериализуется?

Чисто в информацию о классе. Суть в том, что изменение generic типа, который должна вернуть функция влияет на результат выполнения функции. Оно вряд ли так сильно необходимо, но мне кажется. в рамках D это будет сложно сделать. Или можно будет просто проверок повпихивать и я запутался?)

Скорее можно, чем нельзя. Но всё-равно не понятно, что оно там должно выводить и зачем так извращаться.

sum!float([ 1.1 , 2.2 , 3.2 ]);
sum!int([ 1 , 2 , 3 ]);

В настоящих задачах нужно учитывать возможность переполнения. В дробях и целых оно учитывается по разному. Учтено ли это в Вашем несложном примере?
А как в настоящих задачах вы учитываете переполнение при сложении a + b?
Зависит от задач, в том-то и дело. Если это целые неотрицательные, то как-то так:
bool sum(int len, int a[len], int *sum, int *i) {
  *i = 0;
  *sum = 0;
  while ((*i < len) 
      && (a[*i] >= 0) 
      && (*sum <= INT_MAX - a[*i])) 
  {
     *sum += a[*i];
     *i += 1;
  }
  return *i >= len;
}

В других случаях это будет по другому.
int a = ???
int b = ???

int c = a + b

Вы всегда учитываете переполнение при сложении?
Да, кроме тех случаев, где я об этом забываю.
Есть 2-a сценария:
  1. Переполнение невозможно исходя из задачи, потому что известны ограничения на возможные значения. Здесь переполнение, которое может случится только из-за ошибки в алгоритме, должно контролироваться компилятором (gcc -ftrapv, использовать только signed)
  2. Переполнение возможно, потому что данные косвенно или прямо получены из источников, где нужные диапазоны значений не гарантируются. В таком случае нужно проверять возможность переполнения и обеспечить их корректную обработку.

Необработанные переполнения — это возможный источник уязвимостей и других проблем.
Что, разумеется? Вы написали, что
Да нет там ничего сложного.

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

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

Я не увидел
Да нет там ничего сложного.
Не могли бы Вы изобразить в своей интерпретации, как это сделали раньше в сообщении, где писали
Уже даже таких простых шаблонов без выведения типов уже хватило бы, но нет, «это слишком сложно».
UFO just landed and posted this here
Ну все, пойду переписывать все свои Node.js бэки на Go. А если серьезно, как-же достала это выдуманная проблема с языками и платформами (особенное с очень близкими по производительности). Т.е. выбор языка по бенчмаркам куда предпочтительнее, чем скорость разработки функционала / экосистема / синтаксис / высокоуровневость?


В контексте сравнения именно Go vs Node мы имеем вполне сопоставимые экосистемы.

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

Я действительно не могу понять зачем так

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

Относиться уважительно нужно к данным, а не к обрабатывающему их коду. Если у вас ошибки являются частью данных (привет ADT и Option/Either), то вот тогда вы к ним относитесь уважительно.

А если нет Option/Either, то на ошибки надо забивать? Это где вас так научили?

А если нет Option/Either, то на ошибки надо забивать

Нет, их надо добавить. Ах, в го не дженериков.
Нет, их надо добавить

Кому надо?
Тем кто относиться уважительно к данным и тем кто не забивает на ошибки.
Именно, в Go ошибки это такие же данные, поэтому к ним и относятся уважительно. Вы поняли суть.
Относиться уважительно нужно к данным, а не к обрабатывающему их коду. Если у вас ошибки являются частью данных (привет ADT и Option/Either), то вот тогда вы к ним относитесь уважительно.

а не как в go

Вы про Go только вчера узнали, и уже составили мнение о том "как в Go" и им пытаетесь кому-то что-то доказывать?
Удачи.

Я слежу за go с версии 1.0

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

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

Как вас плющит от одной только мысли, что ошибки нужно обрабатывать :)

Если нет Option/Either (или хотя бы инструмента для их реализации), мне не нужен такой язык, увы.

Он есть. Это делается например, вот тут. Но из-за отсутствия дженериков вы вынуждены будете делать приведение интерфейса к нужному значению :) И так каждый раз.

Я правильно понимаю, что interface{} — это, своего рода, any? То есть каждый раз кастовать к any? Потрясающе :)

К сожалению, я не знаю, какой язык вы имеете ввиду. Мне ближе всего в этом вопросе Java и в такой терминологии interface{} — это Object, к которому можно привести любой объект.

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

То есть каждый раз кастовать к any? Потрясающе :)

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


Вы хотите понять, как в Go решают задачи или хотите доказать себе, что Go плохо продуман? Если первое, то можете спрашивать, постараюсь объяснить. Если второе, то это вам к SirEdvin.

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

Окей, объясните мне, как реализовать Option с сохранением типов аргумента и результата. Например, для Option<Number> нужна реализация map :: Option<Number> ~> (Number -> B) -> Option<B>. Типичный представитель библиотечного кода. Как решаются такие вещи, или они не решаются вовсе?

Вы сейчас пытаетесь решение какой-то проблемы из одного языка воплотить в другом языке. Ну тоесть это как если я спрошу «Окей, объясните мне, как в Rust инициировать запуск внеплановый сборщик мусора» или «Окей, как в Haskell запустить горутину».

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

100% ожидаемый ответ, конечно, ну да ладно.


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

100% ожидаемый ответ, конечно, ну да ладно.

Это не да ладно, это в корне меняет всю суть дискуссии.


пишите вы библиотечку для тех же бинарных деревьев.

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


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

Еще более ожидаемый ответ.


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


Я вам привожу наиклассический пример — деревья. Деревья чисел, деревья строк, деревья котов, чего угодно. Так и происходит. Или вы не признаете классические структуры данных и алгоритмы для них, так как в го вы их не используете?


EDIT:


у вас прямо так в проекте задача и стоит

И такое бывает, так как большую часть времени я пишу именно библиотечный код "под все" и не могу позволить иметь 500 функций mapNumber, mapString, mapCat.

Вы понимаете, что вы тут как бы пытаетесь намекнуть

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


вы не признаете классические структуры данных

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


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


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


За "поведение" в Go четко отвечает один ортогональный концепт — это interface. Вы определяете интерфейс для нашего типа, назовем его Node:


type Node interface {
    Less(than Node) bool
}

Этот интерфейс содержит лишь один метод, и любой тип, к которому мы добавим метод Less(Node) bool автоматически будет удовлетворять этому интерфейсу.


Дальше вы реализуете сам алгоритм, где вы уже при проходе используете метод Less(), даже не зная, что там под капотом. Алгоритму и не нужно знать — он работает с поведением типа.


Из очевидных минусов в том, что в функции Less придется приводить интерфейс к конкретному типу, что равно примерно порядка десяткам наносекунд:


type Int int

func (a Int) Less(b Node) bool {
    return a < b.(Int)
}

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


import "github.com/myuser/btree"

...

tree := btree.New()
..
tree.Insert(Int(3))
tree.Insert(Int(4))
tree.Has(Int(3))

и т.д.


Ваша очередь отвечать на вопрос.

UFO just landed and posted this here

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


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

Конечно, так и должно быть в идеале. Это же неявное удовлетворение интерфейса (нету никакихimplements Node).

UFO just landed and posted this here
Тогда кто пишет этот код?

Пользователь библиотеки и owner типа, который он хочет использовать с этой библиотекой.

UFO just landed and posted this here
мне необходимо отдельно явно написать код, который упоминает вашу библиотеку?

Конкретно в этом случае — да, придется. Есть другой подход, для массивов, например — Less(i, j int) bool, тут ничего импортировать не придется.

UFO just landed and posted this here

Ну да. Ведь код вида tree.Insert(Int(3)), которым будет покрыто все ваше приложение нужно оценивать с точки зрения именно производительности, а не с точки зрения "АААААААА, как на этом вообще можно писать".


А из того, зачем нужно бинарное дерево, например, для огранизации небольшой базы знаний в рамках проекта. Конкретно в нашем случае это была база знаний про регионы Украины, в которой можно было бы довольно быстро найти соотношения геолокации к региону страны. А в силу его большого размера, программист, который его писал запихнул в его файл с упрощенной структурой, разумеется, бинарной, для быстрого чтения. Файл большой (2ГБ) в память не засунешь, бегать потоково по нему не будешь, библиотеки конкретно под такую задачу тоже не нашлось.

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

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

Я пытаюсь услышать реальную задачу, серьезно.

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


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

Поздравляю, вы вывели Setoid+Ord. И да, реализуете вашим типом equals и lte и вперед.


Дальше вы реализуете сам алгоритм, где вы уже при проходе используете метод Less(), даже не зная, что там под капотом. Алгоритму и не нужно знать — он работает с поведением типа.

Здорово, только мне нужна map для этих деревьев (а не для типа, так что реализация функтора типом не катит) с ограничением типа аргумента лямбды. Мне нужно деревья строк приводить к деревьях интов (дерево одной команды переиспользовать в другой) с последующим траверсом в Option. С сохранением внутреннего типа значений ноды.
Go?


Из очевидных минусов в том, что в функции Less придется приводить интерфейс к конкретному типу, что равно примерно порядка десяткам наносекунд:

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


tree.Insert(Int(3))
tree.Insert(Int(4))
А если потом вот так?
tree.Insert(String('123')) //синтаксис может быть неточным

PS. Мне, в принципе, уже хватило, чтобы понять, как в го строится разработка — от минимальной задачи, а не от абстракции. Потому-что абстракций толком не построить, инструменты хилые для этого. Да, с какой-то стороны это может быть удобным, ну, мол, да че там касты между деревьями, я ж не буду это каждый день делать. А если тикет прилетит — ну уговорю менеджера не делать, типа мы работаем на 10нс быстрее всего остального, так что деревья скопипастим. Так?

UFO just landed and posted this here

Дельное замечание. (EDIT: и тут включается ССЗБ, на самом деле)
Но поставленного вопроса оно все-равно не решает.

Дано: три команды, которым нужны бинарные деревья

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

А вы забавный :) как вы лихо увиливаете от задачи


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


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


tree := btree.New()
tree.Insert(Int(3))
tree2 := btree.New()
tree2.Insert('abc')

Запретите в tree пихать строки, а в tree2 инты. Это обычное требование к статически типизированному библиотечному коду.


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

как вы лихо увиливаете от задачи

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


и это фееричный факап для типизированного языка

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

Ну вы и троль! :D
Вот задача. Вы: "Это не задача!". Вам: "Ок, вот еще одна". Вы: "Не, не вижу!". А потом выстреливает такое закономерное "ой все".


Просто потрясающе! Жаль вашего работодателя, вы небось с каждым тикетом бегаете к PMO/архитекторам с допросами "Что вы тут напридумывали? Какую вы тут задачу вообще решаете?". Ну просто 10 из 10.


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

Вот смотрите — вы захотели написать что-то такое, что, по вашему, заденет собеседника — постарались и написали. В чём проблема также постараться и ответить на вопрос — «какую проблему вы решали?» :)

Я люблю этот вопрос в дискуссиях про дженерики, потому что на него практически никогда не может ответить — все начинают описывать решение, которое у них есть в голове, а не саму проблему, увиливать от ответа и всячески уходить от темы. Это как раз показывает большую пропасть между теми, кто языки программирования любит как «вещь в себе» — за фишечки и навороты, а кто колбасит код по 6 часов в день ежедневно и решает реальные проблемы.

Жаль, я думал вы будете первый, кто внятно даст ответ на такой простой вопрос.
Вот смотрите — вы захотели написать что-то такое, что, по вашему, заденет собеседника — постарались и написали. В чём проблема также постараться и ответить на вопрос — «какую проблему вы решали?» :)

Я вам талдычу который раз уже. Вас устраивает только формат "Бизнесу нужно срубить с продажи приложения в 2 раза больше денег в этом квартале"? Если go не решает простые задачи, прилетающие после нескольких словев декомпозиции, то так и говорите, что не решает, а не то, что постановка неверная. Иначе мы ходим по кругу.


Это как раз показывает большую пропасть между теми, кто языки программирования любит как «вещь в себе» — за фишечки и навороты, а кто колбасит код по 6 часов в день ежедневно и решает реальные проблемы.

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


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

Я вам талдычу который раз уже.

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


решит задачу с деревьми за пару минут, но не на Go

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

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

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

Ок, в go есть горутины, скажите какую проблему решают горутины?
не решают проблемы, они помогаю в разработке решения.

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


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


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


go есть горутины, скажите какую проблему решают горутины?

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

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

То есть "написать бинарное дерево" не бизнес-задача "а написать приложение, которое эффективно использует все ядра процессора" — это бизнес-задача? Ясно-понятно.

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

Ну то есть у вас в беклоге бизнес-аналитики так и пишут "написать приложение, которые эффективно использует все ядра процессора"?


Мне интересно, вы серьезно не замечаете проблемы с вашей логикой?)

Мне интересно, вы серьезно не замечаете проблемы с вашей логикой?)

Я вам не мешаю сам с собой разговаривать?

А вы не передергивайте.


То есть "написать бинарное дерево" не бизнес-задача "а написать приложение, которое эффективно использует все ядра процессора" — это бизнес-задача?

Это прекрасный пример вашей логики.


какую задачу вы решали

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

> Поставленную. Точка. Что тут непонятного? Вы не выполняете поставленные задачи?

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

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

И пусть же нам всем повезет, и все мы не будем иметь дело

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


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

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


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

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

Аа, команды всмысле teams? Мне прямо уже вас страшно спрашивать, но зачем вам для апишечки, которая пишет в базу 3 сущности, писать бинарное дерево для разных команд(!)? Тоесть я снова спрашиваю, какую проблему вы решали этим? Как-то не сходится пока что, не могли бы вы детальней объяснить?


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

А на чём писали, кстати, что не было готовой библиотеки для binary tree?

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

Ау! Мне на надо их писать в базу, при чем тут база вообще? Вы не умеете мыслить абстрактно и все задачи сводите к записи в базу?


А на чём писали, кстати, что не было готовой библиотеки для binary tree?

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

Ау! Мне на надо их писать в базу, при чем тут база вообще?
Нам надо сделать API для добавления продуктов,

А куда вы их добавляете? В блокчейн? Ну ладно, придираюсь, согласен, хотя хотелось бы понимать задачу полнее. Потому что до сих пор не ясно, как связано "API для добавления продуктов" и "бинарные деревья для трех разных групп разработчиков".


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

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


Но мы же go тут обсуждаем, где их нет.

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

как связано "API для добавления продуктов" и "бинарные деревья для трех разных групп разработчиков"

Это разные люди пишут.


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

А какая разница? На языке с дженериками. Ну, раз интересно, на TS.


описанная вами задача решается без дженериков

Покажите же наконец!


и на интерфейсах

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


кодогенерацией под ваши конкретные типы

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

Это разные люди пишут.

И что из этого следует? Вам нужно API для добавления продукта куда-то. В каком месте возникает бинарное дерево?


Ну, раз интересно, на TS.

О, господи. На ангуляре 4 ещё небось? Что ж вы раньше не сказали, я бы с пониманием относился. Это проясняет ваши взгляды на разработку софта.


Покажите же наконец!

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


И как же интерфейсы без дженериков мне реализуют map для дерева с сохранением типа аргумента лямбды?

Вы же апишечку пишете на TS, откуда у вас появляются фантазии писать "map для дерева с сохранением типа аргумента лямбды". Мне кажется вы решаете больше проблемы, которые сами себе придумываете теми инструментами, которые выбираете (TS, ха-ха), вместо того, чтобы решать конкретные задачи. Это популярная тема среди JS-мира, увы :(


увольте.

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

Вам нужно API для добавления продукта куда-то. В каком месте возникает бинарное дерево?

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


О, господи. На ангуляре 4 ещё небось? Что ж вы раньше не сказали, я бы с пониманием относился. Это проясняет ваши взгляды на разработку софта.

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


Вам же уже два примера кода написали. Вы хотите, чтобы вам лично веки приподняли, или как ещё показать?
Вы же апишечку пишете на TS, откуда у вас появляются фантазии писать "map для дерева с сохранением типа аргумента лямбды". Мне кажется вы решаете больше проблемы, которые сами себе придумываете теми инструментами, которые выбираете (TS, ха-ха), вместо того, чтобы решать конкретные задачи. Это популярная тема среди JS-мира, увы :(
Я бы вас даже не взял, без обид. Впрочем, со временем вы всё равно начнете ценить прагматичность и практичность, поработаете в разных командах, начнете ненавидеть сложность, особенно придуманную и привнесённую, и ценить практичность и элегантную простоту, научитесь отличать лютое Г от красивых решений. Ах да, ещё когда научитесь доступно изъясняться и рассказывать проблему. Без ясной коммуникации никак. Вот тогда может и уволю :)

Браво! Это просто победа! Сохраню ссылку в копилку.


PS. Боже упаси кого-то взять вас в штат.

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

Вы же чуть раньше писали, что вам нужно было API для добавления разных продуктов? Уже отказываетесь от своих же объяснений?


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

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


PS. Боже упаси кого-то взять вас в штат.

Я могу ещё доплатить, чтобы вы не брали людей в штат. Пожалейте людей.

Вы же чуть раньше писали, что вам нужно было API для добавления разных продуктов? Уже отказываетесь от своих же объяснений?

Сектор пруф на барабане!


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

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

Сектор пруф на барабане!
https://habrahabr.ru/post/337098/#comment_10405048

Блин, вы меня запутали. Я спрашивал вас, но за вас ответил rraderio, и все пошло вперемешку. У вас оба ник на r- начинается, поэтому легко было не заметить подвоха. Прошу прощения.


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

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

UFO just landed and posted this here
ну что не понятно в задаче?

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


Так понятней?

Вон вам выше ответили уже.


Давайте сфокусируемся, что мы тут обсуждаем? Вы меня пытаетесь поймать или мы все же пытаемся решить задачу с деревьями на go? Если первое, то ловите на здоровье, но диалога не будет (как собственно его и сейчас нет), если второе — расскажите, как вы будете имитировать полиморфизм. Мне действительно интересно, как это делается. Ну а если не делается, прекратите уже паясничать и ответьте прямо "нет, такие задачи на го не решаются, он хорош в других областях".

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


Формулировать задачу "как вы делаете сохранение типа аргумента лямбды" некорректно, потому что а если в каком-то языке нет лямбды — то что вы вообще спрашиваете? Я приводил пример выше — это как спрашивать "как вы в Хаскелле запускаете горутины". Ну, согласитесь, это некорректно.


Более того, мы же не сферического коня в вакууме обсуждаем, а программирование. Люди не программируют просто так — они решают задачи и проблемы, пишут софт для чего-то. Нет такого бизнеса "компания по написанию монад" или "корпорация написания полиморфических лямбд".


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


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


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


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

"нет, такие задачи на го не решаются, он хорош в других областях"

Ну, то есть, никак не решаются?
Я вас вторые сутки к этому подвожу.


EDIT: вам что, стыдно за го, что на нем нельзя писать полиморфные деревья? странно, вы же вроде за другие преимущества топите… получается, что стыдно?

Ну, то есть, никак не решаются?
Я вас вторые сутки к этому подвожу.

Я правильно вас понял?


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

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

Нет, неправильно.
— В Го нет дженериков? Жуть
— Нет, не жуть, вы ничего не понимаете
— Но как же, классические структуры
— Нет, они не нужны
— Но как же типизация?
— Приведите пример задачи
— Деревья
— Это для студентов
— Но как же, классические структуры
— Они не нужны
— Прекратите, и решите задачу
— Нет не буду, на чем вы пишете
— TS
бабах, небольшое несовладание
— Решите же уже задачу, или согласитесь, что она нерешаема
— Нет не буду
— Ну решите, ну пожалуйста, мне интересны техники эмуляции
— Нет не буду, вы не умеете формулировать задачи, и вообще, делаете какую-то фигню, потому-что реальные программисты деревья не пишут


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

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

Это наш "диалог". Или у вас в голове и такое не укладывается? Ой все?


Или все-таки они не решаются?

UFO just landed and posted this here
Вас и про Go никто не спрашивает, как в Go сделать частичную специализацию С++-шаблонов,

Спрашивают. И рассказывают друг-другу какой Go убогий, потому что там нет С++ шаблонов. Чувствуете уровень?

UFO just landed and posted this here
Нет, там спрашивают вообще про хоть какие-то возможности к обобщённому программированию.

Как бы вам не хотелось смягчить градус неадеквата некоторых комментаторов, но нет. Уже было неоднократно рассказано и про встроенные generic-типы, и про решения многих задач интерфейсами, и развенчан миф, про то, что дженерики нужны на каждом шагу, и показан пример как то, что человек хотел делать дженериками, делается банальными интерфесными типами, и про go generate тоже упомянуто. Этого было достаточно, чтобы ищущий ответ человек, действительно решил углубиться в тему. Но этого всего тут я не вижу, пока только неудачные попытки самих себя убедить, что они умные, а авторы Go идиоты.

аууу, вон ниже вы даже ветку про DI замолчали, потому-что адекватный DI невозможнен
про map я уже выше написал, вы решили половину задачу, решаемую половину

UFO just landed and posted this here
UFO just landed and posted this here
Давайте мы тут все будет жить дружно и постараемся удержаться от подколок на тему наукоёмкости, новизны, илитарности и прочих подобных критериев разных языков и технологий.

Дельно, а почему вам это пришло в голову только после подколок ангуляра, а десятки подколок от js-школоты про Go вы спокойно приняли? :)


А то, знаете, мне тоже много чего есть сказать про Go и про проекты, которые на нём типично пишут.

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

UFO just landed and posted this here
а не людей, разрабатывающих на этих языках, и их подходы.

Вы либо не всё прочитали, либо просто biased слишком. :) В одном из первых комментариев даже с воинствующими мусульманами сравнивали сразу всех, кто пишет на Go и дальше по стандартному списку.


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

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


В целом, я всегда прислушиваюсь ко мнению тех, кто имел равнозначный опыт и с А и Б, и может сравнить. Те, кто работал только с А, и с позиции А поливает грязью Б, всегда неправы и олицетворяют образец невежества. Я с таким, правда, в основном только на Хабре сталкиваюсь, не знаю почему.

UFO just landed and posted this here
Ну это какие-то совсем идиоты были, достаточно было посмотреть на небо и на птиц.

Ну вот вы сами и ответили.


Но Go не настолько выдающийся, чтобы на нём надо было прям писать.

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

UFO just landed and posted this here
А какая ниша-то?

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

UFO just landed and posted this here
Ну, я боюсь, что у вас, что у меня тут весьма нерепрезентативная выборка.

Ну вот смотрите, реальный кейс. Недавно проходил конкурс https://highloadcup.ru, который тут был анонсирован на Хабре. Там очки давались за корректность и за суммарную скорость ответа, если грубо. Был месяц. Понятное дело, что в таких условиях есть смысл тюнить и писать велосипеды, но не суть. Вот распределение языков:


image


C++ решения тут на первом месте по количество, но на втором Go. По рейтингу, в первой десятке.- C/C++, одно на Go, и дальше Go во второй десятке.


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


Народ же на С++/Rust/Java и прочих "правильных языках" дописали свои решения уже ближе к третьей неделе конкурса, заоптимизировали и, понятное дело, свои миллисекунды выиграли. Java программистов в топ50 прошли только 3, как видно из графика, хотя их, как раз было большинство. Ноды, пхп и прочего мусора, там, естественно нет в топ50.


Вобщем, если вам нужно выжимать микросекунды, есть время на то чтобы долго и вдумчиво писать и оптимизировать всё — C++ хороший выбор, high-frequency trading ниша например очень ок. Но среднестатические сервера, где нет реалтайма, где 200 микросекунд погоды не сделают, где всё давно бежит в kubernetes — ваши потраченные недели на чуть более производительный, но нечитабельный и потенциально безопасный код на C++, который компилируется пол часа (против 2 секунд в Go) и сложно поддающийся рефакторингу — того не стоит. Поэтому для 90% современного серверного софта лучшего варианта, чем Go я не вижу. Хаскель — язык, на который надо потратить 4 года, чтобы хоть что-то не стыдное написать — это, понятное дело, глупость и практический опыт применения Хаскелля уже должен был дать понять, что время входа это таки фактор.

UFO just landed and posted this here
Интересно, сколько бы там какой-нибудь питон занял в первые несколько дней.

Писать можно было на чём угодно, и на питоне тоже писали. Питон на порядок, если не два медленнее Go, не понятно, с чего вы ожидаете что питон бы был в топе. По скорости написания Go как раз сравним с Питоном. Вообще, у меня первые субъективные ощущения были в Go, что он чувствуется как динамический скриптовый язык, хотя на самом деле статически типизированный и быстрый (вероятно потому что я много поначалу баловалс с go run, или прямо из vim-а запуская), и из-за субсекундной компиляции это даже не чувствовалось, что там вообще этот этап есть.


Ну, я не в HFT, но производительность, что throughput, что latency, весьма важна.

Без обид, но чхать я хотел на то, что там у вас. Right tool for the right job. Если вам нужна экстрапроизводительность — хоть на асме пишите. Но я как-то ушел из компании, в которой упоротые С++-сники писали рест-бекенд 6 месяцев, который на Go я переписал за сутки. Но это особенная каста — они столько лет шли к тому, чтобы дойти до уровня "могу писать софт, который не течет", что не могут просто так взять и перейти на другой, более адекватный для данной задачи язык, ну и плюс чувство элитарности. Это же подумать только — 5 лет в универе, плюс 3-5 лет практического опыта — это просто чтобы мочь написать сервачок, который не течет и хотя бы 10к держит. Это, блин, 10 лет жизни. Вобщем, с ними всё тяжко, и выбирать нужный инструмент для нужной задачи там, как правило, не умеют. У вас кстати это тоже чувствуется по комментариям, но могу ошибаться.


Хотя, возможно, это мой bias как уже давно знающего человека.

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


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

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


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

Каноническая классика — все кругом дебилы, один я умный! Вам бы в шараге пары вести :)

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

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

UFO just landed and posted this here
Зато я теперь за пару месяцев пишу то, что другие писали бы на каком питоне или том же гоу за девять,

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


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

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


Удивительно, но хаскелистов таки более одного, и споров за стиль не возникает,

Вот именно, что удивительно.

UFO just landed and posted this here
Я могу сравнить с чужими бизнес-планами,

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


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

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


Тяжело. В основном потому, что в опенсорсе у меня всякое нинужно.

А язык, думаете, не причем?


На той работе, за которую мне платят деньги, формат немного другой.

Не разрешают на выходных кодить?

UFO just landed and posted this here
А бекенд-сервис тот вы на плюсах писали? Или чего ж вы тогда сравниваете с тем, что и как другие люди делали?

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


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

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


Им нужна ясность, и чем меньше мусора когнитивного, тем лучше.
Не про Go, увы.

Именно про Go, к счастью.

UFO just landed and posted this here
чтобы нужно было разбираться, что там, блин, наколбашено

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


Значит, вы просто не выучили плюсы достаточно :)

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

UFO just landed and posted this here
Им нужна ясность, и чем меньше мусора когнитивного, тем лучше.

А предметная область входит в мусор?

А предметная область входит в мусор?

Нет, предметная область это essential complexity. Нагрузка, привносимая языками и тулзами, которые мы выбираем — это accidental complexity.


С первой нужно считаться, от неё, как правило, не уйти. Вторую нужно уменьшать настолько насколько это возможно.

UFO just landed and posted this here
Супер, так опишите проблему, для которой вы разрабатываете решение.

Нам надо сделать API для добавления продуктов, у нас много продуктов, но они все отличаются только одним полем, поле quantity(type, value), у одних это (kg, double) у других (pieces, long) и (liters, double). С дженериками мы сделаем один класс и по классу для всех quantity.

Как это будет в go?

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

Вам продакт менеджер так и сказал?

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

Дополню. С дженериками мы сделаем один класс и по классу для всех quantity и один метод save

Отлично, в Go вы сделаете три типа для продуктов:


type Product struct {
   CommonField1 int64
    ...
   CommonFieldN int64
}

type ProductType1 struct {
    Product
    Kilograms float64
}

type ProductType2 struct {
    Product
    Pieces int64
}

type ProductType3 struct {
    Product
    Liters float64
}

Далее, поняв, что все эти продукты объединяются только одной общей операцией Save, создаете интерфейс, с которым будет уже работь функция общающаяся с базой или куда вы там сейвите:


type Saver interface {
    Save() error
}

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


func (p *Product1) Save() error {
    // save to whatever, serialize, convert, check overflows, etc
}

func (p *Product2) Save() error {
    // save to whatever, serialize, convert, check overflows, etc
}

func (p *Product3) Save() error {
    // save to whatever, serialize, convert, check overflows, etc
}

и дальше пользуете это всё вместе:


func (*DB) StoreProduct(s Saver) {
    db.Insert(s.Save())
}
...
prod1 := &Product1{Kilograms: 13.123}
prod2 := &Product2{Pieces: 13}
prod3 := &Product3{Liters: 13.123}

db.StoreProduct(prod1)
db.StoreProduct(prod1)
db.StoreProduct(prod1)

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


UPD. В Save() там скорее всего будет код, который возвращает SQL или подобное, ну или который напрямую в базу лезет, но это менее красиво. Ну вы поняли.


Какие будут вопросы?

Вам понравится Brainfuck. В нем минимально необходимое количество фич для разрабатывания программ.

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

Понятно что можно сделать всё только имея структуры и функции, но если этих quantity у вас будут не 3 а 10, 20, с дженериками я просто добавлю 10, 20 маленьких классов и всё. В go же надо добавить не только новые структуры но и новые методы. Это по суть и цели дженериков.
Понятно что можно сделать всё только имея

Кто-то тут ещё недавно кричал про "фееричный факап", а теперь "понятно".


структуры и функции

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


но если этих qunatity у вас будут не 3 а 10, 20, с дженериками я просто добавлю 10, 20 маленьких классов и всё.

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


А у меня вопрос таки — бинарные деревья тут где нужны были?

Кто-то тут ещё недавно кричал про «фееричный факап»
Вы меня с кем-то путаете

Нет, структур и функций мало.

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

Если у вас добавилось одно свойство, вы будете добавлять новый класс?

Класс, который по сути структура.
Вы меня с кем-то путаете

Потому и написал "кто-то" — тут сложно трекать, кто что написал. Но в этой ветке буквально пару постов выше было.


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

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


Класс, который по сути структура.

Ок. в Go вам не нужно будет добавлять новую структуру, если добавляется новое поле.

Для чего в примере выше вам дженерики?

Для того чтобы написать метод save один раз

если добавляется новое поле

Не новое поле, а новый тип quantity, т.е новый Product4
Кто-то тут ещё недавно кричал про "фееричный факап", а теперь "понятно".
Потому и написал "кто-то" — тут сложно трекать, кто что написал.

А что "понятно"-то? :) Писать можно хоть на бейсике и вполне себе сложные вещи. Но никто ж (почти) не пишет. Можно писать и на сях. Но Go же появился.
Вот только все эти "дурни, не видящие света в потемках, ибо есть go", рыдающие и продолжающие жевать кактус всех этих джав и шарпов, и их создатели внушают куда больше доверия.

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

Ну да, столько фич. Сразу видно — можно доверять. Это так забавно, такое слышать от живого человека. :)

Слушайте, я не понимаю, что вообще вы делаете в этом топике и зачем делаете такие переводы? Идите пишите на своем убогом go, срезая все задачи за "неимением надобности" :) Ну правда, избавьте людей от этого цирка

Видимо, правду люди говорят, гошники хуже джунов-джаваскриптеров.

Видимо, правду люди говорят, гошники хуже джунов-джаваскриптеров.

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

Я боюсь встретить людей, которым вы мозги ломаете. Они ж неадекватые совсем должны быть… =(

Слушайте, я не понимаю

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


Не берите в штат никого больше.


зачем делаете такие переводы

Какие переводы?


А вы, к слову, что полезного делаете, кроме как комментируете чужие посты и светите своим невежством? Это вы так TS-сообщество популяризируете?


Ну правда, избавьте людей от этого цирка

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

Зная ваш бекграунд

Вы знаете мой бекграунд? Вы за мной следите?


Вы даже не понимаете, что вы пишете — не то API для продуктов, не то бинарные деревья для команд

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


Какие переводы?

Вот эта "статья" перевод


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

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


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

Смотрите на здоровье, может толк будет :)

Понял, откуда у меня появилась мысль, что вы пишете потом в базу. Это rraderio написал:


весь обьект продукта надо сохранить в базу

Я вас путаю, или вы вместе работаете?

UFO just landed and posted this here
Нам надо сделать API для добавления продуктов, у нас много продуктов, но они все отличаются только одним полем, поле quantity(type, value), у одних это (kg, double) у других (pieces, long) и (liters, double). С дженериками мы сделаем один класс и по классу для всех quantity.

Ну наконец-то! Давайте ваш биткоин ключ, отблагодарю за труды :)


А что с этим Quantity нужно делать дальше? Поиск, добавление в базу, ещё что-то? Для чего нужен был бинарный поиск?

А что с этим Quantity нужно делать дальше?

весь обьект продукта надо сохранить в базу
UFO just landed and posted this here
Вы так и не описали, какую задачу вы решаете, когда используете все ядра процессора.

Ну это же очевидно. Вот смотрите, визуализация работы Node.JS программ: http://imgur.com/jqsdofy


Понятно, или объяснять ещё?


А ваша цель — максимально эффективно использовать все ядра процессора?

А ваша цель — минимально эффективно их использовать? Узнаете стиль?

UFO just landed and posted this here
UFO just landed and posted this here
То есть, мой ответ снова не засчитывается?

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

UFO just landed and posted this here
UFO just landed and posted this here
мне не нужен такой язык, увы.

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

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

Нет, вы пишите статьи о том что node не нужен
Нет, вы пишите статьи о том что node не нужен

Я уже привык к неадеквату тут в комментариях, но это хит. Покажите мой такой пост.


Данный пост, к примеру, это перевод интервью разработчика Node.js, с его мыслями про Go. Перевод. Интервью. Про Go.

Вот вы перевели, с какой целью?

Дать её прочесть тем, кто не читает статьи на английском. А вы с какой целью комментируете?

Этот фрагмент меня поражает поразительной незамутненностью интервьюируемого.


Насколько же нужно не смотреть вообще по сторонам и не интересоваться ничем кроме своей песочницы, чтобы открыть для себя "блокирующий стиль поверх грин-тредов" с SMP-планировщиком в Go в 2012? Я не удивлюсь, если Дал лет через пять откроет для себя, скажем, Elixir.

А что такого есть в Elixir?

Все то же самое, только по-другому. Местами получше, местами похуже. Язык богаче, косяки есть, но не ужасают. Тулсет неплох.
Рантайм — BEAM — сложно сравнить, просто принципиально другой и обладает рядом killer features, которых нет вообще ни у кого.

Можно по подробнее про фишки-убийцы?

Ну погуглите Erlang killer features. Изоляция данных и ошибок, например. Вообще механика каскадирования ошибок. Per-actor GC. Крутой preemptive планировщик c work stealing. Очень крутые возможности интроспекции, когда ты можешь своими руками потрогать изнутри работающую систему.
Еще есть hot code replacement, которым, впрочем, мало кто пользуется, потому что это мало кому реально нужно.
Порты как метод FFI весьма приятны.
Distribution с его сетевой прозрачностью, к которому я отношусь довольно двояко, но который все же есть и работает из коробки до поры до времени. Рано или поздно приходится начинать думать, но это везде так.

Можно про косяки поподробнее?

Ну, в нем есть не столько косяки, сколько некоторая неконсистентность. Например, вот взять дурацкий синтаксис вызова анонимных функций fun_variable.(:a, :b, :c).


Или вот эти приколы с proplist syntax:


Interactive Elixir (1.4.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> [1, 2, 3, a: 4, b: 5]
[1, 2, 3, {:a, 4}, {:b, 5}]
iex(2)> [1, 2, 3, a: 4, b: 5, 6]
** (SyntaxError) iex:2: syntax error before: 6

iex(2)> a = [1, 2, 3, a: 4, b: 5]
[1, 2, 3, {:a, 4}, {:b, 5}]
iex(3)> [c: 6 | a]
** (CompileError) iex:3: undefined function |/2
    (stdlib) lists.erl:1353: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
iex(3)>

или вот такие истории с мапами


iex(4)> b = %{:a => 5}
%{a: 5}
iex(5)> %{b | :a => 5}  
%{a: 5}
iex(6)> %{b | :a => 6}
%{a: 6}
iex(7)> %{b | :a => 6, b => 7}
** (KeyError) key %{a: 5} not found in: %{a: 6}
    (stdlib) :maps.update(%{a: 5}, 7, %{a: 6})
    (stdlib) erl_eval.erl:255: anonymous fn/2 in :erl_eval.expr/5
    (stdlib) lists.erl:1262: :lists.foldl/3
iex(7)> %{:a => 6, b => 7 | b}
** (CompileError) iex:7: undefined function |/2
    (stdlib) lists.erl:1353: :lists.mapfoldl/3
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    (stdlib) lists.erl:1353: :lists.mapfoldl/3

или proplist syntax который работает только для ключей-атомов


iex(7)> ["c": 6]                 
[c: 6]
iex(8)> ["c": 6][:c]
6

Если идти от языка к стандартной библиотеке, есть вот такое


iex(9)> ["c": 6]["a"]
** (ArgumentError) the Access calls for keywords expect the key to be an atom, got: "a"
    (elixir) lib/access.ex:255: Access.fetch/2
    (elixir) lib/access.ex:269: Access.get/3

Причем, это поведение изменить никак нельзя. because fuck you, that's why
Были еще какие-то шероховатости, уже не помню.


А вот если идти дальше, вот мы видим Plug и Phoenix, и вот они — редкостное говно во целому ряду причин.

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


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


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

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

Типичная речь хейтера.


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

Эпизод, действитльно, интересный, но мне было интересно перевести именно часть про Go для Хабра. Я знаю многих людей, которые пишут на node.js, просто потому, что даже не знают про другие технологии. Подобная статья должна, как минимум, привлечь их внимание, а дальше дело техники. Ведь в 2017-м, действительно, причин не писать серверы на Go очень мало (и это не Аллах говорит, а здравый прагматичный смысл).
А то, что вас это задевает лично, и вы бросаетесь людей называть хейтерами — сочувствую.


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

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

Когда много лет меняешь языки и технологии, слова про яйца становятся очевидными. Да, хотел попробовать новое. Это очевидно, даже если сам человек говорит что го лучше. Не потому что го лучше (он не лучше) и не потому что он хуже (он не хуже). Просто есть задачи где лучше нода, и есть задачи где лучше го. Для каждого шурупа лучше использовать правильную отвертку, и тогда не придется покупать титановые свёрла.

Я знаю многих людей, которые пишут на node.js, просто потому, что даже не знают про другие технологии.

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

Да и писать статью для этих людей в хабр это какое-то лукавство.

Один мой бывший коллега рассказал, что решил попробовать Go на предыдущем проекте после прочтения пары статей о Go на Хабре. В частности одна из них была про то, как после переписывания с Node на Go уменьшили количество серверов с 60 до 2 :) Так что я с вами не согласен.

Хм, может быть, но тут с Ruby переходили.

Видели ли вы фанатов nodejs? У golang хотя бы есть неоспоримые преимущества)

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

Разработчики на Go мне яро напоминают воинствующих мусульман в мире IT.

Тут дело в том, что Go стал модным и подтянулось много так называемой «школоты» которые кроме Go мало, что видели. И именно такие громче всех кричат, что нет бога кроме Go. Да и особенности рунета вносят свою лепту.

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

Тут дело в том, что Go стал модным и подтянулось много так называемой «школоты» которые кроме Go мало, что видели. И именно такие громче всех кричат, что нет бога кроме Go. Да и особенности рунета вносят свою лепту.

Примитивизм Go привлекает меня тем, что наконец-то можно писать в любимом стиле «что вижу, то пою». Без факторей, декораторов и IoC-контейнеров с AOP-ями.

Главным образом, Go сравниваю с Java, C#. Сравнения — не в пользу последних. Видно, что Go создан для корпоративной разработки, оттуда многие «странные» решения типа ошибки компиляции при неиспользуемом импорте.

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

Как раз нет. Go не создан для корпоративной разработки

Как раз да. Go создан для корпоративной разработки. Многие решения в нём очень четко принимались из расчета работы в больших командах (1000+ человек) и больших проектах. Google всё таки, прежде всего, для своей продуктивности язык хотел, а то что и остальным он хорошо зашел это неслучайный побочный эффект.

Ладно. jehy, думаю, вы победили. В этом комменте прекрасно все, начиная от безумного размера команды (1000+, вы серьезно? :)), заканчивая формулировкой. А ведь язык молодой, ему еще жить и жить.


О да. Команда на 1000+ человек, а у нас в языке нужно самому пробрасывать исключения, и все его преимущества сыпятся, когда растет или стек вызовов или сложность проекта, потому что вместо кода пишутся всякие if err!=nil, шаблонные методы для разных типов, потому что шаблонные типы слишком сложно. Это типо команда из 1000+ синьеров?)


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


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


Мне интересно, это продолжительное написание кода на golang приводит к таким странным деформациям и упрощенному видению процессов разработки?

В Google каждый может изменить код в проектах других команд, поэтому потенциально над одним и тем же кодом могут работать больше 1000 людей. Но вы не останавливайтесь, разглагольствуйте. :)

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

так что у linux больше 1кк разработчиков?

Вы хотите развести очередной спор на 100 комментариев о кого, считать разработчиком, а кого нет? В ядре Linux больше 10k разработчиков, да. Как там IDE, загрузилась уже?

Ну так это же важно для вас. Мне то все равно, а вот для вас важно, что Golang разрабатывался для команд из аж 1000 человек.


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

а вот для вас важно, что Golang разрабатывался для команд из аж 1000 человек.

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


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

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

Вон автор nodejs утверждал, что открыл асинхронное программирование.

Вон автор nodejs утверждал, что открыл асинхронное программирование.

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

У вас осталась так же проблемы. Вы верите.

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

Это ваша проблема, увы.

Как раз нет. Go не создан для корпоративной разработки, и именно это многим в нем нравится.

Полагаю, вы в курсе, что Go создан Google? Полагаю, вы догадываетесь, что Google его создал и развивает для своей корпоративной разработки. Пожалуйста, посмотрите первоисточник:
https://talks.golang.org/2012/splash.article

В частности, оттуда:
Go is a programming language designed by Google to help solve Google's problems, and Google has big problems.

Как мы видим, «big problems» это не совсем то, что вы обозначили как «простые функциональные компоненты». Кстати, что это за понятие?

Он создан для простых функциональных компонентов, но как только вам приходится писать на нем большие приложения, все эти «удобные» решения превращаются в то, что вы делаете из go язык, к которому вы привыкли.

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

Там внизу есть список типов корпоративного софта:


Accounting software
Billing Management
Business intelligence
Business process management
Content management system (CMS)
Customer relationship management (CRM)
Database
Enterprise resource planning (ERP)
Enterprise asset management (EAM)
Supply chain management (SCM)
Backup software


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

То есть вы серьёзно полагаете, что Google ничего подобного не создаёт?!
Ну и билинг они для этих и других сервисов вряд ли покупали.

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


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


Или google избранная компания, которая каждый продукт пишет с нуля?


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

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

На чём и как написаны и переписаны сервисы, — я без понятия.
Полагаю, вы в курсе, что Go создан Google? Полагаю, вы догадываетесь, что Google его создал и развивает для своей корпоративной разработки. Пожалуйста, посмотрите первоисточник:

Если я правильно прочитал, то Google достал legacy код на С/С++ и они решили сделать себе С с человеческим лицом. Вот только вы не Google. И вы же не пишете на C свои веб-приложения? А из перечисленных проблем половины вообще не существует в других языках. Вся статья о том, почему нужно использовать go вместо C, ну и я с этим согласен.


Как мы видим, «big problems» это не совсем то, что вы обозначили как «простые функциональные компоненты». Кстати, что это за понятие?

Это я так неправильно назвал микросервисы. Но как я понял из статьи, big problems — оно больше под поддержку уже существующий решений, так как инфрастуктура С в целом очень и очень разнообразна и способов сделать одну вещь может сущестовать десятки. Плюс описанные в статье проблемы C. Про сложность задач тут не говорится, как я понимаю.


Не понял вашу мысль. Кто что из чего делает?

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


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

Если я правильно прочитал, то Google достал legacy код на С/С++ и они решили сделать себе С с человеческим лицом.
Нет, неправильно. Там же в первой главе, сразу:
Go was designed and developed to make working in this environment more productive. Besides its better-known aspects such as built-in concurrency and garbage collection, Go's design considerations include rigorous dependency management, the adaptability of software architecture as systems grow, and robustness across the boundaries between components.

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

Когда вы пишите большой проект на golang, вам приходится заниматься такими штуками, которые в других языках работают сами по себе.
А есть штуки, которые в Go есть, а в других языках ими нужно заниматься.
Я имел ввиду, что простота синтаксиса работает в обе стороны. Чем больше у вас кода, тем больше возникает дублирования, потому что из-за простоты синтаксиса вам от нее не избавится. И приходит время, когда дублирования очень много.
Код на Go для меня понятнее, чем на Java или C#. А дублирование кода или нет — дело десятое.
Код на Go для меня понятнее, чем на Java или C#. А дублирование кода или нет — дело десятое.

Для меня наоборот. Или вы сравниваете простой написанный сервис на golang и что-то монструозное со spring на Java? Так дело не в сложности языка, а в сложности фреймоворка. На golang ситуация лучше не станет.


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

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

Для меня наоборот. Или вы сравниваете простой написанный сервис на golang и что-то монструозное со spring на Java? Так дело не в сложности языка, а в сложности фреймоворка. На golang ситуация лучше не станет.
Да, я сравниваю Go с Java или C#. И дело не только (да и не столько) в Spring. Просто, Spring — это такой мейнстрим в Java размером с Амазонку.

Все современные языки общего назначения (а не общего, — тем более), идут в комплекте со своей моделью проектирования. Ну, или, если хотите, Java-way, C#-way, Go-way, Ruby-way etc.

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

Подскажите? Я знаю только статический анализ и форматирование (впрочем, статическим анализом вам все равно нужно будет занятся). Если вы походите с туза и скажите про асинхронищину, то есть тоже нужно заниматься, как и в любом другом языке, в силу ее сложности.
А с чем сравниваем? Скажу сразу, что сравнивать Go и Python бессмысленно. Это языки из разных ниш.
Это неустранимые особенности языков. У Java, кроме злоупотреблений с рефлексией и манипуляцией байт-кодом, главный бич: многословность, сиречь — тиражирование сущностей без необходимости.

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


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

Проблема в том, что вы игнорируете условия, при которых у вас появляются эти проблемы. Они появляются при больших проектах. И с ними в golang вы начинаете, например, эмулировать ручками generic из Java, гоняя объекты в interface{} и обратно. В коде moby (я делал вот так grep "interface{}" -R . | grep docker | wc -l нашлось 1063 места, где используется этот интерфейс. (То есть да, а еще захватил код из vendor/docker).


Ну и многословноть, это так же про golang. В том же коде 1209(!!!!) строчек вида return nil, err (вместо err еще иногда бывает внезапное оборачивание err в какую-то другую ошибку, типо так: errors.Wrap(validationError{err}, "error reading labels")). Ну, то есть 3627 строчек кода, в которых просто делается проброс ошибки. Не очень многословно, правда? (А это вместе с вендорами, сейчас исправлю.


А с чем сравниваем? Скажу сразу, что сравнивать Go и Python бессмысленно. Это языки из разных ниш.

Ну, давайте сравним с C++. Компилятор возьмем gcc. Или это тоже не в область? Можно еще взять Rust или D, но я про них мало что знаю.

Поправлю, не 1063, а 1405 мест :)

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


Но продолжайте грепать и себя убеждать. Это уже неизлечимо. ) Можете еще на 3 умножить, как вы делали чуть раньше. Будет ещё больше.

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

Обработка ошибки это "решить, что делать с ошибкой на этом уровне и дать это понимание программисту, читающему код". Отдать ошибку наверх — это тоже "обработка ошибки".

Зачем мне эта лишняя когнитивная нагрузка, которая заставляет меня думать "а что же тут происходит", если я и так знаю, что это поведение по умолчанию?)

эта лишняя когнитивная нагрузка

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

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

что это работает в пределах функции, а у нас тут не фунциональщина

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

Ну, для вас может и да. А так обычная у меня логика, мне хватило копания в коде docker и jobber, что бы понять это.


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

что бы дойти до идее о том, что локальное поведение не дает вам информации о программе?)

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

Жду от вас ответ, какая в этом месте используется реализация ICmd. Разумеется, не заглядывая в другие места в коде.

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

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

Записывайте. Вы уже давно сам собой общаетесь — сами придумываете, сами интерпертируете, сами этим себе что-то доказываете.

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

Зацепил вот этот отрывок:


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

Вот я не понял, вроде ж говорит что на новые версии ноды все хорошо налазит. Но нет, это лишь доказывает, что Go лучше. Wat.

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

А как правильно организовать SEO для Angular и Go, если не использовать пререндеринг на node?

С Go есть свои проблемы — я нашел, что в отсутствии нормального IDE очень трудно рефакторить большие иерархии классов, особенно, когда она еще не устоялась и находится в процессе постоянной переработки. Прямо вот очень неудобно. Мы в проекте стараемся использовать Go только для маленьких сервисов, которые процессят данные и запуливают их дальше куда-нибудь.

Даже goglang не помогает? У этим ребят отлично топовые ide.

Сравнивать компилируемые языки и интерпретируемые для веб-разработки это какой-то верх невежества. Видимо необязательно иметь широкий кругозор для того что-бы быть создателем чего-либо.
По факту язык значит не так уж много, экосистема и киллер-фичи решают. Покажите мне джангу на го с автогенериремой админкой, миграциями и какой-нибудь cms ala django cms.
кстати не думал что все так хорошо у го. Но тут 3 продукта, а django это стандарт для веб питона, + есть и django cms и rest framework. И куча плагинов для всего.
Добавьте голосовалку, «На какой будет следующий язык запрыгнет Раян Дал?»
UFO just landed and posted this here
И скажет «модель программирования в Rust намного лучше Go»? :) Ну ок, вернемся к этому комменту через 5 лет.
Замечательный язык Go. Давайте посмотрим как в нем проверка вхождения элемента в массив выглядит:
func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}


И там все через одно место…
UFO just landed and posted this here
В нормальных языках это в одну строчку делается.
UFO just landed and posted this here
1. Что вам мешает использовать эту функцию в одну строчку?
2. Что вам мешает использовать `strings.Contains()` из стандартной библиотеки
3. Как именно это затрудняет написание кода?

Я не шучу, сейчас у вас есть шанс повлиять на развитие Go, написав experience report про то как отсутствие или присутствие какой-то фичи реально мешает писать надежный код. Мне кажется, вам есть что сказать и донести до авторов Go того, что они не понимают.
> `strings.Contains()
Она с целочисленными массивом работать будет?
int [] x = [1,2,3,4,5];

>Как именно это затрудняет написание кода?
А зачем писать лишний код?
Она с целочисленными массивом работать будет?

Нет. это же пакет strings.


А зачем писать лишний код?

В Go нет многих фич и функций вроде map/reduce/filter, и это сделано сознательно, чтобы сделать язык максимально простым. Именно благодаря этому в Go такой низкий порог входа. И далеко не все приложения нуждаются в map/reduce/whatever — это все элементарнейше пишется при необходимости (или используются библиотеки, если совсем лень).


Так что, будете писать experience report? Помогите сделать Go лучше и не через одно место :)

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

>Так что, будете писать experience report?
Зачем? Я на Go не писал и не планирую. Я для себя не вижу ни единой задачи которую он мне позволит решать быстрее\лучше\эффективнее чем Python/D.
К тому же все эти урезания лишь к лишним багам будут приводить.

Не будут.

Будут. Количество велосипедов прямо пропорционально количеству багов.

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


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

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

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

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

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

UFO just landed and posted this here
А как это будет в Java выглядеть?

Давайте я вам лучше на D пример приведу:


[ "foo" , "bar" , "baz" ].canFind( "foo" )
А что под капотом этой строчки? import?

В Go можно тоже организовать подобное:

type SearchableList string[]

func (sl SearchableList) CanFind(search string)…

и вперед, можно его юзать.

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

Ваш пример, так как значений очень мало, проще сделать на map (ассоциативный массив), там уже есть стандартный поиск за O(1). Если же у вас много значений, то разумнее это дело хранить в сортированном виде, и юзать binary search. В sort есть Search, который Binary search производит, также там есть SearchStrings, который делает то, что в вашем примере, но на отсоритрованном массиве. То есть когда допустим ревьювер посмотрит ваш код, увидит велосипед, у него прозвенит звонок, что где-то написано неоптимально. Если вы подключите либу, аналогично.

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

Все имеет свои плюсы и минусы и когда-нибудь я думаю Go станет к этому более лояльным, особенно если вдруг generic'и появятся. Все больше и больше задач не связанных с высокими нагрузками на нем решаются, и рано или поздно это выльется во что-нибудь.
А что под капотом этой строчки? import?

import std.algorithm , std.stdio;

[ "foo" , "bar" , "baz" ].canFind( "foo" ).writeln;
[ 1 , 2 , 3 ].canFind( 1 ).writeln;

import std.math;
[ 1.1 , 2.2 , .3.3 ].canFind!approxEqual( 1.1 ).writeln;

struct Foo { int bar; }
[ Foo(1) , Foo(2) , Foo(3) ].canFind( Foo(1) ).writeln;

не давать делать из коробки то, что будет работать очень неоптимально

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


func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}
Беда с такими программистами, которые используют линейный поиск по массиву O(n) там, где следует использовать ассоциативный массив O(1). Потому всё и тормозит.
UFO just landed and posted this here
Если Вы имеете ввиду [1, 2, 3, 4, 5], что встречается в сообщении, которое автор претензии к Go выложил в ответе собеседнику, так принадлежность к такому множеству вообще решается через битовые маски в Си-подобных языках
0 != ((1 << n) & 62)

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

А о чём это говорит? Что как раз нет большого смысла в однообразном способе решения, за которое так радеет автор претензии к Go, желая работать одинаково и со строками, и с числами, и с бог знает с чем ещё. Когда вместо сферических примеров в вакууме появляется настоящая задача, то всё видится несколько не так.

Лично мне уже изрядно надоели программы, которые еле ворочаются на сверхпроизводительных современных компьютерах.
UFO just landed and posted this here
И всё таки Comdiv верный момент затронул. Это на лабораторных в университете задачи могут звучать как «Напиши алгоритм для всех типов...», а в реальной жизни у вас есть конкретные задачи, конкретные данные, конкретные ограничения и лимиты. Если вы работаете с GPS координатами, то вы не хотите делать алгоритм, который будет работать с любыми типами — это просто ни к чему. Кроме того, вы можете (и должны) эксплуатировать свойства вашего типа данных, чтобы сделать код читабельней и быстрее. Работай вы взамен, скажем, с типами данных, описывающих генетическую информацию, вы бы даже алгоритм, возможно задизайнили иначе.

И в этом один из самых недоговоренных минусов дженериков — их почти всегда переиспользуют. Люди начинают мыслить обобщениями там, где они не нужны и писать фантастическую лапшу там, где нужно сделать функцию в одну строчку с конкретным типом. Концепция design code around data летит в трубу. Зато дженерики, очень удобно, да.
Концепция design code around data летит в трубу

И зачем тогда в go добавили interface? Пишите только функции которые принимают данные.
И зачем тогда в go добавили interface?

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

Но зачем вам поведения? у вас ведь design code around data, т.е. только данные и функции.
в реальной жизни у вас есть конкретные задачи, конкретные данные

Т.е. interface не нужен.
у вас ведь design code around data

А у вас нет? Вы сначала code, а потом смотрите на то, с чем вы вообще работаете?


Т.е. interface не нужен.

Просто наверное жить с такой бинарной логикой.

А у вас нет? Вы сначала code

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

Ну а зачем тогда interface если в реальной жизни у вас есть конкретные задачи, конкретные данные?
Да, я сначала делаю все на интерфейсах, а потом создаю данные

Ясно, а в Go наоборот. Интерфейсы добавляются только тогда, когда нужно абстрагировать поведение, но не раньше.


https://habrahabr.ru/post/276981/

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


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

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

б) от какого именно подхода отказались, и кто именно отказался.

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


Но вы правы, многолетний опыт ничего не стоит.

От того, что вы предлагаете.

От вашего искаженного понимания того, что описал (ничего не предлагая)


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

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

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

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

Вы что, DI в Go не нужен, так как это очень сложно.

От вашего искаженного понимания того, что описал (ничего не предлагая)

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

Ну погодите, инверсия зависимостей есть и в ООП-языках в виде классического DI/IoC и в ФП-языках в виде Reader. Что может предложить мне Go? Инверсия не нужна?

Что может предложить мне Go?

D в SOLID в Go как раз делается с помощью интерфейсов. Я не пойму, вы даже не пытались узнать, как это делается в Go прежде чем комментировать или просто не поняли?


Инверсия не нужна?

Опять бинарная логика. Вы в каком классе школы?

Ясно, а в Go наоборот. Интерфейсы добавляются только тогда, когда нужно абстрагировать поведение, но не раньше.

Ну и что, что интерфейсы? Вам при каждом вызове функции нужно руками собирать все ее зависимости. Или принимать их в аргументах, а при вызове вызывающей провайдить в нее еще и зависимости функций, которые она вызывает. Потрясающе! Дженериков и частичного применения нет, значит Reader не катит. Классов нет, значит constructor injection не катит.


Вы в каком классе школы?

Аргументы кончились?

UFO just landed and posted this here
Но я вполне могу представить, что я могу выделить некоторые общие черты и пожелать соответствующим образом обобщить свой код.

Представить можно всё. Обобщать надо по мере необходимости, а не "потому что могу". Плохая абстракция это хуже, чем дублирование кода, если уж на то пошло.


Кстати, хаскелевские тайпклассы чем-то весьма похожи на гошные interfaces. Только существенно мощнее.

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


Никто не запрещает при этом этому алгоритму быть обобщённым.

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

UFO just landed and posted this here
Есть и обратное мнение.

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


Из этого не следует, что любая абстракция плохая.

А из этого не следует, что любая абстракция хорошая.


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

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


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

Если вы в реальном проекте упретёсь в лоад, когда нужно будет экономить 10 нс, то ничего вам не помешает использовать реализацию binary tree для конкретного типа (с кодогенерацией, или написанной ручками). И не надо рассказывать, что вы во всех проектах используете бинарное дерево для 20+ разных типов, это почти всегда конкретный тип данных.

UFO just landed and posted this here
которые сразу хардкодили все типы

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


Я могу взять вашу реализацию binary tree (или произвольную библиотеку с контейнером) и одним-двумя движениями скодогенерировать реализацию для моего конкретного типа?

Вы же сами выше пример привели. Это чуть больше телодвижений (4-5), но если прям вам совсем это надо, то легко.


Я почти во всех проектах использую уже единожды написанные std::map/std::unordered_map/boost::multi_index/Data.HashMap/etc.

Вы не поняли. Сколько различных типов вы засовываете в бинарное дерево в типичной программе? 1,2,10,100?

UFO just landed and posted this here
11 уникальных троек (тип ассоциативного массива, ключ, значение)

Вы 11 различных типов засовываете в бинарное дерево? А что это вы такое пишете, если не секрет. Правда интересно, что за usecase?

UFO just landed and posted this here
Потому что мне в моей практике массивы из нескольких элементов встречались не раз и не два.

А сколько? Сколько строчек кода Вы сэкономите в своём текущем проекте, если в нём будет задействован generic линейный поиск?

Но если у Вас в задаче есть место, где точно нужен линейный поиск, то это хорошо, что Вы и задействовали линейный поиск. Моё сообщение как раз о том, что многообразие задач не позволяет эффективно долбить одними и теми же подходами.
UFO just landed and posted this here
Не знаю, он там уже и так задействован.
Значит, инвертируйте вопрос. Сколько избыточных строк кода будет, если написать свой линейный поиск?
// вариант линейного поиска для тех, кто любит экономить строки
for (i = 0; i < len && a[i] != b; i += 1);


Но у меня есть просто куча других мест, где задействованы...
Можно глянуть?
UFO just landed and posted this here
Когнитивная нагрузка, по-видимому, обусловлена выбором инструмента. В Си или Go линейный поиск считывается моментально, и именно очевидный простейший алгоритм позволяет быть увереным, что там нет никаких особых деталей, и он делает ровно то, что задано и ничего больше. А писать это нужно, чтобы из-за экономии в 10-20 строк на проект не усложнять язык. Те, кому по душе сложные языки с этим не согласятся, но и обратный подход они-то могу понять?
UFO just landed and posted this here
Не моментально.
Я же говорю по своему опыту, а не из абстрактных соображений.
Кстати, как в одну строку будет выглядеть
Пожалуйста, конкретней, и почему в 1-у строку?
но утверждать, что каждый раз копипастить цикл проще, чем писать слово из нескольких букв
А кто это утверждает? Я не понимаю, зачем там копирование. Вы простые команды в терминале копируете или сами пишете? Так и тут, тем более о копировании речь не идёт, поскольку всегда есть своя специфика. Линейный поиск — это частный случай схемы, которая существует даже тогда, когда нет никакого хранилища, в котором искать. Например, сумма сходящегося ряда воплощается по той же схеме. Что Вы там копировать будете?
Так как язык изучается обычно не более одного раза
Я постоянно обращаюсь к внешней памяти по инструменту, потому что не могу запомнить всех важных деталей. И чем сложней инструмент, тем хуже.
UFO just landed and posted this here
столько же времени и ресурсов, сколько распарсить строку lookup a bs, мне верится очень с трудом.
Конечно не столько же — чтобы достоверно узнать, что делает lookup a bs, нужно будет искать реализацию lookup.

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

Что-то вроде этого?
for (i = 0; i < len && a[i].key != key; i += 1);
if (i < len) {
  found(a[i].value);
} else {
  not_found();
}

Обязательно нужно это влепить в 1-у строку? Я-то и цикл написал в 1-у строку для примера любителям считать строки, а не потому, что я так пишу.
UFO just landed and posted this here
Да хоть в одну, хоть в десять — руками это каждый раз писать сомнительное удовольствие.
Покажите свой вариант.
UFO just landed and posted this here
На хаскеле? Сохраняя вашу нотацию,

Где обработка найденного и не найденного? Почему разные функции?
На плюсах?
Круто, ничего не скажешь
UFO just landed and posted this here
Так и не увидел обработки ненайденного. Я же показывал схематично обработку
UFO just landed and posted this here
Как видно, в полном виде всё выглядит несколько иначе, чем «не сомнительное удовольствие», учитывая также, что в зависимости от типа поиска придётся менять слова, которые не включают в себя полной семантики, поэтому о ней нужно дополнительно помнить.
lookup key a >>= doSmthWithResult
doSmthWithResult (Just val) = ...
doSmthWithResult Nothing = ...

for (i = 0; i < len && a[i].key != key; i += 1);
if (i < len) found(a[i].value);
else not_found();

Все люди разные, и я-то и не спорю, что кому-то первый вариант прийдётся по душе. Но почему же такие люди, не понимают того же самого, что и 2-й вариант другим людям понятней? Не гордыня ли это от ощущения причастности к «истинному» пути?
UFO just landed and posted this here
А чего вы тогда реализацию printf не ищете или не пишете свою, а спокойно (я надеюсь) вызываете библиотечную?

В Go есть негласное соглашение — в stdlib и в языке только то, что работает быстро. Дорогие операции нужно делать явно. Многие новички на это ругаются, но это очень круто.

А чего вы тогда реализацию printf не ищете или не пишете свою
1. Так уж получилось, что printf использую только для примеров, в обычном коде — нет. Знали бы Вы, сколько глюков на ней висит.
2. Чем объёмней и сложней код, тем меньше его пишут врукопашную, но когда нужно специальное поведение, то пишется своё, да. Без этого никуда
UFO just landed and posted this here
Пожалуйста. Это вообще беда Си, из стандартной библиотеки половиной лучше не пользоваться. Наследие диких времён. Но можно и поиронизировать, да.
Я и говорю о множестве всех частных случаев в виде линейного поиска.
А, кстати, раз так, то сумму сходящегося ряда Вы тоже с помощью lookup будете делать?
UFO just landed and posted this here
В общем случае у сходящегося ряда нет красивой формулы. Я имел ввиду численный метод
UFO just landed and posted this here
Но зачем это писать, если можно сэкономить немного когнитивной нагрузки и не писать?

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

UFO just landed and posted this here
А о чём это говорит? Что как раз нет большого смысла в однообразном способе решения, за которое так радеет автор претензии к Go, желая работать одинаково и со строками, и с числами, и с бог знает с чем ещё.

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

Пока не воплощена на практике идея суперкомпиляции Валентина Турчина, это утверждение остаётся благим пожеланием.
UFO just landed and posted this here
Под справляются Вы имеете ввиду, что такое
[1, 2, 3, 4 ,5].contains(n)

автоматически превращается в такое?
0 != ((1 << n) & 62)


Мой скромный опыт показывает, что ассоциативный массив на template С++ не справляется с наколенным воплощением на Си
UFO just landed and posted this here
Исходников С-версии у вас не сохранилось случаем, кстати?
В статье была ссылка на github, иначе в чём смысл разглагольствований?
UFO just landed and posted this here
UFO just landed and posted this here
Здорово, что Вы написали свою программу, но сравнив её с тестом, я очень легко нашёл недостающий компонент:
result.reserve(1023);
Получается, что С++ hash array приближается к скорости C кода только если воспользуется знаниями, которыми обладает разработчик, который может написать hash array самостоятельно — hash функцией и нужными значением резерва. Когда я попытался применить reserve(1023) в тесте, естественно, получил отлуп — map, сделанная через деревья, не имеет такого метода. Единого интерфейса тоже нет.

Также у меня Си код всё равно выполняется быстрей на статистически различимую величину. А С++ hash array в test проекте c reserve() выполняется быстрей на статистически различимую величину, чем представленная Вами программа, полностью написанная на C++.

В общем, я не увидел того, что можно было бы назвать справляется.
UFO just landed and posted this here
А какой тезис вы выдвигаете?
Это означает, что для того, чтобы написать вариант на С++, приближающийся снизу по скорости, надо знать всё то же, что и на Си, плюс нюансы библиотеки С++. Выгоды, на мой взгляд, нет исходя из контекста нашего разговора.

С моим вариантом, с кастомным хешем и reserve? Ради любопытства, а насколько?
Как выяснилось, очень зависит от сочетания компилятора и процессора. На рабочем ноутбуке на c gcc 10%, на домашнем, на котором всё новее — 5%. А на clang Си наоборот, сильно отстаёт.
UFO just landed and posted this here
Нет. Вам не нужно знать ...
Наоборот, это нужно знать. Только тогда, исходя из ограничений задачи, можно выбрать оптимальное представление.
(у вас нет ребалансировки, например),
В том и прелесть, что я делаю только то, что нужно. С++ код к тому же, ощутимо разбухший, хотя это опять-таки сильно гуляет в зависимости от компилятора, его настроек и целевой платформы.
UFO just landed and posted this here

1) Вполне может оказаться, что поиск нужно сделать O(1) раз, и построение хэш-таблицы займет больше времени, чем линейный поиск.
2) Раз уж зашла речь про структуры данных, то механизм вроде шаблонов имхо необходим для их переиспользования.

Проблемма не в node или js, дело в асинхронном коде (походе) этой платформы. На python асинхронный код тоже не прост как синдронный.

Из года в год вижу как видные разрабочтики приходят к пониманю повышенной сложности асихронного кода, например тут неплохо описано Javascript: фрактал отсоса by PerlPower
Так же bobuk высказался «программировать асинхронный код сложно».

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

Скажу жёстче — достаточно иметь голову, а не вестись на хайп.

Отдельные асинхронные блоки не так плохи. Ужасно то, что асинхронные функции можно вызывать только из асинхронных функций, и в итоге весь код в них. На эту тему есть отличная статья: What color is your function?

Прикол в том, что все современное программирование по сути асинхронное. Ооп и фп одинаково упорото превращают простой код в событийного-ориентированный. Эх, старые добрые времена, когда я мог написать код и точно знать в какой конкретно момент он выполнится…
UFO just landed and posted this here
Полностью согласен. Вот бы в кложур добавить типы и выкинуть скобки…
UFO just landed and posted this here

Articles

Change theme settings