Deep Engineering

Go · Подготовка к собеседованию

Вопросы на собеседовании по Go: короткие ответы

Короткий ответ из каждого урока линии — тот же текст, что в уроке, только без подписи «Короткий ответ:», — и вопросы из его раздела «Дальше спросят». Ответы на эти вопросы, механизм и замеры, на которых всё это стоит, — в уроке по ссылке.

  1. 01

    Структуры и методы

    Структура — составной тип-значение из именованных полей, которые лежат в памяти подряд. Присваивание и передача копируют её целиком, но копия поверхностная: срезы, карты и указатели внутри остаются общими, и slices.Clone с maps.Clone этого не меняют — они копируют один уровень. Конструкторов нет — нулевое значение структуры состоит из нулевых значений полей, и хороший тип работает прямо с ним. == разрешён, если сравнимы все поля; поле-интерфейс компилятор пропускает, а паника приходит при выполнении. Методы объявляются на определённом типе своего пакета и по сути — функции с получателем первым аргументом.

    Полный ответ — в уроке →

    Дальше спросят

  2. 02

    Указатели

    Указатель — значение, в котором лежит адрес переменной. В Go всё передаётся по значению, указатель тоже: функция получает копию адреса. Изменение через указатель (*p = 42) видно вызывающему, потому что обе копии ведут к одной переменной; присваивание самому параметру (p = nil) — нет, потому что меняется копия.

    Полный ответ — в уроке →

    Дальше спросят

  3. 03

    Рефлексия

    Рефлексия в Go — это пакет reflect, который по значению в интерфейсе даёт его тип (reflect.Type) и само значение (reflect.Value). Читать можно всегда — неэкспортируемые поля тоже, через Int(), String() и подобные методы, но не через Interface(). Set и SetInt работают, только если CanSet конкретного Value — true: значение адресуемо и получено не через неэкспортируемое поле; поэтому, чтобы заменить саму переменную, передают указатель и зовут Elem(). Элемент среза и запись в карту доступны и без указателя — у Index и SetMapIndex свои условия. Рефлексией пользуются encoding/json, fmt и всё, что работает с неизвестными при компиляции типами; цена — прежде всего поиск по имени, и библиотеки кешируют его по типу.

    Полный ответ — в уроке →

    Дальше спросят

  4. 04

    io.Reader и io.Writer

    io.Reader — интерфейс с одним методом Read(p []byte) (n int, err error): он кладёт в p до len(p) байт и возвращает, сколько положил, и ошибку. io.Writer — то же для записи, но, записав меньше len(p), обязан вернуть ошибку, а ошибку проверяют и при полной записи. Читая, сначала обрабатывают n байт, потом смотрят на err, а io.EOF — это нормальный конец потока. Для типовых задач есть готовые io.Copy, io.ReadFull, bufio.

    Полный ответ — в уроке →

    Дальше спросят

  5. 05

    Инструменты go

    go build компилирует и оставляет бинарник, go run собирает во временный каталог без отладочной информации и запускает; обе берут пакеты из кеша сборки, ключ которого — содержимое входов. gofmt приводит код к единственному формату, и спорить с ним не принято. go vet ловит ошибки, которые компилятор пропускает, — неверный глагол Printf, копию мьютекса, недостижимый код; go test запускает только часть его проверок, поэтому go vet ./... в CI — отдельный шаг.

    Полный ответ — в уроке →

    Дальше спросят

  6. 06

    Функции и замыкания

    Функция в Go — значение: у неё есть тип, нулевое значение nil, её передают и возвращают; сравнивать можно только с nil. Замыкание — функциональный литерал, который ссылается на переменные объемлющей функции, и эти переменные общие: замыкание видит их текущее значение в момент вызова, а не копию на момент создания.

    Полный ответ — в уроке →

    Дальше спросят

  7. 07

    Пакеты и проект

    Пакет — каталог файлов с одним именем в строке package. Снаружи видны только имена с заглавной буквы. Инициализация идёт так: сначала пакеты, которые импортированы, — каждый один раз; внутри пакета — переменные по зависимостям, затем все init() в порядке файлов; и только потом main.

    Полный ответ — в уроке →

    Дальше спросят

  8. 08

    Модули и зависимости

    Модуль — набор пакетов, которые выпускаются под одной версией; go.mod в его корне перечисляет требования — модуль и версию «не ниже», — а go.sum хранит хеши архива и go.mod каждого нужного модуля. Версию выбирает алгоритм минимальной версии: для каждого модуля — наибольшая из требуемых в графе, а не последняя в прокси.

    Полный ответ — в уроке →

    Дальше спросят

  9. 09

    JSON и теги

    В JSON выходят экспортируемые поля, ключ — имя поля или тег json:"…". omitempty пропускает false, 0, "", nil и срезы, карты и массивы нулевой длины, но не структуры — поэтому time.Time{} выходит как "0001-01-01T00:00:00Z". Unmarshal ищет поля без учёта регистра, лишние ключи молча пропускает и не трогает поля, которых во входе нет.

    Полный ответ — в уроке →

    Дальше спросят

  10. 10

    Паттерны конкурентности

    Пул воркеров — N горутин, которые читают задачи из одного канала и пишут результаты в другой; канал задач закрывает раздатчик, канал результатов — отдельная горутина после wg.Wait(). Для задачи на процессоре одновременно работают не больше GOMAXPROCS воркеров, поэтому прибавки сверх этого ждать неоткуда; для задачи, которая ждёт, число ограничивает внешний ресурс, и его ограничивают семафором. Конвейер — стадии через каналы, каждая закрывает свой выход; чтобы стадии не утекали, когда потребитель ушёл, каждое ожидание стадии — и получение, и отправка — стоит в select с ctx.Done().

    Полный ответ — в уроке →

    Дальше спросят

  11. 11

    Срезы

    Срез не владеет данными — это окно над чужим массивом, и два окна могут смотреть в одни и те же ячейки. Поэтому запись через один срез видна во втором, а append в одних случаях дописывает в тот же массив, в других переезжает в новый — и именно поэтому он возвращает срез, а результат присваивают обратно.

    Полный ответ — в уроке →

    Дальше спросят

  12. 12

    Карты

    Карта — это пары «ключ — значение» с доступом по ключу, и отсутствующий ключ даёт не ошибку, а нулевое значение; отличить его от настоящего нуля позволяет форма v, ok := m[k]. Добавьте вторым предложением то, что спрашивают сразу следом: в переменной лежит указатель на структуру рантайма — поэтому карта, переданная в функцию, меняется у вызывающей стороны, и поэтому nil-карту можно читать, но не писать.

    Полный ответ — в уроке →

    Дальше спросят

  13. 13

    Строки и руны

    Строка в Go — неизменяемая последовательность байтов, а не символов, и корректный UTF-8 в ней не обязателен. Поэтому len считает байты, а s[i] даёт байт; for range декодирует UTF-8 и отдаёт кодовые точки с байтовыми смещениями, а на некорректном байте — U+FFFD и шаг в один байт. Устройство добавьте отдельно и как свойство реализации: в gc строка — указатель и длина, подстрока не копирует и потому удерживает весь массив, а += в цикле каждый раз копирует накопленное, отсюда квадратичная работа.

    Полный ответ — в уроке →

    Дальше спросят

  14. 14

    Интерфейсы

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

    Полный ответ — в уроке →

    Дальше спросят

  15. 15

    Получатель метода

    Метод с получателем-значением работает с копией, а метод с получателем-указателем может изменить исходное значение. Получатель — это первый аргумент метода, записанный слева, а аргументы копируются; отсюда и «метод ничего не изменил», и «тип не реализует интерфейс».

    Полный ответ — в уроке →

    Дальше спросят

  16. 16

    Встраивание

    Наследования в Go нет. Есть встраивание — поле без имени, чьи поля и методы можно вызывать напрямую; s.Log() — сокращение для s.Logger.Log(). Это композиция: внешний тип содержит встроенный, а не является им, и метод встроенного типа работает со своим значением, а не с внешним.

    Полный ответ — в уроке →

    Дальше спросят

  17. 17

    Ошибки

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

    Полный ответ — в уроке →

    Дальше спросят

  18. 18

    defer, panic, recover

    У отложенного вызова три момента, а не один. Регистрация и вычисление аргументов — в момент defer, выполнение — при выходе из функции; а если отложенных вызовов несколько, они идут в обратном порядке. Паника разворачивает стек, выполняя по дороге отложенные вызовы каждого кадра, и перехватить её можно только вызовом recover непосредственно в отложенной функции.

    Полный ответ — в уроке →

    Дальше спросят

  19. 19

    Context

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

    Полный ответ — в уроке →

    Дальше спросят

  20. 20

    Планировщик

    Горутина — не поток операционной системы, а единица работы самой программы на Go. Её запускает одно слово go, стоит она килобайты против мегабайтов у потока, а какая из них сейчас на процессоре — решает планировщик внутри процесса. Поэтому горутин бывают тысячи при единицах потоков, и поэтому GOMAXPROCS ограничивает не число горутин, а число выполняющихся одновременно.

    Полный ответ — в уроке →

    Дальше спросят

  21. 21

    Каналы

    Канал — это связь двух горутин, в которой отправка и приём встречаются: пока получателя нет, отправитель ждёт, и наоборот. Буфер это ожидание не убирает, а отсрочивает ровно на свою вместимость. Из закрытого канала читать можно — сначала дочитываются положенные значения; писать в него и закрывать его повторно нельзя. А ветки select выбираются равномерно случайно, а не сверху вниз.

    Полный ответ — в уроке →

    Дальше спросят

  22. 22

    sync и atomic

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

    Полный ответ — в уроке →

    Дальше спросят

  23. 23

    Гонки данных

    Гонка данных — это когда две горутины обращаются к одной ячейке памяти, хотя бы одна пишет, и между обращениями нет синхронизации. Лечится мьютексом, каналом или атомарной операцией. Находится флагом -race: go test -race, и в CI он должен быть.

    Полный ответ — в уроке →

    Дальше спросят

  24. 24

    Стек и куча

    В Go программист не выбирает между стеком и кучей — выбор делает компилятор, и вопрос у него один: переживёт ли значение свой кадр. Если доказать, что не переживёт, нельзя — куча. Поэтому возврат указателя на локальную переменную законен, а & в коде сам по себе о размещении не говорит ничего.

    Полный ответ — в уроке →

    Дальше спросят

  25. 25

    Сборщик мусора

    Сборщик возвращает память объектов, до которых от корней больше нет пути, и делает это одновременно с программой — останавливая её дважды за цикл и ненадолго. Поэтому пауза короткая и от размера кучи не зависит: у меня 16 и 256 мегабайт живых данных дали 39 и 48 микросекунд — рост в 1,22 раза при шестнадцатикратном росте кучи. Растёт при этом не пауза, а работа разметки — доля процессорного времени, и путают с паузой именно её.

    Полный ответ — в уроке →

    Дальше спросят

  26. 26

    Профилирование

    Снять профиль CPU — из бенчмарка через go test -cpuprofile или из сервиса через net/http/pprof — и открыть в go tool pprof -top. Профилировщик сэмплирующий, поэтому наверху окажутся функции, где программа проводит больше всего времени, а короткие вызовы могут не попасть вовсе. Частоту — 100 раз в секунду в go1.24.7 — называют как деталь реализации, а не как свойство языка.

    Полный ответ — в уроке →

    Дальше спросят

  27. 27

    Тестирование

    Тест — функция TestXxx(t *testing.T), табличный тест — срез случаев и одна проверка в цикле, каждый случай — подтест t.Run, его можно запустить отдельно через -run. t.Parallel пускает подтесты параллельно, а освобождать общие ресурсы надо через t.Cleanup. Гонки ищут go test -race, кеш обходят -count=1.

    Полный ответ — в уроке →

    Дальше спросят

  28. 28

    HTTP-сервер

    Обработчик — интерфейс http.Handler, ServeMux раскладывает запросы по путям, промежуточный слой — функция Handler → Handler. Сервер обслуживает каждое соединение в своей горутине (в HTTP/2 — каждый поток), поэтому общее изменяемое состояние обработчиков нужно защищать. В продакшене — свой http.Server с таймаутами и остановка через Shutdown.

    Полный ответ — в уроке →

    Дальше спросят

← Карта линии и итоговая проверка