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

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

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

Дело привычки.

Понятно, что отступы — дисциплинируют, но не все любят когда их заставляют из под палки.

А кроме того, вопрос пишущим на Питоне: когда строка такой длинны, что не помещается на экран, можно ли сделать перенос начинающийся с отступа для удобства чтения? И как такое воспринимает Питон?

Можно использовать символ \ для переноса строки:


foo = 2362366232 + 1423204837 - \
    1744037725 * 47494036263

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


foo = (2362366232 + 1423204837 -
    1744037725 * 47494036263)

Отступ перенесённой строки в этом случае роли не играет, но обычно придерживаются правил с учётом Style Guide

Спасибо!
но не все любят когда их заставляют из под палки

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

Незакрытые скобки должен подсвечивать ваш редактор кода.

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

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

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

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

Это слишком смелое утверждение — программы бывают слишком разные, чтобы утверждать, что упасть — это лучший выход. Ошибка ошибке рознь — большинство «ошибок» будут происходить и программа должна быть к ним готова. Неправильный формат данных в HTTP запросе на ваш сервер и деление на ноль — это разные вещи. И тех случаев, когда да, упасть это рабочее решение, в Go есть panic().
Серьезно. Проверять ошибки вместо обработки исключений — это простейший способ бесполезно потратить кучу времени.

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

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

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

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

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

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

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

А, как известно, лучший вид кода — это отсутствующий код

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

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

Проверка ошибки — это одна инструкция на ассемблере. Какие «современные способы работы с исключениями» будут быстрее?

Вы понимаете, что такое исключительная ситуация?


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

То это не исключение, т.к. у нас есть другой файл.


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

То это не исключение, т.к. у нас есть дефолтное значение.


А если при записи, вы должны залоггировать ошибку, а при закрытии — проигнорировать?

То это прописанная бизнес-логика.


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


«Отсутствующий код для проверки ошибок» — это точно не лучший вид кода, вот гарантирую.

См. мой комментарий чуть выше. Разработчики отказоустойчивых систем с Вами категорически не согласны :-)

Ну, скажем так, некая возможность разделить «ошибка workflow» и «исключительная ситуация» в Go всё таки есть.
Т.е. если это «прописанная бизнес-логика» — надо пользоваться return error.
А если «исключительная ситуация» — надо пользоватсья panic/recover.

Я знаю, просто divan0 смешивает запасной вариант и исключение на уровне понимания.
В зависимости от контекста одна и та же вызов функции может приводить и к тому и к другому, но концептуально это разные вещи. Поэтому во многих языках библиотечные функции имеют 2 формы: одна возвращает статус и результат, а вторая — только результат, а в случае ошибки кидает исключение.
В Go для второго случая придётся в куче мест писать if err != nil { panic(err) }

Это в каких языках? Что-то в Java и C# я ничего такого не видел. Исключения в этих языках это дефолтный способ обработки ошибок, какие бы серьезные они ни были.

Насчет Go, ну напишешь так и всем будет ясно, что тут происходит. Это называется более ясный и читаемый код. Поверьте, видеть код с явной обработкой ошибок существенно упрощает его понимание. Я тоже не понимал ошибок Go до тех пор, пока не написал пару проектов нормальных на нем. Обработка ошибок не доставляет никаких проблем и только упрощает понимание того, как себя код может вести. Сколько раз в языках с исключениями я задавался вопросом — а какое тут может исключение и что будет, если оно вылетит, кто его обработает, где. Где-то это код упрощает, где-то нет и делает его сложнее для понимания.
Это в каких языках?

Ну, например, в Elixir. Там по соглашению, имена методов, которые могут вызвать исключение заканчиваются на !. Допустим, File.open и File.open!. Весьма удобно для разделения штатных и исключительных ситуаций на уровне кода.


P.S. Обратите внимание, что я не говорю, что использовать повсюду исключения — это хорошо, наоборот, это так же плохо как повсюду проверять код возврата :-)
А главная проблема в том, что нет изоляции на уровне пакетов. Если в чужом коде возникла ошибка, она имеет все шансы добраться до вашего основного приложения и даже уронить его.

Давайте уж держаться тех языков, которые используются. Говоря об исключениях, имеют ввиду именно их, а это C# и Java. В меньшей степени С++, потому что там своя атмосфера вообще. Вот именно там исключения это один единственный способ обработки ошибок, без альтернатив. Поэтому и сравнение с Go тут получается удачным. Выбирая из двух вариантов, вариант Go таки дает более простой и понятный код, да еще и более быстрый. И там действительно есть исключения, которые роняют процесс — panic. И то, при большом желании их можно поймать с помощью recover.

Давайте лучше не вводить искусственных ограничений… Если говорить о том, что лучше, то равняться надо на лучшие подходы, а не только на самые популярные в данный момент (C#, Java) и самые старинные (C, Go).

Отчего Object Pascal забыли? Тоже исключения есть, без GC. своя атмосфера так сказать.
Не знаком и не видел, чтобы он всплывал в этих спорах. Так то можно и Obj-C вспомнить, где исключения как бы есть, не совместимы с С++, как бы нигде практически не используют, но некоторые библиотечные функции их все таки кидают. Но благо они обычно означают лишь критическую ошибку в коде и лучше не ловить вообще такие вещи.
Специфика связана с тем, что в OP нет ни GC, ни в чистом виде RAII и объекты должны освобождаться самостоятельно, так что ловить надо всё.

Раз уж С++ был упомянут, то там местами подход "дублирования" функций тоже используется. И в стандартной библиотеке: dynamic_cast в ссылку/указатель, nothrow new и т.д. Примеров не так много, но это скорее потому исключения не особо активно используются. Справедливости ради, можно найти места, где используются только они (например, для регекспов). И в некоторых популярных билиотеках типа буста: функции имеют перегрузку с дополнительным параметром типа system::error_code именно для возврата ошибки без исключения.


Ну и ещё можно вспомнить, что вещи типа std::optional почти дошли до стандартизации и полезны, в том числе, при нежелании кидать исключения.

Разработчики отказоустойчивых систем с Вами категорически не согласны :-)

Я сам разработчик отказоустойчивых систем, и это мы с вами не согласны. :)

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

Как нескромно, о себе во множественном числе ))) Впрочем, давайте не будем опытом мериться.
Лучше приведите хоть 1 пример исключительной ситуации, когда проверка статуса возврата хоть чем-то лучше Let It Crash. Пока Вы приводите примеры штатного поведения… Применять к ним термины "ошибка" и "исключение", на мой взгляд, в принципе некорректно. В случае же исключительной ситуации единственное возможное поведение — это как можно быстрее привести в систему в рабочее состояние после сбоя.

Позвольте я отвечу. Timeout в Java и C# являются исключительной ситуацией, но в большинстве случаев это не Let It Crash, а стандартное поведение того же сетевого кода, которое можно или игнорировать, или использовать для попытки переподключения.

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

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

Так Let It Crash как раз и организует Вам переподключение. Это дефолтное поведение в рамках этой методологии: убить корутину и перезапустить её с чистого листа, чтобы ещё раз попробовать.


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

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

Я не сторонник по любому пустяку перезапускать процесс. Тем более что, вот допустим у вас серверное приложение одним процессом. Именно такой подход наиболее выгоден для Go, ибо горутины. Из-за мелкой ошибки убивать весь процесс что ли со всеми подключениями, которые ни в чем не виноваты. Методология Let It Crash здесь никаким местом. Обработка сетевых ошибок и корректное завершение работы это не Crash и не исключительная ситуация. Это стандартная логика кода.

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

Вы просто не понимаете Let It Crash, потому что не пробовали его никогда… Во-первых, под процессом там понимается что-то типа горутины. Во-вторых, в Go нет поддержки этого подхода из коробки, попробуйте сначала один из языков, который это наивно поддерживает. Пойдет любой ЯП для Erlang VM.

Пока Вы приводите примеры штатного поведения…

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

приведите хоть 1 пример исключительной ситуации, когда проверка статуса возврата хоть чем-то лучше Let It Crash

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

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


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

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

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

Вот тут я вижу в ваших словах непримиримое противоречие. Ошибка при открытии файла само по себе может вполне быть «штатным поведением» — например, мы пытаемся открыть "~/.ssh/id_rsa.pub", а у пользователя ECDSA-ключ, поэтому мы должны попробовать файл с другим именем.

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

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

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


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

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

Хорошо, тут мы сошлись — тогда давайте смотреть дальше. Решение о том, что есть ошибка, а что исключение, принимаем мы там где вызываем функцию открытия файла. У нас есть некая логика, и мы обязаны её записать в виде кода. Код — это ведь не только язык для общения с машиной, это и язык, которым мы объясняем логику программы себе будущему и коллегам, которые будут работать с этим кодом.

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

С исключениями, вы, во первых, с трудом вообще знаете — может там быть ошибка или нет. Но, допустим, знаете. Кто эту ошибку словит — код на уровень выше? На два? На три? Если нужно реализовать бизнес-логику — вам нужно всё равно писать catch с описанием типа исключения (которые всё равно не используют толком, гг), при этом еще try и скобочки — всё это становится гораздо более громоздко. Не говоря уже о том, что flow кода становится разбросанным по разным частям кода, что затрудняет понимания. А всё это вместе создает стимул — ведь это же главное преимущество исключений! — написать один общий catch блок и там просто вывести ошибку, а потом уже решать, хорошо это или плохо.

Про перформанс исключений я молчу вообще — это же тоже не бесплатно.

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

Впрочем, если это не «однотипные», а одинаковые вызовы (например, запись побайтово команд какого-то протокола — тоже ж бывает), то никто вам не мешает написать не if err, а создать враппер вокруг функции write(), который будет проверять ошибку и выбрасывать panic — вот тут этот пример рассмотрен.

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

Я не пойму, у Вас на эту тему какая-то домашняя заготовка что-ли?
Я Вам про Let It Crash, а Вы мне про "кто эту ошибку словит", "try и скобочки"… Не надо исключения перехватывать, они на то и исключения, чтобы уронить подпроцесс и дать ему перезапуститься с чистого листа.
Непонятно в каком месте тут жертвы в читабельности, производительности и понятности? Кода обработки исключений нет вообще, читаемость и понятность у happy path самая шикарная из всех возможных. Если у Вас исключения происходят настолько часто, что затрагивают производительность, то либо Вы их используете для штатных ситуаций (control flow), либо у Вас общесистемные проблемы, возможно SSD на сервере пора менять или что-то в этом роде.

Я Вам про Let It Crash, а Вы мне про «кто эту ошибку словит»

Именно, потому что я пытаюсь вам объяснить про подход к обработке ошибок в Go, а вы отвечаете — «это не ошибка, это штатное поведение».

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

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

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

В Go не хватает нативной поддержки варианта, когда это нештатное поведение. Достаточно было бы сделать аля сопоставление с образцом
file, nil = os.Open("file.txt")


Но так как этого нет, то происходит навязывание рассматривать абсолютно все ошибки, как штатное поведение. Это порождает совершенно лишний boilerplate-код. И я не понимаю, о чём тут спорить… Попробуйте хоть один ЯП, который не принуждает Вас выбирать что-то одно (код возврата vs исключение) в 100% случаев и сами всё поймёте.

А я вот предлагаю попробовать таки Go и узреть, что «boilerplate» код вносит только ясность в код. Все эти сопоставления и различные неявные варианты поведения простого кода это путь к усложнению понимания кода. Это именно то, чего команда Go с самого начала не хотела. Поэтому весь язык построен вокруг «ошибка это значение».

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

И в итоге, этот «boilerplate» код ничуть не усложняет код, а делает его только яснее, потому что сам язык проповедует подход, что ошибки это не boilerplate, а логика программы. Если бы не было всех этих фич, то конечно же мы получили бы C/С++, где обработка ошибок это пытка.

Вот тут между нами разница. Я писал на Go, в том числе для production. Вы же тот подход, о котором я пишу, на практике не пробовали, а сравнить пытаетесь на основании субъективных предубеждений. Для меня нет сомнений, что если Вы попробуете Let It Crash, то тоже осознаете, что подход Go примерно так же коряв, как подход Java. Те же грабли, только в профиль.

Это порождает совершенно лишний boilerplate-код. И я не понимаю, о чём тут спорить…

Ну вы не хотите понять. Это не boilerplate-код, это просто «код». Который не менее важен, чем happy path. Подход Go уравнивает их.

Для меня нет сомнений, что если Вы попробуете Let It Crash

Этот подход хорош для очень узкого круга задач и очень маленького класса ошибок (которые вы называете исключениями). Зачем на этом зацикливаться? Вы будете ронять GUI приложени? Или веб-сервер, которые получил неверный вход? Или (ваше же пример выше) — не найден файл, вы уронили программу — и что, файл появится?
Обработка ошибок — это гораздо более широкая тема и подход Go очень удачно добивается того, что в результате в программах на Go ошибкам уделяется должное внимание. Не это ли важно в конце концов.
Этот подход хорош для очень узкого круга задач

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


вы уронили программу

Let It Crash в принципе не предполагает ронять программу, скорее наоборот. Практически ничто не способно уронить программу, написанную в таком стиле.
Прочитайте, пожалуйста, маленькую статью на эту тему LetItCrash. И когда будете в ней встречать слово process мысленно переводите его как корутина, а не как процесс уровня OS, так будет понятнее суть. А то получается, что Вы какой-то свой смысл вложили в термин и исходя из него делаете неверные выводы.


Вы будете ронять GUI приложени?

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


Или веб-сервер, которые получил неверный вход?

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


Это не boilerplate-код, это просто «код».

Ох, мы опять по второму кругу… Просто код — это всё тот же happy path (как в вашем примере открой либо первый файл, либо второй)


А boilerplate-код это:


res, err := some.Function();
panic(err)

И ещё recover, сиротливо воткнутый куда-то.

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

Если воспользоваться метафорой, то я Вам про BMW X6 рассказываю, а Вы мне упорно доказываете, что Дэу Нексия лучше Лады Веста.
Проблема Go не в том, что сложно написать panic(err), а в том что в нём нет поддержки recovery-политик от слова "совсем". Ну и да, проверять err в каждой штатной ситуации это тоже на порядки неудобнее сопоставления с образцом.
Например, написать вот так (основной happy path отдельно, обработка штатных ошибок отдельно, исключений тут нет) в Go невозможно by design.


    with {:ok, res1} <- first_function(),
         {:ok, res2} <- second_function(res1),
         :ok <- save_result(res2) do
      :ok
    else
      {:error, err} ->
         case err.type do
           Postgrex.Error ->
               # handle err
           _ -> ...
         end
    end

А зачастую бывает удобно именно так. При этом вариант написать аля Go никуда не исчезает для особо разветвлённых happy paths.
Впрочем, насколько я понял, читать про что-то отсутствующее в Go, а тем более изучать, Вы не желаете… Посему опять пытаетесь съехать на хорошо знакомую Вам тему сравнения Дэу с Ладой )))


а вы то к термину "исключение" прицепились и всё запутали.

Ахах, т.е. я виноват, что у вас термин "исключение" при обсуждении Go ассоциируется с control-flow и Java?

Однотипного? Ну, да, вместо того чтобы писать однотипный код можно было потратить время с бОльшей пользой.
То, что автор называет генериками, на самом деле перегрузка функций (пример с CopyBytes из статьи) и макрошаблоны (пример с Max с сайта). Очень интересно, как нужно будет обращаться к этим функциям из Go.
Жило было 14 конкурирующих языков программирования…
а надо было сразу с дженериками создавать, а не петь мантры разработчикам «вам это не нужно»…
Я не до конца в теме (я вообще не в теме), это что-то вроде расширяемого синтаксиса Лиспа, который позволяет плодить свои диалекты Лиспа даже не требуя пересборки исходников Лиспа?
Просто возможность сделать
ArrayList <MyFancyType> 
и не плодить кастомных коллекций.
Оу… Действительно… Люто-бешено плюсую воображаемыми единичками.
Никто не пел. Дженерики были бы, если бы кто-то предоставил подобающее решение, которое устроит команду разработчиков. Есть идеи, исследования, но пока на этом все. Все существующие реализации отвергли по теме или иным причинам.
ИМХО, это как раз тот случай, когда не совсем удачное решение лучше, чем нет решения вовсе.
Поскольку с этим «не совсем удачным» решением пришлось бы жить годы и тащить всю жизнь языка, то наверное нет. Пора уже перестать идти на поводу у тех, кому вот прямо надо и все тут. Отлично живется без дженериков языку и не сильно его просит аудитория. Будет — скажут спасибо. Не будет — ну и ладно.
Мне вот кажутся идеальными Дженерики в СиШарпе. Вот правда, пока дошел до него — все дженерики мне казались не очень. Например в той же Джаве из-за type erasure. В СиШарпе же они работают просто так как должны, очень мощные и гибкие. И чем они плохи для Гоу?
На Java как образец дженериков не смотрите.
Точнее, смотрите, как на образец «что будет, если их даже не планировать, но потом в какой-то момент осознать, что так жить дальше нельзя». Это сейчас настолько повально распространенная вещь для статически типизированных языков, что вполне должна входить в общую образовательную базу.
По теме: Proposal, FAQ, RFC
Показательно, что RFC по нему открыли только в апреле этого года.
На Java как образец дженериков не смотрите.

Так я ж и говорю, что там отвратительные =) Хороши в C#, там тоже добавили не сразу, но поступили мудро, что забили на первые версии и теперь имеют нормальные дженерики.

Просто не получится ли с Гоу так же как с Джавой — вставят какую-то фигню через пару лет типа type erasure и потом все плеваться будут, а они только плечами двигать, мол обратная совместимость, раньше думать надо было?
Если верить FAQ — поддержка потенциальной возможности generics есть изначально, они решили сначала решить вопросы стабильности и производительности рантайма.
НЛО прилетело и опубликовало эту надпись здесь
Я ведь про дженерики, а не про стандартную библиотеку.
НЛО прилетело и опубликовало эту надпись здесь
ну джава на такой шаг ведь не пошла?
НЛО прилетело и опубликовало эту надпись здесь
Дженерики если и будут, то только в Go2, на что гарантии совместимости не будут распространяться. Go1 позволяет добавлять только то, что не сломает текущий код, что позволяет вносить минимальные изменения в спецификацию.
https://github.com/golang/go/issues/15292 тут вот обсуждение и ссылки на отвергнутые предложения. Я не сильно вдавался, но на поверхности выходит, что Go довольно уникальный язык и на него плохо ложатся обычные дженерик как у всех (в частности, плохо ложатся на интерфейсы), а некоторые реализации просто не хочется использовать ввиду их убогости. Вроде C++, что само собой мало кому хочется видеть. Конечно можно было давно что-то добавить, но весь цикл разработки языка прямая противоположность такому подходу — пока что-то не понравится всей команде, то этого не будет в языке. И, надо сказать, для них это отлично сработало.

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

Я вот несколько раз то обуждение прочитал. Может потому что английский у меня не идеален, но я так толком и не понял, почему им не нравятся Дженерики из c#. Про С++ есть прямо: «The problem with C++ generics (without concepts) is that the generic code is the specification of the generic type. That's what creates the incomprehensible error messages. Generic code should not be the specification of a generic type». К сожалению я не совсем понял, т.к. не знаю С++.
Про Джаву тоже: «unlike in Java, so you can find out types at run time. You can also instantiate on primitive types, and you don't get implicit boxing in the places you really don't want it: arrays of T where T is a primitive». Но вот в C# нету такой проблемы.

Мне особенно «нравятся» вот такие аргументы: «One key aspect of Go is that when you write a Go program, most of what you write is code. This is different from C++ and Java, where much more of what you write is types. Genus seems to be mostly about types: you write constraints and models, rather than code». Больше подходит для политического лозунга, а не технического обсуждения.

«Мелочь, а встречается намного чаще, чем необходимость в дженериках» мне, все-таки, кажется, что необходимость в них может варьироваться зависимо от домена.
The problem with C++ generics (without concepts) is that the generic code is the specification of the generic type. That's what creates the incomprehensible error messages. Generic code should not be the specification of a generic type

Тут речь идет о том, что если мы используем какой-то шаблон, ошибка выявится глубоко внутри библиотеки, там где она попробует воспользоваться заданным при специализации типом. Они предлагают определять сразу, подходит ли какой-то тип для специализации шаблона, или нет.
Спасибо.
Это да. Кстати даже в прекрасном Хаскеле type erasure тоже.
НЛО прилетело и опубликовало эту надпись здесь
Считают. Но если даже GC слишком много, то зачем им Go? о_0 Понятно что там есть плюсы свои. Но всетаки.
Я не видел ничего про GC, но написано прямо черным по белому — никакой кодогенерации в рантайме. C# поэтому даже вообще не обсуждается — такой подход по определению не подходит
Вроде как для CoreRT хотели тот же рефлекшен сделать отключаемым. Но я пока активно не слежу за этим.
НЛО прилетело и опубликовало эту надпись здесь
Дженерики были бы, если бы кто-то предоставил подобающее решение, которое устроит команду разработчиков.

Хм, интересная позиция… Разве команда разработчиков не в состоянии разработать решение, которое её же и устроит?

Дженерики они рассматривали и все реализации их так или иначе не устроили. Некоторые серьезные проблемы исходят из самого языка, а именно — интерфейсов. Поскольку это Open Source проект, то дверь оставили открытой https://github.com/golang/go/issues/15292

Ну, я не очень в теме как выстроен процесс разработки Go. Но со стороны это выглядит примерно так: "Мы тут накосячили с архитектурой языка так, что даже сами не можем придумать решение, но у нас Open Source, так что давайте теперь как-нибудь силами сообщества вопрос порешайте, а мы рассмотрим ваши предложения, может быть.."

Нет, это выглядит не так. Мы придумали язык такой, какой он есть (интерфейсы самое главное преимущество языка и никакое отсутствие дженериков этому никак не мешает), а вы тут хотите сверху прикрутить что-то из абсолютно других языков, просто потому что хочется как у них. Все текущие реализации так или иначе криво работают, либо вообще не совместимы с языком. Это не проблема языка, а проблемы этих реализаций. Авторы потратили на это время, не нашли. Более смысла тратить на не очень то нужную языку фичу нет. У нас Open Source, поэтому если кто-то сможет, то пожалуйста. В данный момент есть намного более важные вещи, которые стоит добавить в язык и его библиотеку. Например, нормальная работа с многомерными массивами и вот сейчас бурно обсуждаются type alias'ы.
НЛО прилетело и опубликовало эту надпись здесь
Вот тема https://github.com/golang/go/issues/16339 Внимание, оно огромная и все это сплошной спор. Мне, честно говоря, не особенно нравится самому эта штука и там много споров из-за того, что людям кажется, что Google проталкивает это изменение для себя и не предоставляет нормальной аргументации в пользу. Даже кажется, что поставили перед фактом и просят предоставить аргументы против. Но, в тоже время, во всех остальных языках это есть и вроде живут нормально.

А предложение довольно простое — возможность объявить альтернативное имя для типа/функции/т.д. Что-то вроде
const C => L1.C  // for regularity only, same effect as const C = L1.C
type  T => L1.T  // T is an alias for type L1.T
var   V => L1.V  // V is an alias for variable L1.V
func  F => L1.F  // F is an alias for function L1.F


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

Про все остальные языки я конечно погорячился, но это есть в Rust и Swift как минимум.
НЛО прилетело и опубликовало эту надпись здесь
В Rust, Swift и вообще эта конструкция связана не с абстрактными типам/константами/переменными/функциями а с импортом

Да ладно?


type MyMegaInt = i32;

typealias AudioSample = UInt16
Ой, какой Паскаль :) Удобно, на самом деле, и для специализации шаблонов тоже.
НЛО прилетело и опубликовало эту надпись здесь

Про Go ничего и не говорил. Речь о том, что "синонимы" типам в расте и свифте можно давать независимо от импорта. Или я всё-таки что-то не так понял?


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

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

Теперь понятно, спасибо.


Тем не менее, в расте (насчёт свифта уже не уверен) оно всё равно работает и для импорта и без него.

НЛО прилетело и опубликовало эту надпись здесь
Python синтаксис — нет нет и нет. Тонна ошибок на ровном месте. Особенно с учетом требований Go — отсутствие необходимости писать в IDE или ее подобии (никаких вимов с тонной плагинов) и автоматическое однозначное форматирование любого кода. Один неправильный отступ и до свидания.

В Have реализована работа с шаблонами. Разработчики Google не стали добавлять ее в Go из соображений простоты

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

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

Что?

Начинание правильное, но, по-моему, не те вещи он начал исправлять. Больше похоже на очередной проект, в который человек просто добавил все, что ему захотелось из других языков. Лучше было начать с github и открыть все быги с тегом Go2 — там предостаточно отличных идей и предложений, которые довольно не скоро появятся официально, т.к. судьба Go2 пока еще мутная.
Один неправильный отступ и до свидания

Пишу на Python уже лет 7. За это время видел тонны ошибок в программах из-за динамической природы языка, но ни разу не видел ошибок из-за неправильных отступов. Может хватит уже муссировать этот миф с проблемой indent-based синтаксиса в Python?

struct A:
    x int
    func String() string:
        return fmt.Sprintf("x:%d", a.x)

Я одно не понял, а откуда тут имя 'a'? Похоже на ошибку копипаста.
В целом, мне нравится новый синтаксис — стало нагляднее.

Возможно и не опечатка, в го по идеи должно было быть написано так:


func (a A) String() string

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

Там должно быть this.x
В оригинале есть упоминание self для указателей. Видимо, naming convension дкйствует только для методов, вызываемых по значению.
What about pointer receivers? They are there, too. You declare them by putting a * before function name. The method receiver is always referred to as self.

struct A:
    counter int
    func *Inc():
        self.counter++


Кстати, странно, что про этот куда более используемый кейс — ни слова.
Я так понимаю, про ошибки с табуляцией, «характерные для Python», в основном пишут те, кто Python никогда серьезно не использовал. Потому что те, кто работает с ним каждый день, почему-то никогда на это не жалуются.
Возможно. Но мне идея форматно-несвободного языка как таковая не нравится. На питоне не пишу вообще, но иногда приходится наталкиваться на какие-то экзотические вещи, в которых от случайно поставленного не там пробела или табуляции, или табуляции вместо пробелов что-то ломается и не поймешь где.
От «случайно поставленных не там пробелов или табуляций» в 99% случаев программа просто перестает запускаться, и выдает IndentationError, с указанием строки. Поведение в точности как в других языках при случайно и не там поставленных скобочках.
Я так понимаю, про неудобство есть суп ножкой от стола, в основном пишут те, кто ножкой от стола суп никогда серьезно не ел. Потому что те, кто ест суп ножкой от стола каждый день, почему-то никогда на это не жалуются.
Не аргумент. Тысячи совершенно неглупых людей уже не первый десяток лет поглощают супы ножками от стола, и чувствуют себя прекрасно.
Вот это как раз не аргумент. Аргумент — это объективный недостаток решения Python в виду генерации невидимых ошибок, которые ломают код в рантайме, просто потому что где-то неправильный отступ. И все аргументы в пользу этого от питонистов сводятся к какой-то идеологической проблеме — не хочу и не буду писать скобки, потому что уже есть отступы. То, что это генерируется только новые проблемы и не решает никаких старых, это как бы не важно. Привыкли — замечательно, но это не показатель успешности решения. Показатель это тот факт, что никто из популярных языков такое решение не принял кроме питона.
Из популярных — нет, но судя по всему многие изучение программирования начинают с питона и это оказывает неизгладимый отпечаток. Например в Nim ввели такой же синтаксис, еще где-то видел. Возможно это действительно другая парадигма мышления — ну чтоже, пускай будут разные языки, время рассудит. Я для себя решил что в собственном языке программирования никаких невидимых отступов не будет, только старые добрые скобки :)
Почему питонистам нельзя есть суп ножкой от стола, если куча других людей едят суп ножкой от табуретки и считают что это правильно?
Пример:
if (a == 1) a++; b++;
Это C, если что. Какое количество ошибок в куче программ повылазило только из-за того, что блок может состоять только из одного оператора…
НЛО прилетело и опубликовало эту надпись здесь
1. С — да, старый. Но ведь это решение потом потянули везде где только можно. В Rast разве не так-же?

2. это если человек осознанно пишет. А в таком случае и скобки написать можно.
В Rast разве не так-же?

Неа, в Rust отсутствие скобки будет ошибкой:


if a == 2 a+= 1; 

error: expected `{`, found `a`
 --> <anon>:4:15
  |>
4 |>     if a == 2 a+= 1;
  |>               ^
help: place this code inside a block
И к чему это здесь? В Go такое написать нельзя, к чему здесь С? Да, там есть проблема и приходится дисциплинировать себя, чтобы ее максимально избегать. Ровно тоже самое, что питоновская строчка, которая может съехать и изменить логику всего блока кода.
Дело в том, что проблемы нет. Не генерируются никакие скрытые ошибки, о которых вы пишете. Может, они в вашем представлении должны были бы генерироваться, но не генерируются. Вот такая фигня.
Дело в том, что проблемы нет у вас. Проблема есть у языка и его синтаксиса. Она есть и никуда от заявлений, что у кого-то ее не было, не девается. Совершенно не случайно в современных популярных языках нет этого синтаксиса, а так же нет C синтаксиса, где можно опустить скобки. Go, Rust, Swift не дадут написать условие без скобок, что абсолютно верно.

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

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

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

Один из признаков того, что язык программирования имеет успех, – появление новых языков на его основе. Известным примером является JavaScript. На его базе возникли такие языки, как TypeScript, GorillaScript, Kaffeine, Sweet.js и так далее.

Несколько наивное утверждение. Главная причина появления новых языков «на базе JavaScript» — то, что он встроен в браузеры. Это как говорить о успехе машинных кодов x86 как языка программирования на основе того, что C, C++, Pascal, Haskell, Go и др. компилируются в них. Вторая причина — то, что сам он достаточно неудобен, чтобы многим хотелось как-то его улучшить и/или чем-то его заменить.
Так наоборот, все правильно. Язык успешен и захватил зону рынка. Но неудобен для части людей. Но в силу успешности языка им приходится иметь с ним и его экосистемой дело. И они задумываются о том, что нужно создать свой язык, который нацелен на туже экосистему, но который все неприятные части убирает, а приятные добавляет.

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

Имхо, появление транспилеров — это признак того, что на исходном языке писать жутко неудобно/неприятно/etc., но какие-то другие факторы принуждают к этому. И JavaScript — яркий тому пример.
Своеобразный лайфхак — писать на приятном языке, а генерацию кода на неприятном языке поручить транспилеру. Разве есть другие причины писать транспилер?

Причина писать транспайлер еще может быть в том, что destination language есть подо всё, что шевелится. Как пример, автор NIM совершенно разумно сделал транслятор в C — как следствие он получил возможность компиляции на любую железку, для которой есть C копилятор (т.е. чуть более, чем под любую).

Так я не вижу здесь противоречия. Если автор Nim считал бы С идеальным ЯП, то он не стал бы делать свой язык. Получается, что ему нравятся компиляторы C, но не нравится C как язык.

Скорее автору Nim нравятся целевые платформы компиляторов C, а не сам язык и его компиляторы.

Первая реакция: Фу!


(Это чисто мое мнение, оно может не / не должно совпадать с мнением большинства)

НЛО прилетело и опубликовало эту надпись здесь
code style то я могу сам выбирать. Экономия символов это мелочи. А неприятно то, что при вставке кода из другого источника (даже из другого места этого же файла) в середину существующего кода нарушается не просто красота, а целостность синтаксической структуры. И пока ее не восстановишь — ничего работать не будет. В форматно-свободных языках можно вставить код в любое место и затем просто запустить автоматический форматтер, который все выравняет так как надо. А можно и не выравнивать, пока не будет написан необходимый с точки зрения логики код.
С питоном нужно все делать вручную и каждый раз, потому что там выравнивание — часть синтаксиса языка.
НЛО прилетело и опубликовало эту надпись здесь
Полное ощущение, что название языка выбиралось специально, чтобы превзойти go в «удобстве» поиска в интернете по ключевому слову.
Рано или поздно на планете появится язык программирования, искать спецификацию которого на рабочем компьютере может быть излишне неловко.
например brainfuck.
Ключевое слово для поиска go в интернете – это golang.
Да, это все знают. А для поиска игры go — ключевое слово baduk. Маленькие хитрости, неочевидные при первом знакомстве с предметом. Язык C в этом отношении избежал неоднозначности, при всей краткости названия удалось избежать совпадения с часто используемым словом. Впрочем, когда его так назвали, до поисковых машин было ещё пара десятков лет…

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

А мне норм.
Я бы еще добавил модульную структуру и импорты как в Python, а то в Go это почему то не добавили
Программист должен не скобочки искать или отступы считать, а думать о структуре программы и прочих более общих вещах. Всю черновую работу должны брать на себя IDE. Тогда бы и доводы за или против были бы не с точки зрения «кодерства»…
многие проблемы Go в нем не решены. К примеру, реализация обработки ошибок в Go требует бесполезных затрат времени для написания однотипного кода.

Это смешно, правда.

Эксепшены уже доказали свою несостоятельность, и в Google неспроста от них отказались.

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

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

Вы, наверное, понимаете, что в мире C++ с исключениями всё очень и очень неоднозначно. И что Гугл отказался от их использования в C++ по совсем другим причинам. Которые совсем никак не связаны с тем, что в Java проверяемые исключения.

Причины там описаны, а первая ссылка как раз про Java.

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

Публикации

Истории