5 вещей, которых никогда не должно быть в вашем JS-коде

Рефакторинг похож на Бесконечную историю: вы можете подумать, что все готово, но пока сюжет (проект) продолжается, всегда есть место для новых изменений.

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

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

Вот почему сегодня мы собираемся рассмотреть 5 распространенных проблем, которые можно легко решить с помощью рефакторинга вашего кода JavaScript, и то, как вы можете это сделать.

Магические ценности

Я люблю магию, но ненавижу магические ценности. И я уверен, что это справедливо и для многих из вас.

Магические значения - это простые значения, которые мы используем в нашем коде, и которые сами по себе ничего не значат. Что для вас 2.34? Возникает ли у вас в голове какая-то концепция? Вы понимаете, о чем я говорю, если я просто плюю вам этот номер? Как насчет "home"? Означает ли это что-нибудь для вас, если я не даю вам никакого контекста? Немного, не так ли?

Как насчет MADRID_TAX_VALUE или destinationStr? Я уверен, что вы легко придумаете несколько концепций и вариантов использования этих переменных. А ты не можешь?

В этом проблема «магических значений»: они не имеют внутреннего значения и при использовании непосредственно в нашем коде не обязательно добавляют что-либо человеку, читающему код.

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

Если одно из этих двух вещей не может быть выполнено легко, значит, вы не справились со своей задачей.

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

Рассмотрим следующий фрагмент кода, который вычисляет полную цену (включая городские налоги) предмета на основе цены без налогов и города продажи.

Да, в целом вы знаете, что он делает, но вы не можете точно сказать, на какие города нацелен этот код, если не поищете какую-то ссылку.

Если бы вместо этого вы сделали что-то вроде:

Обратите внимание на то, что функция getfullPrice теперь имеет больше смысла, и вы можете сказать, на какой город влияет каждая условная ветвь. Фактически, вы даже можете понять, что должен делать последний return оператор - без необходимости гадать.

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

Скажи «нет» магическим ценностям!

Злоупотребление примитивными ценностями

Нет ничего плохого в хороших целых числах здесь и там или в использовании строк для многих вещей. Но проблема начинается, когда вы хотите использовать несколько примитивных значений для представления чего-то более сложного.

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

Затем через несколько месяцев вас попросят сохранить аватар и имя пользователя во время регистрации, поэтому вы добавляете еще пару строковых параметров в свою функцию, которая теперь принимает 4 параметра.

Затем, еще через несколько месяцев, вас попросят добавить поле биографии, чтобы сохранить некоторые данные о пользователе, его дату рождения, а также вам необходимо рассчитать возраст пользователя во время регистрации.

Бум! Теперь вы передаете своей функции 6 параметров, не считая таких вещей, как соединение с базой данных, и у вас есть дополнительная логика. У вас может получиться что-то вроде этого:

Этот подход не будет правильно масштабироваться, потому что:

  • Чем больше точек данных вам нужно сохранить, тем больше параметров вам понадобится, а сигнатура функции будет постоянно меняться. Это означает, что вам придется обновлять каждый вызов функции или иметь какое-то поведение по умолчанию, когда новые параметры недоступны.
  • Теперь у вас также есть дополнительная логика внутри вашей функции, которая конкретно не связана с ответственностью функции (например, сохранение пользователя). Код расчета возраста здесь не должен быть, однако, учитывая, что вы имеете дело с примитивными значениями прямо здесь, очень заманчиво добавить его.

Вместо этого попробуйте сгруппировать все эти параметры в одну сущность, которая может расти независимо. В частности, JavaScript позволяет создавать классы и группировать все вместе: не только разные точки данных, но и соответствующую логику (например, код вычисления возраста):

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

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

Дублированный код

Дублированный код - это не проблема JavaScript, но вы всегда должны быть начеку.

Самая простая форма этой проблемы - буквальное дублирование. Когда вы копируете и вставляете логику из одного места в другое. Если вы сделаете это:

  1. Почему? Почему ты бы так поступил?
  2. Вы никому не помогаете.
  3. Вероятно, где-то в мире умер щенок.

Если вы видите, что собираетесь это сделать, остановитесь, прежде чем нажимать клавиши CTRL + V, и подумайте о создании функции, модуля, метода или любой другой вызываемой конструкции, о которой вы можете подумать, и поместите туда код. Удалите его из исходного места и вызовите новую конструкцию (назовем ее функцией) из обоих мест. Таким образом вы сохраняете свой код СУХОЙ (как вы следуете принципу Не повторяйся) и избегаете потенциальной резни щенков.

Теперь есть вторая версия этой проблемы, менее очевидная и способная ускользнуть от неподготовленного взгляда: дублированная логика.

Заметил, как я не сказал «код», я сказал «логика». Это так же плохо, потому что у вас есть код в двух (или более) местах, выполняющих одно и то же, только когда вам нужно поддерживать его, изменять или даже делать инертным, вам нужно будет найти каждый отдельный его экземпляр. .

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

Нравится:

Представьте, что обе функции находятся в очень разных частях вашей кодовой базы. Найти их было бы почти невозможно, если не прочесать все это специально для этого. А если вы не обращаете внимания, то можете их пропустить, учитывая, что их реализация не совсем одинакова. Однако они делают то же самое на фундаментальном уровне, и поэтому вы (или кто-то из вашей команды) должны рассмотреть возможность объединения их в единую функцию, которую можно будет повторно использовать везде.

Независимые компоненты для эпохи микроархитектуры

В конкретном случае Javascript также может иметь смысл превратить вашу функцию / класс в независимый компонент, который можно использовать и поддерживать в проектах / репозиториях.

Монолитные проекты все чаще уходят в прошлое. Микроархитектура прекрасна, но она, несомненно, представляет собой набор проблем.

Совместная работа над модулями в репозиториях и обработка их зависимостей, безусловно, является одной из самых серьезных проблем. К счастью для нас (разработчиков JS), это можно решить с помощью независимых компонентов и Bit.



Ад обратного вызова или бесконечные цепочки обещаний

Я чувствую, что встречаюсь с самим собой, если в наши дни упоминаю термин «ад обратных вызовов», но я все равно собираюсь, потому что уверен, что некоторые из вас все еще работают с обратными вызовами.

Послушайте, не поймите меня неправильно, в обратных вызовах нет ничего плохого, если, конечно, вы ими не злоупотребляете.

Следующий код совершенно нормален, и я бы сказал, вряд ли требует какого-либо рефакторинга:

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

Однако это не нормально:

Это просто трудно читать и поддерживать, иными словами: неправильно.

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

Это совершенно нормально:

Это чистый код, тут не на что жаловаться.

Тем не мение:

Учтите, что вы все еще можете это прочитать, и это намного лучше, чем было бы реализовано с помощью обратных вызовов. Однако, помимо того факта, что вы продолжаете добавлять then вызовов снова и снова, вы также немного ограничены областью действия каждого then оператора. Рассмотрим функцию sendInvites. Если вам нужен адрес электронной почты родителя этих детей, а у вас не реализована правильная модель данных, вам придется каким-то образом вернуть оба приглашения, а также связанные с ними родители как часть функции createBirthdayInvitations.

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

Вместо этого мы можем изменить этот код, чтобы он был чище, проще для чтения и намного приятнее в целом с помощью async/await вот так:

Код читается намного проще, его намного быстрее анализировать мысленно, и это позволяет нам иметь большую гибкость в способе обработки данных, учитывая, что все они находятся в одной логической области. Обратите внимание, как я изменил createBirthdayInvitations, чтобы он также получал список contacts, я просто использовал переменную, мне не пришлось делать никаких других магических действий.

Такой подход обеспечивает столь необходимую гибкость, которая требуется для реальных приложений. Мы знаем об изменениях кода, мы знаем, что бизнес-требования меняются, и нам нужно адаптироваться к ним. Благодаря такому подходу мы получаем необходимую эластичность, чтобы продолжать изменять наш код, не влияя на читаемость. Это беспроигрышный вариант.

Распределить ответственность

Последний момент, который вы также должны попытаться реорганизовать в своем коде, - это распределение ответственности.

Что я имею в виду? Наличие разных компонентов, занимающихся одним и тем же или, по крайней мере, очень связанных действий.

Рассмотрим принцип единой ответственности, который гласит, что ваши компоненты (будь то функции, модули, классы или что-то еще, что вы создаете) должны обрабатывать одну вещь. Единая ответственность. Если вы создаете класс User, вы не хотите, чтобы внутри него была логика для вашего регистратора, вы хотите использовать регистратор там, да, но определить его где-нибудь в другом месте. Или, если вы создаете модуль FileUtils, вам не нужна функция, которая заботится о входе пользователя в вашу систему, это не имеет особого смысла и, вероятно, принадлежит где-то еще.

Что ж, противоположность этому принципу заключается в распределении единой ответственности на несколько компонентов.

Представьте, например, что у вас есть класс User из приведенного выше случая «Злоупотребление примитивными значениями». Но вместо метода getAge у вас есть функция в модуле utils, которая получает объект User и вычисляет его возраст. А затем еще одна функция для вычисления координат адреса пользователя, которая называется geoLocateUser.

Почему ты бы так поступил? Эти функции не будут работать ни с чем, кроме ваших User объектов. Нет смысла иметь их вне класса. Фактически, это усложняет обслуживание, потому что в момент изменения формы класса (т.е. вы изменяете его свойства) вам может потребоваться выйти и обновить все функции, которые от них зависят. И если это широко распространенная проблема, вы можете пропустить несколько.

Учтите эту реальную проблему при добавлении функциональности в существующую систему и убедитесь, что вы добавляете ее там, где это имеет смысл, а не там, где это проще всего. Иногда немного усилий может избавить вас от головной боли в будущем.

Поддержание чистоты кода с помощью рефакторинга - задача нескончаемая, поэтому я рассматриваю ее как совершенно отдельную тему в своей последней книге Code Well With Others. Либо удаляя магические значения, либо убедившись, что вы сохраняете всю связанную логику в одном модуле, вы вносите свой вклад в сдерживание технического долга, и это всегда хорошо!

Какие еще плохие приемы вы бы хотели исключить из кода всякий раз, когда вы их видите? Оставляйте свои идеи в комментариях, и мы сможем их обсудить!

Учить больше