Недавно Яндекс Практикум провёл вебинар «Спроси меня про Go» в формате открытого микрофона: слушатели могли задать любые вопросы о языке Go. Экспертами выступили техлиды курса «Go-разработчик»: Георгий Зуйков — ведущий разработчик в Едадиле и Александр Демиденко — старший разработчик в Яндекс Облаке, в команде Cloudgate.
Видеозапись открытого микрофона можно посмотреть ниже. Для тех, кому удобнее читать, мы подготовили краткий конспект.
Для каких задач подходит Go
Для чего хорош Go, кроме инфраструктурных сервисов типа Kubernetes и Docker?
Георгий Зуйков: У Go в принципе нет ограничений. Он может использоваться для системного программирования, для веба, для чего угодно. Есть сторонние реализации, точнее, кастомные сборки Go, которые подходят для устройств с малым объёмом памяти и малым процессором. Go можно использовать для бигдаты — почему нет? Вопрос только в удобстве, в наличии инструментов и библиотек.
Для чего лучше не использовать Go?
Георгий Зуйков: Я бы не советовал его для написания фронтенда — всё, что с вебом связано, оставьте для JavaScript. Ещё не стоит использовать Go там, где нужно точное управление памятью или супервысокая скорострельность.
Какое применение для Go закладывали при создании языка, а для каких задач Go не планировали, но он нашёл там прекрасное применение?
Георгий Зуйков: Изначально ребята из Google создавали Go как конкурентный язык для С++: быстрая компиляция, быстрая разработка, быстрое прототипирование и близкие по скорости результаты работы приложений. В Go сразу закладывали асинхронность, многопоточность, многопроцессорность, CSP (communicating sequential processes). А ещё Go максимально кроссплатформенный: ты можешь на линуксе скомпилировать бинарники для MacOS, линукса, винды, BSD — на одной машине.
Про устройство Go
Можно ли в Go ограничить потребление сервисом ресурсов операционной системы? Условно говоря, если мы уходим от куберов и нативно на хосте запускаем бесконечное количество сервисов, мы можем смотреть, сколько и какой процент оперативки потребляем? Мне нужно, чтобы хост не падал по памяти и CPU. Можно ли в Go ограничить предел потребления ресурсов? Например, чтобы мой сервис не потреблял больше 10 мегабайт оперативной памяти.
Георгий Зуйков: Как я понял, вы хотите перенести роль ограничения ресурсов на Go? В runtime можно устанавливать размер стека на горутину, которая выделяется. Ты можешь управлять количеством горутин, потому что сам их запускаешь. Но полностью контролировать ограничение ресурсов, как в LXC, Docker и прочих, стандартными средствами языка нельзя.
А если это HTTP-сервис и количество горутин фактически зависит от количества запросов?
Георгий Зуйков: Можно поставить RateLimiter прямо у себя в приложении. В расширенной стандартной библиотеке, которую мы рассматриваем на курсе «Продвинутый Go-разработчик», есть уже готовый пакет, написанный разработчиками Go. Это ровно то, чего вы хотите. Но, положа руку на сердце, я бы этим не занимался. Отдайте эту задачу тем, кто лучше этим умеет заниматься, — cgroup, докерам. У них для этого есть простые настройки.
Существуют Infra и Nginx — они мне не нравятся, потому что нужно балансировать трафик между кластерами. Я хочу скинуть это на Go и написать свой прокси, который будет круче. Это норм? Go вытянет?
Георгий Зуйков: В стандартной библиотеке есть пакет, который, собственно, и занимается реверс-проксированием. А дальше всё зависит от тебя. В принципе он достаточно быстрый. Он даже даёт возможность работать на низком уровне именно с запросом, а не с самим пакетом — но это вряд ли тебе нужно.
Есть ли в Go каркасы или другие средства для View [webUI] типа кнопок, как в RAD Delphi?
Георгий Зуйков: Нет, встроенного нет. Кстати, по поводу UI в Go — уже существует довольно много готовых биндингов. Поэтому на Go можно писать UI, даже кроссплатформенный, насколько я помню.
Я слышал, что в Go есть проблема, связанная с тем, что не хватает «синтаксического сахара» и стандартных библиотек — приходится много самим писать. Так ли это?
Георгий Зуйков: У Go достаточно богатая расширенная стандартная библиотека. Конечно, не такая, как в Python или Java, но нужно понимать, что языку на данный момент всего 10 лет. Нельзя от 10-летнего языка требовать, чтобы он сравнился с 30-летними по богатству стандартных библиотек. Но, например, всё, что связано с криптографией, в Go на очень высоком уровне. Есть отличный пакет для работы с веб — можно вообще ничего не брать, а по одной стандартной библиотеке весь веб-сервис написать. А ещё есть много внешних библиотек от сообщества. Не найти библиотеку, которая решает твой вопрос, очень сложно.
Теперь по поводу синтаксического сахара. Прозвучало слово «проблема», но проблемой это может казаться тем, кто привык работать с языками с большим количеством синтаксического сахара, например с Python. В Go нет даже тернарного оператора. Разработчики языка не хотят добавлять фишечки ради фишечек.
На предыдущей конференции Яндекса прозвучало, что в Go все аргументы передаются как значения. Это действительно так? И почему?
Георгий Зуйков: Обычно в языках есть два варианта: по указателю и по значению. В Gо выбрали передачу аргументов по значению.
Если мы передаём ссылку, как она передается по значению?
Георгий Зуйков: Представь себе память как квадратные ячейки, идущие друг за другом. В каждую ячейку можно положить какое-то значение. Ты кладёшь 42 и говоришь, что у этой ячейки есть адрес 0001. Затем ты говоришь, что у тебя есть переменная «а» и она указывает на адрес, где лежит 42. Это и есть передача по значению. Когда ты передаешь «а» в функцию, у тебя физически берется значение 42 и кладётся в другую ячейку. Получается, у тебя есть два разделённых значения — 42 и 42. Со вторым ты можешь делать что угодно, это никак не меняет первое.
Как у Go обстоят дела с ORM? Люблю писать SQL-запросы и не люблю URL. Как часто ORM в других проектах используется? Какие вообще планы развития?
Георгий Зуйков: В самом языке ORM нет — есть в сторонних библиотеках типа GORM. Хочу сказать, что в Go отлично заходят query builder'ы. Волшебная штука на стыке автоматизации и ручного управления, которое мы любим в SQL.
Есть сервер на Go, вся железная память отдана под кеш. Просто map[string]interface{}. Приходит большое количество новых клиентов, и кеш начинает активно вымещаться. Если интенсивность достаточно высока, то может случиться так, что новые значения помещены в кеш, а старые ещё не почищены. В итоге приложение падает с out of memory. Как с этим бороться?
Георгий Зуйков: Не использовать map[string]interface{}, в первую очередь. Есть нормальные пакеты, которые реализуют LRU-cache. Нужно понять, какая у тебя стратегия кеширования. Их много разных. В Go есть такая штука — race-детектор. Она используется в тестах, в runtime. Тебе, скорее всего, был бы интересен LRU-кеш. LRU-кешей на Go много, они быстрые, хорошие, там можно настраивать ватерлинии по памяти, политики вымывания и так далее. Почти все написаны с учётом особенностей Go-шного сборщика мусора: они нормально выделяют память и переиспользуют её.
Как бороться с тем, что память может быть занята полностью, и garbage collector просто не успевает её почистить? Это проблема не только Golang, а вообще любого языка cо сборщиком мусора.
Георгий Зуйков: Когда ты удаляешь элемент из map, garbage collector придёт за тем, что там лежало. Он вызывается как минимум раз в две минуты. Это действительно не только Go-шная проблема — во многих языках есть риск израсходовать память, прежде чем сборщик мусора будет успевать её освобождать.
В LRU получается выгоднее, потому что память будет вычищаться гораздо раньше, более оптимально будет использоваться?
Георгий Зуйков: Нужно не путать: LRU — это стратегия кеширования. То, как она реализована конкретно для Go, — это уже вопрос про память. Стратегия кеширования — про бизнес-логику и способ обеспечить больший кешхит. LRU выделяют большой блок памяти и постоянно перезатирают данные внутри него. Вместо того, чтобы отдавать что-то garbage collector’у, он просто перезаписывает новый блок в кеш. У тебя постоянно выделен, условно, гигабайт памяти. Garbage collector приходит раз в две минуты и уходит ни с чем — и всё работает быстро.
В чём назначение вендоринга, как им пользоваться?
Георгий Зуйков: Фактически это локальный кеш зависимостей твоего проекта. В корне проекта создаётся папка vendor, внутри неё по папочкам раскладываются зависимости. Когда компилятор станет собирать приложение, он в первую очередь будет искать зависимости в локальном вендоре твоего проекта. Если не найдёт там, пойдёт в GOPATH и пакет стандартной библиотеки, если совсем нигде не нашёл. Вот так иерархически он будет идти по папочкам и пытаться найти то, что ты у него попросил заимпортировать.
Правильно я понимаю, что команда «go mod tidy» скачивает в GOPATH модули локально, а «go mod vendor» работает на основе этого же файла go mod, только рядом файлики скачивает?
Георгий Зуйков: Нет, не совсем. Когда у тебя есть «go mod» файлик, GOPATH уже не работает. Папочка, в которой лежит «go mod», — это обособленный Go-шный модуль. Внутри папочки твой проект. «go mod tidy» проходится по коду и пытается убрать из файлика «go mod» зависимости, которых в коде нет, и добавить те, которые есть. Он приводит твои зависимости в порядок.
Как дела с reverse engineering на Go? Есть ли стандартные инструменты для дизассемблирования бинарников? И ловили ли вирусы, написанные на Go?
Георгий Зуйков: Отлично с дизассемблированием. По поводу вирусов: где-то год назад ходили по сети коммерческие малвари, написанные на Go, можно новости поискать.
Как ложатся шаблоны «банды четырёх» на Go? Ведь в Go нет наследования.
Георгий Зуйков: В Go нет наследования, но есть embedding, технически он похож на наследование. На самом деле из классических четырёх парадигм ООП хуже всего в Go с полиморфизмом. А в остальном нормально.
Где лучше использовать массивы вместо слайса?
Георгий Зуйков: Там, где точно знаешь размерность. Массив вместо слайсов используется, например, в библиотеке для работы с UUID, потому что ты точно знаешь, что UUID — это всегда 16 байт. Это удобно.
Может ли фреймворк быть быстрее стандартной библиотеки?
Георгий Зуйков: Легко. Стандартная библиотека не претендует на пальму первенства. Всегда есть маленькие библиотечки, которые могут быть быстрее.
Как обойти невозможность передать через gRPC null-значения? Условно: мне необходимо понимать, когда установлено true, когда false, когда не было передано вообще.
Георгий Зуйков: Использовать options или идти работать в Google и насаждать там своё видение.
Ваше мнение по поводу DI — стоит ли использовать?
Георгий Зуйков: Смотря в каком виде. Сам по себе DI хорошо ложится на интерфейс. Есть две сторонние библиотеки от Facebook и Google, которые занимаются автоматизированным DI.
Какие библиотеки используются для тестирования и создания моков?
Георгий Зуйков: Стандартная библиотека уже имеет пакет testing — сам по себе он неплох, но слишком базовый. Если хочется чуть больше — есть пакет testify, его используют почти все. Для моков я использую GoMock, но есть люди, которые используют mockery. Этих генераторов моков из интерфейсов в принципе достаточно много, все они делают одни и те же вещи. Поэтому посмотрите, какой по виду приятнее.
Переход на Go с других языков
Замечаю, что в Go переходят много PHP-разработчиков. С какого языка лучше заходить?
Георгий Зуйков: Вопрос хороший. Я переходил с PHP и Python. Многие из моих знакомых переходили с Python, некоторые с С++. Я бы сказал так: в принципе, с любого, если у тебя есть базовое понимание языков. Go — максимально С-подобный язык. Если ты хоть какой-то С-подобный язык практиковал, понимаешь, как работает программирование и как устроены С-подобные языки, можно спокойно на Go переходить, в нём ничего волшебного нет. Он понятный, маленький и простой.
Если он С-подобный, у него есть все возможности работы с памятью — выделение, размерность? Ведь в PHP их нет.
Георгий Зуйков: Runtime, scheduler и всё остальное прячет работу с памятью. Непрямым методом можно выделять слайсы — они же массивы в PHP. Но это опционально. Можно просто сделать слайс с размерностью 0: пока ты будешь в него что-то накладывать, он несколько раз повыделяет памяти, покопирует туда-сюда под капотом. Если ты знаешь, сколько тебе надо, можешь указать размерность одним из аргументов при создании слайса. Никаких прямых возможностей работать с памятью в Go нет, он максимально дружелюбный и для питонистов, и для PHP-шников — никаких заморочек, как в С.
Последние два года я пишу в основном на C#. Мне хотелось бы изучить новый язык разработки, и выбор пал на Go — перспективный, молодой язык. Вопрос первый: как вообще у Go с выбором фреймворков под веб-разработку? Например, в C# доминирующую позицию занимает asp.net и других альтернатив нет (может, они и есть, но они не такие популярные). В качестве фреймворков у Go что? Можете порекомендовать, в какую сторону начать изучение?
Георгий Зуйков: Для веб-разработки в Go всё особенно хорошо. Стандартная библиотека достаточно полноценная, чтобы в принципе никаких фреймворков не использовать, — можно взять чистую стандартную библиотеку и просто на ней фигачить. Дополнительно я использую только роутеры, которые умеют поддерживать параметры в запросе, middleware готовые. Если будешь искать фреймворки, заходи на сайт awesome-go.com в раздел про HTTP-фреймворки. Единственное: выбирай те, которые совместимы со стандартной библиотекой.
Я понимаю, что после многих языков Go может показаться сильно аскетичным, но такая уж парадигма у языка. Максимально маленький размер кейвордов — 50 ключевых слов. Никакого синтаксического сахара там нет. Единственное, что ты можешь сделать, — это кодогенерация. Это есть в нашем курсе. В стандартной библиотеке Go есть инструментарий, позволяющий работать с абстрактным синтаксическим деревом кода. Возможности тут безграничны, можно организовать любой препроцессинг. Но написать придётся руками.
Пишу на Java, там есть центральный репозиторий, где даже указываются уязвимости, найденные в пакетах. Есть ли что-то подобное в Go?
Георгий Зуйков: Поначалу Go создавался как децентрализованный — если посмотреть на импорты в Go, то чаще всего пакеты там называются Github.соm / что-то непонятное / название пакета. Так было до определённого момента. В 1.11 начали добавлять вендоринг и модули, работать с менеджментом зависимостей. И через какое-то время представили штуку, которая называется goproxy. Это такой большой кеш зависимостей, который хостится у разработчиков языка, — через него тянутся все пакеты. При этом центрального репозитория как такового нет. Каждый человек может поднять свой goproxy где-нибудь на DigitalOcean и через него тащить все свои пакеты.
Возможно ли устроиться на работу Go-разработчиком, имея маленький опыт коммерческой разработки на другом языке, например Python?
Георгий Зуйков: Всё индивидуально. Если команда готова вложиться в обучение, почему нет? Иногда ищут людей на позиции джуниора с переходом на другой язык.
Будущее Go
Каким вы видите будущее языка Go в плане научно-технического применения, в областях, где есть массивы больших данных, матрицы, вектора, радиосигналы, видео? В этой сфере сейчас доминируют языки C, C++, Python. Хотелось бы узнать, есть ли у Go какие-то перспективы в плане, например, повышения производительности за счёт использования SIMD-инструкций процессора (single instruction, multiple data) или чего-то ещё? Это первое. Второе: будут ли появляться библиотеки типа NumPy, как на Python, или PyQt, чтобы писать хотя бы какие-то простые десктоп-технологички?
Георгий Зуйков: Изначально разработчики такое назначение языка не закладывали — как и у Python, будем честны. Уже существует достаточно большое количество разных библиотек, чтобы с этим работать. Никаких проблем с использованием всяких хитрых инструкций, процессоров для ускорения нет. В Go есть свой макроассемблер. По поводу библиотек для Big Data — они уже есть. Для работы с CV есть биндинги к OpenCV. Кажется, была какая-то нативная реализация. Нельзя сказать, что это быстрее С++, но зачастую может быть быстрее, чем на Python.
Используются ли в Яндексе эти библиотеки?
Георгий Зуйков: В Яндексе есть своя реализация хранилища. В Google это называется Big Table, у нас — Yandex Table. Такая большая штука для Big Data. Отдельная команда писала биндинги и воркеры для неё на Go. И получилось так, что в некоторых ситуациях Go-шные биндинги и воркеры работали на 20–25% быстрее, чем С++. Просто из-за того, что большая часть данных гоняется на gRPC, — она более нативна для Go.
Вопрос про Gо 2.0. Новости об этой версии появились года четыре назад, если не больше, даже какие-то дизайн-листы публиковались, но сейчас ничего предметного не гуглится. Известно ли что-то про перспективы? Если известно, то на что это будет похоже? И второй вопрос: бьют ли в продакшен-коде за unsafe?
Георгий Зуйков: По поводу Go 2.0. Ребята говорят, что у них есть backward compatibility promises, то есть договор об обратной совместимости в языке: они не выпускают фичи, которые ломают предыдущие программы. Go 2.0 — довольно метафизическая вещь, где все ошибки исправлены, но это, конечно, далёкое-далёкое будущее. Единственное, что про него известно, — это то, что разработчики не хотят повторения Python — они хотят попробовать сделать минимально инвазивные изменения языка.
Александр Демиденко: Про unsafe: нужно понимать, ради чего это делать, потому что чаще всего в этом нет необходимости. Unsafe обычно используется там, где нужно состыковать язык Go с какой-то эксклюзивной внешней библиотекой. Область применения у unsafe достаточно узкая.
Как вы считаете, можно ли Go считать прямым наследником языка Limbo из Plan 9?
Георгий Зуйков: С учётом того, что это тот же Роб Пайк, у которого Limbo не взлетел и который пропагандирует CSP, то, наверное, да, в какой-то мере это работа над ошибками. Но надо понимать, что Go — наследник только в смысле многопоточности. А так Limbo — интерпретируемый, Go — компилируемый, и по синтаксису они не очень-то похожи.
Вопросы про курс «Go-разработчик»
Изначально Go ориентирован на веб-сервисы, поэтому хотелось бы в курсе увидеть такие темы, как систем-дизайн и разработка приложения, более направленные на микросервисы и паттерны, связанные с этим. Из описания программы мне показалось, что этой части не хватает. Или она есть? Мне рассказывали про курс Python в Яндекс Практикуме, и там, как ни странно, эта часть есть, а вот в Go, где я ожидал её увидеть, особенно в продвинутой части, её почему-то нет.
Георгий Зуйков: Расскажу про цели курса и про то, как мы его проектировали, — надеюсь, это ответит на вопрос. Курс по Go ориентирован на людей, у которых есть опыт программирования. Подразумевается, что студент имеет практические навыки программирования от года и больше. Мы исходили из представления, что за год человек, имея опыт коммерческого программирования, уже сталкивался с микросервисной архитектурой. У нас есть на курсе работа с вебом, но мы хотим максимально сосредоточиться на языке и его составляющих.
Что нужно знать для поступления?
Для любого из наших курсов нужно знание основ бэкенд-разработки. Обычно эти знания появляются после одного или двух лет работы бэкенд-разработчиком. Однако, если вы окончили курсы по программированию, вы тоже сможете учить Go с нами. Для курсов «Go-разработчик» и «Продвинутый Go-разработчик» вам дополнительно потребуются знания синтаксиса Go.
Сколько длится курс?
Основной курс — три месяца, продвинутый — полгода.
Есть ли возможность не проходить обычный курс, а взять только часть продвинутого?
На данный момент нет, потому что пререквизит к продвинутой части — полностью написанный код большого проекта на курсе «Go-разработчик».
Какой проект в итоге получится?
Вы напишете один учебный проект и один выпускной, а на курсе «Продвинутый Go-разработчик» — два выпускных. На старте нужно выбрать, какой сервис вы будете писать в течение курса: сервис сокращения URL (HTTP-сервер) или сервис сбора метрик и алертинга (сервер и агент). Сначала вы пишете минимальный рабочий прототип, затем расширяете его по мере прохождения новых тем и фич. В конце обучения самостоятельно пишете дипломный проект с нуля.
Какие документы вы выдаёте?
После курса «Go-разработчик» вы получите сертификат о повышении квалификации, после курса «Продвинутый Go-разработчик» — диплом о профессиональной переподготовке. Это официальные документы о дополнительном образовании.
Изображение Go gopher, которое используется в курсе, является модификацией изображения маскота, созданного Renee French, и лицензируется на условиях CC BY 3.0.