Чистый код: функции (на Javascript)

Вы устали бороться со сложными, трудными для понимания функциями в кодовой базе? Вы хотите писать функции, которые легче поддерживать, повторно использовать и менее подвержены ошибкам? Если да, то вам нужно узнать о «Чистом коде: Функции». Следуя рекомендациям, изложенным в книге Роберта С. Мартина «Чистый код», вы сможете научиться писать функции, которые легче читать, тестировать и поддерживать. В этом сообщении блога мы рассмотрим ключевые концепции функций чистого кода и предоставим вам практические советы и примеры, которые помогут вам реализовать эти концепции в вашем собственном коде. С нашей помощью вы сможете поднять свои навыки кодирования на новый уровень и писать функции, с которыми приятно работать. Итак, вы готовы узнать о функциях чистого кода? Давайте погрузимся!

Содержание

· Маленькие функции лучше больших
· Функции должны делать одну вещь
· Функции должны иметь описательные имена
· Функции должны иметь ограниченное количество аргументов< br /> · Функции не должны иметь побочных эффектов
· Функции не должны полагаться на глобальные переменные
· Функции не должны быть слишком глубоко вложенными
· Использовать исключения, а не Коды ошибок
· Заключение

Маленькие функции лучше, чем большие

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

// Large function
function processOrders(orders) {
  const total = orders.reduce((acc, order) => acc + order.price, 0);
  const orderIds = orders.map(order => order.id);
  const orderCount = orders.length;
  const orderSummary = `You have ${orderCount} orders with IDs ${orderIds.join(', ')} for a total of $${total}.`;
  console.log(orderSummary);
  return orderSummary;
}

// --------------------------------------------------------------

// Refactored into smaller functions
function getTotalPrice(orders) {
  return orders.reduce((acc, order) => acc + order.price, 0);
}
function getOrderIds(orders) {
  return orders.map(order => order.id);
}
function getOrderCount(orders) {
  return orders.length;
}
function generateOrderSummary(orders) {
  const total = getTotalPrice(orders);
  const orderIds = getOrderIds(orders);
  const orderCount = getOrderCount(orders);
  return `You have ${orderCount} orders with IDs ${orderIds.join(', ')} for a total of $${total}.`;
}
function logOrderSummary(orders) {
  const orderSummary = generateOrderSummary(orders);
  console.log(orderSummary);
  return orderSummary;
}

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

Функция getTotalPrice() берет массив ордеров и возвращает их общую цену, используя функцию reduce() для суммирования цены каждого ордера. Функция getOrderIds() принимает массив заказов и возвращает массив их идентификаторов с помощью функции map(). Функция getOrderCount() принимает массив заказов и возвращает их количество, используя свойство length. Наконец, функция generateOrderSummary() принимает массив заказов и использует три меньшие функции для создания итоговой строки. Функция logOrderSummary() принимает массив заказов и выводит сводную строку на консоль.

Функции должны делать одну вещь

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

// Function that performs multiple tasks
function processUserInput(input) {
  const inputLines = input.split('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
  const trimmedLines = inputLines.map(line => line.trim());
  const nonEmptyLines = trimmedLines.filter(line => line !== '');
  const formattedLines = nonEmptyLines.map(line => `> ${line}`);
  return formattedLines.join('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
}
// --------------------------------------------------------------
// Refactored into smaller functions
function splitInput(input) {
  return input.split('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
}
function trimLines(lines) {
  return lines.map(line => line.trim());
}
function removeEmptyLines(lines) {
  return lines.filter(line => line !== '');
}
function formatLines(lines) {
  return lines.map(line => `> ${line}`);
}
function joinLines(lines) {
  return lines.join('\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n');
}
function processUserInput(input) {
  const lines = splitInput(input);
  const trimmedLines = trimLines(lines);
  const nonEmptyLines = removeEmptyLines(trimmedLines);
  const formattedLines = formatLines(nonEmptyLines);
  return joinLines(formattedLines);
}

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

Функция splitInput() принимает строку и с помощью метода split() разбивает ее на массив строк. Функция trimLines() принимает массив строк и использует метод map() для удаления всех пробелов в начале и конце каждой строки. Функция removeEmptyLines() принимает массив строк и использует метод filter() для удаления любых пустых строк. Функция formatLines() принимает массив строк и использует метод map() для добавления символа > в начало каждой строки. Функция joinLines() принимает массив строк и использует метод join(), чтобы объединить их в одну строку, разделенную символами новой строки. Наконец, функция processUserInput() использует меньшие функции для обработки входной строки.

Функции должны иметь описательные имена

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

// Function with a vague name
function processData(data) {
  // ...
}
// --------------------------------------------------------------
// Refactored with a descriptive name
function processUserData(userData) {
  // ...
}

В приведенном выше примере функция processData() имеет расплывчатое имя, которое не дает никакого указания на то, что она делает. Переименовав его в processUserData(), мы можем сделать его более понятным и уменьшить потребность в комментариях.

Функции должны иметь ограниченное количество аргументов

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

// Function with too many arguments
function createPerson(name, age, address, phone, email) {
  // ...
}

// --------------------------------------------------------------
// Refactored to use an object instead
function createPerson(personData) {
  // ...
}
// Now, we can pass an object with properties for name, age, address, phone, and email:

const personData = {
  name: 'John Doe',
  age: 30,
  address: '123 Main St',
  phone: '555-555-1234',
  email: '[email protected]'
};
const person = createPerson(personData);

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

Функции не должны иметь побочных эффектов

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

// Example with side effects
let arr = [1, 2, 3];
function popLastElement() {
  arr.pop(); // modifies the external state of `arr`
}
popLastElement();
console.log(arr); // Output: [1, 2]

// --------------------------------------------------------------
// Example without side effects
function getLastElement(arr) {
  return arr[arr.length - 1]; // returns a new value without modifying the input
}
let arr = [1, 2, 3];
let lastElement = getLastElement(arr);
console.log(lastElement); // Output: 3

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

Функции не должны полагаться на глобальные переменные

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

// Example relying on global state
let x = 0;
function increment() {
  x++;
}
increment();
console.log(x); // Output: 1

// --------------------------------------------------------------
// Example without relying on global state
function increment(x) {
  return x + 1;
}
let y = 0;
y = increment(y);
console.log(y); // Output: 1

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

Функции не должны быть слишком глубоко вложены

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

// Example with deeply nested functions
function add(a, b) {
  function increment(x) {
    return x + 1;
  }
  let c = increment(a);
  let d = increment(b);
  return c + d;
}
console.log(add(1, 2)); // Output: 5

// --------------------------------------------------------------
// Example with flatter function structure
function increment(x) {
  return x + 1;
}
function add(a, b) {
  let c = increment(a);
  let d = increment(b);
  return c + d;
}
console.log(add(1, 2)); // Output: 5

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

Используйте исключения, а не коды ошибок

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

// Example with error code
function divide(a, b) {
  if (b === 0) {
    return -1; // error code for divide by zero
  }
  return a / b;
}
let result = divide(4, 0);
if (result === -1) {
  console.log("Error: Divide by zero"); // error handling with error code
}

// --------------------------------------------------------------
// Example with exception
function divide(a, b) {
  if (b === 0) {
    throw new Error("Divide by zero"); // throws an exception
  }
  return a / b;
}
try {
  let result = divide(4, 0);
} catch (e) {
  console.log("Error: " + e.message); // error handling with exception
}

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

Заключение

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

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