Go - крайне простой язык программирования и там всё сделано крайнее просто. Интерфейсы в Go - это попытка реализовать то, что во всяких любимых народом шлакокнижках за авторством Р. Мартина и других называется интерфейсом.
Исключения запутывают поток выполнения, в отличие от. И поэтому чаще всего на них забивают, повсюду ставя throws или один catch (Exception) на весь блок. Для закрытия ресурсов вообще отдельная конструкция (try with resources).
Конкретные примеры можно найти в куче отклонённых предложений. Я лично не особо с ними знаком, но могу привести такой пример, возможно, слегка не по теме — сейчас нельзя добавить дженерик замыканию, потому что нужно сохранить нулевые значения, а без указания конкретного типа это невозможно. Подобные ограничения, мне кажется, делают невозможным подход «давайте всё спишем у C++/Java/C#».
Срез это не копирование и не владение. Это просто ссылка на область памяти (массива). Логично и понятно. Почти как обычные указатели в С. Не знаю, может ты с С не был знаком, перед изучением Go, потому и «не зашло».
Во всех языках (C, Java, Python) этот приём используется вручную, но в Go его встроили прям в язык. Вот как выглядит аналогичный код на разных языках.
sort(arr, 0, len / 2); // C
arraycopy(src, 0, src.length / 2, ...); // Java
print_half(lst, 0, len(lst) / 2); Python
println(s[0:len(s) / 2]); // Go
В Питоне есть похожая нотация, но она делает копию а не ссылку.
Ну вот видишь ты throw new Exception, где он обработается? С return сразу будет видно.
Это несущественный код только для тех, кто привык писать ненадёжную скриптуху, ломающуюся от каждого пинка.
Один catch это гораздо лучше, чем миллиард if-ов, спасибо за аргумент в мою пользу.
Ну представь, что этот блок ловит исключения из 15 вызовов. По тексту кода вообще не видно что может пойти не так.
Захочешь добавить новый код и не поймёшь, правильно ли сейчас обработается твоё исключение одним общим блоком.
В общем, лень ≠ простота.
Или defer, ага.
defer — конструкция общего назначения, не обязательно для закрытия ресурсов. А try with resources — магия, которую добавили после провального try-finally.
Разумеется, не на Луне. Только найти это место не очень удобно. По иерархии вызовов и типов придётся попрыгать и не ошибиться.
Нет, не видно.
Как это не видно? Обработка происходит в 99% случаев первым if-ом.
Это не важно. Важно, что идёт так.
То есть работа с ошибками не важна. Ну тогда мой аргумент про «ненадёжную скриптуху» в силе. Только непонятно что ей делать в энтерпрайзной Джаве или Шарпе.
Чего это не поймёшь?
Представь, что обработка ошибки отправляет какое-то межсервисное событие. Оно нужно для нового кода? Хз.
try-finally
Который превращает код в лапшу, если, например, закрытие неоткрытых ресурсов запрещено. Поэтому и был придуман try with resources.
[try with resources] … не помешал в Go, кстати говоря.
Зачем? Я ни разу не видел, чтобы defer плохо справлялся с очисткой ресурсов. Есть пример?
Разумеется, не на Луне. Только найти это место не очень удобно. По иерархии вызовов и типов придётся попрыгать и не ошибиться.
И на Go придётся. Потому, что в 99% случаев там будет if err := bla(); err != nil { return err; } и это в хорошей программе, а в плохой вообще ничего не будет, лол.
Как это не видно? Обработка происходит в 99% случаев первым if-ом.
Нет, обработка происходит в 99% случаев где-то сверху по стеку.
То есть работа с ошибками не важна.
Конечно же работа с ошибками важна. Но не как в Go. А централизованно. Что позволяет гарантировать корректную работу с ошибками. А не как в го - проверил - хорошо. Не проверил - всем пофиг.
Поэтому
Ну тогда мой аргумент про «ненадёжную скриптуху» в силе. Только непонятно что ей делать в энтерпрайзной Джаве или Шарпе.
Ненадёжная скриптуха это как раз Go. Ибо там, чтобы обработать ошибку хоть как-то, нужно прилагать усилия. Примерно как в баше, лол. А в Java нужно прилагать усилия, чтобы НЕ обработать ошибку. Что автоматом повышает надёжность программы на порядок.
Про то, что информативность стектрейса на порядок выше какой-то рандомной строчки в Go - я даже не говорю.
Исключения для исключительных ситуаций. Они в принципе не должны использоваться. Использовать исключения для обработки ошибок – всё равно, что сжигать свой дом, для выведения тараканов.
Обработка ошибок в Гошке лучше всего. Не говно-errno, не исключения, не игнорирование.
И на Go придётся. Потому, что в 99% случаев там будет if err := bla(); err != nil { return err; } и это в хорошей программе, а в плохой вообще ничего не будет, лол.
Есть ветвление. И оно есть явно. В чём пробелма? Ошибка никогда не возникает сама по себе. И у неё есть контекст. Вместо говнища вида failed something и километровой дрисни из стека, как в Яве. Ошибка возникает и ей даётся контекст. Поэтому в реальности это выглядит так
if err != nil {
return fmt.Errorf("crap at %q with %d: %v", a, b, err)
}
В принципе – единственный способ уйти от ошибок вида 0x8394832 Broken unknown crap 2. И километровой дирсни из раскрученного стека.
А раскрутка стека на каждый чих – это вообще нечто.
Конечно же работа с ошибками важна. Но не как в Go. А централизованно. Что позволяет гарантировать корректную работу с ошибками. А не как в го - проверил - хорошо. Не проверил - всем пофиг.
В случае ошибок го паникует. Го не будет терпеть попытку записи в закрытый файл, деление на ноль или разыменование нулевого указателя. С другой стороны, го спокойненько возвращает дефолтное значение при чтении несуществующего значения из хеш-таблицы, то есть в философии го это вообще не ошибка и не исключение. И эта философия неплохо работает на практике.
Хлам – это твоё мнение. Всегда думал, что go чисто утилитарный язык, а не язык фанатиков.
Утилитарное – это мнение говнохранителей, которым не нравится Го. Ввиду лишь только того, что из него выкинули всё говно.
Ну да, как Вам может показаться, что без говна он перестал быть тёплым и мягким. Но в этом и есть его фишка. Так что проходите мимо. Не задерживайтесь.
Настолько логично и понятно, что это основной вопрос на собесах. Ссылаться на Си, объясняя косяк в современном языке, ну такое. И в Си как раз такого не будет, там всё логичнее, так как крнтроля за памятью никто не обещал.
Настолько логично и понятно, что это основной вопрос на собесах.
Так на собесах все базовое спрашивают, на то они и собеседования. :)
Ссылаться на Си, объясняя косяк в современном языке, ну такое. И в Си как раз такого не будет, там всё логичнее, так как крнтроля за памятью никто не обещал.
Я тебе пытаюсь показать, что это не косяк а Go это развитие C а не каких-то там питонов. Go это C с GC. Авторы сами говорили, что Go это была замена для C++.
Дай погадаю. Ты бывший пхпшник :) Отсюда и комплексы.
Ожидаемо мимо. Видеть очевидное теперь комплекс? Не знал. Это в кругах кого, если не секрет? Говнохранителей?
Мир не стоит на месте. Примите уже это. Всё что Вы знаете устаревает со скоростью минута в минуту. Если лично Вы перестали успевать, то в этом никто не виноват. Никто, кроме Вас.
Go - делать достаточно быстрые бинарники так, чтобы с рынка легко хантились разработчики, которые смогут это поддерживать. Требование доступности разработчиков на рынке здесь прямо основное. Язык ограничен в сложности как раз для этого, а компиляется в бинарник для скорости. Заточен под написание бизнес-логих в бигтехах: яндексы, озоны, VK, гуглы. Когда надо налабать быстро некую новую систему и чтобы оно было машинным кодом, но при этом чтобы исходник выглядел не как жопа на C++. Управление памятью вынесли в GC, как раз чтобы эти жопошники изобретатели аллокаторов в C++ пошли лесом и не плодили костылей.
C++ - делать максимально упорото производительные бинарники, где разраб должен думать про всё, максимальный контроль человека. Иногда это надо. Это типа как армейское оборудование и вооружение на рынке. Допускают только профессионалов, хантить их тяжелее, можно оторвать себе кусок жопы неудачным выстрелом и расхерачить пол-страны и потерять все полимеры. Относятся с опаской.
Rust - когда не хочется GC, хочется приблизиться к C++, но при этом гарантировать на этапе компиляции отсутствие дедлоков, проездов по памяти чужого массива или запись по указателю, по которому не планировалось записывать или разлочивание того, что не планировалось разлочивать и т.п. В некоторых вещах C++ обогнать не получается, потому что некоторые проверки в Rust неизбежно делаются в рантайме за счёт CPU, а не компилятора (всякие там if-ы на границы массива). Писать на Rust сложно, потому что тебя постоянно бьют по башке палкой, буквально выясняется что как ты раньше писал - это в корне ошибочно на каждом шагу и нужно больше точности и определённости. Но у Rust есть злые враги - системы формальной верификации программ: для любой программы на голом Си можно описать некий «Образ» на специальном языке (их штуки 3 видел, забыл уже), а потом специальная машина доказывает, что софтина формально корректна и ей негде падать. Вроде бы таким макаром делают микроядра гипервизоров, которые внутри айфона сверху надзирают над iOS чтобы когда хакеры получили root всё равно не смогли доступ к драйверу модема получить.
TypeScript - довольно неплохая штука: буквально статически типизируемый JS с дополнительными свистоперделками, выглядит достаточно неплохо, строго и не сложно. Транслируется в JS. JS умирать не собирается, глубоко укоренился в браузерах, выполняется тоже быстро, потому что компиляется в машинный код JIT-ом.
На самом деле, Го, один из самых (если не самый) продуманный и целостный язык.
Пайк, конечно, один из самых крутых программистов современности, но в желании сделать просто, он завёл го куда-то не туда. На универсальный язык он явно не тянет.
Все таки, фокус был не на универсальности, а создать язык для современных сетевых сервисов. Заменить в этой нише C++ в Гугле. Но, судя по тому, что на нем пишут от консольниых утилит и до высоконагруженных сетевых сервисов, и СУБД язык получился достаточно универсальным.
Я, как и авторы языка, не против дженериков. Мнение, что их добавили злые манагеры — конспирология. Их добавили Иэн Лэнс Тейлор (ключевая фигура в команде) и Роберт Грисмер (один из авторов языка).
В Go, конечно, тоже придётся попрыгать. И return, и throw – похожие механизмы прерывания потока выполнения. Но в случае с Go тебе достаточно подняться на один уровень вверх и ты уже стоишь на строке, которая пробрасывает или обрабатывает ошибку. “return fmt.Errorf” — это тоже полезная информация, сразу сообщающая «не здесь». В Java недостаточно подняться на один уровень вверх, придётся ещё проверить список throws, окружающий try {} catch блок, который дополнительно зависит от иерархии типов. Нет локальности проброса как лексического понятия, то есть возможности понять поведение кода, глядя только на текущую функцию и её непосредственное окружение, а имеющаяся лексическая информация вроде throws неполна.
Исключения изначально были созданы для обработки исключительных ситуаций. Именно поэтому им позволено так легко ломать поток выполнения. Но не все ошибки должны быть исключительными. Нет ничего исключительного, достойного сказать «не моя проблема», если, например, файл вдруг не открылся. Java попыталась адаптировать исключения, сделав их более контролируемыми, но тем самым смешала два принципиально разных понятия в одну неразличимую кучу. (Unchecked исключения делают проблему ещё острее.)
А централизованно.
Например, ловим мы IOException. Его может выбросить бд, устанавливающая соединение через соккет, или запись в логи. При записи в логи не нужно прерывать работу метода. Это не редкость. И в чём преимущество централизации?
Что позволяет гарантировать корректную работу с ошибками. А не как в го - проверил - хорошо. Не проверил - всем пофиг.
Да, Java даёт определённые гарантии, но при этом поощряет перекладывание исключения в throws. Учитывая то, кто каллером может быть потенциально кто угодно, всегда есть надежда, что где-то в глубине есть могучий Атлант try-catch, который точно знает что делать с твоим исключением.
Но и в Go тоже есть гарантии. Ты не можешь просто написать data, err := hello(), не обработав err (компилятор запретит). Как раз нужно приложить усилие, чтобы НЕ обработать err — написать странную конструкцию data, _ := hello(). Так делают, но крайне редко, в проде почти никогда, потому что компилятор постоянно сталкивает программиста лбом с ошибками.
(Функции, возвращающие ошибку как один аргумент — меньшинство, но да, это недостаток. Однако, здесь мы переходим от темы про исключения к теме про result.)
Примерно как в баше, лол.
Очень большое преувеличение. В Go ошибки – это значения. Соответственно, улучшая работу со значениями (например, запретом на неиспользуемые переменные), мы улучшаем обработку ошибок. В баше ошибки – коды возврата, то есть протекающая абстракция ОС, которую мы можем получить магической переменной $?. Функции в шелле не возвращают ошибки как значения (строго говоря, функция в шелле вообще ничего не «возвращает» в смысле обычных языков).
информативность стектрейса на порядок выше какой-то рандомной строчки в Go
Никто не запрещает захватывать стек-трейс в Go. Никто, вроде, не считает это неправильным.
Не спорю с тем, что в Шарпе сделали хорошо. Но у подхода Го тоже есть своё достоинство — единая абстракция, покрывающая все нужды и предлагающая идеальный баланс между удобством, простотой модели и производительностью. Неявное копирование можно потерпеть, оно на практике нечасто приводит к проблемам.
С этим согласен, если рассматривать go как платформу целиком, особенно для создания сетевых сервисов и системных утилит, то у него практически нет альтернатив.