Go для Erlang-разработчика: Фокус на практике и трудоустройстве

Интенсивный курс, адаптированный для удержания внимания: короткие сессии, мгновенная практика и сравнение парадигм Erlang и Go. Вы освоите синтаксис, модель конкурентности и инструменты, необходимые для прохождения собеседований.

1. Быстрый старт: императивный синтаксис, строгая типизация и отказ от виртуальной машины BEAM

Быстрый старт: императивный синтаксис, строгая типизация и отказ от виртуальной машины BEAM

Привет! Если ты читаешь это, значит, ты решил расширить свой арсенал и добавить Go к своим навыкам Erlang. Это отличный выбор для рынка труда.

Я знаю, что у тебя СДВГ, поэтому мы не будем тратить время на воду. Мы будем двигаться быстро, четко и фокусироваться на том, что действительно отличается. Твой мозг привык к паттернам Erlang: неизменяемость, рекурсия, акторы. Go сломает эти паттерны, но взамен даст тебе мгновенную обратную связь от компилятора и простую ментальную модель.

Давай разберем три главных шока, которые ждут тебя при переходе: императивность, типы и отсутствие BEAM.

1. Императивный мир: Забудь про рекурсию (почти)

В Erlang ты привык описывать что должно быть сделано. В Go ты описываешь как это сделать, шаг за шагом.

Переменные изменяются

В Erlang N = N + 1 — это математическая ложь и синтаксическая ошибка (если N уже связано). В Go — это норма жизни.

> Изменяемость (mutability) — это не баг, а фича в Go. Память переписывается. Это экономит ресурсы, но требует внимания.

Смотри разницу:

Erlang (Рекурсия):

Go (Цикл):

В Go нет хвостовой рекурсии (Tail Call Optimization). Если ты попытаешься написать бесконечный цикл через рекурсию, как в Erlang, ты получишь переполнение стека (stack overflow). Для повторения действий всегда используй for. В Go это единственный вид цикла.

!Слева: Рекурсивный подход Erlang. Справа: Итеративный подход Go с изменяемым состоянием.

Почему это хорошо для СДВГ?

Императивный код читается сверху вниз. Тебе не нужно держать в голове стек вызовов. Ты видишь строку, и она делает ровно то, что написано. Это снижает когнитивную нагрузку при чтении чужого кода.

2. Строгая статическая типизация: Компилятор — твой лучший друг

В Erlang типизация динамическая. Ты часто узнаешь об ошибке, когда процесс падает с function_clause или badmatch в продакшене.

В Go компилятор не даст тебе собрать программу, если типы не сходятся. Это мгновенная обратная связь, которая так важна для нашего мозга. Ты получаешь дофаминовое подкрепление каждый раз, когда код компилируется успешно.

Объявление типов

В Go ты обязан сказать, чем является переменная (или позволить компилятору вывести это явно).

Структуры вместо Рекордов

В Erlang у нас есть Records (которые на самом деле кортежи) и Maps. В Go основным строительным блоком данных является Struct (структура).

Erlang:

Go:

Если ты попытаешься положить строку в поле Age, Go просто не скомпилируется. Никаких сюрпризов в рантайме.

Интерфейсы: Утиная типизация

Это самая мощная фича Go. Тебе не нужно явно писать implements, как в Java. Если структура имеет методы, которые требует интерфейс, она автоматически его реализует.

Представь, что у тебя есть поведение GenServer. В Go ты бы описал интерфейс:

Любая структура, у которой есть метод Work() error, считается Worker. Это дает гибкость динамических языков, но с защитой статической типизации.

3. Отказ от BEAM: Прямой доступ к железу

Это, пожалуй, самый важный ментальный сдвиг.

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

Go компилируется в нативный машинный код.

Что это значит на практике?

  • Единый бинарник: Результат сборки (go build) — это один файл. В нем уже есть все библиотеки и рантайм Go. Его можно просто скопировать на сервер и запустить. Никаких установок Erlang/OTP на целевой машине.
  • Скорость запуска: Программа на Go стартует за миллисекунды. BEAM требует времени на разогрев.
  • Управление памятью: В Erlang у каждого процесса своя куча (heap). Сборка мусора происходит по-процессно и не блокирует всю систему. В Go сборщик мусора (Garbage Collector) общий для всей программы. Современный GC в Go очень быстрый, но он работает иначе, чем в BEAM.
  • !Go работает напрямую с операционной системой, минуя виртуальную машину.

    Где "Let it crash"?

    В Erlang мы привыкли: "Пусть падает, супервизор поднимет".

    В Go паника (panic) — это катастрофа. Если горутина паникует и это не перехвачено, падает вся программа, а не только один процесс.

    Поэтому в Go принят другой подход к ошибкам: явная проверка.

    Для Erlang-разработчика это выглядит многословно. Но для рынка труда это стандарт надежности. Ты всегда знаешь, где и почему может сломаться твой код.

    Резюме для твоего мозга

    Давай закрепим основные отличия, чтобы они отложились в памяти:

    | Характеристика | Erlang (BEAM) | Go (Native) | | :--- | :--- | :--- | | Синтаксис | Декларативный, функциональный | Императивный, процедурный | | Переменные | Неизменяемые (Immutable) | Изменяемые (Mutable) | | Повторение | Рекурсия | Цикл for | | Типизация | Динамическая, сильная | Статическая, сильная | | Исполнение | Байт-код в VM | Машинный код (бинарник) | | Ошибки | Let it crash | Проверяй if err != nil |

    Почему это поможет тебе найти работу?

    Бизнес любит Go за предсказуемость.

  • Проще нанимать: Go-код читается одинаково, кто бы его ни написал.
  • Проще деплоить: Один бинарный файл вместо сложной структуры релизов OTP.
  • Производительность: Там, где Erlang берет конкурентностью, Go берет сырой скоростью вычислений (CPU bound задач).
  • Ты уже знаешь распределенные системы благодаря Erlang. Добавив к этому производительность и популярность Go, ты становишься уникальным специалистом, способным решать задачи, которые не под силу "чистым" гоферам или эрлангистам.

    В следующей статье мы нырнем в то, ради чего мы здесь собрались — Горутины и Каналы, и посмотрим, как они соотносятся с процессами Erlang и receive.

    Готовься, будет интересно!

    2. Смена парадигмы: горутины и каналы как альтернатива процессам и почтовым ящикам

    Смена парадигмы: горутины и каналы как альтернатива процессам и почтовым ящикам

    Привет! Мы продолжаем наш спринт по изучению Go. В прошлой статье мы разобрались с синтаксисом и типами. Теперь пришло время затронуть святая святых для любого Erlang-разработчика — конкурентность.

    Ты привык мыслить процессами. Твой мозг автоматически декомпозирует задачу на независимые акторы, которые шлют друг другу сообщения. У меня для тебя две новости. Хорошая: Go тоже построен на идее CSP (Communicating Sequential Processes), как и Erlang. Плохая (или интересная): реализация кардинально отличается.

    Для твоего мозга с СДВГ это будет похоже на смену правил в любимой видеоигре. Механика похожа, но кнопки другие, и физика мира изменилась. Давай разберемся, как не выстрелить себе в ногу и получить удовольствие от go func().

    Горутины: Легче, чем пух, опаснее, чем кажется

    В Erlang ты создаешь процесс через spawn. В Go ты запускаешь горутину (goroutine) через ключевое слово go.

    Сходства

  • Легковесность: И процессы Erlang, и горутины очень дешевые. Горутина на старте занимает около 2 КБ стека (который растет динамически). Ты можешь запустить миллионы горутин, и твой ноутбук не расплавится.
  • Планировщик: И там, и там есть свой планировщик (scheduler) в user-space. Операционная система видит лишь несколько потоков, а рантайм языка жонглирует тысячами задач поверх них.
  • Фундаментальное различие: Память

    Вот здесь нужно остановиться и вдохнуть поглубже.

    * Erlang: Процессы полностью изолированы. У каждого своя куча (heap) и свой сборщик мусора. Если один процесс падает или сходит с ума, он не портит память соседа. Share nothing. * Go: Горутины работают в общем адресном пространстве. Все горутины видят одни и те же глобальные переменные и могут иметь указатели на одни и те же структуры в куче.

    !Слева: Изолированная память процессов Erlang. Справа: Общая память горутин в Go.

    Это значит, что в Go возможны гонки данных (data races). В Erlang ты физически не можешь изменить переменную в памяти другого процесса. В Go — запросто, и часто случайно.

    > Do not communicate by sharing memory; instead, share memory by communicating. > — Роб Пайк

    Эту мантру ты будешь слышать постоянно. Хотя Go позволяет лезть в общую память (используя мьютексы, о которых позже), идиоматичный путь — использовать Каналы.

    Запуск горутины

    Синтаксис максимально прост, что дает мгновенный дофаминовый отклик:

    В Erlang у тебя есть PID. В Go у горутины нет идентификатора. Ты не можешь послать сообщение "вон той горутине по ID". Ты не можешь убить конкретную горутину снаружи. Ты можешь только попросить её остановиться через канал или контекст.

    Каналы: Типизированные трубы вместо почтового ящика

    В Erlang у каждого процесса есть почтовый ящик (mailbox). Ты кидаешь туда все что угодно: атомы, кортежи, списки. Это бездонный мешок.

    В Go каналы (channels) — это типизированные проводники данных. Представь их как трубы, по которым могут лететь только объекты определенной формы.

    Типы каналов

  • Небуферизированные (Unbuffered): Это поведение по умолчанию. Отправка блокируется, пока кто-то не начнет читать. Чтение блокируется, пока кто-то не начнет писать. Это точка синхронизации (rendezvous point).
  • Буферизированные (Buffered): Имеют емкость. Отправка не блокируется, пока есть место. Чтение не блокируется, пока есть данные.
  • Erlang (Mailbox):

    Go (Channel):

    Главные отличия от Mailbox

    * Строгая типизация: Ты не можешь послать int в канал типа string. Компилятор ударит по рукам. * Блокировка: В Erlang отправка ! почти никогда не блокирует процесс. В Go отправка в заполненный или небуферизированный канал останавливает горутину. Это мощный инструмент для управления потоком данных (backpressure), которого в Erlang приходится добиваться сложными путями. * Закрытие: Канал можно закрыть (close(ch)). Чтение из закрытого канала возвращает нулевое значение типа и флаг того, что канал закрыт. Попытка записи в закрытый канал вызывает панику.

    Select: Твой новый receive

    В Erlang receive ищет сообщения в ящике, сопоставляя их с паттернами сверху вниз.

    В Go конструкция select позволяет ждать операций на нескольких каналах одновременно.

    Важный нюанс для Erlang-разработчика

    В Erlang receive детерминирован порядком паттернов. В Go, если готовы несколько кейсов одновременно, select выберет один из них случайным образом. Это сделано специально, чтобы избежать голодания (starvation) одних каналов за счет других.

    Паттерны: Как перестроить мышление

    1. Worker Pool вместо создания процессов на лету

    В Erlang мы часто делаем spawn на каждый чих. В Go это тоже допустимо, но часто используется паттерн Worker Pool, где фиксированное число горутин разбирают задачи из общего канала.

    !Паттерн Worker Pool: несколько горутин читают из одного канала.

    2. Done Channel вместо Link

    В Erlang, если процесс падает, сигнал летит по линкам. В Go для отмены операций используется context или специальный канал done.

    Опасности и "СДВГ-ловушки"

    Твой мозг захочет скорости. Go дает эту скорость. Но вот где ты можешь споткнуться:

  • Deadlocks (Взаимные блокировки): Если все горутины спят, ожидая каналов, рантайм Go запаникует и убьет программу: fatal error: all goroutines are asleep - deadlock!. В Erlang система просто бы висела.
  • Утечки горутин: Если ты запустил горутину, которая пишет в канал, который никто не читает, она зависнет навсегда. Сборщик мусора её не соберет. Это утечка памяти. Всегда думай: "Как эта горутина завершится?".
  • Почему это круто для трудоустройства?

    Работодатели ищут людей, которые понимают конкурентность, а не просто синтаксис. Твой опыт в Erlang бесценен. Ты уже понимаешь, что такое состояние гонки, дедлоки и распределенные системы.

    В Go ты получаешь инструменты, которые позволяют писать высокопроизводительный код, работающий на всех ядрах процессора, с минимальными накладными расходами. Знание того, как мапить процессы Erlang на горутины Go, делает тебя идеальным кандидатом для переписывания легаси или создания высоконагруженных микросервисов.

    В следующей части мы разберем Интерфейсы и композицию — то, что заменяет нам поведения (behaviours) и наследование, и делает архитектуру Go такой гибкой.

    3. Архитектура приложений: интерфейсы, композиция структур и явная обработка ошибок вместо Let it crash

    Архитектура приложений: интерфейсы, композиция структур и явная обработка ошибок вместо Let it crash

    Привет! Мы уже разобрали синтаксис, типы и даже научились запускать горутины. Но сейчас мы подходим к самому сложному моменту для твоего мозга, привыкшего к Erlang. Мы будем говорить об архитектуре.

    В Erlang архитектура часто диктуется OTP: у тебя есть дерево супервизоров, gen_server, gen_fsm. Ты заполняешь колбэки (handle_call, handle_cast), и система работает. В Go нет OTP. Нет супервизоров (в том виде, к которому ты привык). Нет наследования поведений.

    Для человека с СДВГ это может звучать как хаос. «Где мои рельсы? Где структура?» — спросишь ты. Не паникуй. В Go структура создается через три кита: Интерфейсы, Композицию и Явную обработку ошибок.

    Давай построим ментальную модель, которая поможет тебе писать поддерживаемый код и проходить собеседования.

    1. Интерфейсы: Утиная типизация на стероидах

    В Erlang у нас есть behaviours. Ты пишешь -behaviour(gen_server), и компилятор бьет тебя по рукам, если ты не реализовал нужные функции. Это контракт.

    В Go интерфейсы — это тоже контракты, но они неявные (implicit). Это значит, что тебе не нужно писать implements или указывать конкретный интерфейс при создании структуры.

    Правило утки

    > Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка.

    В Go это работает буквально так:

    В этот момент и File, и Database автоматически стали Saver. Ты можешь написать функцию, которая принимает Saver, и ей будет все равно, что ты туда передашь — файл или базу.

    Почему это круто для СДВГ?

    Тебе не нужно проектировать всю иерархию типов заранее (что часто вызывает паралич анализа). Ты просто пишешь код. Если ты видишь, что две разные штуки делают одно и то же, ты постфактум объявляешь интерфейс и используешь его. Это снижает когнитивную нагрузку на старте проекта.

    !Иллюстрация принципа интерфейсов: неважно, какая структура снаружи, главное, что она имеет нужный метод.

    2. Композиция вместо Наследования (и вместо OTP callbacks)

    Erlang-разработчики редко думают в терминах наследования классов, но gen_server — это по сути шаблонный метод. Ты наследуешь поведение сервера.

    В Go нет наследования. Вообще. Child extends Parent — это не про Go. Вместо этого Go использует Композицию (Composition) и Встраивание (Embedding).

    Представь, что ты собираешь робота. Вместо того чтобы говорить «Этот робот является потомком модели Т-800», ты говоришь «У этого робота есть рука от Т-800 и нога от C-3PO».

    Встраивание структур

    Это называется promoted methods. Методы встроенной структуры становятся доступны на уровне внешней структуры. Это выглядит как наследование, но это просто синтаксический сахар для srv.Logger.Log(...).

    Для твоего мозга это означает: собирай сложные объекты из простых кирпичиков. Не строй глубокие иерархии, в которых легко запутаться. Строй плоские, понятные конструкции.

    3. Ошибка — это просто значение (Прощай, Let it crash)

    Вот мы и добрались до самой болезненной точки миграции.

    В Erlang философия: Let it crash. Если что-то пошло не так (файл не открылся, сеть отвалилась) — падай. Супервизор заметит смерть процесса, перезапустит его, и система самоисцелится.

    В Go философия: Handle it explicitly.

    Если горутина запаникует (panic), и ты это не перехватишь (через recover, что делается редко), упадет вся программа. Весь твой бинарник. Все миллион пользователей отключатся.

    Поэтому в Go ошибки — это обычные значения, которые нужно проверять.

    Паттерн if err != nil

    Почти каждая функция, которая делает что-то опасное, возвращает два значения: результат и ошибку.

    Почему это хорошо для рынка труда и твоего кода?

  • Локальность: Читая код, ты видишь, где может возникнуть проблема и как она решается. Тебе не нужно искать супервизор на пять уровней выше, чтобы понять логику перезапуска.
  • Надежность: Ты контролируешь каждый шаг. Для высоконагруженных систем, где Go сияет, «падать и подниматься» может быть слишком дорого. Дешевле обработать ошибку на месте.
  • Для СДВГ это работает как чек-лист. Ты пишешь вызов функции, ставишь запятую, пишешь err, и твой мозг получает сигнал: «Ага, здесь может быть засада, надо её обработать». Это держит тебя в фокусе.

    !Сравнение подходов Let it crash и Explicit Error Handling.

    4. Defer: Уборка за собой

    В Erlang процессы изолированы. Если процесс умирает, его память и ресурсы (обычно) очищаются. В Go, если ты открыл файл или соединение с БД и забыл закрыть — это утечка ресурсов.

    Чтобы не держать в оперативной памяти (твоей головы) необходимость закрыть файл в конце функции, используй defer.

    defer гарантирует выполнение очистки. Это твой страховочный трос.

    Резюме: Как перестроить мышление

    Давай соберем все вместе в таблицу перевода понятий:

    | Концепция | Erlang | Go | | :--- | :--- | :--- | | Абстракция | Behaviours (gen_server) | Interfaces (Reader, Writer) | | Связь кода | Callbacks | Composition (Embedding) | | Ошибки | Let it crash (Exceptions) | Values (if err != nil) | | Ресурсы | Авто-очистка при смерти процесса | Явное закрытие (defer Close()) |

    Домашнее задание для мозга

    Когда будешь писать код на Go, лови себя на мысли: «А кто это перезапустит, если оно упадет?». И отвечай себе: «Никто. Я должен проверить err прямо здесь».

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

    В следующей статье мы поговорим про Инструментарий: go mod, линтеры и то, как настроить IDE, чтобы она писала половину кода за тебя (что критически важно для нас с тобой).

    Держи фокус, ты справляешься отлично!

    4. Real-world задачи: создание высоконагруженных HTTP-сервисов, работа с JSON и базами данных

    Real-world задачи: создание высоконагруженных HTTP-сервисов, работа с JSON и базами данных

    Привет! Мы прошли через синтаксис, горутины и интерфейсы. Твой мозг уже перестроился с рекурсии на циклы и с акторов на каналы. Теперь пришло время для самого вкусного — практики, за которую платят деньги.

    В мире Erlang ты привык к Cowboy, Yaws или Mochiweb. Ты знаешь, что поднять HTTP-сервер — это создать приложение OTP, настроить супервизоры, подключить зависимости через rebar3. Это надежно, но требует много «церемоний».

    В Go всё иначе. Здесь «батарейки включены». Мы напишем полноценный сервис, используя только стандартную библиотеку (ну, почти). Для твоего СДВГ это будет подарком: результат виден сразу, кода мало, всё работает быстро.

    Сегодня мы соберем три кита бэкенда: HTTP, JSON и Базы данных.

    1. HTTP-сервер: Cowboy больше не нужен

    В Go стандартная библиотека net/http — это промышленный стандарт. В отличие от многих других языков, где стандартный сервер годится только для тестов, в Go на net/http держат трафик Cloudflare и Google.

    Самый быстрый старт

    В Erlang тебе нужно описать маршруты, скомпилировать, запустить релиз. В Go:

    Всё. Это готовый веб-сервер.

    Как это работает внутри? (Модель конкурентности)

    В Erlang Cowboy создает новый процесс (actor) на каждое TCP-соединение.

    В Go сервер net/http запускает новую горутину на каждый входящий запрос.

    !Модель one-goroutine-per-request в Go.

    Тебе не нужно думать о пулах потоков или спавне процессов. Это происходит автоматически. Если придет 10 000 запросов, Go запустит 10 000 горутин. Учитывая, что они занимают по 2 КБ стека, это абсолютно штатная ситуация.

    Handler — это просто интерфейс

    Помнишь, мы говорили про интерфейсы? Вся магия HTTP в Go строится вокруг одного интерфейса:

    Любая структура, у которой есть метод ServeHTTP, может быть сервером. Это дает невероятную гибкость для создания middleware (прослоек), но об этом ты узнаешь чуть позже.

    2. JSON: Прощай, боль с кортежами

    Вспомни работу с JSON в Erlang. Библиотека jiffy или jsx. Ты получаешь карты (maps) или списки кортежей [{<<"key">>, <<"value">>}]. Тебе нужно вручную валидировать поля, проверять наличие ключей. Это утомляет.

    В Go работа с JSON — это декларативное удовольствие благодаря тегам структур.

    Struct Tags

    Ты описываешь структуру данных и вешаешь на поля специальные ярлыки (tags). Пакет encoding/json использует рефлексию (reflection), чтобы прочитать эти теги и заполнить структуру.

    Marshalling и Unmarshalling

    * Marshal — превратить структуру в JSON (строку байт). * Unmarshal — превратить JSON в структуру.

    Для твоего мозга это огромное облегчение. Ты один раз описал форму данных, и дальше работаешь с ней как с родным объектом языка. Если JSON не соответствует структуре (например, вместо числа пришла строка), Decode вернет ошибку, которую ты обработаешь.

    3. Базы данных: database/sql и пул соединений

    В Erlang ты наверняка использовал poolboy для управления пулом соединений к PostgreSQL или MySQL. Ты вручную брал воркера из пула, делал запрос и возвращал его обратно (или использовал обертку, которая это делает).

    В Go стандартная библиотека database/sql уже содержит пул соединений.

    Абстракция драйвера

    Go использует интерфейсы для драйверов БД. Ты пишешь код, используя database/sql, и он почти не меняется при смене базы (Postgres, MySQL, SQLite). Тебе нужно только импортировать нужный драйвер.

    Выполнение запросов

    Здесь всё императивно и явно. Помни про defer для закрытия строк!

    !sql.DB автоматически управляет пулом соединений.

    4. Context: Управление временем жизни запроса

    Это концепция, которой нет в Erlang в явном виде (там таймауты часто "вшиты" в gen_server:call). В Go для управления отменой операций и таймаутами используется пакет context.

    Представь: пользователь сделал HTTP-запрос, твой сервер начал тяжелый запрос в БД, а пользователь закрыл браузер. Зачем продолжать грузить базу?

    В Go объект http.Request уже содержит контекст. Мы обязаны передавать его дальше — в базу данных, во внешние API.

    Для высоконагруженных систем это критически важно. Это спасает ресурсы сервера от бесполезной работы.

    Собираем всё вместе: Твой первый микросервис

    Давай объединим знания. Вот как выглядит типичный обработчик в Go-сервисе:

  • Принять запрос.
  • Декодировать JSON в структуру.
  • Валидировать данные.
  • Сходить в БД (с контекстом!).
  • Вернуть ответ JSON.
  • Почему это продается?

    Работодатели любят Go за то, что этот код скучный.

    * Нет магии макросов. * Нет сложного состояния процессов. * Типы данных базы мапятся на структуры один-в-один. * Контекст гарантирует, что зависшие запросы не убьют базу.

    Ты, как Erlang-разработчик, приносишь сюда понимание того, почему это важно. Ты знаешь цену падениям и таймаутам. Go просто дает тебе более простые и стандартные инструменты для решения этих проблем.

    В следующей статье мы поговорим про инструментарий и экосистему: модули, линтеры и то, как настроить IDE, чтобы писать код со скоростью мысли.

    5. Инструментарий профи: тестирование, профилирование pprof и подготовка к вопросам на собеседовании

    Инструментарий профи: тестирование, профилирование pprof и подготовка к вопросам на собеседовании

    Привет! Мы прошли долгий путь. Ты уже знаешь синтаксис, понимаешь, как работают горутины, и умеешь строить архитектуру без супервизоров. Твой код компилируется, и это уже дает мощный дофаминовый заряд.

    Но чтобы получить оффер на Senior Go Developer (а с твоим бэкграундом в Erlang на меньшее соглашаться не стоит), нужно не просто писать код, который работает. Нужно уметь доказать, что он работает правильно, быстро и не течет по памяти.

    Для нашего мозга с СДВГ инструменты Go — это спасение. Они стандартизированы, встроены в язык и не требуют долгих настроек. Никаких войн между eunit и common_test. Есть только go test.

    Сегодня мы разберем «джентльменский набор» гофера: тестирование, профилирование и то, о чем тебя обязательно спросят на собеседовании.

    1. Тестирование: Table-Driven Tests

    В Erlang ты привык к eunit для модульных тестов и common_test для интеграционных. В Go все тесты живут рядом с кодом. Если у тебя есть файл service.go, тесты лежат в service_test.go.

    Базовая анатомия

    Table-Driven Tests (Табличные тесты)

    Это паттерн, который ты полюбишь. Он идеально структурирует хаос. Вместо того чтобы копипастить assert десять раз, мы объявляем структуру тестовых данных и бежим по ней в цикле.

    Для СДВГ это работает как заполнение бланка: ты просто добавляешь новые кейсы в таблицу, не трогая логику проверки.

    !Визуализация паттерна Table-Driven Tests: данные отделены от логики проверки

    Бенчмарки

    В Erlang замерить производительность функции бывает нетривиально. В Go это встроено. Просто создай функцию, начинающуюся с Benchmark.

    Запуск: go test -bench=.

    2. Профилирование: Где мой Observer?

    В Erlang у нас есть великий и ужасный Observer. Ты запускаешь его и видишь все процессы, память, графики. Это лучшее, что есть в BEAM.

    В Go нет GUI из коробки, но есть pprof. Это инструмент, который позволяет заглянуть под капот работающей программы с минимальным оверхедом.

    Как подключить?

    Если у тебя веб-сервис (а мы делали его в прошлой статье), просто добавь один импорт:

    Теперь твой сервис отдает диагностику по адресу /debug/pprof/.

    Что искать?

    Ты можешь снять профиль CPU, памяти (heap), блокировок (mutex) и горутин.

    Чтобы визуализировать это, используй команду:

    Это откроет в браузере граф потребления памяти. Ты увидишь, какая функция выделяет больше всего объектов.

    Для Erlang-разработчика важно: В Erlang утечки памяти редки (процесс умер — память очистилась). В Go утечки горутин — частая проблема. Если ты видишь в pprof, что количество горутин растет и не падает — ты где-то забыл выход из select или чтение из канала.

    3. Линтеры: Автоматический Code Review

    Наш мозг часто пропускает мелкие детали. Запятые, неиспользуемые переменные, ошибки в обработке ошибок.

    В Erlang есть Dialyzer. Он мощный, но медленный и иногда непонятный.

    В Go стандартом индустрии является golangci-lint. Это агрегатор, который запускает сразу десятки линтеров.

    Он проверит:

  • errcheck: не забыл ли ты проверить ошибку.
  • govet: подозрительные конструкции.
  • staticcheck: стиль и баги.
  • gocyclo: цикломатическую сложность (чтобы ты не писал функции на 500 строк).
  • Установи его и запускай перед каждым коммитом. Это сэкономит тебе часы на код-ревью.

    4. Подготовка к собеседованию: Вопросы-ловушки

    Ты идешь на позицию Go-разработчика с бэкграундом Erlang. Тебя будут гонять по внутренностям, чтобы понять, перестроил ли ты мышление.

    Вот топ-5 тем, где тебя будут ловить.

    Вопрос 1: Как работает Garbage Collector в Go по сравнению с Erlang?

    Твой ответ: В Erlang сборка мусора происходит per process. Когда один процесс собирает мусор, остальные работают. Это дает предсказуемую латентность (latency).

    В Go сборщик мусора общий для всей программы. Это Stop-the-World (STW), но в современных версиях Go паузы составляют микросекунды. Go использует Concurrent Mark and Sweep алгоритм. Он работает параллельно с твоим кодом, используя часть CPU.

    Вопрос 2: Устройство Слайсов (Slices)

    Тебя спросят: «Что будет, если передать слайс в функцию и изменить его?»

    Твой ответ: Слайс — это легковесная структура из трех полей: указатель на массив (ptr), длина (len) и емкость (cap). При передаче в функцию копируется заголовок слайса. Если мы изменим элемент по индексу (s[0] = 1), исходный массив изменится. Но если мы сделаем append, и емкости не хватит, Go выделит новый массив, и исходный слайс во внешней функции не увидит изменений.

    !Схематичное устройство Slice: Pointer, Length, Capacity

    Вопрос 3: Планировщик (Scheduler) и M:N

    Твой ответ: Как и в Erlang, в Go есть планировщик в user-space. Модель называется GMP: * G (Goroutine): сама горутина. * M (Machine): реальный поток ОС. * P (Processor): логический контекст (обычно равно числу ядер CPU).

    В отличие от Erlang, где планировщик вытесняющий (preemptive) по количеству редукций (вызовов функций), в Go вытеснение стало полноценным только в последних версиях (через сигналы ОС). Раньше горутина могла захватить процессор в плотном цикле навсегда.

    Вопрос 4: Каналы и буферизация

    Тебя спросят: «Что будет, если писать в nil канал? А в закрытый?»

    Шпаргалка: * Запись в nil канал: вечная блокировка (deadlock, если нет других горутин). * Чтение из nil канала: вечная блокировка. * Запись в закрытый канал: panic. * Чтение из закрытого канала: возвращает zero-value и false.

    Вопрос 5: Интерфейсы и nil

    Это классика. var i interface{} = (*int)(nil). Равен ли i nil?

    Твой ответ: Нет! Интерфейс внутри — это пара (type, value). В данном случае это (*int, nil). Сам интерфейс не nil, потому что у него заполнен тип. nil интерфейс — это (nil, nil).

    Стратегия на собеседовании

    Не скрывай свой опыт в Erlang. Наоборот, используй его как козырь.

  • Ты понимаешь конкурентность глубже, чем средний джун-гофер.
  • Ты знаешь, что такое распределенные системы.
  • Ты понимаешь цену ошибкам.
  • Когда тебя спрашивают про каналы, проводи параллели с mailbox, но сразу уточняй отличия (типизация, блокировки). Это покажет, что ты не просто выучил синтаксис, а понимаешь суть.

    Заключение курса

    Мы прошли путь от «Где мои рекурсии?» до «Обожаю if err != nil».

    Go — это язык прагматиков. Он не пытается быть самым умным или самым красивым. Он пытается быть самым эффективным инструментом для доставки ценности бизнесу. Для человека с СДВГ это идеальный баланс: быстрый результат, строгие правила, которые не дают отвлечься, и огромный спрос на рынке.

    Твоя задача сейчас — взять пет-проект (тот самый HTTP-сервис из прошлой статьи), покрыть его тестами, прогнать линтером и выложить на GitHub. Это и будет твоим билетом на собеседование.

    Удачи, коллега! Встретимся в продакшене.