본문 바로가기
Javascript

😇 좋은코드 나쁜코드 6장 예측 가능한 코드를 작성하라

by frontChoi 2026. 8. 31.
반응형

매직값 반환금지

매직값은 버그를 유발할 수 있다.

아래 예제를 보면 나이값이 없는 경우도 있는데, -1으로 리턴하고

평균을 계산시에 계산에 오류가 발생할 수 있다

class User {
  #age;
  constructor(age) {
    this.#age = age;
  }
  // 매직값인 -1을 리턴한다
  getAge() {
    if (!this.#age) return -1;
    return this.#age;
  }
}

const user1 = new User(31);
const user2 = new User(30);
// 나이값이 없을수도 있다
const user3 = new User();

const userList = [user1, user2, user3];

function getAvgAge() {
  sum = 0;

  for (user of userList) {
    sum += user.getAge();
  }

  return sum / userList.length;
}
// 평균값에 -1도 포함하여 계산한다
console.log(getAvgAge());

널,옵셔널 또는 오류를 반환하라

호출하는 쪽에서는 코드의 세부사항에 대해서 모를 수도 있다. 그러므로 null을 반환시킴으로써,

오류를 알리는것이다

class User {
  #age;
  constructor(age) {
    this.#age = age;
  }

  getAge() {
    // 빈값인 경우 Error를 리턴함으로서, 호출하는쪽에서 인지하도록 한다
    if (!this.#age) {
      throw new Error("age is empty");
    }
    return this.#age;
  }
}

const user1 = new User(31);
const user2 = new User(30);
const user3 = new User();

const userList = [user1, user2, user3];

function getAvgAge() {
  sum = 0;

  for (user of userList) {
    // 빈 값일 경우 오류를 일으킨다
    sum += user.getAge();
  }

  return sum / userList.length;
}
console.log(getAvgAge());

예상치 못한 부수효과는 피해라

💡함수 호출이 함수 외부에 초래한 상태 변화

함수 반환 값 이외에 다른효과 있다면 그것은 부수효과

 

  • 사용자에게 출력 표시
  • 파일이나 데이터베이스에 무언가를 저장
  • 다른시스템을 호출하여 네트워크 트래픽 발생
  • 캐시 업데이트 혹은 무효화

의도적인 부수효과


💡displayErrorMessage 는 오류를 표시하는 것이며, canvas에 에러 표시를 하는데, 이것은 의도적인 부수효과이다

 

const Color = {
  RED: "red",
};

class Canvas {
  drawText(message, color) {}
}

class UserDisPlay {
  /** @type {Canvas} */
  #canvas;

  /**
   * @param {Canvas} canvas
   */
  constructor(canvas) {
    this.#canvas = canvas;
  }

	displayErrorMessage (message) {
		// 부수효과로 캔버스가 업데이트 된다
    this.#canvas.drawText(message, Color.RED);
  }
}

예기치 않은 부수 효과는 문제가된다


💡getPixel은 함수명만 보면 pixel을 가져오는 함수이다.

다만 내용을 보면 canvas를 다시 그리는 로직이 있어서, 이것은 상세내용을 보기전까지는 부수효과가 발생한다

 

class UserDisPlay {
  /** @type {Canvas} */
  #canvas;

  /**
   * @param {Canvas} canvas
   */
  constructor(canvas) {
    this.#canvas = canvas;
  }

  displayErrorMessage(message) {
    // 부수효과로 캔버스가 업데이트 된다
    this.#canvas.drawText(message, ColorConstants.RED);
  }

  getPixel(x, y) {
    // 다시 그리기 효과로 부수효과가 발생한다
    this.#canvas.redraw();
    //로직 수행
  }

 
}

부수효과는 많은 비용이든다


💡

this.#canvas.redraw(); 같은 경우 잠재적으로 많은 비용이 들 수 있다

가령 captureScreenshot 에서 화면에서 getPixel를 호출한다면 문제가 될 수 있다

 

class UserDisPlay {
  /** @type {Canvas} */
  #canvas;

  /**
   * @param {Canvas} canvas
   */
  constructor(canvas) {
    this.#canvas = canvas;
  }

  displayErrorMessage(message) {
    this.#canvas.drawText(message, ColorConstants.RED);
  }

  getPixel(x, y) {
    this.#canvas.redraw();
    //로직 수행
  }

  // 스크린샷을 호출하여 반복문 만큼 redraw를 하게 된다.
  captureScreenshot() {
    const image = new Images(100, 100);

    for (let x = 0; x < image.getWidth(); x++) {
      for (let y = 0; y < image.getHeight(); y++) {
        // getPixel에서 부수효과 발생하여 느려질수 있음
        image.setPixel(x, y, this.getPixel(x, y));
      }
    }
  }
}

입력 매개변수를 수정하는것에 주의하라


💡신규 이용자에게 무료 평가판을 제공하는 기능이 존재한다고 가정한다

 

/**
 * @typedef {Object} User
 * @property {number} id
 * @property {string} name
 */

/**
 * @typedef {Object} Invoice
 * @property {number} id
 * @property {number} amount
 */

/**
 * @typedef {Object} OrderBatch
 * @property {() => Map<User, Invoice>} getUserInvoices
 * @property {() => Set<User>} getFreeTrialUsers
 */
const user1 = {
  id: 1,
  name: "철수",
};

const user2 = {
  id: 2,
  name: "영희",
};

const user3 = {
  id: 3,
  name: "민수",
};
/** @type {OrderBatch} */
const orderBatch = {
  getUserInvoices() {
    return new Map([
      [
        user1,
        {
          id: 101,
          amount: 10000,
        },
      ],
      [
        user2,
        {
          id: 102,
          amount: 20000,
        },
      ],
      [
        user3,
        {
          id: 103,
          amount: 30000,
        },
      ],
    ]);
  },
  getFreeTrialUsers() {
    return new Set([user1, user2, user3]);
  },
};

/**
 * @param {Map<User,Invoice>} userInvoices
 * @param {Set<User>} userWithFreeTrial
 */
function getBillableInvoices(userInvoices, userWithFreeTrial) {
  // 무료 이용자는 다 제거한다
  for (const user of userWithFreeTrial) {
    userInvoices.delete(user);
  }

  return userInvoices.values();
}

/**
 *
 * @param {OrderBatch} orderBatch
 */
function processOrders(orderBatch) {
  const userInvoices = orderBatch.getUserInvoices();
  const usersWithFreeTrial = orderBatch.getFreeTrialUsers();
  // 무료 이용자는 다 제거한다
  sendInvoices(getBillableInvoices(userInvoices, usersWithFreeTrial));
  // * userInvoices에서 무료이용자는 제거되므로, 서비스 이용이 불가능하다 * 
  enableOrderedServices(userInvoices);
}

function sendInvoices(invoices) {
  // 송장을 발송하는 로직을 수행한다
}

/**
 * @param {Map<User,Invoice>} userInvoices
 */
function enableOrderedServices(userInvoices) {
  // 서비스사용을 활성화 하는 로직을 수행한다
}

processOrders(orderBatch);

만약 무료 이용자에게도 서비스를 사용하고 싶다고 기능을 제공한다고 하면

getBillableInvoices에서 무료이용자를 매개변수로 받고 매개변수 원본을 수정하여, enableOrderedServices에서 서비스가 활성화 되지 않는 버그가 발생하게 된다

변경하기 전에 복사하라

/**
 * @param {Map<User,Invoice>} userInvoices
 * @param {Set<User>} userWithFreeTrial
 */
function getBillableInvoices(userInvoices, userWithFreeTrial) {
  return [...userInvoices]
    .filter((user) => !userWithFreeTrial.has(user))
    .map(([, invoice]) => invoice);
}

위 코드는 이전과 달리 원본을 훼손하지 않고 복사후 필터걸어서 리턴한다

미래를 위한 열거형 처리


💡열거형은 사용 가능한 값들을 미리 정해놓은 타입/구조이다

다른 사람의 코드 결과를 사용해야 할때가 있다.

 

const PredictedOutCome = Object.freeze({
  COMPANY_WILL_GO_BUST: "COMPANY_WILL_GO_BUST",
  COMPANY_WILL_MAKE_A_PROFIT: "COMPANY_WILL_MAKE_A_PROFIT",
});

/**
 * @typedef {typeof PredictedOutCome[keyof typeof PredictedOutCome]} PredictedOutComeType
 */
/**
 *
 * @param {PredictedOutComeType} prediction
 */
function isOutcomeSafe(prediction) {
  if (prediction === PredictedOutCome.COMPANY_WILL_GO_BUST) {
    return true;
  }

  return false;
}

위와 같이 사용하는 경우 isOutcomeSafe 에서 PredictedOutCome이 새로운 값이 추가가 된다면 false를 리턴하고, 만약 isOutcomeSafe의 코드가 멀리 떨어져있다면, 예상치 못한 오류가 발생할 수 있다

Switch 이용하기

const PredictedOutCome = Object.freeze({
  COMPANY_WILL_GO_BUST: "COMPANY_WILL_GO_BUST",
  COMPANY_WILL_MAKE_A_PROFIT: "COMPANY_WILL_MAKE_A_PROFIT",
});

/**
 * @typedef {typeof PredictedOutCome[keyof typeof PredictedOutCome]} PredictedOutComeType
 */
/**
 *
 * @param {PredictedOutComeType} prediction
 */
function isOutcomeSafe(prediction) {
  switch (prediction) {
    case PredictedOutCome.COMPANY_WILL_GO_BUST:
      return false;
    case PredictedOutCome.COMPANY_WILL_MAKE_A_PROFIT:
      return true;
  }

  throw new Error("Unhandled prediction : " + prediction);
}

isOutcomeSafe("WORLD_WILD_END");

에러를 일으킴으로써, 예상치 못한값에 대해 처리한다

반응형

'Javascript' 카테고리의 다른 글

🐶 좋은코드 나쁜코드 4장 오류  (0) 2026.08.05
Promise.all / Promise.allSettled  (0) 2026.05.26
Web-Client DIP(Dependency inversion principle)  (0) 2026.05.15
😌 DIP  (0) 2026.05.11
Queue으로 작업실행  (0) 2025.05.19

댓글