Duit Foundation | Никита Синявин

Duit Foundation | Никита Синявин 

Разработчик создает инструменты для разработчиков

7subscribers

27posts

Showcase and bundles

3
A bundle is a collection, compilation, playlist, or catalog of posts that saves you from searching through the blog and lets you buy the entire content series in a single click.
goals3
0 of 20 paid subscribers
Маркер того, что делаю все правильно
$0 of $1 246 raised
На разработку логотипа/маскота Duit.
$0 of $12 457 raised
Устраиваю "корпоратив" для самых активных контрибьюторов

Большая уборка

Ситуация: у вас на руках легаси проект. Хорошо, что в нем уже Dart v3 (и на том спасибо), плохо, что на дворе уже Dart v3.9. Все это приправлено полным игнорированием существования понятия код-стайла. И я даже не говорю о критически важной для проекта документации (например, структуры байткода)! 
В проекте можно встретить большое кол-во кода, который, мягко говоря, не соответствует хоть каким-то правилам кодирования. Но пути назад уже нет! Поэтому придется как-то с этим легаси жить и мириться. Или нет?
Казалось бы, code style и линтер, вещь настолько базовая, что на нее даже не обращаешь внимания при старте нового проекта (ведь все сеты правил уже придуманы), может отсутствовать и наносить вполне ощутимый урон всему процессу разработке. Причем как индивидуальной, так и командной.

Стратегия

Как было сказано выше, код проекта альтернативно хороший. И глобально, у нас есть лишь две стратегии того, как мы можем с таким кодом обойтись. 
- Переписать все с нуля. Хочется, но нужно держать свои шаловливые рученки подальше. Создавать новые файлики и пихать в них код в надежде, что старые проблемы уйдут сами собой - безумие. Выполнить огромный пласт работы в попытках достичь эфемерного "лучше" - так себе стратегия в долгосрок.
- Эволюционный рефакторинг. Не берем на себя лишнего - потихоньку переписываем проект по частям, с четкими целями на итерацию, постепенно вникая в подкапотную машинерию и находя пути для оптимизаций. С моей точки зрения - зрелый подход, который требует безумной дисциплины. Очень легко перейти границу между плановым рефакторингом и обнаружить себя в 3 часа ночи на кухне в одних трусах, кружкой кофе в руке, в бесплодных попытках порешать проблему, которую сам себе и создал.
Определившись с методами исполнения, я решил взяться за благородную миссию - вычистить код от очевидного мусора и привести его к виду, который хотя бы не больно читать. Плохой код - дело поправимое. И вот что мы сделаем в первую очередь, обозначив границы первой итерации:
- Повысим минимальную версию Dart SDK для пакета. Доступ к современным библиотекам, языковые фичи - все это поможет нам уже в ближайшем будущем.
- Настроим линтер и исправим его замечания. Повысим качество кодовой базы, что позволит, как минимум, сохранить глаза.
- Добавим barrel-файлы и исправим структуру импортов.

Dart SDK has matter

Текущая минимальная версия Dart SDK в проекте - v3.0.0 (май 2023 года). Если говорить по фактам - это уже ооооочень старая версия SDK. И на начальном этапе рефакторинга мысль перейти на более актуальную версию кажется очень даже неплохой и перспективной.
Во-первых, мы получаем килотонны крутых языковых фич: Records, Patterns, модификаторы классов, extension types и многие-многие другие. Весь этот арсенал способен сделать сложный код проще.
Во-вторых, мы получаем более высокую производительность. Каждая новая версия Dart SDK несет с собой оптимизации компилятора и стандартной библиотеки языка. Более эффективный нативный код, более умная сборка мусора, новые прагмы и возможности - лишь малая часть преимуществ от использования свежих версий SDK.
В-третьих, мы получаем доступ к современным и поддерживаемым библиотекам. Конечно, не хотелось бы, чтобы язык программирования тянул с собой половину pub.dev, но что-то может по итогу пригодиться :)
По итогу я повысил версию Dart в проекте до v3.6.0. И не спроста именно до нее, ведь:
- Эта версия уже содержит большинство интересующих меня фич и механик
- Эта версия поддерживает pub workspace, что будет полезно при дальнейшей реорганизации проекта
- Эта версия относительно новая, но при этом позволит встраиваться в бОльшее количество Flutter-приложений. Dart 3.6 => Flutter 3.27
Это первый и очень важный шаг, который окупится многократно на всех последующих этапах рефакторинга.

Lint, lint them all

Важно: линтер (который реплицирует командный code style) - это не про эстетику (точнее, не только про нее), не про запятые, не про табы против пробелов. Это про мышление.
- Отсутствие code style - гарантированная амнезия, поскольку никто не в курсе, как же надо писать тот или иной код. Читабельность падает, сложность восприятия растет.
- Отсутствие единых правил делает рефакторинг, мягко говоря, затруднительным.
- Когда разные части кодовой базы противоречат друг другу - даже современная IDE не сможет помочь с поиском нужной сущности.
В противовес непричесанному коду, код красивый приятно читать и, что важно, с ним комфортно работать сторонним разработчикам. Мы же помним, что Hetu++ все-таки open source проект :)
Нехитрым образом я проапдейтил (читай добавил) целый сет правил линтера в проект. Вот они: https://github.com/Duit-Foundation/hetu_xx/blob/main/packages/hetu_xx/analysis_options.yaml 
После прогонки линтера и исправления его замечаний проект буквально “встал с колен”: снизилось количество шумных диффов, повысилась читаемость и навигация по коду.

Структура импортов

Проект большой, содержит много директорий и сущностей, которые безобразнейшим образом переплетены друг с другом с помощью импортов. И ладно, если на этапе с линтером мы добились того, что используем package imports и они хотя бы не режут глаз, то теперь перед нами встала другая проблема - лапша из директив, которую невозможно читать.
Вместо лаконичного импорта сущностей пакета (читай - внутренней директории) - ворох импортов каждой отдельной сущности. Они пересекаются, дублируются и совершенно не поддаются анализу.
Тут я включил режим паладина и зачитав молитву начал приводить импорты в порядок, создавая barrel-файлы. Эдакие сборники всех экспортируемых сущностей из директории, что позволяет заменить десяток импортов всего лишь одной короткой директивой.
Но главный бенефит этого мува заключается даже не в том, что импорты теперь выглядят короче и красивее, во как! Вся соль в том, что теперь анализировать связи между модулями стало гораздо проще! В будущем это нам очень поможет и не раз.

Красота по-китайски

Китайские представления о прекрасном меня знатно озадачили и работа меня слегка потрепала. В общем, не обошлось без трудностей:
- Размер проекта и его лайаут. С одной стороны - это целый язык программирования поверх Dart. С другой стороны он настолько запутан, что кажется монструозно большим. Неудобный, нелогичный, местами избыточно раздробленная структура проекта создала немало предпосылок для полета на Татуин в процессе работы.
- Отсутствие авто-фикса для ряда проблем. То есть заменить кавычки с одинарных на двойные мы можем, а сделать то же самое с интерполированной строкой (`"${x.x}{y.y}"`) - нет???
- Лапша из импортов. Вручную удалять и импортировать - единственный адекватный путь для того, чтобы организация зависимостей действительно работала, как надо.

Границы уборки

Как было сказано выше - очень легко перейти границу итерации, не имея максимально четкого плана. Я столкнулся и интересным классом проблем - линтер подсвечивает проблему, но исправления являются потенциально опасными. Давайте разберемся на примере:
Простая функция, которая выглядит вполне безобидно, но является большой занозой в заднице.
1. Она является вложенной и определена внутри метода. Само по себе это не круто.
2. Она замыкает в себе параметры функции верхнего уровня!
Может быть я чересчур распереживался и на самом деле ситуация не так страшна? Не думаю. Меня могли бы успокоить тесты, эти тесты даже есть. Но они не тестируют ровным счетом ничего (об этом - в будущих статьях)! Я как разработчик не могу быть уверен, что даже минимальные исправления не приведут к развалу всей машинерии :)
Поэтому пришлось, скрипя зубами, выделить подобные места в отдельный список, к которому предстоит вернуться потом...

Первые итоги

Всего лишь линтер, обновлённый SDK и пара десятков barrel-файлов - казалось бы, мелочи. Но именно они превратили полный хаос в чуть более управляемую систему. 
Код стал понятнее, структура - прозрачнее, а я - спокойнее :)
Мы сняли первый слой пыли, но впереди - куда более сложная работа: тесты, архитектура, оптимизация. 
С этого момента начинается настоящая (ну, почти) инженерия.
Go up