За годы работы программистом я просмотрел, возможно, сотни кодовых баз. Слишком много, чтобы посчитать точно. Я много мучился с пониманием того, где в большинстве случаев находится значимый код. Обычно просьба подсказать, на что я должен обратить внимание, и указания в тикетах подталкивают меня вперед. Медленно, но уверенно, я буду понимать, что делает код. И вы тоже поймёте. У некоторых людей это получается лучше, а у некоторых — медленно. От этого не должно быть стыдно. В большинстве случаев код сложен. Но я нашёл простой инструмент, который облегчит вам задачу. Он называется code-complexity, и вы можете использовать его следующим
образом:
npx code-complexity . --limit 20 --sort ratio
# Вы можете также использовать --filter '**/*.js', чтобы применить паттерн для фильтрации файлов
Он выведет похожий на этот результат:
file | complexity | churn | ratio |
---|---|---|---|
src/cli.ts | 103 | 8 | 824 |
test/code-complexity.test.ts | 107 | 7 | 749 |
.idea/workspace.xml | 123 | 6 | 738 |
Здесь будут показаны самые большие и наиболее часто изменяемые файлы. Вероятность того, что эти файлы имеют ключевое значение для понимания приложения, довольно высока. Прочитайте их и разберитесь. Подробно о том, что означают данные из таблицы, будет объяснено далее.
Сложность и текучесть
В этой главе я объясню концепты сложности (complexity) и текучести (churn), когда дело касается кода. Это основа для понимания методики, которую мы тут используем, чтобы улучшить понимание кодовой базы.
Что такое Сложность?
Сложность может быть определена разными способами. Как правило, для оценки сложности кода используется уровень вложенности функций. Код с небольшими функциями и скомпонованным поведением обычно более читабельный и лёгкий для понимания. Таким образом, можно сказать, что сложный код состоит из нескольких глубоко вложенных функций, и в основном это действительно так. Однако вложенность трудно отслеживать, поэтому надо найти другую метрику.
Длинные функции обычно находятся в больших файлах. Если люди помещают много всего в одну функцию, то они также часто складывают всё в один файл. Так что в теории мы могли бы взять за метрику и количество строк кода. Существует множество пакетов утилит, которые решают эту задачу. Один из них называется sloc. Он выводит количество строк кода в файле. Но не используйте его напрямую: инструмент, о котором я упомянул ранее, включает его по умолчанию.
В заключение можно сказать, что сложные файлы либо супер-вложенные, либо супер-длинные. Одна из этих вещей обычно идёт вместе с другой, что отлично, потому что анализ длины файла, как правило, легче, чем анализ вложенности.
Что такое Текучесть?
Текучесть немного сложнее для объяснения. Но давайте начнём с чего-нибудь. Текучий файл — это файл, в котором много изменений. Но что это значит?
В файле происходит много изменений, когда, очевидно, много людей изменяют его. Но как кто-то может это измерить? История git'а говорит нам, как часто файл фиксировался. Так что мы можем быть уверены, что файл, вероятно, будет изменён. Обычно это значит, что файлы такого типа являются основной точкой приложения. Проблема, однако, заключается в том, что часто сюда включаются конфигурационные файлы, но вы можете просто исключить их из анализа.
Чему может научить нас Сложность + Текучесть?
Сейчас, после изучения понятий сложности и текучести, мы можем сфокусироваться на их комбинации. Файлы, которые чаще всего изменяются и являются действительно сложными, должны подвергнуться рефакторингу. И в большинстве случаев естественно то, что такие файлы могут быть ядром приложения. Основная логика пишется в них напрямую или в связанных файлах. Так что давайте посмотрим, как мы можем анализировать их дальше.
Детальная проверка файлов
Моя методика детальной проверки файлов довольно проста. Сначала я просматриваю файл и проверяю, как называются экспортируемые функции. В идеале, я записываю их. Внутренние функции в начале не важны для понимания. Получив представление обо всех экспортируемых функциях, я в первую очередь проверяю, есть ли какие-нибудь unit-тесты. Если у функции есть параметры, то я также стараюсь их записать. С типами TypeScript и Flow становится гораздо проще получить общее представление о структуре.
Губка Боб детально проверяет код
Unit-тесты — хороший для старта подход, позволяющий увидеть, как работают функции. Чтобы понять функции, вам, наверное, достаточно посмотреть на вход, имя функции и на то, что она возвращает. В большинстве случаев вам помогают в этом типы, а unit-тесты показывают крайние случаи для функции, и как они могут быть использованы. Так что во многом этого достаточно, чтобы понять функцию. По крайней мере, если вы знаете язык программирования. Если вам хочется глубже погрузиться в функцию, то так и сделайте, но вам совсем не обязательно заниматься этим. Почему? Объяснение в следующей главе.
Почему не нужно понимать каждую деталь?
Детальное понимание функции может быть важно. Но в ходе онбординга более важны многие другие вещи. Вы не сможете понять каждый бит приложения за короткий промежуток времени, но понимание основных частей должно дать вам представление о том, где выполняется основная логика приложения.
С этими знаниями вы можете нырять в ваши первые задачи. В идеале, команда должна была подготовить небольшие задания, чтобы дать вам приятный онбординг. Если это не так, спросите менеджера или старших разработчиков в вашей команде: подходит ли вам какая-либо из текущих задач. Убедитесь, что донесли до них полученные вами знания о кодовой базе, чтобы они понимали, насколько глубоко вы разобрались.
Хорошей идеей для первой задачи будет также организовать парное программирование с другими разработчиками из команды. Обязательно скажите им, что вы хотите больше печатать, а они будут больше в качестве наблюдателя, для того чтобы вы научились самостоятельно ориентироваться в кодовой базе. Благодаря такому онбордингу и простым тикетам, вам не придётся вдаваться в подробности. Детали кода будут обнаружены уже в ходе этапа реализации во время исправления багов или добавления нового функционала. Чем больше тикетов вы сделаете, тем больше подробностей вы узнаете о кодовой базе. Но оглядывайтесь назад на текучесть и сложность, потому что они могут меняться со временем.
Отладка деталей?
Теперь работа над кодовой базой будет связана ещё с одной большой вещью — отладкой. В ходе работы над первыми задачами вы, вероятно, уже научитесь, как запускать приложение локально, как запускать unit, интеграционные и e2e тесты, если они есть. Они становятся жизненно важными, когда вы реализовываете что-то, потому что добавление тестов позволит убедиться, что приложение работает так, как от него ожидается. Зачастую эти тесты охватывают много кода и являются в некотором роде абстрактными. В таких случаях вам нужно научиться отладке кода. Поскольку множество тестов выполняются в среде Node.js, мы быстро рассмотрим, как отлаживать приложения на базе Node.js. Множество инженеров используют console.log
для отладки, и он полностью работоспособен в этом плане. Но если вам нужно следовать более крупным структурам кода, я могу порекомендовать использовать подходящий отладчик. JavaScript и TypeScript поддерживают ключевое слово debugger
, тем не менее, запустить тесты и приятно работать в них с отладчиком довольно проблематично, потому что внутри Node.js немного сложно создать инструменты разработчика из окна браузера и подключить их к программе. Другим вариантом было бы использовать вашу IDE или редактор для подключения отладчика, который поддерживается в них. Например, Visual Studio Code поддерживает отладку Node.js приложений напрямую в IDE. Гайд об отладке Node.js в VS Code можно найти здесь.
Отладка — это само по себе искусство. Вы должны чувствовать себя комфортно, используя точки останова и отвечая на вопрос, что значат функции "перешагнуть" и "войти". Это будет крайне полезно при отладке вложенных функций.
Примеры
В этой главе я пройдусь по некоторым кодовым базам с этой методикой, чтобы объяснить, где находится основное ядро приложения, и как упомянутый выше процесс может помочь вам быстрее познакомиться с кодовой базой.
Blitz.js
Blitz.js — это фреймворк, построенный поверх Node.js. Он позиционирует себя как Ruby on Rails для JavaScript/TypeScript. Команда работает над этим фреймворком более года, и было бы интересно посмотреть, где находится их ядро логики.
Первым шагом, конечно, склонируем репозиторий в папку и затем выполним:
npx code-complexity . --limit 20 --sort ratio
file | complexity | churn | ratio |
---|---|---|---|
nextjs/packages/next/compiled/webpack/bundle5.js | 91501 | 1 | 91501 |
nextjs/packages/next/compiled/webpack/bundle5.js | 91501 | 1 | 91501 |
nextjs/packages/next/compiled/webpack/bundle4.js | 74436 | 1 | 74436 |
packages/cli/src/commands/generate.ts | 228 | 28 | 6384 |
packages/cli/src/commands/new.ts | 177 | 35 | 6195 |
packages/generator/src/generators/app-generator.ts | 235 | 23 | 5405 |
packages/generator/src/generator.ts | 283 | 19 | 5377 |
packages/server/src/stages/rpc/index.ts | 184 | 28 | 5152 |
packages/server/test/dev.test.ts | 190 | 27 | 5130 |
packages/core/src/types.ts | 160 | 28 | 4480 |
packages/server/src/next-utils.ts | 176 | 25 | 4400 |
packages/generator/templates/app/app/pages/index.tsx | 240 | 18 | 4320 |
packages/server/src/config.ts | 116 | 37 | 4292 |
packages/core/src/use-query-hooks.ts | 184 | 22 | 4048 |
nextjs/test/integration/file-serving/test/index.test.js | 3561 | 1 | 3561 |
examples/auth/app/pages/index.tsx | 210 | 16 | 3360 |
packages/cli/src/commands/db.ts | 75 | 44 | 3300 |
.github/workflows/main.yml | 132 | 24 | 3168 |
packages/cli/test/commands/new.test.ts | 141 | 19 | 2679 |
examples/store/app/pages/index.tsx | 181 | 14 | 2534 |
packages/display/src/index.ts | 158 | 16 | 2528 |
Как видите, есть множество несвязанных файлов, которые можно отфильтровать (например, папку компиляции), но для начального анализа этого достаточно.
Мы видим, что здесь важны несколько каталогов:
- packages/cli
- packages/generator
- packages/server
- packages/core
Если мы получим задачу, то, по крайней мере, уже будем знать, где искать соответствующий код. Изначально, я бы попытался разобраться в файлах из packages/core
, чтобы понять, что они делают. Затем понять тесты, если они есть, и тогда у вас должно появиться хорошее понимание того, что делает Blitz.
React.js
React.js — это фронтенд фреймворк, который почти каждый веб-разработчик сейчас знает. Чего большинство людей не знает, так это как структурирована кодовая база и какие её основные части. Так что давайте посмотрим на это.
npx code-complexity . --limit 20 --sort ratio
Выполнение команды приведёт к следующему результату:
file | complexity | churn | ratio |
---|---|---|---|
packages/eslint-plugin-react-hooks/**tests**/ESLintRuleExhaustiveDeps-test.js | 7742 | 51 | 394842 |
packages/react/src/**tests**/ReactProfiler-test.internal.js | 4002 | 95 | 380190 |
packages/react-reconciler/src/ReactFiberWorkLoop.new.js | 2373 | 139 | 329847 |
packages/react-reconciler/src/ReactFiberWorkLoop.old.js | 2373 | 114 | 270522 |
packages/react-dom/src/server/ReactPartialRenderer.js | 1379 | 122 | 168238 |
packages/react-reconciler/src/ReactFiberCommitWork.new.js | 2262 | 71 | 160602 |
packages/react-devtools-shared/src/backend/renderer.js | 2952 | 54 | 159408 |
packages/react-reconciler/src/ReactFiberBeginWork.new.js | 2903 | 53 | 153859 |
scripts/rollup/bundles.js | 760 | 199 | 151240 |
packages/react-reconciler/src/ReactFiberHooks.new.js | 2622 | 56 | 146832 |
packages/react-dom/src/client/ReactDOMHostConfig.js | 1018 | 140 | 142520 |
packages/react-reconciler/src/ReactFiberHooks.old.js | 2622 | 50 | 131100 |
packages/react-reconciler/src/**tests**/ReactHooks-test.internal.js | 1641 | 74 | 121434 |
packages/react-dom/src/**tests**/ReactDOMComponent-test.js | 2346 | 51 | 119646 |
packages/react-dom/src/**tests**/ReactDOMServerPartialHydration-test.internal.js | 2150 | 49 | 105350 |
packages/react-noop-renderer/src/createReactNoop.js | 966 | 109 | 105294 |
packages/react-reconciler/src/ReactFiberCommitWork.old.js | 2262 | 46 | 104052 |
packages/react-reconciler/src/ReactFiberBeginWork.old.js | 2903 | 35 | 101605 |
packages/react-reconciler/src/**tests**/ReactIncrementalErrorHandling-test.internal.js | 1532 | 62 | 94984 |
packages/react-refresh/src/**tests**/ReactFresh-test.js | 3165 | 29 | 91785 |
Здесь мы видим, что два подпакета, наверное, наиболее интересны для понимания:
- packages/react-dom
- packages/react-reconciler
Понимание React Fiber и того, как работает частичный отрисовщик react-dom даст вам хорошее представление об архитектуре React. Приятным моментом в коде React является то, что он отлично документирован комментариями, хотя по началу он всё равно сложен.
Venom — TypeScript клиент для Whatsapp
Venom — это библиотека для взаимодействия с Whatsapp. Вы можете отправлять сообщения через эту библиотеку и делать множество других вещей. Она немного практичнее, потому что именно подобными приложениями вы будете заниматься в своей повседневной работе. Так что давайте выполним нашу обычную команду:
npx code-complexity . --limit 20 --sort ratio
file | complexity | churn | ratio |
---|---|---|---|
src/lib/jsQR/jsQR.js | 9760 | 5 | 48800 |
src/lib/wapi/wapi.js | 474 | 44 | 20856 |
src/api/layers/sender.layer.ts | 546 | 36 | 19656 |
src/lib/wapi/store/store-objects.js | 362 | 24 | 8688 |
src/controllers/initializer.ts | 178 | 48 | 8544 |
src/lib/wapi/jssha/index.js | 1204 | 5 | 6020 |
src/api/layers/retriever.layer.ts | 171 | 29 | 4959 |
src/types/WAPI.d.ts | 203 | 24 | 4872 |
src/api/layers/host.layer.ts | 258 | 17 | 4386 |
src/api/layers/listener.layer.ts | 206 | 21 | 4326 |
src/controllers/browser.ts | 141 | 29 | 4089 |
src/controllers/auth.ts | 192 | 21 | 4032 |
src/api/model/enum/definitions.ts | 589 | 6 | 3534 |
src/api/whatsapp.ts | 95 | 30 | 2850 |
src/lib/wapi/functions/index.js | 97 | 24 | 2328 |
src/api/layers/profile.layer.ts | 82 | 22 | 1804 |
src/lib/wapi/business/send-message-with-buttons.js | 323 | 5 | 1615 |
src/api/layers/group.layer.ts | 115 | 14 | 1610 |
src/api/layers/controls.layer.ts | 76 | 20 | 1520 |
src/api/model/message.ts | 114 | 11 | 1254 |
Здесь мы можем увидеть, что есть каталоги, которые имеют важное значение:
- src/lib
- src/api
- src/controllers
Как мы можем видеть, в каталоге src/lib
лежат автоматически сгенерированные файлы. В идеале, мы могли бы отфильтровать их, но пока давайте посмотрим на другие файлы.
src/api/layers/sender.layer.ts
и src/api/layers/retriever.layer.ts
не сложные, но имеют много изменений. Поэтому каждый раз, когда функциональность добавляется или удаляется, затрагиваются эти файлы. Это основные файлы приложения и их понимание даст вам хорошее представление о том, как структурирована кодовая база и на чём вам следует сконцентрироваться.
Откуда взялась эта методика?
Эта методика анализа кодовой базы изначально возникла из книги, которая описывала рефакторинг больших кодовых баз: Software Design X-Rays Адама Торнхилла (Adam Tornhill). Это отличная книга, которая научит вас многому: как структурировать код и какие части стоят рефакторинга. Отличная книга. Я думаю, что каждый программист должен прочитать её в какой-то момент, потому что она поможет взглянуть на кодовую базу с другой стороны. Работая над проектом, люди будут знакомиться с различными частями программного обеспечения и, конечно же, у них будет своя особая "область" кода, где им будет супер комфортно. Является ли этот код хорошим и понятным — это еще один вопрос, на который пытается ответить эта книга.
Software Design X-Rays Адама Торнхилла
Основываясь на рефакторинге, мы также можем применить полученные знания, чтобы понять, какие части приложения важны. Надеюсь, я объяснил вам это в данной статье.
Другие языки
Утилита code-complexity тесно связана с кодовыми базами на JavaScript и TypeScript. Для других языков, таких как Java, C#, Python и PHP существуют другие инструменты, но есть один общий инструмент, который работает для большинства кодовых баз, это code-maat. Это инструмент, созданный автором книги, упомянутой в прошлой главе.
С его помощью вы можете проанализировать проект и прийти к тем же выводам, которые упоминались в статье.
Заключение
Я надеюсь, вам понравилась эта статья, и ваша жизнь стала немного проще. Вход в новую кодовую базу сложен, и особенно в постоянно меняющимся мире JavaScript за ней трудно поспевать. С инструментами и процессами, представленными в этой статье, вам, возможно, будет проще вписаться в новую кодовую базу. Не стесняйтесь поделиться этой статьёй со своими коллегами, а также расскажите им о методиках, которые вы используете. Большинство моих знакомых разработчиков не знают об анализе текучести и сложности, а ведь это может быть очень полезно для всех. Так что поделитесь этим!