Go для начинающих: дорожная карта изучения с нуля

В этой статье собрал маршрут изучения Go от первой запущенной строки кода до первого проекта и собеседования. Я собрал этапы в порядке, в котором их удобнее проходить: каждый следующий опирается на предыдущий. Эта дорожная карта подойдёт тем, кто пишет свою первую строчку кода, и тем, кто переходит на Go с другого языка. Внутри есть ссылки на отдельные разборы. Открывайте их, когда дойдёте до соответствующего этапа и захотите копнуть глубже.

Карту необязательно проходить строго по порядку. Лучший способ, естественно, это изучать сверху и двигайтесь вниз. Так у вас меньше шансов застрять на теме, к которой ещё не готовы.

Почему стоит учить Go

Go создавали в Google как ответ на конкретную боль: большие кодовые базы на C++ и Java медленно собирались, а параллельный код писался тяжело. Из этой задачи выросли три свойства, ради которых язык учат до сих пор.

Больше узнать о предыстории языка и почему язык получился именно таким, какие компромиссы заложили его авторы, какой инженерный путь развития языка прошёл как технология — я подробно разобрал в статье про историю появления 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

Здесь же стоит пораньше разобраться с двумя темами, на которых новички часто спотыкаются:

  1. Преобразование типов: в Go нет неявных приведений, число в строку само не превратится. Способов несколько, и у каждого своя область применения — я разобрал их в статье про преобразование int в string.
  2. Ключом 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-бота. Важно довести до конца хотя бы что-то одно. Только при полной реализации всех этапов (сервер + база + тесты + сборка) ваши знания сложатся в навык.

Когда захотите проверить себя или начать собеседоваться, ориентируйтесь по тому, что спрашивают на разных уровнях. Я разобрал это для трёх грейдов.

Эти статьи можно использовать как чек-лист пробелов: пройдитесь по темам и отметьте, что вы уже уверенно знаете.

Внешние ресурсы

Итог

Эта дорожная карта это ориентир. Не воспринимайте это как расписание. Порядок этапов отражает то, как темы опираются друг на друга: синтаксис до интерфейсов, интерфейсы и ошибки до конкурентности, рабочий код до его оптимизации. Но проходить их можно и без конкретного порядка, перепрыгивая туда, где появилась задача.

Правило, которое я бы повторил и выделил отдельно:

Учить язык в отрыве от практики бесполезно.

Читать про горутины можно бесконечно, но полное понимание придёт к вам только, когда вы запустили их сами и поймаете первую гонку данных. Используйте эту карту так: берите этап, читайте связанный разбор, сразу пишите свой маленький пример — и переходите к следующему.


Теги: