А сегодня поговорим о том, как сделать код читаемым и тестируемым ^_^
Расскажите о своих приёмах, как вы улучшаете кодовую базу на проектах? Какие применяете методы, принципы, эвристики?
Я пока начну 🧶
Самое простое (и одновременно сложное 😃) — это нейминг.
Хорошие и внятные имена для переменных и функций — это очень мощный инструмент.
(Первая книга на тему, которую я прочёл — это «Читаемый код» Фаучера:
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.Моё любимое — не используйте аббревиатуры, пожалуйста 😃
twitter.com/crocodoyle/sta…
(А если используете, обязательно документируйте.)
Всё настраиваемое лучше сразу выносить в конфиги. Пусть даже по началу это будет объект в том же файле.
Это и читаемость улучшит, и тестируемость повысит (о тестах дальше).
Используйте паттерны проектирования.
Есть замечательная подборка паттернов на том же Гитхабе, очень советую посмотреть:
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:
- youtube.com/watch?v=1SGb-l…
- 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…
(Если в проекте не принято иначе, конечно.)
Я люблю проверять имена всех экспортируемых сущностей на понятность со стороны пользователя. «Если я буду импортировать эту функцию из модуля, я пойму, что она делает? какова область её ответственности? как её использовать?»
Часть треда снова ускакала не туда, простите :–/
twitter.com/jsunderhood/st…
Дальше, чтобы код был понятнее, его должно быть как можно меньше 😃
Всё, что можно может сделать браузер, лучше отдать ему — он сделает это лучше, оптимальнее и быстрее.
Нужно сериализовать форму?
Используем 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 тормозит на глубоких деревьях 😅
Завтра поговорим о том, как расти в разработке. Обсудим, что лучше:
- учиться в университете,
- окунуться в боевую разработку,
- найти ментора,
- читать книги,
- участвовать в опен-сорсе.