jsunderhood Profile picture
Коллективный аккаунт для фронтенд-разработчиков.

Apr 28, 2021, 70 tweets

А сегодня поговорим о том, как сделать код читаемым и тестируемым ^_^

Расскажите о своих приёмах, как вы улучшаете кодовую базу на проектах? Какие применяете методы, принципы, эвристики?

Я пока начну 🧶

Самое простое (и одновременно сложное 😃) — это нейминг.

Хорошие и внятные имена для переменных и функций — это очень мощный инструмент.

(Первая книга на тему, которую я прочёл — это «Читаемый код» Фаучера:
bespoyasov.ru/blog/the-art-o… )

Хорошее имя для сущности: короткое, но полное и описательное.

На Гитхабе есть классный чеклист по неймингу сущностей:
github.com/kettanaito/nam…

Я люблю проверять имена всех экспортируемых сущностей на понятность со стороны пользователя.

«Если я буду импортировать эту функцию из модуля, я пойму, что она делает? какова область её ответственности? как её использовать?»

Например, внутри модуля пользователя функция `create` выглядит органично, не дублирует контекст, короткая, описывает действие:

Но если функцию импортировать и начать использовать, то уже не так очевидно:

А вот тут — наоборот:

Годный шаблон для названий можно вот тут посмотреть:
github.com/kettanaito/nam…

Шаблон A/HC/LC:
prefix? + action (A) + high context (HC) + low context? (LC)

В идеале по названию переменной должно быть понятно, функция это, булево значение или что-то ещё.

Для булевых значений можно использовать префиксы:
should, is, has, will, did.

Для функций — первым словом лучше поставить глагол действия:
get, set, update, remove, delete, push…

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

`let mounted = false` — тут сложно подумать, что mounted что-то кроме boolean.

Моё любимое — не используйте аббревиатуры, пожалуйста 😃


(А если используете, обязательно документируйте.)

Всё настраиваемое лучше сразу выносить в конфиги. Пусть даже по началу это будет объект в том же файле.

Это и читаемость улучшит, и тестируемость повысит (о тестах дальше).

Используйте паттерны проектирования.

Есть замечательная подборка паттернов на том же Гитхабе, очень советую посмотреть:
github.com/kamranahmedse/…

Как правило, большую часть наших задач уже решили за нас до нас 😃

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

Паттерны удобно использовать в связке с SOLID. Некоторые из принципов прямо подразумевают какой-то из паттернов.

Мы с twitter.com/dex_157 в нашей книжке о принципах SOLID добавляли разделы с паттернами под каждый принцип.

ota-solid.vercel.app

Чаще рефакторите код, но без фанатизма ¯\_(ツ)_/¯

Писать код и рефакторить — это как «писать» и «редактировать статью», сложно делать одновременно.

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

А ещё (самое сложное для меня) надо купировать перфекционизм 😅

Отрефакторить до идеала сложно, а чаще всего не нужно.

Пользуемся правилом 20/80 — 20% усилий должны приносить 80% результата.

ru.wikipedia.org/wiki/Закон_Пар…

Чтобы рефакторить безопасно, пишите тесты.

Физерс в «Эффективной работе с легаси» писал о том, как можно рефакторить старый неповоротливый комбайн:

bespoyasov.ru/blog/working-e…

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

Покрыть швы тестами, а уже потом начинать рефакторинг.

Я пробовал, это и правда работает.

Как найти хороший шов?

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

В хорошо написанном коде такие места выделены явно, потому что модули слабо зацеплены.

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

В книжке много техник, как работать с кодом, когда вы уже определились со швом.

Типа, как заменить зависимость:
- одну зависимость за раз;
- определить, какую зависимость хотим поменять;
- покрыть шов тестами;
- вынести текущий код в отдельный класс;
- заменить класс на другой.

Раз уж мы о книгах заговорили, то ещё «Чистый код» Мартина могу посоветовать:
bespoyasov.ru/blog/clean-cod…

Хотя и считаю, что «Эффективная работа с легаси» шире, глубже и практичнее ¯\_(ツ)_/¯
bespoyasov.ru/blog/working-e…

Ещё офигенная и практичная книга — “Debug it!” («Отдебажь это!», простите за кустарный перевод):
bespoyasov.ru/blog/debug-it/

Она вся состоит из рецептов, как работать с багами из-за непонятного кода.

Там даже содержание — это уже рецепт! 😃

Глава 1 — Исследовать обстановку
Глава 2 — Воспроизвести проблему
3 — Определить причину
4 — Исправить
5 — Как не допустить такой ошибки в будущем
...и т. д.

Внутри каждой главы есть списки действий под ситуацию. Короче, рекомендую.

От читаемости к тестированию! 🦸
Используйте TDD 🙂

TDD мне экономит кучу времени. К нему надо привыкнуть, потому что сперва приходится «вывернуть мозги», но он быстро окупается.

С ним:
- Исчезает проблема «дополнительной работы»
- Писать тесты и рефакторить входит в привычку
- Рефакторить безопаснее
- Видно сущности, делающие слишком много
- API проектируется до реализации, и-за чего становится удобнее

Я недавно делал доклад о TDD на Frontend-crew:
-
- bespoyasov.ru/talks/podlodka…

Там рассказываю об этом подробнее:
- как внедрить на проекте;
- как использовать;
- как сделать тесты проще.

Как упростить тесты при работе по TDD:

- Чаще использовать чистые функции
- Обращать внимание на зацепление кода
- Тестировать только свой код
- Использовать удобные инструменты
- Потратить время на удобную инфраструктуру

С TDD можно искать пахнущий код. Код пахнет, если:

- Тестов слишком много по сравнению с другими модулями
- Описание ссылается на несвязанные вещи
- Ожидание от теста оформлено невнятно
- Подготовка теста слишком сложная
- Тест проверяет детали реализации
- Тест всегда зелёный

Там же я рассказываю, как помочь увидеть пользу от TDD (и тестов вообще) руководству.

Коротко: говорим только об измеряемых параметрах, проводим исследования, сравниваем.

Я ещё когда-то написал книжку с примером разработки крестиков-ноликов: bespoyasov.ru/ttt-tdd/

Там показываю, как переходить по циклу TDD, в какой момент приступать к рефакторингу, на что обращать внимание.

TDD можно использовать и при работе с React тоже.

Недавно я проводил воркшоп об использовании TDD при разработке React-приложений:
bespoyasov.ru/talks/?full#1

Он длинный, около 5 часов, но там я прохожусь по всем основным концепциям, а именно, как тестировать:

- ...Функций бизнес-логики.
- Функции, возвращающие случайные значения.
- Простые компоненты.
- Кастомные хуки, их связь с компонентами.
- Работу со стором.
- Асинхронные функции и вызовы API.
- Пользовательские действия: клик, клавиатура.

Так-с, пора работать!
Продолжим во время обеда 😃

Продолжим!

Чем же так хорош TDD для тестируемости и читаемости?

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

Напомню стандартный цикл разработки по TDD. В нём 3 этапа:

- красная зона — на ней мы пишем тест, проверяем, что он падает по нужной причине;
- зелёная — пишем реализацию, которая тест проходит;
- синяя — рефакторим код и тесты.

Когда мы пишем сперва тест, мы автоматически следим за тем, чтобы вызывать функцию было удобно.

Под этим я подразумеваю и аргументы, которые в функцию надо передать, и зависимости, которые нужно создать перед тестом.

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

Когда мы проверяем, с какой причиной падает тест, он становится таким, которому можно доверять.

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

Правильные тесты сразу же закрывают написанную функциональность.

Рефакторить код становится безопасно: если мы что-то по пути поломаем, мы узнаем об этом мгновенно по покрасневшим тестам.

А ещё TDD — это единственный способ безопасно (или даже вообще хоть как-то) отрефакторить легаси 😃

Кроме TDD тестируемость улучшит Dependency Injection.

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

DI — это не обязательно контейнеры и всё такое страшное, можно использовать кустарный DI через объект с зависимостями в конце.

Об этом я тоже недавно писал пост:
bespoyasov.ru/blog/di-ts-in-…

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

Мне нравится руководство по тип-аргументам в C#:
docs.microsoft.com/en-us/dotnet/c…

(Если в проекте не принято иначе, конечно.)

Часть треда снова ускакала не туда, простите :–/

Дальше, чтобы код был понятнее, его должно быть как можно меньше 😃

Всё, что можно может сделать браузер, лучше отдать ему — он сделает это лучше, оптимальнее и быстрее.

Нужно сериализовать форму?
Используем FormData:
developer.mozilla.org/en-US/docs/Web…

Там есть конечно трудности со всякими кастомными контролами, но процентов 80 случаев можно покрыть только ей.

Нужно сделать ленивую загрузку картинок?
Испольуем `loading="lazy"`:
developer.mozilla.org/en-US/docs/Web…

Опять же, полифилим только для тех, у кого это не работает и только если надо 🙂

Ну вы поняли 😃
Кнопки — кнопками, короче.

Непонятное стороннее API лучше прятать за фасадом, чтобы намерение было выразительнее:
github.com/kamranahmedse/…

Если язык позволяет, то для выбора из нескольких вариантов используйте pattern matching:
- docs.microsoft.com/en-us/dotnet/c…

(Это ещё и безопаснее в некоторых случаях.)

Для TS тоже есть реализации!
- github.com/nrdlab/pattern…

Снова перерыв на работу 🙂
Вечером продолжим!

Продолжим!

Ещё один приём, который улучшает читаемость — CQS, command-query separation.

- bespoyasov.ru/blog/commands-…

Запрос — функция, которая возвращает результат и не имеет сайд-эффектов.

Команда — функция, которая меняет состояние системы и ничего не возвращает.

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

Ещё один приём рефакторинга, который улучшает читаемость — вынесение деталей в метод или функцию:
- bespoyasov.ru/blog/missing-a…

Это чем-то похоже на фасад, но над своим кодом:
- github.com/kamranahmedse/…

В этом случае мы как бы прячем детали за названием функции.

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

Детали мы будем смотреть уже отдельно, если потребуется. А смысла функции отражён в названии.

Ещё одна важная часть читаемого кода — это ошибки и стек-трейс.

За сообщениями об ошибках тоже надо следить, потому что это такая же часть кодовой базы.

- bespoyasov.ru/blog/make-erro…

Чем чище и понятнее ошибки, тем проще дебажить код ¯\_(ツ)_/¯

Кстати, вот мы говорили про вынесение кода в функцию или метод.

Можно делать это не руками, а встроенными инструментами рефакторинга в IDE.

Например, вот для VS Code инструкция:
- code.visualstudio.com/docs/editor/re…

Ну и конечно — документация!

В ней нам стоит описывать не «как оно работает», а «почему оно работает именно так».

Ответ на вопрос «почему?» — это важный кусок контекста задачи, который может потеряться. Лучше его зафиксировать в документации.

А на вопрос «как оно работает», считаю, должны отвечать тесты 🙂

Подведём итоги за сегодня 🙂

- Хорошая читаемость снижает когнитивную нагрузку при чтении кода.
- Паттерны проектирования, эвристики и рефакторинг помогают улучшить читаемость.
- Тесты помогают рефакторить безопасно.
- Чтобы рефакторить легаси, удобно использовать швы.
- ...

- ...Грамотная архитектура улучшает тестируемость.
- TDD также улучшает тестируемость и помогает разбивать легаси на модули.
- DI — не обязательно контейнеры, можно проще.
- React тормозит на глубоких деревьях 😅

Завтра поговорим о том, как расти в разработке. Обсудим, что лучше:

- учиться в университете,
- окунуться в боевую разработку,
- найти ментора,
- читать книги,
- участвовать в опен-сорсе.

Share this Scrolly Tale with your friends.

A Scrolly Tale is a new way to read Twitter threads with a more visually immersive experience.
Discover more beautiful Scrolly Tales like this.

Keep scrolling