На стороне своих правил в ESlint

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! Меня зовут Владимир Земсков, я работаю в B2C билайна, в команде билайн Про, где пишу бэк-офис для нашей системы. Мы помогаем нашей рознице продавать лучше и больше. Для соблюдения нужного уровня качества кода в билайне мы используем и ESlint, и тесты, и ревью, и особый тип — внутренние договорённости. 

Когда я пришёл в компанию, то мне захотелось найти ESlint-плагин, который бы помог автоматизировать часть работы. Я поискал, не нашёл, и в итоге решил написать свой. Из всей этой истории и родился сегодняшний рассказ. В первой части мы обсудим договорённости и их важность для разработки. А во второй — поговорим про то, как эти договорённости переносить в ESLint, как это упростит жизнь и действительно ли это это так сложно, или можно что-то упростить по пути.

Что там с договорённостями

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

При этом он может на основе договорённостей из прошлого проекта писать код. А ещё нам становится легче онбордить нового разработчика. Все мы понимаем, что разработчик приходит в команду с каким-то своим бэкграундом. Ну, если это совсем не какой-то зеленый джун. При наличии у нас договоренностей такой разработчик сможет с ними ознакомиться и писать код не так, как он привык, а так, как нужно нам

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

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

Договорённости для React

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

Поэтому, если вам нужно использовать много хуков, то вы можете декомпозировать свой компонент на несколько более мелких компонентов, и тогда уже использовать вместе с этим компонентом хуки. Либо вынести, например, в отдельный хук предоставление публичного API через return, тогда у вас значительно сократится сам компонент и станет легче для понимания.

Второе — правило трёх DOM-ов. Важно отметить, что это не относится к React-компонентам. Компонентов может быть много, но стилизованных DOM-ов, например, div, section, нужно стараться писать как можно меньше. Мы в команде остановились на трех. Конечно, в UI-ките это может не сработать, но для бизнес-проектов и для продуктового кода (например, для того же бэк офиса) оно сейчас хорошо работает.

Легковесные props. Можно сравнить компоненты с функциями. И если у вас ходят аргументы, в которых массивы какие-то, большие объекты, и в целом этих аргументов становится много, то это очень неудобно, так что стоит ограничивать пропсы. И я думаю, что в целом это можно настраивать и договариваться.

Компоненты != вёрстка. Если в составном компоненте у вас есть какая-то вёрстка, то лучше ее просто вынести отдельно для того, чтобы можно было её использовать и не засорять компонент с какой-то логикой.

Бизнес-логика != разметка. В данном случае это название компонентов. Если вы делаете какой-то компонент, то не стоит его называть, допустим, «кнопка». Если вы делаете кнопку покупки, не стоит называть ее «кнопкой покупки». И, соответственно, пропсы лучше не называть “purchase”. 

Children first. Для декомпозиции мы можем использовать либо random props, либо Children first. Например, если мы хотим добиться более легких пропсов, можно прокинуть в функции компонента через random props, а можно и через Children first. Но в целом стоит придерживаться того, чтобы прокидывать с children, потому что так у вас будет строиться иерархическая структура, это нагляднее и удобнее

Отсутствуют глобальные классы CSS. Думаю, что с этой проблемой уже давно в фронтенд-коммьюнити всё решили, есть и CSS-модули, и CSS/JS. В целом глобальные классы могут зааффектить вёрстку в неожиданном месте, поэтому не стоит их делать. Лучше для каждого компонента прописать свои CSS-классы. 

Слой layout — думаю, у многих такое есть в приложениях, когда отдельно выделяются хедеры, основной layout, и при этом layout содержит только флаги, которые говорят, что что-то надо отрисовать. И, кроме отступов, других CSS-стилей не содержится.

Договорённости для Redux

Использовать connect вместо useSelector и useDispacth. Это позволит нам разгрузить render-функцию, вынести логику в connect и легче всем этим управлять.

Отсутствуют «слепые» спреды в reducer-ах. Они должны менять все данные в state, названия reducer на основе данных, а не компонентов. Это про то, что не стоит привязываться к компонентам, а лучше делать их на основе именно данных.

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

Вычисляемые значения находятся в селекторах. Благодаря этому мы можем сделать минимизацию через reselect, например, и тоже вынести это в отдельный слой. Легче управлять и легче понимать, что у нас там достается.

Экшены не являются сеттерами. 

Читаемые типы экшенов. 

Экшены не дублируются. 

Тут всё понятно — компонент, которому нужен стор, соответственно, подключен к стору, чтобы не пробрасывать через 10 компонентов процессы и пропсы. 

Объект в mapDispatchToProps вместо функции, если есть такая возможность.

Почему важно автоматизировать правила?

Во-первых, правила могут потеряться. Вообще, когда я пришел в билайн, так и случилось. Можно, конечно, их добавлять в MR, что тоже сделано на некоторых проектах, но при таком раскладе у вас всё равно остается ментальная нагрузка на ревьюера. Он должен смотреть их у себя, освежить в памяти, потом проверять весь код. А если, например, у нас будет есть ESlint-плагин, то сразу видно, что здесь есть, например, disable. И сразу вопрос а почему disable? И, собственно, легче уже становится проверять,

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

При этом важно понимать, что мы живем в реальной жизни, и действительно покрыть все 100% кейсов правилами бывает довольно тяжело. Поэтому, если есть возможность, то можно и не покрывать 100% кейсов. 

ESlint

Думаю, что многие из вас уже с ним знакомы и сталкивались в работе. Скорее всего, даже заполняли .eslintrc. В целом он состоит из ядра и плагинов. Плагины представляют собой набор правил, который определяет — правильный код или неправильный код, соответствует он стандартам или нет. Также в них можно передавать параметры, настраивать исправления. Скорее всего, вы использовали подобное — либо в баше через флаг фиксы запускали, либо в IDE quick fix в VScode делали. 

В целом, ESlint строит AST и затем, используя правила конфигурации, определяет, соответствует узел AST правилу или не соответствует, проверяются и различные аспекты кода. Это названия переменных, неправильное использование ключевых слов, стилевые ошибки и так далее.

AST-дерево

Картинки для наглядности взял из этого поста на Хабре. Это то, как вообще ESlint видит нашу программу. AST-дерево — это структура данных, в котором каждый узел это либо переменные, либо объявления. Здесь мы можем увидеть, что если import declaration это как раз импорты, variable declaration в объявлении переменных, это функции экспорт дефолта.

С одной стороны, это кажется довольно сложным для восприятия и для того, чтобы написать ESlint-плагин. Но есть такой инструмент, который называется AST-Explorer, который буквально всю рутину по работе с AST-деревом берет на себя.

Что здесь важно? Важно выбрать парсер, тот, который у вас используется на проекте, потому что можно столкнуться с оличиями в построении AST-дерева. В целом, если честно, я такого особо не заметил. Но рекомендую все-таки использовать тот парсер, который у вас стоит.

AST-explorer
AST-explorer

Давайте пройдемся немного по AST-explorer. В левом верхнем углу мы пишем код. И при этом, если мы нажмём на код, то у нас автоматически в правом верхнем углу подсветится нода AST-дерева, которая используется. Это работает и в обратную сторону. Вы можете идти по этому AST-дереву, изучать его, искать то, что вам нужно.

В нижнем левом углу можно написать свое правило, и в нижнем правом углу уже будет оно автоматически также сразу срабатывать — прописывать ошибку, подсвечивать ноду, в которой произошла ошибка. В целом очень удобно, рекомендую. По факту вам не нужно самому в голове что-то представлять, к чему там обратиться и прочее. Просто идёте на AST-explorer, ставите нужный парсер и смотрите, что там к чему, как всё работает. 

Как только я решил написать свой плагин, я подумал — наверное, это какая-то очень сложная вещь, и тут нужны суперзнания в computer science и не только. AST-дерево фактически — это экспорт объекта, который имеет несколько полей. Сейчас мы по ним пройдемся. Конечно, здесь в большей степени буду пересказывать документацию. Вы можете зайти на сайт ESLint-плагина и увидеть то же самое, там в целом всё доступно и интересно написано.

Архитектура программы в ESlint
Архитектура программы в ESlint

Meta — это какая то мета-информация.

Type — я всегда использовал problem. Но также есть suggestion и layout, которые используются для того, чтобы работать с пробелами, с точками и запятыми.

Docs — в пользовательских плагинах использовать необязательно, оно нужно для ESlint-а, для сайта, там отображается документация. Но я рекомендую использовать это чисто для наглядности, чтобы потом вы могли к этому вопросу вернуться.

Fixable говорит о том, может ли плагин исправлять что-то и вносить автоисправления. Если ему проставить код, то сможет.

Schema — там описываются параметры, которые вы можете использовать позже и которые задаются как раз в .eslintrc-файле.

И самое важное — это create, который принимает в себя контекст. По факту весь код у нас сосредоточен в этой функции. В return мы можем обратиться к AST-нодам, например, к импортам или JSXOpeningElement. И если вам надо посчитать количество дивов, допустим, если у вас произошла ошибка, то у контекста есть свойство report, в котором как раз можно описать эту ошибку и дать ссылку на ноду, в которой произошла эта ошибка. И если есть автоисправления, также и есть функция fix. Вы с этой нодой можете что-то сделать — поменять название и прочее.

В целом же, если вы хотите засетапить проект, то можно использовать npm-пакет от ESlint. Там под капотом идет yaml, который за вас все файлы вам соберет в нужную структуру, в нужные файлы и подключит все необходимые пакеты. Также вы можете, конечно же, скопировать с гитхаба очень много опенсорс-решений для ESLint, есть много плагинов, можете посмотреть, как всё устроено. Для того чтобы тестировать, есть npm-пакет rule-tester в целом, ничего необычного нет.

Есть свойство valid, где вы передаете валидный плагин, есть invalid, насколько я помню, где вы проверяете, что сообщение выводится правильно для невалидного кода. Если вы писали юнит-тесты, думаю, тоже никаких проблем с этим не возникнет.

Рекомендую добавлять документацию. У нас для неё развёрнут selfhosted confluence. Но можно на гитхабе сделать github pages, например, и добавить туда документацию и ссылки.

Если вы захотите писать для React, то я вам рекомендую посмотреть, например, в официальный репозиторий React. Из интересного — там хуки просто по названию определяются и все. 

Можно обойтись вообще без плагина, если вдруг есть у вас такие кейсы. 

В ESlint есть такое свойство, no-restricted-syntax, оно по факту работает как CSS-селектор, чтобы было понятно для фронтендеров, но по AST-дереву. Есть ситуации, в которых его не стоит использовать, например, когда имеется накопительный эффект, то есть, например, количество дивов, которые вам надо сосчитать. Или если у вас есть какая-то сложная логика, которую лучше протестировать — тут тоже лучше загнать её в rule-tester , чем потом по AST-дереву вручную все тестировать и отлаживать.


Что же до плана развития — сейчас плагин существует и активно работает у меня в проекте, придерживаясь трёх правил для React. Вообще, я хочу добавить туда ещё проекты и автоматизировать ещё больше правил. Само собой, с учётом того, что не надо добавлять все 100% кейсов.

Выводы

Очень важно иметь договорённости — они улучшают работу разработчика, помогают выводить продукты быстрее, и писать более качественный код.

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

Ну и в целом, ESlint — отличный инструмент, который легко расширяется (можно даже ChatGPT для этого использовать, он выдаст болванку, которую можно будет допилить).

Полезные материалы по теме:

  • Как работает JS: абстрактные синтаксические деревья, парсинг и его оптимизация

  • ESLint. Анатомия правил линтинга: разбираем структуру, создаём собственное правило для React-приложения

  • Спецификация на GitHub

PS В скором времени мы немного поменяем подход в этом плане, о чём тоже напишем пост

Источник: https://habr.com/ru/companies/beeline_tech/articles/797937/


Интересные статьи

Интересные статьи

Мы с радостью сообщаем об общей доступности (GA) PostgreSQL 14 на платформе Azure с опцией Hyperscale (Citus). Насколько нам известно, это первый случай, когда крупный облачный провайдер объявляет GA ...
В кодинге главное — не кодинг Как вы думаете, что такое программирование? Написание кода? Написание хорошего кода? Нет. Это только часть истины. Программирование — это не про кодинг. П...
Безопасность важна. Никто не хочет выступать против безопасности. Поэтому все замалчивают проблему с npm audit. Но кто-то должен сказать. Думаю, придётся мне. npm audit работает при...
Правильный учет оплачиваемого времени критически важен для многих сфер. Желаете ли вы улучшить процедуры биллинга, или ищете возможность сэкономить затраты, необходимо по...
К 2020 году вы не могли не заметить, что миром правят данные. И, как только речь заходит о работе с ощутимыми объёмами, появляется необходимость в сложном многоэтапном ко...