Одна из причин, почему трудно называть вещи

Это еще один урок, извлеченный из моих приключений по рефакторингу большого проекта JS. Легкая цель любого рефакторинга — удалить повторяющиеся блоки кода и превратить их в повторно используемые функции. Держите его СУХИМ. Сделанный. Почти…

Прочитав и обсудив статью Санди Мец о том, что неправильная абстракция дороже дублирования, я начал пересматривать свою привычку дедуплицировать код. Я усердно извлекал только тот код, который вызывался каждый раз, или код, который полагался на одни и те же переменные; код, который совместно варьировался. Таким образом, мой функциональный эквивалент мог принять переменную и вывести результат всего кода, который полагался на эту переменную. В своей работе по рефакторингу я пытался ограничить количество параметров, которые принимает каждая новая функция, так как это красный флаг того, что функция пытается учесть слишком много сценариев (и может быть неправильной абстракцией), но я столкнулся с другой проблемой. : называю свои новые блестящие функции.

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

Настораживающие признаки нарушения принципа единой ответственности

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

Поговорка «код является документацией» оживает, если вы пишете код, который читается как документация. Именование вещей помогает в этом. Бьюсь об заклад, вы можете догадаться, что делает приведенный ниже код (если не как):

switch (getABTestVariant()) {
  case 'a':
    // do nothing, control group
    break;
  case 'b':
    removeDollarSigns(document.querySelectorAll('.price'));
    break;
  case 'c':
    removeZeroCents(document.querySelectorAll('.price'));
    break;
  default:
    throw INVALID_VARIANT_CHOSEN;
}

В этом коде вы не знаете, как работают getABTestVariant, removeDollarSigns или removeZeroCents, но вы можете сказать по их именам, что они должны делать. Этим функциям было легко дать имена, потому что каждая из них выполняет одно действие. В моей кодовой базе этот шаблон получения варианта и изменения DOM на основе этого варианта повторяется снова и снова. Было бы заманчиво абстрагировать этот шаблон в функцию, которая принимает массив функций и селектор для элемента DOM, но как бы мы ее назвали? В конце концов, он извлечет имя варианта, просмотрит DOM в поисках элемента, условно применит функции к этому элементу и, возможно, выдаст ошибку.

Попробуем modifyElement. Затем потребитель напишет modifyElement('.price', [removeDollarSigns, removeZeroCents]);, что, по-видимому, применит все эти модификации к элементам. Хм... как насчет runVariant('.price', [removeDollarSigns, removeZeroCents]); Итак, какой вариант мы используем? Проблема здесь в том, что хотя этот код ковариабелен, он не является целостным. Это становится ясно, когда мы пытаемся использовать функцию, которая запускает этот код. Мы вынуждены передавать параметры, которые кажутся несвязанными. Подпись не имеет смысла. Или, скорее, это имеет другой смысл, чем то, что было задумано.

Вывод

В вашем коде неизбежно будет некоторое дублирование, поэтому остерегайтесь попыток СУШИТЬ свой код до такой степени, что вы пишете несвязанные функции только потому, что код внутри ковариабелен. При написании функций обратите внимание на то, насколько сложно описать и назвать функцию, сколько параметров она требует и как выглядит сигнатура. Эти маленькие лакмусовые бумажки помогут вам узнать, есть ли у вашей функции Единая Ответственность.