Чистый код: функции (на 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()
возвращает код ошибки, когда второй аргумент равен нулю, что может быть трудно понять и обработать. Путем рефакторинга для выдачи исключения мы можем сделать обработку ошибок более естественной и понятной.
Заключение
В заключение, важно следовать рекомендациям при написании функций, чтобы улучшить общее качество нашей кодовой базы. Поступая таким образом, мы можем гарантировать, что наши функции будут не только читаемыми и пригодными для повторного использования, но и поддерживаемыми в долгосрочной перспективе. Это может помочь уменьшить количество багов и ошибок, которые могут возникнуть из-за плохо написанного кода, что может сэкономить время и ресурсы в долгосрочной перспективе.
Стоит отметить, что чистый код — это не только написание работающего кода; речь также идет о написании кода, который можно легко понять, протестировать и модифицировать с течением времени. Поэтому рекомендуется уделить время тщательному рассмотрению изложенных выше передовых практик при написании функций, чтобы оптимизировать качество и удобство сопровождения вашего кода.