액션과 계산, 데이터 구분하기

hs·2025년 11월 2일

데이터

  • 프로그램에서 처리되는 정보나 값, 이벤트에 대한 결과
    • 이름, 전화 번호, 구매 리스트 등
  • 직렬화를 통해 전송하거나 저장, 읽기가 쉽다.
  • 동일성 비교가 쉽다.

계산

  • 부수 효과가 발생하지 않는 순수 함수
    • 사칙연산, 문자열 합치기 등
  • 입력으로 출력을 계산
    • 입력에만 의존하며 같은 입력에 항상 같은 출력을 반환
  • 테스트가 쉽다

액션

  • 부수 효과가 발생하는 함수 (순수 함수X)
    • 메일 전송, 파일 입출력 등
  • 실행 시점과 횟수에 의존 → 결과가 달라질 수 있다.

구분하는 이유

  • 계산과 데이터는 상대적으로 예측과 테스트가 쉽다.
  • 액션은 시점과 횟수에 의존하고, 부수 효과가 발생하기 때문에 예측과 테스트가 어렵다.
    • 액션을 호출하는 함수는 액션이 된다.
  • 액션이 미치는 범위를 최소화하기 위해 계산과 데이터로 구분한다.

액션 나누기

  • 가능한 액션을 적게 사용하고 대신 계산을 사용할 수 있는지 고려하면 좋다.
  • 액션과 관련 없는 코드는 모두 제거한다.
    • 결정이나 계획과 관련된 부분은 계산이 될 가능성이 높다.
  • 계산도 더 작은 계산과 데이터로 나눌 수 있는지 고려한다.
  • 데이터는 다른 영향을 주지 않는다.
    • 데이터를 먼저 찾는 것이 좋다.
    • 데이터를 알면 동작에 대해 알 수 있다.

액션 나누기 예시

요구사항: 친구를 10명 이상 추천한 구독자에게 높은 등급의 쿠폰을 보내준다.

첫 번째 방법

// 1. 구독자 목록을 가져온다 (액션)
const subscribers = fetchSubscribers();

// 2. 쿠폰 목록을 가져온다.(액션)
const coupons = fetchCoupons();

// 3. 구독자 목록에서 친구를 10명 이상 추천한 구독자를 뽑아낸다. (계산)
const bestSubscribers = findBestSubscribers(subscribers);

// 4. 쿠폰 목록에서 높은 등급의 쿠폰을 뽑아낸다. (계산)
const bestCoupon = findBestCoupons(coupons);

// 5. 3번에서 뽑아낸 구독자에게 4번에서 뽑아낸 쿠폰을 발송한다. (액션)
bestSubscribers.forEach(subscriber => sendMail(subscriber, bestCoupon))

// DB에서 구독자 목록을 가져온다.
function fetchSubscribers() {
  return [
    { email: 'user1@gmail.com', count: 10 },
    { email: 'user2@gmail.com', count: 4 },
    { email: 'user3@gmail.com', count: 1 },
    { email: 'user4@gmail.com', count: 15 },
  ]
}

// DB에서 쿠폰 목록을 가져온다.
function fetchCoupons() {
  return [
    { code: '5% 할인쿠폰', rank: 'bad' },
    { code: '10% 할인쿠폰', rank: 'good' },
    { code: '30% 할인쿠폰', rank: 'best' },
  ]
}

// 좋은 구독자를 뽑아낸다.
function findBestSubscribers(subscribers) {
  const bestSubscribers = [];
  
  for (let i = 0; i < subscribers.length; i++) {
    const subscriber = subscribers[i];
    
    if (subscriber.count >= 10) {
      bestSubscribers.push(subscriber);
    }
  }
  return bestSubscribers;

  // return subscribers.filter(subscriber => subscriber.count >= 10);
}

// 좋은 쿠폰을 뽑아낸다.
function findBestCoupons(coupons) {
  for (let i = 0; i < coupons.length; i++) {
    const coupon = coupons[i];
    if (coupon.rank === 'best') {
      return coupon
    }
  }

  // return coupons.find(coupon => coupon.rank === 'best');
}

// 메일을 보낸다.
function sendMail(subscriber, coupon) {
  const content = {
    to: subscriber.email,
    coupon: coupon,
  }
  // 메일을 보낸다고 가정
  console.log(content)
}

개선 방법

1. 1 ~ 4번 과정 동일
2. 5번 과정을 계산과 액션으로 나눈다

a. 좋은 구독자에게 좋은 쿠폰을 발송할 목록을 만든다.
b. 만들어진 목록으로 메일을 발송한다.
// 5번 과정 분리
// 5-1. 발송 메일 목록을 만든다. (계산)
const mailList = createMailList(bestSubscribers, bestCoupon);

// 5-2. 메일을 발송한다. (액션)
sendMail(mailList);

// 메일 목록을 만든다.
function createMailList(subscribers, coupon) {
  const mailList = [];
  for (let i = 0; i < subscribers.length; i++) {
    mailList.push({
      subscriber: subscribers[i],
      coupon,
    })
  }

  return mailList;

  // return subscribers.map(subscriber => ({
  //   subscriber,
  //   coupon,
  // }))
}

// 메일을 보낸다.
function sendMail(mailList) {
  for (let i = 0; i < mailList.length; i++) {
    // 메일을 보낸다고 가정
    console.log(mailList[i])
  }
}

메일 목록을 계산으로 분리하여 테스트를 좀 더 수월하게 할 수 있다.

profile
sh

0개의 댓글