Гид по технологиям

Рефакторинг длинных цепочек if...else в JavaScript

4 min read JavaScript Обновлено 23 Dec 2025
Рефакторинг if...else в JavaScript
Рефакторинг if...else в JavaScript

13-дюймовый MacBook Pro с открытым кодом рядом с цветком

Введение

Условные операторы — основа JavaScript. Они позволяют выполнить код в зависимости от условия true/false. Но при длинных цепочках if…else читаемость быстро страдает. В этой статье показаны приёмы для упрощения таких конструкций: guard clauses, ранние return, вынос логики в функции и альтернативные подходы.

Кратко: цель — уменьшить вложенность и сделать намерения очевидными.

Проблема: сложные вложенные цепочки

Рассмотрим исходный пример с вложенной логикой проверки возраста:

function canDrink(person) {
  if (person?.age != null) {
    if (person.age < 18) {
      console.log("Still too young")
    } else if (person.age < 21) {
      console.log("Not in the US")
    } else {
      console.log("Allowed to drink")
    }
  } else {
    console.log("You're not a person")
  }
}

const person = { age: 22 }
canDrink(person)

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

Охранные проверки (Guard clauses)

Если внешний if покровительствует всей функции, чаще всего его можно превратить в охранную проверку — то есть выход из функции при невыполнении условия. Это уменьшает уровень вложенности.

Пример с guard clause:

function canDrinkBetter(person) {
  if (person?.age == null) return console.log("You're not a person")

  if (person.age < 18) {
    console.log("Still too young")
  } else if (person.age < 21) {
    console.log("Not in the US")
  } else {
    console.log("Allowed to drink")
  }
}

Теперь ключевой путь чтения расположен слева, а исключения — обработаны вверху. Код легче воспринимать.

Ранние return вместо единственного return

Строгое правило «один return в функции» часто заставляет писать лишнюю вложенность. Контрпример: несколько ранних return делают намерения ясными и избавляют от else-блоков.

function canDrinkBetter(person) {
  if (person?.age == null) return console.log("You're not a person")

  if (person.age < 18) {
    console.log("Still too young")
    return
  }

  if (person.age < 21) {
    console.log("Not in the US")
    return
  }

  console.log("Allowed to drink")
}

Преимущество — каждый условный блок завершает выполнение, и вам не нужно читать скрытую логику в else.

Вынос логики в отдельные функции

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

Функция, которая возвращает строку-результат:

function canDrinkResult(age) {
  if (age < 18) return "Still too young"
  if (age < 21) return "Not in the US"
  return "Allowed to drink"
}

function canDrinkBetter(person) {
  if (person?.age == null) return console.log("You're not a person")

  const result = canDrinkResult(person.age)
  console.log(result)
}

Разделив проверку и форматирование/логику вывода, вы делаете код модульным и удобным для тестов.

Альтернативы if…else

Иногда лучше использовать другие подходы:

  • switch — удобно для дискретных значений;
  • таблица соответствий (lookup table) — для сопоставления вход => действие без сложных условий;
  • объектно-ориентированный подход / полиморфизм — для сложной поведенческой логики;
  • функции высшего порядка — для композиции правил.

Пример таблицы соответствий для простых состояний:

const ageRules = [
  { check: age => age < 18, result: () => "Still too young" },
  { check: age => age < 21, result: () => "Not in the US" },
  { check: () => true, result: () => "Allowed to drink" }
]

function canDrinkByTable(person) {
  if (person?.age == null) return console.log("You're not a person")

  const rule = ageRules.find(r => r.check(person.age))
  console.log(rule.result())
}

Таблица упрощает добавление новых правил и делает поведение очевидным.

Когда рефакторинг может не подойти

Counterexamples / случаи, когда guard clauses и множественные return не лучшие:

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

В таких ситуациях используйте шаблон try/finally или гарантируйте выполнение завершающих операций в одном месте.

Ментальные модели и эвристики

  • Читай сверху вниз: сначала обработай исключения, затем основной путь исполнения. Это уменьшает когнитивную нагрузку.
  • Правило трёх: если внутри функции более трёх уровней вложенности, подумайте о рефакторинге.
  • Single responsibility: функция должна решать одну задачу. Если есть несколько причин для изменения функции — разделяйте.

Мини‑методика рефакторинга условных цепочек

  1. Найдите внешние проверки, оборачивающие весь код. Превратите их в guard clauses.
  2. Замените вложенные else на ранние return там, где это безопасно.
  3. Вынесите длинную логику в вспомогательные функции с понятными именами.
  4. Рассмотрите таблицу правил или switch для дискретных случаев.
  5. Напишите юнит‑тесты для каждой ветки.
  6. Проверяйте: уменьшилась ли вложенность? стало ли понятнее сообщение об ошибке?

Чеклист для роли: разработчик / ревьюер

Разработчику:

  • Убираю лишнюю вложенность через guard clause.
  • Выношу логику в отдельную функцию при росте сложности.
  • Добавляю тесты для каждой ветки.

Ревьюеру:

  • Видна ли основная последовательность выполнения сразу после чтения кода?
  • Нет ли скрытых побочных эффектов в ранних return?
  • Соответствует ли поведение требованиям и нет ли регрессий?

Критерии приёмки

  • Читаемость: основной путь выполнения должен быть очевиден.
  • Тесты: покрыты все основные ветки и исключения.
  • Поддержка: добавление нового правила занимает минимальное число изменений.

Словарь в одну строку

  • Guard clause — ранняя проверка, которая сразу прекращает выполнение, если условие не выполнено.
  • Lookup table — структура данных, связывающая условие с действием.
  • Полиморфизм — замена условных разветвлений распределением поведения по типам объектов.

Риски и смягчение

  • Несвоевременный выход из функции может пропустить очистку ресурсов. Решение: использовать finally или централизованную очистку.
  • Множество return усложняет трассировку стека. Решение: логирование в начале и конце функции.

Примеры альтернатив — switch и обработка по объектам

Switch (иногда полезен для диапазонов с дополнительной логикой):

function canDrinkSwitch(person) {
  if (person?.age == null) return console.log("You're not a person")

  switch (true) {
    case person.age < 18:
      console.log("Still too young")
      break
    case person.age < 21:
      console.log("Not in the US")
      break
    default:
      console.log("Allowed to drink")
  }
}

Полиморфизм (пример для расширяемости):

class PersonChecker {
  constructor(person) { this.person = person }
  check() { return "Default" }
}

class AgeChecker extends PersonChecker {
  check() {
    const age = this.person?.age
    if (age == null) return "You're not a person"
    if (age < 18) return "Still too young"
    if (age < 21) return "Not in the US"
    return "Allowed to drink"
  }
}

const checker = new AgeChecker({ age: 22 })
console.log(checker.check())

Полиморфизм полезен, если правила зависят от типа объекта и часто растут.

Краткое резюме

Используйте guard clauses и ранние return, чтобы убрать вложенность. Вынесите проверочную логику в отдельные функции или таблицы правил. Рассмотрите switch, lookup table или полиморфизм при усложнении условий. Всегда покрывайте ветки тестами и проверяйте, что критические операции выполняются независимо от того, где происходит выход из функции.

Важно: нет универсального рецепта. Выберите подход, который делает код понятным и поддерживаемым для вашей команды.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как увидеть время отправки сообщения на iPhone
iPhone

Как увидеть время отправки сообщения на iPhone

Макрофотография дешево — практическое руководство
Фотография

Макрофотография дешево — практическое руководство

Изменить hostname в Ubuntu — краткое руководство
Linux

Изменить hostname в Ubuntu — краткое руководство

Как изменить поля в Google Docs — инструкция
Google Docs

Как изменить поля в Google Docs — инструкция

Бюджет с нулевым остатком — как составить
Финансы

Бюджет с нулевым остатком — как составить

Восстановить черновики в Typora
Инструкции

Восстановить черновики в Typora