Как лучше передавать параметры в функции с большим количеством аргументов?
Что проверяют
Умеете ли вы проектировать сигнатуру так, чтобы вызов читался без открытия исходника, и видите ли, когда длинный список аргументов говорит о проблеме в самой функции. Следом часто просят показать, как это типизировать в TypeScript и где ставить значения по умолчанию.
Как отвечать
Начните с правила: до трёх аргументов позиционно нормально, дальше порядок перестаёт запоминаться, а вызов createUser('Ann', 30, true, false) ничего не говорит читателю. Дальше каркас:
- объект параметров с деструктуризацией:
function createUser({ name, age, isAdmin = false } = {}). Имена видны в месте вызова, порядок не важен, новое необязательное поле не ломает старые вызовы; - главное отдельно, настройки в объекте: так устроены
fetch(url, init)иaddEventListener(type, listener, options). Основной аргумент позиционный, остальное опционально; - типизация:
interface CreateUserOptionsс явными необязательными полями, значения по умолчанию сливаются через{ ...defaults, ...options }; - группировка связанных значений: вместо
from, to, stepодин объектrange, вместо пяти полей адреса одинaddress; - сигнал к рефакторингу: если параметров восемь, функция скорее всего делает несколько дел. Её лучше разбить, чем прятать проблему в объект.
В React это тот же подход: пропсы и есть объект параметров, и там же видно, что десяток булевых пропсов хуже одного variant с набором значений. Следить за длиной сигнатур помогает правило ESLint max-params.
Про производительность скажите честно: создание объекта на каждый вызов стоит копейки и заметно только в очень горячих циклах.
Где спотыкаются
Забывают = {} в конце сигнатуры. Вызов createUser() без аргументов падает с TypeError, потому что деструктурировать undefined нельзя.
Не знают, что значения по умолчанию срабатывают только на undefined. Переданный null пройдёт как есть и сломает логику ниже.
Сливают настройки через spread и не думают о вложенности: { ...defaults, ...options } копирует поверхностно, и переданный options.retry целиком заменит вложенный объект из defaults, а не дополнит его.