Comments 49
Парадигмы можно не менять, просто создавая ещё один контейнер. В одном думаем на одном языке, в другом на втором. И так далее. Изоляции полной не получится, поэтому положительное из одного захочется притащить в другие, при наличии возможности.
Оказалось, это важная идея самого языка, которая связана с тем, что интерфейсы принято объявлять в месте использования, а не реализации.
Это ключевая идея интерфейса в принципе. Либо shared contract, либо объявление в месте использования. Актуально для любого языка.
В Java мы складываем рядом в одном пакете интерфейсы (например,
PaymentGateway
) и их реализации (например,StripePaymentGateway
илиPayPalPaymentGateway
).
Чур меня!
Пример 2: транзакции.
Скрытый текст
@Transactional
public void processPayment(Payment p) {
try {
accountService.debit(p);
journalRepository.save(p);
mq.send(new PaymentMessage(p));
} catch (Exception e) {
throw new PaymentFailed("processing error", e);
}
}
Будьте осторожны с такой обработкой ошибок. Передали Person p is nil
и получили PaymentFailed
= сомнительно. Ошибки разработчика должны быть либо 500, либо хотя бы залогировать оригинальную ошибку явно, перед тем как возвращать такую обертку. Актуально для любого языка.
Аннотации делают магию. Но… в какой-то момент у меня стало периодически возникать ощущение, что магии слишком много.
Много магии тем более опасно, чем более бездумно разработчики на нее полагаются. Для неумелого волшебника любое заклинание может быть фатальным.
Вместо аннотаций есть "теги", ... Но, так как теги не имеют своей логики ... Затем передать структуру в какую-то функцию, которая извлечет эти теги и сделает что-то, основываясь на их содержании
Вы уж определитесь. По факту, те же яйца, только в профиль.
В общем это как Orbit и Dirol. Какая разница, что жевать 5 минут для попытки замоскировать несвежее дыхание, если через 10 все равно все станет ясно? Так что не жуйте жевачки, а чистите зубы, делайте профигиену, лечите кариес и вообще питайтесь сбалансированно. А от смены марки жевачки эффекта не будет.
У вас странное мнение об аннотациях в джаве. У них там тоже никакой собственно логики нет почти никогда. Тоже нужен обработчик, который аннотацию найдет и что-то сделает. Если в спринге не подключен соответствующий бин, то @Transactional
это просто текст.
Я хорошо знаком с JVM, но не знаком с Go. И до этого был наслышан про ужасы этого языка, но до сих пор не могу понять, почему там можно неявно реализовывать интерфейсы, но все так боятся любой другой магии...
Про методы структур не понял. Для меня, как для не знакомого с Go человека, синтаксически они выглядят как внешние функции, которые можно вызывать как будто бы это методы. Понятно, что если 1ый аргумент -- не указатель, то null туда не передать, и так же понятно, что с указателями этот синтаксический сахар прекрасно работает даже если они null.
В примере с символами вы явно потеряли пробел во всех подсчётах
Вывод с go playground
s := "Привет 🌍😊" // 8 chars, 20 bytes
fmt.Println(len(s)) // 20 🔢 (bytes)
fmt.Println(len([]rune(s))) // 8 🔣 (chars)
fmt.Println([]rune(s)) // [1055 1088 1080 1074 1077 1090 127757 128522]
21
9
[1055 1088 1080 1074 1077 1090 32 127757 128522]
Ну и вообще, если вы хотите изучить Go, рекомендую ещё раз подумать и изучить Kotlin). После Java/C# всем отлично заходит
На Котлин вакансий мало.
А так я бы выучил.
Знаю Шарп, Раст и другие матерные слова
За пробел спасибо, убрал его.
Я его при переносе из редактора на автомате добавил, а цифры обновить забыл
Про язык - а для чего учить Kotlin? У меня ассоциация, что Kotlin - это менее популярная Java со всеми теми же плюсами и минусами (как минимум, основными)
Причём на нем меньше вакансий для меня как разработчика. И меньше кандидатов для меня как тимлида... Насколько я понимаю, основная сфера kotlin - это разработка под Android. А для бека он так и не занял существенную долю рынка, разве нет?
Под android сейчас как раз везде kotlin или kotlin в перемешку с java, это действительно основная сфера. На стыке с ios тоже есть. На беке с вакансиями хуже. Там, пожалуй, с go действительно найти команду значительно проще (я сам с мобилками в основном работал). Хотя когда мне нужно какой-то свой сервер написать, я просто пишу его на kTor: удобно и код с фронтом можно пошарить, если понадобится.
Про менее популярную java с теми же плюсами и минусами не соглашусь.
В котлине поправили многословность джавы, навалили синтаксический сахар на конструкторы и сеттеры/геттеры
Разобрались с NPE: там возможность предать null описывается на уровне типов (
String?
-- это как String в джаве, аString
-- это точно не null). Без проверки на null методы класса просто не вызвать, это отсекает кучу ошибок ещё на компиляции. Хотя проверка может выглядеть какstr!!.length()
-- тут может вылететь NPE как в дажаве, но таких мест в коде обычно не очень много. Немного костыльно, но по сравнению с Java/(старым) C# -- очень приятноДобавили функции и свойства-расширения: они объявляются вне класса, но синтаксически используются как будто бы они члены класса
Ввели suspend-функции -- это штука, на которой работают корутины (в go они называются горутины) и генераторы как в питоне. И ещё глубокая рекурсия, но этого в реальном коде я не встречал
В котлине удобно писать DSL. Ну верней скорее удобно использовать, а удобство письма зависит от того, как много ошибок вы хотите проверить на этапе компиляции
В общем, плюсов у него много. Минусы тоже есть, в основном, наверное, действительно общие с джавой, потому что они появляются из-за JVM. Но люди, перешедшие с джавы на котлин, обратно мягко говоря не рвутся. Возможно, у меня перекос из-за сферы работы, но проектов чисто на джаве без котлина я очень давно не видел
Ну и вообще, если вы хотите изучить Go, рекомендую ещё раз подумать и изучить Kotlin).
Kotlin прикольный. Довелось на нем проект написать. Но это по сути джавазаменитель. Да добавили null-безопасность(на которую библиотеки кладут прибор), да более приятный синтаксис и прикольные фичи но... Никаких новых идей или ультимативных киллер-фич, которые бы помогли ответить на вопрос - зачем брать этот язык вместо Java.
Взять тот же Go. Он набрал популярность из-за определенных фич: минималистичность, позволяющая нанимать не только гоферов но и всяких джавистов и очень быстро их переучивать, производительность, решение проблемы асинхронных операций из коробки, на выходе простой бинарник без необходимости морочить голову виртуальными машинами. Это аргументы. А что предлагает Kotlin? Null-safe? Этого мало. Корутины? Так они и так везде есть, так сейчас еще в Java завезли виртуальные потоки. Красивый и удобный синтаксис? Не самый сильный аргумент. Возможность писать все на Kotlin: фронтенд, мобилки, бэкенд и т.д? Хорошая идея, но не взлетело. В немалой степени еще проблема что Kotlin узник JVM и в огромном числе проблема он просто не может перегнать Java. Удел Kotlin по идее - заменить Java, но не смотря не очень существенные успехи, тоже не взлетело в той мере в которой хотелось бы.
По итогу остается послевкусие. Язык то выучен полноценный и время на это потрачено но вот существенной пользы от этого мало.
После Java/C# всем отлично заходит
По джаве да, но и то не всем. По C# большие сомнения. C#/.net с его экосистемой настолько круче Kotlin практически во всем, что я даже не знаю какие мотивы должны быть у человека для того чтобы ему это зашло.
Почему не раскрыта тема, что го отличается от Java в применении ООП
Очень рекомендую заиметь привычку инициализировать структуры через функции New. Даже если это выглядит так:
func NewBillingService() *BillingService {
return &BillingService{}
}
Так вы имеете конструктор, в который всегда можно добавить параметры.
В целом совет хороший, но использовать такое тоже стоит не везде (в примере из статьи - стоит). Там, где есть хотя бы теоретическая возможность совершить нечаянную ошибку при инициализации - стоит. Там, где нужно заложить возможность будущего развития и не хочется заранее давать гарантию, что zero-значение структуры это корректное значение данного типа - тоже стоит. Но в остальных случаях лучше поступать ровно наоборот: приложить немного дополнительных усилий для того, чтобы zero-значение поддерживалось как корректное. Это как бы штатная best practice.
Интересно посмотреть мнение со стороны :)
В основном пишу на java/kotlin, но время от времени пишу что-то небольшое на go.
Помимо моментов в статье могу отметить момент с generic'ами (функции структуры не могут принимать не объявленные в сигнатуре generic'и, но жить можно), а также перегрузку методов (тут мне порой прям больно :) )
Прочитал статью и так и не понял кто заставляет автора писать в джаве аннотации вместо кода.
проверяемые исключения вас компилятор заставит обработать (если не гасить их, разумеется). А в го нужно весь код обмазывать проверками ? Лучше бы тогда сделали монаду either.
NPE можно поймать в 2 случаях: ты джун и возвращаешь из методов null-ы или ты используешь чужую либу, в которой такой возврат не задокументирован.
NPE можно поймать в 2 случаях: ты джун и возвращаешь из методов null-ы или ты используешь чужую либу, в которой такой возврат не задокументирован.
Вы наверно очень внимательный и дотошный. Или дальше hello word не заходили.
Да нет, поддержу вышесказанное. На null чаще всего жалуются те же люди, что через раз пишут return null. И это как бы тоже выбор, и не всегда плохой, но есть вагон альтернатив, начиная от использования корректных значений по-умолчанию.
в случае ошибки в Java коде, нам нужно будет смотреть в портянку stack trace и пытаться понять, какая из строчек выдала ошибку.
В Go ошибка сразу покажет, где именно и что сломалось
При всех минусах исключений, вот как раз в место нахождения ошибки там полный порядок. В Go все очень сильно зависит от того насколько удачно ошибка написано. Если вам вернется "number is incorrect" - удачи по всему проекту искать хотя бы примерно откуда это прилетело.
опции не обратить внимание на ошибку — у нас нет
Пытаемся удалить файл которого нет:
filename := "./1.txt"
err := os.Remove(filename)
if err != nil {
log.Fatal(err)
}
fmt.Printf("File %s is removed", filename)
...
2025/06/23 09:32:05 remove ./1.txt: The system cannot find the file specified.
exit status 1
А что будет если разработчик просто забудет или не увидит что функция возвращает ошибку?
filename := "./1.txt"
os.Remove(filename)
fmt.Printf("File %s is removed", filename)
...
File ./1.txt is removed
В Java строка представляет собой неизменяемую последовательность
char
.
Уже давно, это последовательность байт а не char.
В Go тип
string
— это неизменяемый срезбайтов
(UTF-8 по умолчанию).
It’s important to state right up front that a string holds arbitrary bytes. It is not required to hold Unicode text, UTF-8 text, or any other predefined format. As far as the content of a string is concerned, it is exactly equivalent to a slice of bytes. Rob Pike.
"А что будет если разработчик просто забудет или не увидит что функция возвращает ошибку?"
Я, видимо, слишком привык жить с линтерами, которые не дают так делать...
ide подсветит визуально и на пред коммите, потом линтер упадет, потом тест упадет
ну и те кто пишут на го не забывают такого, плюс копилот сам дополнит обработку
Если вам вернется "number is incorrect" - удачи по всему проекту искать хотя бы примерно откуда это прилетело.
В Го принято ошибки врапать на каждом уровне вызова. Поэтому даже если какая-то функция ошибку плохо описала, то по функциям выше по стеку будет понятно, откуда она.
Говорят, мир где принято делать все правильно и реальные мир иногда не одно и тоже
В Го принято ошибки врапать на каждом уровне вызова
number is incorrect: number is incorrect: number is incorrect
С другой, в случае ошибки в Java коде, нам нужно будет смотреть в портянку stack trace и пытаться понять, какая из строчек выдала ошибку.
что значит пытаться? вы не знаете как читать стек трейсы? И кстати именно “трейс” вам может сообщить очень много касательно условий возникновения ошибки, а не просто “еррор, транзакция упала”, а что это за транзакция, кто ее вызвал?...
короче вкратце про go: несколько удачных идей и очень много неудачных. на сколько я понимаю это во много особенности платформы на которой он запускается (как бы натив), но авторы и комьюнити зачем-то запустили оголтелый маркетинг что “так и надо! что это не баг а фича!”
Главное, мне кажется, это исключения и дженерики. По обработке ошибок в го не топтался только ленивый, а куцые дженерики меня до сих пор напрягают - если в джаве можно объявить один дженерик у типа, а другой дженерик у метода, то в го так нельзя, будет ошибка компиляции.
defer tx.Rollback()
@transactional, а также try with resources позволяет одной строкой сделать два действия, открыть и закрыть.
Разбиение этого кода на две строки даёт возможность совершить ошибку.
Такие ошибки может быть сложно поймать, так как многие вещи всё же закроются самостоятельно при выходе из программы.
Мне такой подход в go не нравится.
Хорошо написано. Про разыменовывание успел позабыть, потому что всегда начеку с ссылками.
Выскажу свое мнение про Go...
Для начала скажу что писать на языке в 2025 году, где нет типовых операция типа filter/map/reduce и прочее это очень больно. Возьмем простой пример где у нас есть коллекция юзеров и нам над отфильтровать их например по росту, а потом сгруппировать по нему же. Как это будет выглядеть примерно выглядеть на JS/C#/Java/Kotlin/Rust и пр.:
users.filter { it.height > 100 }.groupBy { it.height }
А как это будет выглядеть на Go?
Я не готов набирать столько текста и тем более читать на экране столько портянок с кастомной фильтрацией и группировкой. По моему это никак не вяжется с вашим первым принципом наглядностью. Где каждый будет реализовывать эти операции по своему, а джуны еще наверняка и сделают типовые ошибки для новичков при группировке, забыв проинициализировать маппу для группировки пустой коллекцией и словят NPE.
И я не могу понять как с этим можно смириться и как это может нравиться - писать стоько шаблонного кода.
По моему мнению основное приемущество Go о котором тут не сказано, это хорошая утилизация ресурсов, за счет его модели многопоточности из коробки, что особенно актуально в текщих реалиях в облачных платформах для деплоя сервисов. А также компиляция в нативный машинный код, что в целом положительно сказываетс на производительности и быстром старте приложения, что опять сейчас очень актуально.
Но опять же в 2025 году, это уже не является чем то уникальным, в отличии от 2015 года)
А как это будет выглядеть на Go?
Как-то так (на import u "github.com/rjNemo/underscore"
):
u.GroupBy(u.Filter(users, func(u User) bool { return u.height > 170 }), func(u User) int { return u.height })
Но в реальном коде, конечно, это будет записано иначе:
tallUser := func(u User) bool { return u.height > 170 }
userHeight := func(u User) int { return u.height }
u.GroupBy(u.Filter(users, tallUser), userHeight)
По сути вся "дикая разница" сводится к тому, что в Go нет возможности опустить func(u User) bool
и func(u User) int
.
Хорошо, что в Golang есть библиотеки которые решают данные проблемы.
Но перед тем как постить свой пост, я интереса ради поинтересовался у знакомых гофферов... и ответ был одинаков - у нас так не принято, все пишем руками или создаем утилитные функции.
И убедился что в стандартном golang sdk таких функций нет.
Вообще такую фразу "у нас так в го не принято" я слышал много где) да и видел код реальных проектов Golang на гитхабе, которые пестрят разными утилитными файлами и хелперами, которые как я понимаю кочуют из проекта в проект копипастой и код похож на старый добрый Pascal.
Я конечно могу ошибаться, поправте меня если я не прав, но те примеры которые вы скинули, они создают после каждой операции новый массив, и как я понял такого понятия как стримминг элементов коллекции в go нет, в отличии от скажем c#/java/kotlin, что сильно нагружает GC.
Но я не хочу сказать что go плохой, каждый язык призван для решения своей задачи, как я написал выше сильная сторона go это хорошая утилизация ресурсов, иначе его бы не выбирали скажем крупные маркетплейсы. И в место того чтобы появлялись статьи которые рассказывали про то как мы после перехода на go сэкномили на железе и прочих ресурсов в датацентрах например, регулярно появляются статься как я перешел на go c c#/java/kotlin и пр. и мне нравится набирать "километры" кода и читать их. Извините, но я не могу в это поверить, по синтаксису он не так хорош как языки что я привел выше и на которых я имею опыт.
ответ был одинаков - у нас так не принято
В общем и целом - это правда. Более того, использованную мной в примерах библиотеку github.com/rjNemo/underscore
я впервые увидел когда подбирал пример для ответа. :) Это не значит, что до этого я такими библиотеками не пользовался вообще - пользовался, но другой (github.com/samber/lo
- она на 2 порядка популярнее, пример с ней тоже есть в playground, я с него начал, но он получился немного длиннее ожидаемого, поэтому я полез искать альтернативы).
Тем не менее: а Вы не задумывались, а почему "не принято"? Не спрашивали знакомых гоферов? Неужели все гоферы мазохисты и любят писать кучу лишнего кода?
Я лично считаю, что "не принято" потому, что этот инструмент чуть менее доступен (чем в языках, где он встроен в синтаксис или является частью стандартной библиотеки), и поэтому не используется бездумно где надо и где не надо, а применяется только в тех случаях, когда он заметно улучшает читабельность. И таких случаев на практике оказывается достаточно мало (ну, по крайней мере в типовых Go-шных проектах вроде утилит и бэкенда), чтобы это оправдывало затягивание в проект ещё одной зависимости и использование не совсем типичного для Go стиля записи логики. Ради того, чтобы в одном месте в коде написать такой функциональный конвейер никто в проект это затягивать не станет - а проектов где такое было бы удобно в большом количестве мест действительно мало.
те примеры которые вы скинули, они создают после каждой операции новый массив
Там разное есть, включая и ровно так же выглядящие конвейеры которые обрабатывают элементы параллельно используя группу горутин и стриминг через каналы без параллельной обработки. Но в целом - да, создают новый массив. Часто это не проблема (основной объём занимают сами элементы, а в массиве лежат только ссылки, так что если в самом массиве не десятки миллионов элементов то новый массив заметных проблем не создаёт). Где проблема - очень легко написать такие же хелперы использующие заранее выделенные массивы (в т.ч. берущие их из пула и возвращающие в него после использования). Ну или пойти по пути "не принято" и в том единственном месте где массивы очень большие и при этом требовательные к скорости обработки написать цикл ручками - помимо прочего это позволит объединить filter и group и выполнять обе операции за один проход.
сильная сторона go это хорошая утилизация ресурсов
Нет, сильная сторона - это как раз ясность и читабельность кода. Да-да-да, внезапно оказалось, что очень простой язык намного проще читать, не смотря на отсутствие синтаксического сахара позволяющего в очень "краткую и наглядную" запись (вроде Вашего примера) и не смотря на "избыточную и многословную" обработку ошибок и кучу boilerplate кода. Такой вот парадокс!
И в место того чтобы появлялись статьи которые рассказывали про то как мы после перехода на go сэкномили на железе и прочих ресурсов в датацентрах например, регулярно появляются статься как я перешел на go c c#/java/kotlin и пр. и мне нравится набирать "километры" кода и читать их.
Это всё правда. Никто не будет писать статьи про то, как ему нравится набирать и читать лишние километры кода - поэтому пишут о другом. Конечно, никто не любит ни набирать лишний и абсолютно типовой код ни читать его. Любить это - не любят. Но по факту - читать такой код действительно намного легче. Но это не значит, что это будут любить - гошники все годы существования языка ноют на эту тему и просят сделать обработку ошибок как-то иначе.
Забавный факт: после рассмотрения сотен идей и предложений как именно стоит изменить обработку ошибок команда Go спустя 15 лет попыток что-то с этим сделать приняла решение… перестать пытаться! :) Случилось это буквально на днях - пару месяцев назад: https://go.dev/blog/error-syntax. И ключевая причина в том, что до сих пор неясно для чего конкретно нужно изменить обработку ошибок: чтобы стало легче читать/писать логику обработки ошибок, или чтобы стало проще НЕ писать эту логику. Если проблема в первом, то команда Go предлагает упростить написание типовых конструкций использованием сниппетов в IDE, и упростить их чтение используя чисто визуальные фичи IDE (позволяющие "свернуть" или ещё как-то сокращённо отображать типовые блоки обработки ошибок). А если во втором - то нет, это в Go никто затаскивать не станет принципиально.
Иными словами, не смотря на то, что никому не нравится ни писать ни читать такой код - он достаточно полезен и необходим, чтобы ничего с этим (на уровне языка) не делать. И лично я с этой позицией полностью согласен.
Извините, но я не могу в это поверить, по синтаксису он не так хорош как языки что я привел выше и на которых я имею опыт.
Go прекрасен по синтаксису! То качество, которое Вы подразумеваете, не находится в категориях хорош/плох. Вы говорите о возможности языка кратко описывать сложные операции. У этой возможности помимо плюсов есть и очень значительные минусы: от потери контроля над производительностью (потому что неясно что там внутри происходит) до усложнения читабельности вплоть до превращения языка в "write-only language" (я 20 лет писал на Perl и хорошо представляю, как это выглядит на практике).
Я не против синтаксического сахара. Я очень даже "за". И мне его в Go не хватает. НО. У него есть цена, и я не уверен, что готов её заплатить в Go. Ясный, простой и легко понимаемый код (не только в плане логики, но и в плане того как это будет работать "на железе") - это достаточно большое достоинство, и я не уверен, что добавление синтаксического сахара сделает язык лучше, а не хуже. Иными словами, есть языки, которые лишним сахаром уже не испортишь, но есть и другие языки. :)
Заранее извиняюсь, но продолжим душную дискуссию)
Не спрашивали знакомых гоферов? Неужели все гоферы мазохисты и любят писать кучу лишнего кода?
Не поверите, спрашивал. Добавлю контекста - это два моих давнишних институтских друга с которыми вместе закончили вуз в 2008г. Сейчас они оба работают го-девелоперами, в достаточно крупных компаниях. Мнения разделились - один ничего не хочет слышать и го просто самый лучший язык, и рассказывает и показывает ихние утилитные файлы которые они копируют из проекта в проект, что у меня кроме смеха ничего не вызывает. Второй признает проблему, мы кстати с ним вместе ранее работали в одном из цветных банков на jvm стеке. Так вот он признает проблему и говорит, что ему этого не хватает, но го ему все равно очень нравится) И также обронил фразу что простота го обманчива (об этом я далее напишу) Когда я попросил его написать пример из моего первого поста, он сделал простой цикл с мапой) ну это так к слову…
Также сошлюсь еще на видео с Кириллом Макевниным (создателем Хекслет, кстати его видео про ментальное программирование сильно на меня повлияли как мне думается в лучшую сторону) где он задает похожие вопросы гоферу и получает похожие ответы, что так принято, а толком почему не говорит. Но гофер в видео также отмечает что ему всеже не хватает стандартной библиотеки. (Видео давнишнее и смотрел я его давно, а не искал специально чтобы Вам ответить, там они затрагивают еще момент с дженериками, но сейчас это уже не так актуально)
И таких случаев на практике оказывается достаточно мало (ну, по крайней мере в типовых Go-шных проектах вроде утилит и бэкенда)
Я вот в бытность бэкендером регулярно использовю Stream API java/kotlin и почти любая операция с коллекциями это обращение к ним, хотя может быть проблема во мне) Да и весь код который я вижу по работе и не только - аналогично.
Нет, сильная сторона - это как раз ясность и читабельность кода.
Опять же, я не был бы столь категоричен, т.к. на многих докладах/конференциях отмечается, что реализация многопоточности в го из коробки на “зеленых” тредах, да к тому же еще в 2010 году, является во многом эталонной и не приводит к “раскраске” кода async/await и как следствие хорошо утилизирует ресурсы. В идеале конечно хотелось бы увидеть статью от кого то из крупных маркетплейсов или соц.сетей почему они выбрали го, и мне кажется не в последнюю очередь из за ресурсов, купить 1000 или 1100 серверов, как по мне большая разница в текущих реалиях.
Я не против синтаксического сахара. Я очень даже "за". И мне его в Go не хватает. НО. У него есть цена, и я не уверен, что готов её заплатить в Go
Я же не призываю сделать из go kotlin) Я и сам считаю что kotlin даже в чем то переборщил, вот например в данный момент я переписываю kotlin код за молодыми и дерзкими, где использовано все что только можно из языка и лямбда в лямбде и лямбдой погоняет) И для командной разработки backend если бы я сейчас выбирал язык то остановился бы на java, как нечто среднем.
Но конструкции типа filter/map/reduce уже настолько прочно вошли в текущую жизнь, и уже так хорошо изучены и отлажены, что даже в java(которую часто ругают за медленное развитие) уже существуют более 10 лет, то странно ждать там подвоха. Тот же underscore помню использовали на фронте еще в 2010, когда в JS еще не завезли этих операций. А го получается вообще как будто голый язык, где грубо говоря нет ничего кроме циклов, условий и структур. Поэтому я и сравнил его с Pascal и го-код мне очень напоминает институтские лабы на Pascal) Я не говорю что надо добавить все, но такие базовые вещи очень напрашиваются. И основная стезя го это всеже не геймдев/железо/операционки где производительность особенно важна.
Go прекрасен по синтаксису! То качество, которое Вы подразумеваете, не находится в категориях хорош/плох
И проблема тут несколько глубже и не в том что надо писать лишний код и его читать. Это также вероятность допустить ошибку в том примере который я привел, т.к. значения мапы надо будет проиницировать пустым списком прежде чем добавлять элементы, иначе - привет NPE. Это также дополнительное время на отладку. Вот для примера: посадите 10 разных человек написать ту задачу что я привел, на го Вы с большей вероятностью увидете большее разнообразие решений, с большей вероятностью допустить ошибку, тогда как на почти любом языке где есть эти операции, код будет более стандартизованным и вероятность ошибки будет меньше.
Про простоту го, которая обманчива, как выразился мой второй друг. Получается что го очень простой, но простой с точки зрения компилятора, т.к. мало ключевых слов и конструкций по сравнению с другими языками (и по моему тернарного оператора там тоже нет… ), из за этого каждый будет “рисовать так как он видит” и мы увидим больше разнообразие решений, одних и тех же типовых задач - что на мой взгляд не есть хорошо. И когда я вижу типовой код на стримах я сразу улавливаю суть, в то время как с го приходить больше вчитываться в код. Те же исходники kubernetes уже давно стали притчей во языцех.
Я вот в бытность бэкендером регулярно использовю Stream API java/kotlin и почти любая операция с коллекциями это обращение к ним, хотя может быть проблема во мне)
А откуда берутся эти коллекции на бэке? В большинстве моих проектов коллекции лежали в БД, операции фильтрации/группировки для них были реализованы в SQL, и в результате Go-шному коду обычно уже особо нечего фильтровать и группировать. И да, в этих проектах не было ORM, разумеется - подозреваю что в Вашем коде основным источником коллекций может быть он.
И основная стезя го это всеже не геймдев/железо/операционки где производительность особенно важна.
Это не совсем верно. У требований к производительности есть несколько уровней:
RTOS (где нужно чёткое понимание за какое время выполняется конкретный код и случайные отклонения от него в принципе неприемлемы),
OS (где неприемлемы внезапные неопределённые задержки - вроде те, которые может давать GC),
высокопроизводительных приложений (где от производительности зависит в т.ч. упомянутое Вами количество серверов),
десктопных приложений (где плавность UI важнее реальной производительности приложения),
всего остального.
Go в этом списке посередине. И хотя он действительно не на первых двух уровнях, но на его уровне всё ещё актуален ручной контроль за выделениями памяти и лишними копированиями в памяти (как раз тот Ваш вопрос "а не создаются ли тут массивы" был именно про это и для Go это действительно важный момент).
По этой причине команда Go считает важным то, чтобы разработчикам на Go было легко видеть все потенциально проблемные в плане производительности места в коде. И по этой причине они не хотят изменять язык так, чтобы это стало менее очевидным. Да, это не единственный и не самый критичный фактор, но он есть и он тоже учитывается.
простой с точки зрения компилятора
И это тоже очень важно, кстати, потому что создаёт возможность легко написать тьму утилит работающих с кодом на Go. По-моему ни в одном языке нет столько линтеров, сколько есть для Go (в golangci-lint встроено больше сотни AFAIR, многие из которых содержат десятки и сотни разных проверок, и всё это используется большинством проектов на Go), плюс всякие кодгены и т.д. А в конечном счёте вся эта экосистема утилит облегчает жизнь именно разработчикам на Go.
по моему тернарного оператора там тоже нет
Нет. Грусть-печаль по этому поводу на меня изредка накатывает, но - крайне редко. Потому что обычно он не нужен. И, что самое интересное, минимум в трети таких случаев я, будучи вынужден выкручиваться без него, находил более элегантное решение, в котором он не требовался. И да, в библиотеках он есть, иногда я даже пользуюсь, но… это всё не то, чем когда он есть в языке, тут спору нет.
на го Вы с большей вероятностью увидете большее разнообразие решений, с большей вероятностью допустить ошибку,
Даже не знаю, что на это ответить. В теории - да, это звучит логично. На практике - нет, не сталкивался. Это всё ровно такой же код, как и любой другой. Этого кода - немного, даже если ручками писать цикл. Этот код покрывается такими же тестами, как и любой другой. Этот код прекрасно выносится в библиотеки при желании. Код как код, никаких реальных проблем он не создаёт. Так мы дойдём до аргументов в стиле "в языке A есть библиотека для X, а в языке B такой библиотеки нет, поэтому функционал X придётся писать вручную и с ашипками" - но в этом же нет никакого смысла.
исходники kubernetes
Куб - это ужоснах, что в исходниках что как готовое приложение. Я реально не понимаю, как он стал стандартом в индустрии - и стараюсь сталкиваться с ним как можно меньше. Такое можно найти на любом языке, и в качестве примера "типового кода на Go" я это рассматривать отказываюсь категорически - это вообще ни разу не типовой код.
В большинстве моих проектов коллекции лежали в БД,
Я не пойму Вы это серьезно? Да коллекция повсюду! Не в каждом приложении есть БД, и не всегда взаимодействие идет только с ней, есть еще и внешние рест-сервиса, брокеры сообщений, входные параметры их валидация, конвертация.. да всего не перечислить. Посмотрел Ваш код на гитхаб, открыл самый верхний репозиторий на го и что я там увидел? Правильно тоже коллекции.
Самое смешное мой первый друг также сказал что у него все БД и он все делает на ее стороне. На что я ему пошутил, что теперь придется добавлять базу в каждый проект, чтобы туда выгружать данные и на стороне бд решать проблему filter/map/reduce. Да и есть кейсы когда проще что то вычислить уже по имеющимся данным, чем делать второй запрос или делать страшные вещи проде union или outer join натягивая сову на глобус.
И орм тут тоже не причем, я работал в проектах с орм и без него. Извините, но данный аргумент никак не могу принять.
Go в этом списке посередине. И хотя он действительно не на первых двух уровнях, но на его уровне всё ещё актуален ручной контроль за выделениями памяти и лишними копированиями в памяти (как раз тот Ваш вопрос "а не создаются ли тут массивы" был именно про это и для Go это действительно важный момент).
Так я поэтому и задал вопрос, про создание массивов после каждой операции, и те библиотеки что вы показали, именно так и делают - грузят память. А в c#/java/kotlin такой проблемы нет по дефолту в sdk! Все уже давно как я писал отлажено и оптимизировано. Java вообще всегда создает stream и не создает лишних списков там где это не надо, в котлин можно управлять этим поведением. И основная область применения у jvm стека также как и у го это бэкэнд, и она тоже хорошо с этим справляется, большинство банков используют этот стек.
Даже не знаю, что на это ответить. В теории - да, это звучит логично. На практике - нет, не сталкивался.
Вот честно, не знаю даже что опять и думать) Вот взять мой реальный случай с двумя друзьями описанный выше, первый показал мне свои утилитные файлы хелперы, которые копирует из проекта в проект (все по канонам го), другой реализовал “ручками” с мапой и проходом по циклу. Даже тут мимо… Я не первый день живу и вижу как пишут код, чем больше свободы, тем больше проблем. А го как раз из за своей “бедноты” дает больше возможностей написать код по разному.
Это как пример - заставить разные бригады собирать каркасные дома, или вывалить перед ними кучу кирпичей и сказать стройте. Ну очевидно же те кто будет строить из более крупных и готовых блоков, построят +- одинаковые и проверенные временем решения, где во втором случае каждый сам себе художник. (Понятное дело, тут тоже надо знать меру (в разнообразии элементов) и не “свалиться” в kotlin, о чем я также писал выше)
В общем мне кажется наша дискуссия зашла в тупик. Идеальных языков не бывает. Я не хочу сказать что го плохой язык, но у него также есть свои слабые стороны, очень бедная сдк и синтаксис. А у меня такое ощущение что Вы хотите мне доказать обратное что он идеален и приводите странные аргументы с Бд и что все пишут на го одинаково, с чем я категорически не согласен.
P.S. Вот лично я не ничего против не имею про исключения в го, ну есть у них такой подход - ок. Обработка исключений как по мне это всегда более бизнес специфичный кейс.
С интерфейсами мне тоже все понятно, что есть метод куда нужно передать разные объекты, по месту создается нужный интерфейс который покрывает все возможные вариации входных типов и вуаля “утиная” типизация в действии.
В целом мне достаточно того что я у Вас услышал вскользь “грустные нотки” и про тернарные операторы и про конвейерные операции, но лично я не хочу писать на “Pascal” и иметь проблемы о которых писал выше. Но в любом случае спасибо Вам за дискуссию. я все)
Вот поменять обработку ошибок создатели Го отказались.
А какое объяснение невключения мап/редьюс итп в стандартную библиотеку было от них?
Вкратце - пока нет консенсуса как именно это должно быть реализовано.
Чуть подробнее: два года назад Russ Cox (техлид команды Go на тот момент) предложил реализовать это на итераторах. И он же спустя два года предложил отложить эту тему в связи с отсутствием консенсуса в коммьюнити. Решили подождать и посмотреть, какие реализации сами проявятся в сторонних библиотеках и смогут завоевать популярность.
В целом, появлению таких штук в Go раньше сильно мешало отсутствие дженериков, так что развитие этой темы сдвинулось с мёртвой точки не так давно, когда появились дженерики.
Нет, сильная сторона - это как раз ясность и читабельность кода. Да-да-да, внезапно оказалось, что очень простой язык намного проще читать, не смотря на отсутствие синтаксического сахара позволяющего в очень "краткую и наглядную" запись (вроде Вашего примера) и не смотря на "избыточную и многословную" обработку ошибок и кучу boilerplate кода. Такой вот парадокс!
Не нужно считать создателей других ЯП идиотами, которые не догадались вернутся в 80-десятые за читабельностью. Любое достоинство вытекает из недостатка и Go не исключение, то что в одном случае будет читабельно и хорошо, в другом превратиться в тыкву. За примером ходить далеко не нужно, любая предметная область где можно зарулить с DSL на Go будет вызывать кровь из глаз. Потому что нельзя победить проблемы просто радикально упоровшись во что-то одно.
У меня недавно был удачный пример, сначала написал приложение на Go, потом по воле обстоятельств пришлось переписать его на C#. Спойлер, чуда не произошло. Проект на C# выглядел заметно приятнее: был минималистичнее, проще и читабельней чем на Go. Это я не про то что Go плохой а скорей про то надоели эти сказки про его ультимативную читабельность.
Забавный факт: после рассмотрения сотен идей и предложений как именно стоит изменить обработку ошибок команда Go спустя 15 лет попыток что-то с этим сделать приняла решение… перестать пытаться! :) Случилось это буквально на днях - пару месяцев назад: https://go.dev/blog/error-syntax. И ключевая причина в том, что до сих пор неясно для чего конкретно нужно изменить обработку ошибок: чтобы стало легче читать/писать логику обработки ошибок, или чтобы стало проще НЕ писать эту логику.
Многие гоферы охренели после этой новости, а многие назвали это бессилием
Любое достоинство вытекает из недостатка
Мне больше нравится формулировка "ограничения создают возможности".
DSL на Go будет вызывать кровь из глаз
Однозначно.
Спойлер, чуда не произошло. Проект на C# выглядел заметно приятнее: был минималистичнее, проще и читабельней чем на Go.
Вы не там ищете чудо. Я и на Perl писал приятный, минималистичный, и очень читабельный код. Но читабельность Perl заканчивалась ровно в тот момент, когда я открывал чужой код на Perl. Можете ли Вы утверждать, что любой код на C# такой же читабельный, как любой код на Go?
Многие гоферы охренели после этой новости, а многие назвали это бессилием
Даже не сомневаюсь в этом. Вопрос в том, какой процент из них хотел чтобы стало проще НЕ писать обработку ошибок?
Но читабельность Perl заканчивалась ровно в тот момент, когда я открывал чужой код на Perl. Можете ли Вы утверждать, что любой код на C# такой же читабельный, как любой код на Go?
Нет, точно также как не могу утверждать что любой код на Go будет такой же читабельный как на C#. Это вообще палка о двух концах. Там где Go будет превосходить в одном месте, из-за этих же качеств будет проигрывать в другом. Уже писал про DSL, там где нужна концентрация на правилах и более высокоуровневой логике а не реализации, Go в хлам проиграет. Другой пример стандартизация. Из-за минимализма, в Go отказались добавлять в стандартную библиотеку и язык многие вещи, которые разработчики вынуждены весосипедить или подключать разношерстные библиотеки. Как итог зоопарк решений вместо единообразия. Разработчики других языков работают с одними и теми же решениями одинаковым образом а в Go работают с зоопарками и велосипедами, где новому разработчику будет проще освоиться на проекте думаю вопрос риторический.
В общем все относительно. Не отрицаю что в Go многое сделано для читабельности но не нужно это гиперболизировать.
В Шарп читабельность добавляли ценой кучи несовместимых версий языка.
Так что, если изволите писать под Вин7, скажем, уже придётся, и довольно много такого сахара переписывать. И хорошо, если это коснётся только своего кода, а не зависимостей.
Меня подзадолбало - ой моей уникальной утилите на 100кб требуется новейший нет-рантайм.
А в коде, где такой бизнес-логики много, будут созданы вспомогательные методы и функции вроде таких:
func (u User) Height() int { return u.height }
func UserTallerThan(height int) func(User) bool {
return func(u User) bool { return u.height > height }
}
и дальше Ваш пример будет выглядеть вот так:
u.GroupBy(u.Filter(users, UserTallerThan(170)), User.Height)
По моему мнению основное приемущество Go о котором тут не сказано, это хорошая утилизация ресурсов, за счет его модели многопоточности из коробки, что особенно актуально в текщих реалиях в облачных платформах для деплоя сервисов. А также компиляция в нативный машинный код, что в целом положительно сказываетс на производительности и быстром старте приложения, что опять сейчас очень актуально.
На самом деле скорей нет чем да. Все тоже самое можно получить взяв тот же C# к примеру. Сила Go в минимальных сроках его изучения с одной стороны и не дурных характеристиках - с другой. По итогу для компании - это палочка выручалочка. Можно хантить разрабов с других языков и быстро их переучивать на Go.Скилл некромантии. Чтобы дать бизнесу утилизацию ресурсов памяти и процессора, для начала нужно найти ресурсы в виде разрабов.
Главный приоритет языка — наглядность
Неявная имплементация интерфейсов и объявление в месте использования
Какая то шизофрения в принципах
Это типичное первое впечатление при знакомстве с языком. Помню, я, когда начинал осваивать Go, первым делом нашёл стороннюю тулзу, которая мне показывала где какие интерфейсы реализованы. Потому что эта "неявность" меня тогда сильно напрягла - мол, как это так, где-то реализован возможно полезный мне интерфейс, а я-то и не в курсе!
Прикол в том, что с тех пор она мне так ни разу и не пригодилась. Здесь нет проблемы, вообще.
А вот пользы от объявления интерфейсов по месту использования довольно много.
10 непривычных моментов в Go для Java разработчика