Как безболезненно уменьшить If Else сложность кода спагетти

внешний интерфейс Шаблоны проектирования Язык программирования дизайн

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

что такое код лапши

Так называемый «спагетти-код» обычно используется при обработке сложных бизнес-процессов. Обычно он удовлетворяет следующим характеристикам:

  • длинное содержание
  • нарушение структуры
  • Глубоко вложенный

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

if...ifтип

Этот тип структуры кода выглядит следующим образом:

function demo (a, b, c) {
  if (f(a, b, c)) {
    if (g(a, b, c)) {
      // ...
    }
    // ...
    if (h(a, b, c)) {
      // ...
    }
  }

  if (j(a, b, c)) {
    // ...
  }

  if (k(a, b, c)) {
    // ...
  }
}

Блок-схема выглядит следующим образом:

if-if-before
if-if-before

Он вложен сверху внизif, чтобы поток управления внутри одной функции продолжал расти.Не думайте, что сложность возрастает только линейно по мере роста потока управления.. Мы знаем, что функции имеют дело с данными, и каждаяifВ общем, будет логика обработки данных. Тогда даже при отсутствии вложенности, если таких абзацев 3if, то по каждомуifВыполнять или нет, существует 2 ^ 3 = 8 состояний данных. Если сегментов 6, то 2^6 = 64 состояния. В результате при увеличении масштаба проекта возрастает сложность отладки функции.экспоненциальныйрост! Это по порядку величин соответствует опыту «Мифа о человеко-месяце».

else if...else ifтип

Этот тип потока управления кодом также очень распространен. В форме:

function demo (a, b, c) {
  if (f(a, b, c)) {
    if (g(a, b, c)) {
      // ...
    }
    // ...
    else if (h(a, b, c)) {
      // ...
    }
    // ...
  } else if (j(a, b, c)) {
    // ...
  } else if (k(a, b, c)) {
    // ...
  }
}

Блок-схема выглядит следующим образом:

else-if-before
else-if-before

else ifВ конце концов, будет введена только одна из ветвей, поэтому вышеуказанного взрыва комбинации не произойдет. Однако сложность также не является низкой при глубокой вложенности. Предполагая 3 уровня вложенности, каждый уровень имеет 3else if, то выходов будет 3^3=27. Если каждый выход соответствует способу обработки данных, то инкапсуляция такого количества логики в функцию явно нарушает принцип единой ответственности. Кроме того, два вышеуказанных типа можно легко комбинировать, что еще больше увеличивает сложность и снижает читабельность.

Но почему в эпоху различных продвинутых фреймворков и библиотек классов такой код все еще появляется так часто? Моя личная точка зрения заключается в том, что повторно используемые модули действительно могут позволить нам писать меньше [кода шаблона], но независимо от того, как инкапсулирован сам бизнес, разработчикам все равно нужно писать логику. И даже простойif else, что также позволяет усложнить поток управленияэкспоненциальныйрост. С этой точки зрения,Если у вас нет базовой грамотности в программировании, как бы быстро вы ни освоили лучшие фреймворки и библиотеки классов, в проекте будет бардак..

стратегия рефакторинга

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

базовый вариант

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

if-if-after
if-if-after

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

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

Но так ли это на самом деле? Таким образом, мы смогли разделить большую функцию с 64 состояниями на 6 меньших функций, которые возвращают только 2 разных состояния, и основную функцию, которая вызывает их одно за другим. Таким образом,Скорость роста сложности каждой функции снижается с экспоненциального уровня до линейного уровня.

С этим мы решилиif...ifВведите код лапши вверх, затем дляelse if...else ifкакой тип?

Справочная таблица

заelse if...else ifтипа спагетти-кода, одна из самых простых стратегий рефакторинга — использование так называемых интерполяционных таблиц. Он инкапсулирует каждыйelse ifЛогика в:

const rules = {
  x: function (a, b, c) { /* ... */ },
  y: function (a, b, c) { /* ... */ },
  z: function (a, b, c) { /* ... */ }
}

function demo (a, b, c) {
  const action = determineAction(a, b, c)
  return rules[action](a, b, c)
}

каждыйelse ifЛогика в переписывается как отдельная функция, тогда мы можем разделить процесс следующим образом:

else-if-lookup
else-if-lookup

Для языков сценариев, которые по своей сути поддерживают отражение, это тривиальная техника. Но для более сложныхelse ifусловие, этот метод переориентирует сложность потока управления, чтобы решить проблему [какую ветвь выбрать]determineActionсередина. Есть ли лучший способ справиться с этим?

Схема цепочки ответственности

В приведенном выше примере таблица поиска реализована с помощью пар ключ-значение, и для каждой ветвиelse if (x === 'foo')В таком простом суждении'foo'Его можно использовать в качестве ключа рефакторинговой коллекции. но если каждыйelse ifветвьСодержит сложные условные суждения и требует порядка исполнения, то мы можем использовать шаблон цепочки ответственности для лучшего рефакторинга такой логики.

правильноelse ifС точки зрения, обратите внимание, что каждая ветвь на самом делеСудя сверху вниз и, наконец, введя только один из нихиз. Это означает, что мы можем добиться такого поведения, сохранив массив [правил суждения]. Если правило совпадает, выполняется ветвь, соответствующая этому правилу. Такой массив мы называем [цепочка ответственности], а поток выполнения в этом режиме следующий:

else-if-chain
else-if-chain

С точки зрения реализации кода мы можем определить иelse ifСовершенно эквивалентные правила:

const rules = [
  {
    match: function (a, b, c) { /* ... */ },
    action: function (a, b, c) { /* ... */ }
  },
  {
    match: function (a, b, c) { /* ... */ },
    action: function (a, b, c) { /* ... */ }
  },
  {
    match: function (a, b, c) { /* ... */ },
    action: function (a, b, c) { /* ... */ }
  }
  // ...
]

rulesУ каждого изmatchа такжеactionАтрибуты. В это время мы можем преобразовать исходную функциюelse ifПерепишем обход массива цепочки ответственности:

function demo (a, b, c) {
  for (let i = 0; i < rules.length; i++) {
    if (rules[i].match(a, b, c)) {
      return rules[i].action(a, b, c)
    }
  }
}

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

Суммировать

На самом деле спагетти-код легко появляется в стиле «грубой и быстрой» разработки, не задумываясь. Много простых и грубых [добавьте сюдаif, где несколькоreturn] метод исправления ошибок вкупе с отсутствием комментариев легко ухудшить читаемость кода и усложнить его.

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

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