Функция upgradeUser реализует какой-то сценарий бизнес логики, соответственно она должна абстрагировать от деталей реализации. Но почему-то функция не абстрагирует от низкоуровневых ошибок, которые возвращают используемые в ней функций. Например: мы хотим проапгрейдить юзера, а в результате получаем ошибку ввода-вывода сети или http.ProtocolError или url.Error. Как я должен различать и обрабатывать их? Их даже заллогировать корректно нельзя, т.к. непонятен контекст. Часть ошибок может быть по моей вине, если я указал некорректный endpoint, с остальными ошибками, я скорее всего ничего сделать не могу. Также не забываем, что код, который будет использовать функцию upgradeUser, по хорошему, ничего не должен знать о деталях реализации. И что делать, если в процессе поддержки приложения мы отказались от http и перешли на бинарный протокол? Переписывать все места, где используется upgradeUser чтобы корректно обработать ошибки?
Делаем выводы:
1) Реализация этой функции далека от совершенства, т.к. в ней текут абстракции — не пишите так;
2) Если вы собираетесь вернуть полученную ошибку «наверх» без изменения (заворачивания в другую) трижды подумайте;
3) Следует четко понимать и определять какие ошибки может возвращать ваша функция, детали реализации функций скрывайте, в части обработки ошибок: заворачивайте их в более высокоуровневые либо логгируйте и возвращайте другую;
4) При правильной работе с ошибками разницы нет какой механизм вы используете, если писать корректный код на js с теми же async/await то рука будет болеть к вечеру от написания тонны try {} catch(), особенно если учитывать что там смешан поток обработки ожидаемый ошибочных и неожиданных исключительных ситуаций, и всегда надо будет прокидывать ошибку наверх вручную, если мы не знаем как ее обрабатывать.
У меня складывается впечатление, что автор просто или путает или отождествляет DI/IoC с Service Locator. Надо отделить мухи от котлет. Почему в статье нет пояснения к используемой терминологии?
DI — Dependency Injection, внедрение зависимостей, на самом деле очень банальная вещь: если вы определили конструктор с параметрами, вы уже воспользовались внедрением зависимостей через параметры конструктора. Различают также внедрение зависимостей через метод класса (в котором иногда выделяют две разновидности). Но я бы не рекомендовал пользоваться последним методом без необходимости, т.к. способ внедрения зависимостей через параметры конструктора более нагляден.
IoC — Inversion of Control, наилучшая формулировка, на мой взгляд такая: «Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций». Первая рекомендация вытекающая из такой формулировки это: «Используйте интерфейсы для определения зависимостей». Следование только этому правилу уже позволит построить менее связанную архитектуру, которую очень просто тестировать с помощью модульных тестов и легко изменять реализацию какой-то части, при необходимости.
Очень рекомендую почитать про DI и IoC на англоязычной википедии.
Далее, если у вас очень сложный проект (сотни классов), то вы можете упростить себе жизнь (или усложнить) воспользовавшись Service Locator. Использование Service Locator невозможно без DI и IoC, но не наоборот: если вы используете DI и IoC, то это не означает автоматическое использование Service Locator.
Проблема: Если переписать эту функцию на JavaScript “в лоб”, то строка, переданная в качестве аргумента, будет скопирована. При работе с большими строками это приведет к лишней трате процессорного времени на их копирование и увеличению потребления памяти.
Решение в Node.JS: Чтобы предотвратить копирование больших строк при передаче в функцию, пришлось либо переводить их в бинарный формат (Buffer), либо оборачивать в объект и передавать его.
Раза 4 перечитал это место, так и не понял где «собака зарыта». В JS строки неизменяемые и передаются в функции по ссылке. При этом никакого копирования самого значения строки не происходит. Для чего их «оборачивать в объект»?
Как раз наоборот: когда компилятор C++ строит AST и встречает int name..., он не знаете что будет дальше — объявление переменной или функции, поэтому эту информацию надо или запоминать где-то или возвращаться назад по исходнику. К тому же для языков с подобными грамматиками сложнее программировать восстановление после сбоев во время синтаксического анализа. Для паскаля как раз проще, для него отлично подходит обычный леворекурсивный парсер без наворотов.
Выброс исключения тоже тут не поможет т.к. вы можете ошибиться при его выброса. Если вы можете выбросить исключение, значит вы можете и сразу обработать ошибку и вернуть невалидный результат.
А я и не говорил что это делает программист в коде явно. В 99% случаев непредвиденные исключения выбрасывает рантайм: выход за границы массива, разыменование нулевого указателя и т.п.
Я не вижу, в каких случаях стоит ловить какие-то исключения. Если это segmentation fault — не за чем его ловить, просто падаем.
Расскажите это тем, кто эксплуатирует высоко нагруженные web-сервера, что если вы ошиблись при кодировании и какой-то запрос к серверу приводит в каком-то месте к выходу за границы массива, то в таком случае вы предпочитаете молча падать вместе с парой сотней одновременно обрабатывающихся запросов, не успев даже вернуть 500 — Internal Server Error в ответ на проблемный запрос.
… если это пользовательское исключение — можно его заменить на возвращение invalid value.
Если я читаю JSON конфиг, то получение вместо него XML — точно такая же непредвиденная ситуация
Для вас будет неожиданностью, что формат конфига может не соответствовать заявленному? Да одна запятая, и все — парсер не поймет ваш конфиг. И XML тут вообще притянут за уши, для парсера JSON без разницы что конфиг — это валидный XML, главное что это не валидный JSON. Потом рассказывайте девопсу, что для вас стало неожиданностью когда он по ошибке подсунул программе чужой xml конфиг, вместо родного JSON, и программа вывалила стектрейс вместо вменяемого сообщения о некорректном месте в конфиге.
Если я исполняю пользовательские формулы, то ошибка в них — такая же предвиденная ситуация, как и отсутствие опционального файла.
Я разве что-то писал про интерпретатор формул? Но тут вы правы, это предвиденная ошибка. Вот видите, мы говорим об одном и том же.
Не низкому уровню приложения решать какие ситуации являются предвиденными, а какие — нет.
Не припомню чтобы я писал о чем-то подобном. Не могли бы вы более развернуто описать это свое утверждение?
Благодарю, великолепный вопрос.
Когда вы вызываете процедуру, всегда может произойти ошибочная ситуация, и вызов процедуры не приведет к желаемому результату. Теперь подумаем, по какой причине это может произойти?
Например отсутствует файл, неверный формат входных данных, нарушен контракт вызываемым кодом (передан нулевой указатель) — все эти ситуации описаны в документации и должны быть корректно обработаны программистом, они просто являются результатом работы процедуры (предвиденные ошибочные ситуации): когда вы парсите текст — знаете что его формат может не соответствовать ожидаемому, когда открываете файл — знаете что он может отсутствовать и т.д. Более того, вы всегда знаете в чем причина такого поведения, как обработать такие ситуации и где они возникают — выдать сообщение что файл не найден (или создать его), что данные не соответствуют требуемому формату и т.д.
Если же мы возьмем ситуацию к которой приводит логическая ошибка в программе: опечатались, не закодировали возможную ветвь обработки, ошибка в расчетах и т.д., то это непредвиденное исключение, ненормальное поведение программы. Вы никогда не знаете когда и где оно возникнет. Вставка проверок тут не поможет — вы банально можете ошибиться при кодировании самой проверки, да и компилятор сам вставляет подобные проверки для отлова исключительных ситуаций. Если возникает такая ситуация, то лучшее что вы можете сделать (в общем случае) — это корректно завершиться (вы же не знаете где и что произошло и к чему это приведет, если продолжить выполнение, не можете предусмотреть все ситуации, ибо потенциально их очень много). Бороться с подобными ситуациями можно только одним способом — патчем, и только в таком случае нужен стектрейс чтобы отладить программу.
Надеюсь я ответил на ваш вопрос.
Неужели? Почему же тогда в комментарии, с которого началась эта дискуссия автор радостно объясняет нам важность такого разделения, да еще как будто в го первыми до него додумались.
Не фантазируйте пожалуйста. У букв нет эмоций, мы сами окрашиваем текст в те или иные эмоции, когда читаем его. В комментарии я объяснял подход Go к этим вопросам, а решать насколько это важно и актуально должен каждый сам. И то, что в Go это было реализовано впервые, я тоже не писал. Ничто не ново под солнцем, и даже CSP, модель которого повлияла на дизайн корутин в Go, была описана в 1978 г.
Получается, мало того, что идея далеко не нова, так она еще и успела провалиться много лет назад. Зачем же это затаскивают в го? Наступают на те же грабли?
Вот на этом месте остановимся по подробней. Где она успела провалиться? В C++ — based языках? Поправьте меня, если это не так.
Итак, давайте по полочкам.
Я писал уже и повторю еще раз, что С++, C# и Java (и не только — JavaScript, например) используют единую модель при работе с ошибками и исключениями: единый механизм для работы с ними — throw, try-catch-finally; общая иерархия классов ошибок/исключений (C#, Java); даже если вы захотите работать с ошибками как с возвращаемыми значениями, стандартная библиотека (и не только) будет упорно вставлять палки в колеса, ибо так не принято в экосистеме. Да, Java разделяет эти понятия через checked и unchecked exceptions, об этом написано чуть ли не в каждом учебнике, но использует опять же единые механизмы, да и реализация тоже не безгрешна: Mikanor привел уже с пяток проблем вызванных ей. И это данность.
Резюмируем. В C++ и C# вообще не разделяются эти понятия, в Java же хоть и разделяются, но используются единые механизмы для работы с ними, к тому же местами подводит реализация. Так от чего в Java провалилась идея разделения предвиденных ошибок и непредвиденных исключений? Может из-за реализации? Тогда к Go это не имеет ни какого отношения, там другая реализация, там совершенно различаются механизмы (это что касается вопроса «Зачем же это затаскивают в го?»).
И да, если абстрактный программист в вакууме утверждает, что «это плохой дизайн», «слишком много кода (проверок)», «мне нравится работать с ошибками вот так-то» — это вкусовщина, на вкус и цвет все фломастеры разные. Давайте оперировать фактами: «эта реализация приводит к таким-то и таким-то проблемам вот в таких случаях» или «это не работает вот в таких-то и таких-то случаях».
Почитайте про парсеры. И это таки видится более частным применением, нежели по прямому значению — завалить программу.
Благодарю за дельное замечание. Да, механизмы panic/recover используются в стандартной библиотеке для «передачи управления наверх» в сложных алгоритмах, все как вы написали (например, encoding/json/decode.go:199). Но это инкапсулировано в библиотеке и ошибка декодирования будет возвращена вам библиотечной функцией в качестве значения, а при непредвиденной исключительной ситуации произойдет panic (encoding/json/decode.go:139). Если есть механизм, отлично подходящий для решения какой-то проблемы, почему бы им не пользоваться? Но только это не должно влиять на клиентский код и интерфейс-то все равно должен соответствовать общепринятым в экосистеме рекомендациям.
Наверное, когда дело доходит до panic/recover, то ключевое слово здесь «наверх» (передача управления наверх). Не в вызывающую процедуру, а на верхний уровень библиотеки/обработчика запроса/программы.
Возможно, некоторые не понимают разницу между «предвиденной ошибкой» и «непредвиденной исключительной ситуацией». Путают или смешивают эти понятия, что не мудрено, ибо C++, C#, Java и др. идеологически используют единый подход для работы с ними: throw, try-catch-finally.
Какая же разница между этими понятиями? Простой пример. Допустим, вы хотите прочитать данные из файла. Эта операция может завершиться как успехом, так и может произойти ошибка: файл не существует, недостаточно прав и т.д. Все эти ситуации определены и описаны, они не должны приводить к падению и должны обрабатываться программой: выдать сообщение об отсутствии файла, запросить привилегии и т.д.
Возьмем теперь непредвиденную исключительную ситуацию: разыменование нулевого указателя. Никакой программист в здравом уме не будет делать это нарочно, это может произойти только в результате непредвиденной ошибки в коде. Предусмотреть подобные ошибки нельзя и они могут произойти в любом месте. Что же с ними делать? После такой ошибки практически невозможно восстановить нормальное поведение программы и единственное что можно сделать, это упасть как можно скорее и не усугублять дело, предварительно записав/отправив отладочную информацию (стектрейс, дамп и т.п.) и, по возможности, приведя данные к консистентному виду и освободив ресурсы. И да, я понимаю что в случае с веб-сервером, например, программа не должна падать, но смысл похожий — надо немедленно завершить обработку запроса и вернуть Internal Server Error.
Вернемся к Go. Он идеологически разделяет подход к работе с этими сущностями. Для предвиденных ошибок — это возвращаемое значение, это такой же результат работы процедуры и вы должны его обрабатывать (вы же не игнорируете результат, который возвращает процедура поиска первого вхождения подстроки в строке, даже если это "-1"?). Для непредвиденных исключительных ситуаций — это defer, panic и recover.
Go бескомпромисен, он побуждает, или даже, вынуждает программиста использовать те практики, которые считаются хорошими в мире Go. Если у вас вызывает дискомфорт такое отношение или если для вас хорошие практики отличны от того, что принято в Go, то конечно же у вас будет негативное впечатление от этого ЯП. Но это вопрос вкусов и к техническим деталям не относится. Да, Go местами сильно отличается от мейнстримовых ООП ЯП. Но разве не разумно иметь разный подход к работе с разнородными сущностями и не смешивать их?
Вот смотрите, вы же в C# понимаете отличия struct от object? Где они располагаются, как передаются в качестве параметра? Также, думаю, вам вполне понятны значения модификаторов ref и out? А давайте добавим сюда еще unmanaged код, где можно проводить операции с указателями, stackallock и fixed. Получается что в C# тоже не все просто. Кому-то тоже это может не нравится, а кто-то настолько привык, что даже не замечает. Видимо дело в привычке.
Видел интервью с одним инженером, основателем своего бизнеса в области акустических систем. Там больше говорилось про бизнес-модель. Но и заикался он про кучу имеющихся патентов, в частности говорил что у него есть технологии, позволяющие изготавливать совершенно плоские колонки (толщиной как лист обычного ватмана и практически любой площади) с высочайшим качеством звучания. Еще он говорил, что не развивают это направление только потому, что в представлении обывателя дорогая акустическая система — это массивные, деревянные колонки, а не несколько листочков бумаги на стенах. Если найду видео, кину ссылку.
Несколько месяцев назад наткнулся на аналогичный материал от наших ученых:
Видео длится пол часа, но чтобы понять о чем речь, достаточно посмотреть минут 10.
И вот теперь, в России эти разработки блокируют не нужны, но они обретают новую жизнь за границей.
Всем надо пользоваться с умом. Есть соглашение, об этом написано в статье Effective Go — Errors, что сигнатуры библиотечных методов и функций должны соответствовать определенному формату, а именно, возвращать error, если может иметь место исключительная ситуация. Но внутри кода своей библиотеки вы можете использовать и механизм panic — recover, когда это оправдано (даже для обработки предвиденных ошибок).
Например есть цепочка вызовов функций и исключительная ситуация может возникать где-нибудь на глубине пятого уровня. Чтобы не пробрасывать вручную и не писать в каждой функции проверки типа if err {return err}, уместно использовать panic и просто отловить проброшенное исключение на верхнем уровне. Почему это будет дорого? Ведь .NET и Java используют именно такую модель обработки ошибок. А каком геморрое вы говорите? И почему это будет не Go-way? Ведь если существуют подобные механизмы языка, значит ими можно пользоваться, но и это не значит что их следует пихать везде, где вздумается. Более того, такой способ используют сами разработчики, например в пакете regexp, вот здесь кратко описано что, где и зачем: Effective Go — Recover
Функция upgradeUser реализует какой-то сценарий бизнес логики, соответственно она должна абстрагировать от деталей реализации. Но почему-то функция не абстрагирует от низкоуровневых ошибок, которые возвращают используемые в ней функций. Например: мы хотим проапгрейдить юзера, а в результате получаем ошибку ввода-вывода сети или http.ProtocolError или url.Error. Как я должен различать и обрабатывать их? Их даже заллогировать корректно нельзя, т.к. непонятен контекст. Часть ошибок может быть по моей вине, если я указал некорректный endpoint, с остальными ошибками, я скорее всего ничего сделать не могу. Также не забываем, что код, который будет использовать функцию upgradeUser, по хорошему, ничего не должен знать о деталях реализации. И что делать, если в процессе поддержки приложения мы отказались от http и перешли на бинарный протокол? Переписывать все места, где используется upgradeUser чтобы корректно обработать ошибки?
Делаем выводы:
1) Реализация этой функции далека от совершенства, т.к. в ней текут абстракции — не пишите так;
2) Если вы собираетесь вернуть полученную ошибку «наверх» без изменения (заворачивания в другую) трижды подумайте;
3) Следует четко понимать и определять какие ошибки может возвращать ваша функция, детали реализации функций скрывайте, в части обработки ошибок: заворачивайте их в более высокоуровневые либо логгируйте и возвращайте другую;
4) При правильной работе с ошибками разницы нет какой механизм вы используете, если писать корректный код на js с теми же async/await то рука будет болеть к вечеру от написания тонны try {} catch(), особенно если учитывать что там смешан поток обработки ожидаемый ошибочных и неожиданных исключительных ситуаций, и всегда надо будет прокидывать ошибку наверх вручную, если мы не знаем как ее обрабатывать.
DI — Dependency Injection, внедрение зависимостей, на самом деле очень банальная вещь: если вы определили конструктор с параметрами, вы уже воспользовались внедрением зависимостей через параметры конструктора. Различают также внедрение зависимостей через метод класса (в котором иногда выделяют две разновидности). Но я бы не рекомендовал пользоваться последним методом без необходимости, т.к. способ внедрения зависимостей через параметры конструктора более нагляден.
IoC — Inversion of Control, наилучшая формулировка, на мой взгляд такая: «Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций». Первая рекомендация вытекающая из такой формулировки это: «Используйте интерфейсы для определения зависимостей». Следование только этому правилу уже позволит построить менее связанную архитектуру, которую очень просто тестировать с помощью модульных тестов и легко изменять реализацию какой-то части, при необходимости.
Очень рекомендую почитать про DI и IoC на англоязычной википедии.
Далее, если у вас очень сложный проект (сотни классов), то вы можете упростить себе жизнь (или усложнить) воспользовавшись Service Locator. Использование Service Locator невозможно без DI и IoC, но не наоборот: если вы используете DI и IoC, то это не означает автоматическое использование Service Locator.
Раза 4 перечитал это место, так и не понял где «собака зарыта». В JS строки неизменяемые и передаются в функции по ссылке. При этом никакого копирования самого значения строки не происходит. Для чего их «оборачивать в объект»?
Расскажите это тем, кто эксплуатирует высоко нагруженные web-сервера, что если вы ошиблись при кодировании и какой-то запрос к серверу приводит в каком-то месте к выходу за границы массива, то в таком случае вы предпочитаете молча падать вместе с парой сотней одновременно обрабатывающихся запросов, не успев даже вернуть 500 — Internal Server Error в ответ на проблемный запрос.
А описание ошибки не нужно?
Я разве что-то писал про интерпретатор формул? Но тут вы правы, это предвиденная ошибка. Вот видите, мы говорим об одном и том же.
Не припомню чтобы я писал о чем-то подобном. Не могли бы вы более развернуто описать это свое утверждение?
Когда вы вызываете процедуру, всегда может произойти ошибочная ситуация, и вызов процедуры не приведет к желаемому результату. Теперь подумаем, по какой причине это может произойти?
Например отсутствует файл, неверный формат входных данных, нарушен контракт вызываемым кодом (передан нулевой указатель) — все эти ситуации описаны в документации и должны быть корректно обработаны программистом, они просто являются результатом работы процедуры (предвиденные ошибочные ситуации): когда вы парсите текст — знаете что его формат может не соответствовать ожидаемому, когда открываете файл — знаете что он может отсутствовать и т.д. Более того, вы всегда знаете в чем причина такого поведения, как обработать такие ситуации и где они возникают — выдать сообщение что файл не найден (или создать его), что данные не соответствуют требуемому формату и т.д.
Если же мы возьмем ситуацию к которой приводит логическая ошибка в программе: опечатались, не закодировали возможную ветвь обработки, ошибка в расчетах и т.д., то это непредвиденное исключение, ненормальное поведение программы. Вы никогда не знаете когда и где оно возникнет. Вставка проверок тут не поможет — вы банально можете ошибиться при кодировании самой проверки, да и компилятор сам вставляет подобные проверки для отлова исключительных ситуаций. Если возникает такая ситуация, то лучшее что вы можете сделать (в общем случае) — это корректно завершиться (вы же не знаете где и что произошло и к чему это приведет, если продолжить выполнение, не можете предусмотреть все ситуации, ибо потенциально их очень много). Бороться с подобными ситуациями можно только одним способом — патчем, и только в таком случае нужен стектрейс чтобы отладить программу.
Надеюсь я ответил на ваш вопрос.
Не фантазируйте пожалуйста. У букв нет эмоций, мы сами окрашиваем текст в те или иные эмоции, когда читаем его. В комментарии я объяснял подход Go к этим вопросам, а решать насколько это важно и актуально должен каждый сам. И то, что в Go это было реализовано впервые, я тоже не писал. Ничто не ново под солнцем, и даже CSP, модель которого повлияла на дизайн корутин в Go, была описана в 1978 г.
Вот на этом месте остановимся по подробней. Где она успела провалиться? В C++ — based языках? Поправьте меня, если это не так.
Итак, давайте по полочкам.
Я писал уже и повторю еще раз, что С++, C# и Java (и не только — JavaScript, например) используют единую модель при работе с ошибками и исключениями: единый механизм для работы с ними — throw, try-catch-finally; общая иерархия классов ошибок/исключений (C#, Java); даже если вы захотите работать с ошибками как с возвращаемыми значениями, стандартная библиотека (и не только) будет упорно вставлять палки в колеса, ибо так не принято в экосистеме. Да, Java разделяет эти понятия через checked и unchecked exceptions, об этом написано чуть ли не в каждом учебнике, но использует опять же единые механизмы, да и реализация тоже не безгрешна: Mikanor привел уже с пяток проблем вызванных ей. И это данность.
Резюмируем. В C++ и C# вообще не разделяются эти понятия, в Java же хоть и разделяются, но используются единые механизмы для работы с ними, к тому же местами подводит реализация. Так от чего в Java провалилась идея разделения предвиденных ошибок и непредвиденных исключений? Может из-за реализации? Тогда к Go это не имеет ни какого отношения, там другая реализация, там совершенно различаются механизмы (это что касается вопроса «Зачем же это затаскивают в го?»).
И да, если абстрактный программист в вакууме утверждает, что «это плохой дизайн», «слишком много кода (проверок)», «мне нравится работать с ошибками вот так-то» — это вкусовщина, на вкус и цвет все фломастеры разные. Давайте оперировать фактами: «эта реализация приводит к таким-то и таким-то проблемам вот в таких случаях» или «это не работает вот в таких-то и таких-то случаях».
Благодарю за дельное замечание. Да, механизмы panic/recover используются в стандартной библиотеке для «передачи управления наверх» в сложных алгоритмах, все как вы написали (например, encoding/json/decode.go:199). Но это инкапсулировано в библиотеке и ошибка декодирования будет возвращена вам библиотечной функцией в качестве значения, а при непредвиденной исключительной ситуации произойдет panic (encoding/json/decode.go:139). Если есть механизм, отлично подходящий для решения какой-то проблемы, почему бы им не пользоваться? Но только это не должно влиять на клиентский код и интерфейс-то все равно должен соответствовать общепринятым в экосистеме рекомендациям.
Наверное, когда дело доходит до panic/recover, то ключевое слово здесь «наверх» (передача управления наверх). Не в вызывающую процедуру, а на верхний уровень библиотеки/обработчика запроса/программы.
Какая же разница между этими понятиями? Простой пример. Допустим, вы хотите прочитать данные из файла. Эта операция может завершиться как успехом, так и может произойти ошибка: файл не существует, недостаточно прав и т.д. Все эти ситуации определены и описаны, они не должны приводить к падению и должны обрабатываться программой: выдать сообщение об отсутствии файла, запросить привилегии и т.д.
Возьмем теперь непредвиденную исключительную ситуацию: разыменование нулевого указателя. Никакой программист в здравом уме не будет делать это нарочно, это может произойти только в результате непредвиденной ошибки в коде. Предусмотреть подобные ошибки нельзя и они могут произойти в любом месте. Что же с ними делать? После такой ошибки практически невозможно восстановить нормальное поведение программы и единственное что можно сделать, это упасть как можно скорее и не усугублять дело, предварительно записав/отправив отладочную информацию (стектрейс, дамп и т.п.) и, по возможности, приведя данные к консистентному виду и освободив ресурсы. И да, я понимаю что в случае с веб-сервером, например, программа не должна падать, но смысл похожий — надо немедленно завершить обработку запроса и вернуть Internal Server Error.
Вернемся к Go. Он идеологически разделяет подход к работе с этими сущностями. Для предвиденных ошибок — это возвращаемое значение, это такой же результат работы процедуры и вы должны его обрабатывать (вы же не игнорируете результат, который возвращает процедура поиска первого вхождения подстроки в строке, даже если это "-1"?). Для непредвиденных исключительных ситуаций — это defer, panic и recover.
Go бескомпромисен, он побуждает, или даже, вынуждает программиста использовать те практики, которые считаются хорошими в мире Go. Если у вас вызывает дискомфорт такое отношение или если для вас хорошие практики отличны от того, что принято в Go, то конечно же у вас будет негативное впечатление от этого ЯП. Но это вопрос вкусов и к техническим деталям не относится. Да, Go местами сильно отличается от мейнстримовых ООП ЯП. Но разве не разумно иметь разный подход к работе с разнородными сущностями и не смешивать их?
Видео длится пол часа, но чтобы понять о чем речь, достаточно посмотреть минут 10.
И вот теперь, в России эти разработки
блокируютне нужны, но они обретают новую жизнь за границей.Например есть цепочка вызовов функций и исключительная ситуация может возникать где-нибудь на глубине пятого уровня. Чтобы не пробрасывать вручную и не писать в каждой функции проверки типа
if err {return err}
, уместно использовать panic и просто отловить проброшенное исключение на верхнем уровне. Почему это будет дорого? Ведь .NET и Java используют именно такую модель обработки ошибок. А каком геморрое вы говорите? И почему это будет не Go-way? Ведь если существуют подобные механизмы языка, значит ими можно пользоваться, но и это не значит что их следует пихать везде, где вздумается. Более того, такой способ используют сами разработчики, например в пакете regexp, вот здесь кратко описано что, где и зачем: Effective Go — Recover