javascript

Я участвовал в Advent of Code каждый год, начиная с 2021, и мне есть что сказать

  • суббота, 27 декабря 2025 г. в 00:00:03
https://habr.com/ru/companies/skbkontur/articles/980476/

Меня зовут Стас Федянин, я ведущий инженер-программист в Контуре. Решаю задачки в формате AoC с 2021, и стараюсь каждый год писать на новом для себя языке программирования — уже попробовал Kotlin, Zig, Dart, JavaScript и Gleam. На мой взгляд, несколько недель в декабре — это отличный период, чтобы сформировать базовое представление о языке, опробовать решение всех фундаментальных проблем, которые регулярно встречаются при программировании: операции со строками, списками и другими коллекциями, рефакторинг методов и отладка кода.

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

Что такое Advent Of Code? Это ежегодная активность от Эрика Вастла. С первого декабря на сайте Advent Of Code каждый день открывается новая задачка и толпы программистов помогают Санте спасать рождество, решая их и зарабатывая звёзды.

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

AoC 2021. Kotlin

В первый год своего участия я взялся за Kotlin, так как мне его порекламировал друг. JetBrains сделали репозиторий-шаблон для решения Advent of Code на этом языке. Нажал Use this template -> клац-клац -> склонировал репозиторий -> можно писать код, очень удобно. :)

Репозиторий с решениями

https://github.com/BurningMarshmallow/aoc-2021

Среда разработки

Код писал в IntelliJ IDEA от упомянутых JetBrains. Учитывая, что это их основной продукт, внутри очень много разных возможностей, по ощущениям мне хватило 5% от всего арсенала. 😅

Кусочек одного из решений
Кусочек одного из решений

Фишки языка

Если кратко, то от Kotlin остались приятные впечатления: код получается немногословный (много синтаксического сахара, точки с запятыми ставить не надо), читабельный, работает шустро. Пример программы.

Большую часть фишек я узнал в процессе решения задач, но ближе к концу декабря JetBrains написали гайд "Tips and Tricks for Solving Advent of Code". Из избранного:

windowed() для скользящего окна

В Advent of Code иногда пригождается функция скользящего окна размером в N — сначала элементы с 1 по N-й, потом со 2 по N+1-й, и так далее.

Also logging

В Kotlin есть функция области видимости also, которая принимает объект и выполняет блок кода в его контексте. С помощью неё можно добавить side-эффект в виде логирования объекта для отладки.

Есть и другие функции области видимости, подробнее тут. Но иногда с их помощью получается нечитаемый код, как и с любыми функциями с side-эффектами. :)

it: неявное имя единственного параметра

Если лямбда-выражение имеет только 1 параметр и компилятор способен самостоятельно определить сигнатуру, то объявление параметра можно опустить вместе с ->. В качестве названия параметра можно использовать it.

AoC 2022. Zig

В 2022 я решил поучаствовать на языке Zig. Цитируя Wiki:

Главная цель языка — конкурировать c (и стать лучше) C, и, в то же время, быть более простым, чем C++, Rust и другие.

Условно это C на стероидах, следовательно, программы работают быстро, но при этом меньше рисков выстрелить себе в ногу. Язык молодой (появился в 2015 году), активно разрабатывается, однако к концу этого года последняя версия языка всё ещё 0.15.2, до 1.0 не добежали (хотя в 2019 уже было видео The Road to Zig 1.0).

Разбор задачи

В 2022 мне посчастливилось записать на YouTube разбор задачи 14-го дня, по пути чуть больше рассказал про язык на конкретных примерах. :)

Репозиторий с решениями

https://github.com/BurningMarshmallow/aoc-2022

Среда разработки

Код писал в Visual Studio Code с плагином Zig. Плагин даёт подсветку синтаксиса и форматирование. Никаких плагинчиков для рефакторинга (выделения метода/переименования) я тогда не нашёл (спустя время фич стало побольше), а во время AoC засучил рукава и рефакторил по старинке. :)

Кусочек одного из решений
Кусочек одного из решений

Фишки языка

Псевдонимы для типов

В первые дни были непривычны названия примитивных типов по аналогии с C. Поэтому довольно быстро завёл псевдонимы для типов, благо, в Zig это делается легко:

Аллокаторы

По наследству от C Zig забрал ручное управление памятью (никаких скрытых выделений памяти :)), которую нужно выделять через специальные объекты-аллокаторы. Причём эти аллокаторы бывают разные: кто-то работает с фиксированным буфером (и, если размер выделяемой памяти известен заранее — это оптимальный вариант), кто-то умеет выделять память динамически. Подробнее о том, как выбрать подходящий аллокатор.

В коде это выглядит примерно так:

При этом всё, что было выделено, надо не забывать освобождать, иначе будет утечка памяти. Zig гордится тем, что он умеет находить подобные утечки ещё на этапе компиляции, пример из документации:

Есть возможность запланировать освобождение памяти после выхода из текущего scope-а с помощью конструкции defer (с её помощью можно откладывать выполнение и других функций, просто deinit() одна из самых частых потребностей), этим язык похож на Go.

Работа с ошибками

Другая ключевая особенность Zig, на которую создатели языка делают ставку — это работа с ошибками. По сути, каждая функция в вашей программе может быть одного из двух видов: безопасной (не возвращает ошибку) или опасной (может вернуть ошибку). Если функция опасная, это должно быть отражено в сигнатуре функции, например, с помощью Error union type (!u64 вместо u64). Пример из документации:

На уровне выше надо обработать ошибку, к примеру, получая значение через try .... Этим поведением язык напоминает тот же Go, я не сразу привык так работать с ошибками (и иногда казалось, что на это уходит лишнее время), но потом освоился, конкретно ! + try не сильно затрудняют написание кода.

AoC 2023. Dart

В 2023 в качестве языка для решения задач я выбрал Dart — язык программирования, созданный Google.

Почему именно он? Основную роль в выборе сыграл список Most loved languages из опроса Stack Overflow, где я выбирал язык от most к менее loved из тех, что я ещё не знаю, и которые мне было бы интересно изучить. Dart был top-1. :)

С другой стороны, у него несложный синтаксис для обучения (хотя нашлось несколько нюансов, которые смогли меня удивить), плюс я предполагал, что у меня будет хватать времени пописать визуализации на Flutter для пары дней Advent of Code (*но время всё-таки было ограничено, поэтому визуализациями я пожертвовал 😅).

Для краткого экскурса в язык рекомендую это 2-минутное видео на YouTube.

Репозиторий с решениями

https://github.com/BurningMarshmallow/aoc-2023

Среда разработки

Код писал в Visual Studio Code с плагином Dart. В отличие от плагина для Zig, здесь уже были все базовые средства для рефакторинга (Extract Method, Inline Variable, Rename), так что писать и отлаживать код было гораздо проще.

Кусочек одного из решений
Кусочек одного из решений

Фишки языка

Продвинутый pattern matching

В языке присутствует довольно продвинутый pattern matching. К примеру, есть возможность распаковывать значения из списка (очень удобно для простого парсинга):

Можно распаковывать даже сложные объекты:

Шаблоны можно применять в присваиваниях, циклах for и for-in, switch-case, а также в особенной конструкции if-case (условия тоже не обделили):

Подробнее про pattern matching в Dart.

Spread оператор

В Dart есть поддержка spread оператора (...), а также его дополнительной версии, которая умеет работать с нуллабельными коллекциями (...?) в списках, словарях и множествах. С помощью них можно лаконично описывать добавление всех элементов из одной коллекции в другую.

Например, так можно записать присвоение значений одного списка другому:

Пример использования null-aware spread оператора (...?):

Для больших подробностей и примеров по spread оператору можно обратиться к исходному proposal.

Cascade notation

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

При этом объект-результат может быть null, в таком случае следует использовать null-shorting cascade:

AoC 2024. JavaScript

В 2024 я решил расслабиться и выбрал JavaScript. :) Немного непривычной была работа с коллекциями (нужно было в явном виде вызывать values() для дальнейшей обработки), с BigInt чуть повозился, но в остальном всё прошло гладко. В качестве основы я взял репозиторий за 2023 год другого участника Advent of Code (@nanot1m), в нём была готовая удобная инфраструктура для работы с 2D-массивами, графами и продвинутый парсер входной строки.

Репозиторий с решениями

https://github.com/BurningMarshmallow/aoc-2024

Среда разработки

Код писал в Visual Studio Code, дополнительных плагинов не ставил, так как была вся база для рефакторинга (выделение метода, переименования и тд).

AoC 2025. Gleam

В этом году я захотел порешать задачки на свежем языке Gleam, который 4 марта 2024 вышел в стабильную версию 1.0.0. Язык  функциональный, и старается вобрать в себя хорошие практики из Erlang и Elixir (и, так же, как они, для рантайма использует BEAM), а также Rust, OCaml и других языков. Если хотите узнать больше — рекомендую послушать подкаст Podlodka или глянуть FAQ.

Репозиторий с решениями

https://github.com/BurningMarshmallow/aoc-2025

Среда разработки

Код писал в Zed, поставив официальное расширение Zed Gleam. К сожалению, LSP местами сырой, выделяя часть кода в отдельный метод с помощью авторефакторинга иногда разваливалась компиляция и приходилось править вручную, но со временем я приноровился.

Фишки языка

Нет return statement

В качестве возвращаемого значения используется последнее вычисленное значение в методе. Это чуть урезает количество используемых символов, при этом, на мой взгляд, читаемость не страдает:

Может возникнуть вопрос, а как быть с ситуациями, когда нужно в начале метода проверить некоторое условие, и сразу вернуть определённое значение (early return)? В таких ситуациях помогает конструкция use <- bool.guard(condition, value_to_return) (подробнее про bool.guard, про use).

Todo

Ключевое слово todo можно использовать, если часть кода ещё не написана. При компиляции Gleam выдаёт предупреждение — «todo found», чтобы напомнить, что в коде есть незавершённые участки, но при этом компиляция успешно проходит. Если же выполнение действительно дойдёт до todo, программа аварийно завершится и выведется сообщение:

Record pattern matching

Для объединения данных различных типов в один кастомный тип используются records. Для них можно использовать продвинутый pattern matching, позволяющий извлекать несколько полей в различные переменные, аналогично pattern matching в списке.

Можно использовать символ подчеркивания _ или синтаксис .. для удаления ненужных полей.

Выводы

За эти пять лет я попробовал довольно разные языки. 

  • Если сравнивать по комфорту: мне было очень комфортно писать на Kotlin и некомфортно на Zig (он слишком многословен для меня). 

  • Я восхищался композициями функций в Gleam, а также дизайном некоторых конструкций языка, при этом местами упирался в функциональную парадигму. 

  • С JavaScript и Dart мой комфорт был умеренный: всего хватало, хотя субъективно Kotlin был чуть приятнее. 

Были как сырые языки (тот же Zig, который всё ещё не выпустил версию 1.0), так и проверенные временем, как JavaScript. Это был классный опыт, который расширил мой кругозор в том, как можно писать код! :)

Призываю и вас в следующем году поучаствовать в Advent of Code, и взять для этого новый для себя язык программирования! И делитесь своим мнением об упомянутых в статье языках: согласны, не согласны?


В этом году молодые ребята из Контура, Сбера и Т-Банка вместе участвовали в АoC 2025, и все 12 дней делились впечатлениями об этом в нашем Telegram-канале. Несмотря на то, что Адвент уже позади, канал и чат живее всех живых — присоединяйтесь, чтобы оставаться на связи и не теряться. 🙌 Мы там даже уже запланировали активность на 1 января — Новогодний контест 2026. 😉 Всё начнётся в 15:00 по московскому времени, так что выспаться точно успеете!