В этой статье собрал маршрут изучения Go от первой запущенной строки кода до первого проекта и собеседования. Я собрал этапы в порядке, в котором их удобнее проходить: каждый следующий опирается на предыдущий. Эта дорожная карта подойдёт тем, кто пишет свою первую строчку кода, и тем, кто переходит на Go с другого языка. Внутри есть ссылки на отдельные разборы. Открывайте их, когда дойдёте до соответствующего этапа и захотите копнуть глубже.
Карту необязательно проходить строго по порядку. Лучший способ, естественно, это изучать сверху и двигайтесь вниз. Так у вас меньше шансов застрять на теме, к которой ещё не готовы.
Почему стоит учить Go
Go создавали в Google как ответ на конкретную боль: большие кодовые базы на C++ и Java медленно собирались, а параллельный код писался тяжело. Из этой задачи выросли три свойства, ради которых язык учат до сих пор.
- Простота. В Go сознательно мало синтаксиса. Нет наследования классов, нет перегрузки операторов, нет исключений. Спецификацию языка возможно прочитать за вечер. Это означает, что чужой код читается довольно легко, потому что в Go-проекте редко встречаешь конструкцию, о существовании которой не знал.
- Конкурентность из коробки. Горутины и каналы реализованы как часть языка, а не библиотека. Запустить тысячу параллельных задач здесь проще, чем в большинстве языков. Это собственно во многом определило, почему на Go написаны Docker, Kubernetes, Prometheus и в целом значимая часть всей современной облачной инфраструктуры.
- Быстрая компиляция и один бинарник. Go собирается в один статический исполняемый файл без внешних зависимостей. Его удобно размещать в Docker-образ и выкатывать в продуктивную среду. Это стало залогом популярности языка в backend разработке, DevOps и создании CLI утилит.
Больше узнать о предыстории языка и почему язык получился именно таким, какие компромиссы заложили его авторы, какой инженерный путь развития языка прошёл как технология — я подробно разобрал в статье про историю появления Go.
Этап 0. Установка и первый запуск
Язык можно попробовать ничего не устанавливая себе на компьютер. У Go есть официальная песочница, где код пишется и запускается прямо в браузере. В основном используется для первых экспериментов и для того, чтобы поделиться примером. Список онлайн-инструментов и их отличия, я собрал в обзоре онлайн-компиляторов для Go.
Когда будете готовы писать всерьёз, то поставьте Go локально с сайта go.dev/dl или установите через свой пакетный менеджер. Проверить установку:
$ go version
go version go1.24.0 linux/amd64
Минимальная программа выглядит так:
package main
import "fmt"
func main() {
fmt.Println("Привет, Go!")
}
Запустить можно одной командой:
$ go run main.go
Привет, Go!
На этом простейшем примере уже видно несколько правил Go. Точка входа — функция main в пакете main. Импорты (т.е. подключение зависимостей) перечисляются в явном виде вверху файла, а неиспользуемый импорт завершается ошибкой компиляции (а не предупреждением линтера, как в других языках). Точки с запятой не нужны в конце строки, фигурная скобка обязана стоять на той же строке, что и объявление структур/функций/методов. Форматирование и код-стайл не обсуждается, потому что команда go fmt приводит код к единому стилю во всём языке, поэтому споров про отступы в Go сообществе не бывает.
Этап 1. Базовый синтаксис
Итак, давайте научимся выражать базовую логику: переменные, условия, циклы, функции, базовые структуры данных.
Переменные объявляют через var или через короткую форму := внутри функций:
var count int = 10 // полная форма с типом
name := "Go" // тип выводится автоматически
Из управляющих конструкций в Go есть только if, for и switch. Отдельного вида цикла while (как в других языках) нет — его роль играет for без условия или с одним условием:
for i := 0; i < 3; i++ {
fmt.Println(i)
}
for count > 0 { // аналог while
count--
}
for { // аналог бесконечного цикла
fmt.Println("я никогда не завершусь")
}
Две самые часто используемые в языке структуры данных это слайсы (динамические массивы) и map (словари):
numbers := []int{1, 2, 3}
numbers = append(numbers, 4)
ages := map[string]int{"Анна": 30}
ages["Иван"] = 25
Здесь же стоит пораньше разобраться с двумя темами, на которых новички часто спотыкаются:
- Преобразование типов: в Go нет неявных приведений, число в строку само не превратится. Способов несколько, и у каждого своя область применения — я разобрал их в статье про преобразование int в string.
- Ключом
mapможет быть комбинация значений. В Go удобнее использовать структуру как составной ключ вместо вложенных словарей. Почему так быстрее и как это устроено внутри — в разборе составных ключей map.
Этап 2. Структуры, методы и интерфейсы
В Go нет “классических"классов. Их роль выполняют структуры (struct) и методы, привязанные к типам:
type User struct {
Name string
Age int
}
func (u User) Greet() string {
return "Привет, " + u.Name
}
Интерфейс в Go описывает поведение как набор методов. Реализация интерфейсов в языке неявная. Это значит что тип не объявляет явно «я реализую этот интерфейс»: достаточно, чтобы у него были нужные методы. Это и есть “утиная типизация” на этапе компиляции:
type Greeter interface {
Greet() string
}
// User автоматически удовлетворяет Greeter — он реализует Greet()
var g Greeter = User{Name: "Анна"}
Утиная типизация — это концепция в программировании, при которой тип или класс объекта не является определяющим фактором. Важнее то, какие методы и свойства поддерживает объект, а не его конкретный тип. Название происходит от фразы «если нечто выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка»
В редких случаях требуется убедиться, что тип точно реализует интерфейс и поймать ошибку при компиляции, а не в рантайме. Для этого есть короткая идиома проверки, которую я описал в заметке про проверку соответствия интерфейсу на этапе компиляции. Интерфейсы стоит освоить постепенно: пока их нет, Go выглядит языком про функции и структуры, а с ними становится понятно, как на нём собирают гибкую архитектуру.
Этап 3. Обработка ошибок
В Go нет исключений. Ошибка представляет собой обычное значение, которое функция возвращает наравне с результатом, а вызывающий код проверяет его явно:
file, err := os.Open("config.yaml")
if err != nil {
return fmt.Errorf("не удалось открыть конфиг: %w", err)
}
defer file.Close()
Конструкция if err != nil уже визитная карточка Go. Новичков она поначалу раздражает многословностью, но за ней стоит идея: ни один сбой не теряется молча, путь возможные ошибки будут явно видны прямо в коде.
Обработка ошибок и сбоев это целый класс задач в разработке со своим наборов паттернов. В Go есть оборачивание через %w, sentinel-ошибки, errors.Is и errors.As, объединение через errors.Join. Всё это систематизировано в статье про идиоматичную обработку ошибок.
Освойте работу с ошибками как следует. Корректная обработка ошибок важная часть любой разработки.
Этап 4. Конкурентность: горутины и каналы
То ради чего многие и приходят в Go. Горутина — это очень дешёвый поток выполнения; запускается она ключевым словом go:
go doWork() // функция выполнится параллельно
Горутины общаются через каналы. Воспринимайте их как типизированные «трубы» данных. По ним безопасно передавать значения между параллельными задачами:
ch := make(chan int)
go func() { ch <- 42 }() // отправляем значение
value := <-ch // получаем
У нас так же есть select для работы с несколькими каналами и пакет sync с набором примитивов синхронизации. Это большая тема, и осваивать её лучше после того, как уверенно держите в голове функции, структуры и ошибки. Иначе легко погрязнуть в гонках данных, которые трудно отлаживать и тестировать.
Из практических инструментов уже на этом этапе пригодятся два. sync.WaitGroup — чтобы дождаться завершения группы горутин; в свежих версиях у него появился более безопасный метод, об этом — в разборе sync.WaitGroup в Go 1.25. А когда параллельные задачи могут падать с ошибками и нужно корректно собрать первую из них и отменить остальные, на помощь приходит errgroup — про него есть отдельная статья про errgroup.
Этап 5. Даты, время и строки
Стандартные, но коварные темы. Работа со строками в Go завязана на различии байтов и рун (rune — это символ Unicode), на пакетах strings и strconv, на strings.Builder для эффективной сборки текста. В Go строка не равна последовательности символов, и недопонимание тут приводит к багам с кириллицей и эмодзи.
Отдельного упоминания заслуживает форматирование дат. В Go оно сделано не как в других языках: вместо %Y-%m-%d используется «эталонное время» 2006-01-02 15:04:05. На этом спотыкаются даже оптыные разработчики. Подробное объяснение логики такого выбора собрал в статье про форматирование дат и времени в Go. Разобравшись один раз, вы перестанете гуглить это при каждой задаче с датой и временем.
Этап 6. Стандартная библиотека и сеть
В Go богатая стандартная библиотека. Возможно лучшая из всех из всех языков программирования. Полноценный HTTP-сервер реализуется без сторонних фреймворков и библиотек, с помощью стандартного net/http:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Привет от сервера")
})
http.ListenAndServe(":8080", nil)
}
Когда дойдёте до написания клиентов, важно понять, как http.Client переиспользует TCP-соединения и почему обязательно дочитывать тело ответа до конца. Об этой неочевидной ловушке есть статье про переиспользование соединений в http.Client. А когда дело дойдёт до запуска сервиса в продакшене, пригодится умение корректно его останавливать, не обрывая запросы на полпути. В этом вам поможет разбор graceful shutdown в Go.
Этап 7. Тестирование
Тесты в Go являются часть языка и инструментария, отдельных фреймворков для базовых вещей не требуется. Тестовая функция начинается с Test, лежит в файле *_test.go и запускается командой go test:
func TestGreet(t *testing.T) {
got := User{Name: "Анна"}.Greet()
want := "Привет, Анна"
if got != want {
t.Errorf("получили %q, ожидали %q", got, want)
}
}
В Go широкий инструментарий для тестов: табличные тесты, моки через интерфейсы, httptest, измерение покрытия. Подробности тестирования собраны в практическом руководстве по тестированию в Go. Отдельная техника тестирования это фаззинг, когда тест сам генерирует входные данные и ищет ошибки или падения. Я так смог найти реальный баг в популярном JSON-парсере и описал весь путь в статье про фаззинг.
Этап 8. Производительность и инструменты
Итак. Вы уже пишете рабочий код. Пора научиться измерять его производительность. В Go бенчмарки встроены в тот же go test: функции с префиксом Benchmark дают воспроизводимые цифры. Как их писать, как читать результаты и испортить данные своих замеров шумными данными — в статье про бенчмарки и оптимизацию в Go.
Заодно стоит освоить остальной инструментарий, который идёт в комплекте: go vet ищет подозрительные конструкции, go fmt форматирует, go mod управляет зависимостями, а профайлер pprof показывает, где код тратит время и память. Главный принцип оптимизации в Go (и не только в нём) — сначала измерить, вносить изменения и снова замерять.
Этап 9. Базы данных
Почти любой реальный сервис ходит в базу. Со стандартным пакетом database/sql Go работает через драйверы. Например, для PostgreSQL выбор драйвера влияет на производительность и удобство работы. Сравнение pgx, database/sql и sqlx с бенчмарками собраны в статье про драйверы PostgreSQL для Go.
В работе с СУБД есть много нюансов, которые нужно изучать глубок. Так например, когда дойдёт до загрузки больших объёмов данных в PostgreSQL, наивная вставка по строке окажется слишком медленной. Нюансы того как это делать правильно и сравнение подходов VALUES и UNNEST с замерами подробно описал в разборе быстрой массовой вставки в PostgreSQL.
Этап 10. Первый проект и собеседования
Карта обучения бесполезна без практики. Лучший способ закрепить всё перечисленное — написать небольшой проект целиком. Например: CLI-утилиту, HTTP-API с базой данных или Telegram-бота. Важно довести до конца хотя бы что-то одно. Только при полной реализации всех этапов (сервер + база + тесты + сборка) ваши знания сложатся в навык.
Когда захотите проверить себя или начать собеседоваться, ориентируйтесь по тому, что спрашивают на разных уровнях. Я разобрал это для трёх грейдов.
- темы собеседования junior-разработчика — базовый минимум, который стоит закрыть первым;
- вопросы для middle — конкурентность, архитектура, базы данных;
- собеседование senior — где к языку добавляется отдельный архитектурный этап.
Эти статьи можно использовать как чек-лист пробелов: пройдитесь по темам и отметьте, что вы уже уверенно знаете.
Внешние ресурсы
- A Tour of Go — интерактивный тур по языку в браузере, отличный первый шаг после этого обзора.
- Документация на go.dev — официальная точка входа во всё: установка, обучение, спецификация.
- Effective Go — как писать идиоматичный код; читать стоит только после того, как освоите базовый синтаксис.
- Go by Example — короткие примеры почти на каждую тему стандартной библиотеки, удобно как начальный справочник.
Итог
Эта дорожная карта это ориентир. Не воспринимайте это как расписание. Порядок этапов отражает то, как темы опираются друг на друга: синтаксис до интерфейсов, интерфейсы и ошибки до конкурентности, рабочий код до его оптимизации. Но проходить их можно и без конкретного порядка, перепрыгивая туда, где появилась задача.
Правило, которое я бы повторил и выделил отдельно:
Учить язык в отрыве от практики бесполезно.
Читать про горутины можно бесконечно, но полное понимание придёт к вам только, когда вы запустили их сами и поймаете первую гонку данных. Используйте эту карту так: берите этап, читайте связанный разбор, сразу пишите свой маленький пример — и переходите к следующему.
Теги: