Недавно я делал обзор кода JavaScript и наткнулся на кусок классического императивного кода (большой старый цикл for) и подумал: вот возможность улучшить код, сделав его более декларативным. Хотя я был доволен результатом, я не был на 100% уверен, насколько (или даже если) действительно был улучшен код . Итак, я подумал, что найду минутку и обдумаю это здесь.

Императивные и декларативные стили

Чтобы ограничить обсуждение, в императивном коде вы явно указываете каждый шаг того, как вы хотите, чтобы что-то было сделано, тогда как с декларативным кодом вы просто говорите, что именно вы хотите сделать. . В современном JavaScript это чаще всего сводится к тому, что некоторые методы поздней модели Array и Object предпочитают циклам с телами, которые проводят много сравнений и сохранения состояния. Даже несмотря на то, что эти новомодные методы могут сами выполнять сравнение и сохранение состояния, они скрыты от просмотра, и вы, вообще говоря, остаетесь с кодом, который декларирует то, что он хочет, а не требует только того, как этого достичь.

Императивный кодекс

Давайте разберемся в мыслительном процессе, необходимом для понимания того, что здесь происходит.

  1. JavaScript не типизирован, поэтому выяснение типов возвращаемых значений и аргументов является первой проблемой.
  2. Из имени функции и двух операторов return, которые возвращают буквальные логические значения, мы можем предположить, что тип возвращаемого значения является логическим.
  3. Название функции предполагает, что два аргумента могут быть массивами, и использование иглы.length и haystack.indexOf подтверждает это.
  4. Цикл выполняет итерацию по массиву иглы и выходит из функции, возвращая false, если текущее индексированное значение массива игла не найдено в массиве стог сена.
  5. Если цикл завершается без выхода из функции, то несовпадений не обнаружено и возвращается истина.
  6. Таким образом, если все значения массива игла (в любом порядке) находятся в массиве стог сена, мы получаем истинный результат, в противном случае - ложь.

Декларативный кодекс

Примечание. Совет Майкл Людер-Роузфилд с пропеллером, который предложил это решение, которое намного проще, чем предыдущая версия, в которой использовалось сокращение.

Это заняло меньше строк, но вам все равно нужно разбить его, чтобы понять, что он делает. Посмотрим, чем отличается этот процесс.

  1. JavaScript не типизирован, поэтому выяснение типов возвращаемых значений и аргументов является первой проблемой.
  2. По имени функции и возвращаемому результату каждого метода массива мы можем предположить, что тип возвращаемого значения является логическим.
  3. Название функции предполагает, что два аргумента могут быть массивами, как и значения по умолчанию, которые теперь добавлены к аргументам в целях безопасности.
  4. Вызов Needle.every называет свое текущее значение «el» и проверяет, присутствует ли оно в массиве haystack, используя haystack. включает.
  5. Вызов Needle возвращает true или false, буквально сообщая нам, включен ли каждый элемент в массиве Need в массив haystack. .

Сравнения

Теперь давайте взвесим относительные достоинства каждой реализации.

Императив

Плюсы

  1. Синтаксис почтенного цикла for всем известен.
  2. Функция немедленно вернется, если будет обнаружено несоответствие.
  3. Цикл for вероятно, быстрее (хотя это не имеет большого значения при небольшом размере массива, с которым мы имеем дело).

Минусы

  1. Код длиннее: 7 строк, 173 символа.
  2. Наличие двух выходов из функции, как правило, не очень хорошо, но для достижения единственного выхода потребуется еще немного больше времени.
  3. Хотя цикл действительно выполняет итерацию по всей длине массива иглы, он должен быть явным, и нам нужно визуально проверить инициализатор, условие и проверку приращения. Там могут закрасться ошибки.
  4. Сравнение результата вызова haystack.indexOf с -1 кажется неуклюжим, потому что имя метода не дает вам подсказки о том, что он вернет, если элемент не найден (-1 в отличие от null или undefined ).

Декларативная

Плюсы

  1. Код короче: 2 строки, 102 символа.
  2. Функция немедленно вернется, если будет обнаружено несоответствие.
  3. Возвращается результат одного выражения, поэтому сразу становится очевидно, что функция пытается сделать.
  4. Использование Needle.every приносит удовлетворение, потому что название метода подразумевает, что мы получим истинный или ложный результат, И нам не нужно явно управлять механизмом итераций.
  5. Использование haystack.includes приносит удовлетворение, потому что название метода подразумевает, что мы получим истинный или ложный результат, И нам не нужно сравнивать его ни с чем.

Минусы

  1. Каждый вызов вероятно медленнее (хотя это не имеет большого значения при небольшом размере массива, с которым мы имеем дело).

Заключение

Обе эти реализации, вероятно, можно было бы улучшить. Во-первых, и это не имеет ничего общего с императивным и декларативным, имени функции и аргументам можно было бы дать более ясные имена. Имя функции, кажется, указывает на то, что мы хотим знать, является ли один из массивов элементом другого. Имена аргументов, кажется, подтверждают это. Фактически, мы просто хотим знать, совпадает ли содержимое двух массивов, не обращая внимания на порядок. Это непреднамеренное неверное направление создает мыслительные трения, которые мешают нам с первого взгляда понять реализацию любого.

Помимо проблем с именами, похоже, что у декларативного подхода больше плюсов, чем минусов, поэтому чисто численно я собираюсь объявить его победителем.

Ожидается, что реализация декларативного кода улучшит читаемость. Как это влияет на производительность - это другой вопрос, который, безусловно, следует учитывать, особенно если обрабатывается большой объем данных. Если это не сильно влияет на производительность, то более читабельная кодовая база - это более управляемая кодовая база.

Если вы видите другие плюсы или минусы, которые я пропустил для любого из этих претендентов, или не согласны с моим приближением к их достоинствам, не стесняйтесь оставлять свои комментарии. И снова спасибо Майклу Людер-Роузфилд за это.