깨끗한 코드: 함수(Javascript)

Mar 26 2023
코드베이스에서 복잡하고 이해하기 어려운 기능으로 어려움을 겪고 있습니까? 더 유지 관리가 가능하고 재사용이 가능하며 오류가 덜 발생하는 함수를 작성하고 싶습니까? 그렇다면 "Clean Code: Functions"에 대해 배워야 합니다. Robert C.

코드베이스에서 복잡하고 이해하기 어려운 기능으로 어려움을 겪고 있습니까? 더 유지 관리가 가능하고 재사용이 가능하며 오류가 덜 발생하는 함수를 작성하고 싶습니까? 그렇다면 "Clean Code: Functions"에 대해 배워야 합니다. Robert C. Martin의 책 "Clean Code"에 요약된 모범 사례를 따르면 더 쉽게 읽고, 테스트하고, 유지 관리할 수 있는 함수를 작성하는 방법을 배울 수 있습니다. 이 블로그 게시물에서는 클린 코드 함수의 주요 개념을 살펴보고 이러한 개념을 자신의 코드에 구현하는 데 도움이 되는 실용적인 팁과 예제를 제공합니다. 우리의 도움으로 코딩 기술을 한 단계 끌어올리고 즐겁게 작업할 수 있는 함수를 작성할 수 있습니다. 자, 클린 코드 함수에 대해 배울 준비가 되셨나요? 다이빙하자!

내용의 테이블

· 작은 함수가 큰 함수보다 낫다
· 함수는 한 가지 일을 해야 한다
· 함수는 설명적인 이름을 가져야 한다
· 함수는 제한된 수의 인수를 가져야 한다
· 함수는 부작용이 없어야 한다
· 함수는 전역 변수에 의존해서는 안 된다
· 함수는 너무 과하지 않아야 한다 깊이 중첩
· 오류 코드가 아닌 예외 사용
· 결론

작은 함수가 큰 함수보다 낫다

작은 함수는 일반적으로 한 가지 작업을 수행하고 목적이 명확하기 때문에 읽고 이해하기가 더 쉽습니다. 또한 검토하거나 수정할 코드가 적기 때문에 테스트 및 유지 관리가 더 쉬워집니다. 반면 큰 함수는 더 복잡하고 이해하기 어려워 버그나 오류가 발생할 수 있습니다.

// 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;
}

이 getTotalPrice()함수는 주문 배열을 사용하고 reduce()각 주문의 가격을 합산하는 함수를 사용하여 총 가격을 반환합니다. 이 getOrderIds()함수는 주문 배열을 사용하고 map()함수를 사용하여 ID 배열을 반환합니다. 이 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);
}

이 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) {
  // ...
}

함수에는 제한된 수의 인수가 있어야 합니다.

많은 수의 인수를 사용하는 함수는 사용, 이해 및 테스트하기가 더 어려울 수 있습니다. 가능한 경우 인수 수를 3~4개 미만으로 유지하는 것을 목표로 합니다. 인수 수를 줄이는 한 가지 방법은 인수를 단일 개체 또는 데이터 구조로 전달하는 것입니다.

// 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);

함수는 부작용이 없어야 합니다.

함수가 자체 범위 밖의 상태를 수정하면 부작용이 발생합니다. 부작용으로 인해 함수의 동작을 테스트하고 추론하기가 더 어려워지고 예기치 않은 동작이 발생할 수도 있습니다. 함수는 자신의 매개변수와 지역 변수에서만 작동해야 하며 범위 밖의 상태를 수정해서는 안 됩니다.

// 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

함수는 전역 변수에 의존해서는 안 됩니다.

전역 변수는 기능을 이해하고 테스트하기 어렵게 만들고 예기치 않은 동작을 유발할 수 있습니다. 함수는 자체 매개변수와 로컬 변수에서만 작동해야 하며 전역 상태에 의존해서는 안 됩니다. 또한 다른 컨텍스트에서 함수를 더 쉽게 재사용할 수 있습니다.

// 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

함수는 너무 깊게 중첩되어서는 안 됩니다.

깊이 중첩된 함수는 읽고 이해하기 어려울 뿐만 아니라 복잡한 제어 구조로 이어질 수 있습니다. 함수는 여러 수준의 중첩 함수를 정신적으로 추적할 필요 없이 제어 흐름을 쉽게 이해할 수 있는 방식으로 구성되어야 합니다.

// 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

오류 코드가 아닌 예외 사용

오류 코드는 코드에 대한 추론을 어렵게 만들고 복잡한 제어 구조로 이어질 수 있습니다. 예외는 더 자연스러운 제어 흐름과 더 쉬운 오류 처리를 허용합니다. 함수는 오류 코드나 값을 반환하는 대신 오류나 예기치 않은 동작을 나타내기 위해 예외를 발생시켜야 합니다.

// 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
}

결론

결론적으로 코드베이스의 전반적인 품질을 향상시키기 위해 함수를 작성할 때 모범 사례를 따르는 것이 중요합니다. 그렇게 함으로써 우리는 함수를 읽고 재사용할 수 있을 뿐만 아니라 장기적으로 유지 관리할 수 있음을 보장할 수 있습니다. 이렇게 하면 잘못 작성된 코드로 인해 발생할 수 있는 버그 및 오류의 수를 줄이는 데 도움이 될 수 있으므로 장기적으로 시간과 리소스를 절약할 수 있습니다.

깨끗한 코드는 작동하는 코드를 작성하는 것만이 아니라는 점은 주목할 가치가 있습니다. 또한 시간이 지남에 따라 쉽게 이해하고 테스트하고 수정할 수 있는 코드 작성에 관한 것입니다. 따라서 코드의 품질과 유지 관리성을 최적화하기 위해 함수를 작성할 때 위에 설명된 모범 사례를 신중하게 고려하는 시간을 갖는 것이 좋습니다.