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

Есть две основные парадигмы программирования, которые разработчик может использовать при написании программы:

  • императив (процедурный, объектно-ориентированный)
  • декларативный (функциональный, реактивный)

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

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

В этой статье я возьму в качестве примера процедуру, реализующую «сложную» логику, и покажу, как изменить эту процедуру, приняв более декларативный стиль программирования. Я также буду использовать JavaScript в качестве языка реализации.

И вот она, поистине великолепная процедура:

function transformStringOfTokensIntoArrayOfUpperCaseTokens (stringOfTokens, separator) {
  let result = [];
  if (stringOfTokens !== undefined && separator !== undefined) {
    if (stringOfTokens === '') {
      return result;
    } else {
      const tokens = stringOfTokens.split(separator);
      for (let i = 0; i < tokens.length; i++) {
        result.push(tokens[i].toUpperCase());
      }
      return result;
    }
  } else {
    return result;
  }
};

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

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

Давайте посмотрим, как выглядит псевдокод для логики:

- Define the default result of the function to be an empty array
- Verify that the parameters are defined otherwise return the default result
- If the string parameter is empty return the default result
- Otherwise split the string using the separator into an array of token
- Upper case each token and add them to the result
- Return the result that contains the upper case token in the array

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

Но действительно ли это было необходимо? Вся эта сложность для превращения строки в массив токенов верхнего регистра?

Как мы могли бы вместо этого описать требуемое преобразование в виде последовательности логических шагов?

Возможно, это могло бы выглядеть так:

> From a given string and given a separator
| Split the string using the separator into an array
| Then upper case each item in the array
| Then return the array

Хорошо, это интересно, теперь та же логика представлена ​​в виде последовательности шагов, где каждый шаг принимает входные данные и дает результат. Но как мы могли это реализовать?

const transformStringOfTokensIntoArrayOfUpperCaseTokens = (stringOfTokens = '', separator = '') => {
  
  return stringOfTokens.split(separator)
    .map((item) => item.toUpperCase);
};

Это действительно так?

Так что же изменилось с этой новой реализацией?

Глядя на это, хотя эта реализация выполняет ту же процедуру, что и предыдущая, в ней есть фундаментальные отличия.

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

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

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