Какие базовые методики существуют для уменьшения сложности алгоритма или кода
Что проверяют
Различаете ли вы две сложности: вычислительную (сколько операций на n элементов) и сложность кода (насколько его трудно читать и менять), и знаете ли приёмы для каждой. На фронтенде следом часто просят найти квадратичный кусок в конкретном фрагменте кода.
Как отвечать
Приёмы для алгоритма:
- хэш-структуры:
SetиMapвместоincludes,indexOfиfindвнутри цикла.a.filter(x => b.includes(x))работает за O(n·m), а сnew Set(b)за O(n + m); - предварительная обработка: один раз сгруппировать или отсортировать данные, построить словарь по
id, а не искать по массиву в каждом рендере; - приёмы для массивов: два указателя, скользящее окно, префиксные суммы, бинарный поиск по отсортированным данным;
- мемоизация: кэш результатов чистой функции, в React
useMemoдля действительно дорогих вычислений; - меньше работы вообще: виртуализация длинных списков,
debounceдля поиска по вводу, пагинация на сервере.
Помните цену встроенных методов: shift, unshift, splice в середине массива и includes работают за O(n), а push и pop за O(1).
Приёмы для кода:
- ранний возврат: guard clauses вместо вложенных
ifна пять уровней; - декомпозиция: маленькие чистые функции с понятными именами, вычисления отдельно от побочных эффектов;
- таблица вместо ветвлений: объект соответствий вместо длинного
switch; - устранение дублирования: общий код выносится в функцию или хук, но без абстракций ради абстракций.
Контроль: правило complexity в ESLint и метрика когнитивной сложности в SonarQube подсвечивают функции, которые пора разбить.
Где спотыкаются
Смешивают два понятия и на вопрос про алгоритм рассказывают про SOLID и паттерны. Стратегия делает код гибче, но асимптотику не меняет.
Оптимизируют там, где n маленький. Массив из десяти пунктов меню не нуждается в Map, а читаемость страдает.
Расставляют useMemo и useCallback на каждую строку. Мемоизация сама стоит памяти и сравнений зависимостей, и без замера в React Profiler часто ничего не даёт.