코드를 생성하는 능력과 문제를 해결하는 능력은 같지 않다

vx_developer·2026년 9월 4일

코테보다가

목록 보기
4/26
post-thumbnail

프로그래밍을 처음 배울 때는 주어진 문제를 코드로 구현하는 연습을 많이 한다.

예를 들어 다음과 같은 문제가 주어진다.

쿠폰 목록을 만료일이 가까운 순서로 정렬하라.

function sortCouponsByExpiry(coupons) {
  return [...coupons].sort(
    (first, second) =>
      new Date(first.expiresAt) -
      new Date(second.expiresAt)
  );
}

이 코드는 원본 배열을 복사한 뒤 각 쿠폰의 만료 시각을 비교하여 가까운 순서로 정렬한다.

문제가 이미 명확하게 정의된 상황이라면 적절한 해결책이다.

지금은 이 정도의 코드도 AI에게 요청하면 바로 생성할 수 있다. 다른 언어로 변환하거나, 테스트를 추가하거나, 함수 이름을 개선하는 일도 어렵지 않다.

그런데 실제 서비스에서 개발자에게 전달되는 요구사항은 보통 알고리즘 문제처럼 정리되어 있지 않다.

사용자가 중요한 쿠폰을 놓치지 않게 해주세요.

이 요구사항을 받았을 때 바로 만료일 정렬 코드를 만드는 것이 문제 해결일까?

아직은 아니다.

  • 중요한 쿠폰은 무엇인가?
  • 만료일이 가까우면 항상 중요한가?
  • 이미 사용한 쿠폰도 보여줘야 하는가?
  • 만료일이 없는 쿠폰은 어디에 배치해야 하는가?
  • 전체 목록을 정렬해야 하는가?
  • 가장 급한 쿠폰 하나만 알려주면 되는가?
  • 알림을 보내야 하는가, 화면 순서를 바꿔야 하는가?
  • 무엇이 바뀌면 문제가 해결되었다고 판단할 수 있는가?

코드를 생성하는 능력은 주어진 작업을 구현하는 능력이다. 문제를 해결하는 능력은 그보다 앞에서 무엇을 구현해야 하는지 정의하고, 가능한 방법 중 적절한 것을 선택하는 능력이다.

실제 서비스에서 문제 해결은 코드를 작성하는 일이 아니라, 원하는 결과를 명확히 정의하고 그 결과에 가장 적합한 처리 방법을 선택하는 과정이다.

사용자가 말한 기능과 해결해야 할 문제는 다를 수 있다

쿠폰 서비스 사용자 크리스가 다음과 같은 의견을 남겼다고 생각해보자.

만료일이 가까운 쿠폰을 위에 보여주세요.

이 문장은 해결해야 할 문제처럼 보이지만 실제로는 사용자가 생각한 해결 방법에 가깝다.

크리스가 겪은 상황을 조금 더 살펴보면 다음과 같을 수 있다.

  1. 크리스는 여러 사람에게 쿠폰을 받았다.
  2. 최근에 받은 쿠폰이 목록 위에 표시되었다.
  3. 오래전에 받은 쿠폰은 화면 아래로 밀려났다.
  4. 그중 하나가 다음 날 만료되었지만 발견하지 못했다.
  5. 사용하지 못한 채 쿠폰이 만료되었다.

크리스가 원하는 진짜 결과는 목록의 정렬 자체가 아니다.

사용할 수 있는 쿠폰이 있다는 사실을 만료 전에 발견하고 싶다.

이제 여러 해결 방법을 생각할 수 있다.

  • 전체 쿠폰을 만료일순으로 정렬한다.
  • 7일 안에 만료되는 쿠폰에 표시를 추가한다.
  • 가장 먼저 만료되는 쿠폰을 화면 위 배너에 보여준다.
  • 만료 하루 전에 알림을 보낸다.
  • 사용 완료 및 만료된 쿠폰을 기본 목록에서 제외한다.

모두 같은 문제를 해결하려는 방법이지만 구현해야 하는 코드와 필요한 데이터는 서로 다르다.

사용자가 제안한 기능을 그대로 구현하는 것도 필요할 수 있다. 그러나 개발자는 그 기능이 실제 문제를 해결하는지 먼저 확인해야 한다.

문제를 정의하지 않으면 올바른 코드로 잘못된 기능을 만들 수 있다

“쿠폰을 만료일순으로 정렬한다”는 문장만 보고 코드를 생성하면 다음과 같은 결과를 얻을 수 있다.

function sortCouponsByExpiry(coupons) {
  return [...coupons].sort(
    (first, second) =>
      new Date(first.expiresAt) -
      new Date(second.expiresAt)
  );
}

이 코드는 정상적으로 실행될 수 있다. 하지만 실제 데이터에서는 예상하지 못한 문제가 생긴다.

const coupons = [
  {
    title: "Dinner Date",
    status: "USED",
    expiresAt: "2026-09-05T13:00:00Z",
  },
  {
    title: "Movie Night",
    status: "ACTIVE",
    expiresAt: null,
  },
  {
    title: "Coffee Together",
    status: "ACTIVE",
    expiresAt: "invalid-date",
  },
];

만료일이 없는 쿠폰과 잘못된 날짜가 포함되어 있다. 이미 사용한 쿠폰도 가장 위에 나타날 수 있다.

코드는 요청받은 정렬을 수행했지만 사용자가 중요한 쿠폰을 발견하도록 돕지는 못한다.

문제는 정렬 문법에 있지 않다.

다음 내용이 결정되지 않은 상태에서 구현부터 시작했기 때문이다.

  • 어떤 쿠폰을 목록에 포함할 것인가
  • 유효한 만료일은 어떤 형식인가
  • 만료일이 없는 쿠폰은 어떻게 처리할 것인가
  • 이미 사용했거나 취소된 쿠폰은 어디에 표시할 것인가
  • 현재 시간은 사용자 기기와 서버 중 어디를 기준으로 할 것인가

코드를 정확하게 생성했어도 입력과 규칙이 잘못 정의되어 있다면 서비스의 결과는 올바르지 않다.

모호한 요구사항을 입력과 출력으로 바꾸면 문제가 선명해진다

“중요한 쿠폰을 놓치지 않게 한다”는 문장을 컴퓨터가 처리할 수 있는 문제로 바꿔보자.

먼저 입력을 정의한다.

  • 현재 로그인한 사용자의 쿠폰
  • 쿠폰의 상태
  • 남은 사용 횟수
  • 만료 시각
  • 현재 서버 시간

그다음 출력과 규칙을 정의한다.

  • 사용할 수 있는 쿠폰만 기본 목록에 포함한다.
  • 만료일이 있는 쿠폰은 가까운 순서로 배치한다.
  • 만료일이 없는 쿠폰은 만료일이 있는 쿠폰 뒤에 배치한다.
  • 7일 안에 만료되는 쿠폰에는 isExpiringSoon을 표시한다.
  • 날짜 형식이 잘못된 쿠폰은 일반 결과에 포함하지 않고 오류를 기록한다.

이제 요구사항을 하나의 처리 과정으로 표현할 수 있다.

function buildAvailableCouponList(coupons, serverNow) {
  return coupons
    .filter((coupon) =>
      isCouponAvailable(coupon, serverNow)
    )
    .map((coupon) => ({
      ...coupon,
      isExpiringSoon: isWithinDays(
        coupon.expiresAt,
        serverNow,
        7
      ),
    }))
    .sort(compareCouponExpiry);
}

이 함수는 단순히 배열을 정렬하지 않는다.

  1. 사용할 수 없는 쿠폰을 제외한다.
  2. 곧 만료되는 쿠폰인지 계산한다.
  3. 사용 가능한 쿠폰을 만료 순서로 정렬한다.

현실의 “중요한 쿠폰”이라는 표현이 필터링 조건, 계산된 상태와 정렬 기준으로 바뀌었다.

이때 status, remainingUses, expiresAt은 데이터베이스에 저장된 원본 데이터다. isExpiringSoon은 현재 시간과 만료 시각을 이용해 계산한 데이터다.

function isWithinDays(expiresAt, now, days) {
  if (!expiresAt) {
    return false;
  }

  const expiryTime = new Date(expiresAt).getTime();

  if (!Number.isFinite(expiryTime)) {
    return false;
  }

  const difference = expiryTime - now.getTime();
  const limit = days * 24 * 60 * 60 * 1000;

  return difference > 0 && difference <= limit;
}

isExpiringSoon을 데이터베이스에 영구 저장하면 시간이 지나면서 실제 상태와 달라질 수 있다. 오늘은 7일 이상 남았더라도 내일은 만료 임박 쿠폰이 될 수 있기 때문이다.

따라서 만료 시각을 Source of Truth로 두고, 만료 임박 여부는 필요한 시점에 계산하는 편이 자연스럽다.

문제를 제대로 정의하면 어떤 데이터를 저장하고 어떤 값을 계산해야 하는지도 구분할 수 있다.

출력이 하나라면 전체 목록을 정렬할 필요가 없다

여기서 기획 방향이 바뀌었다고 생각해보자.

전체 목록을 만료일순으로 보여주는 대신, 홈 화면에 가장 먼저 만료되는 쿠폰 하나만 표시하기로 했다.

필요한 출력은 다음과 같다.

현재 사용할 수 있는 쿠폰 중 만료 시각이 가장 가까운 쿠폰 하나

이 경우 모든 쿠폰을 정렬한 뒤 첫 번째 쿠폰을 가져올 수 있다.

function findNextExpiringCoupon(coupons, now) {
  const sortedCoupons = buildAvailableCouponList(
    coupons,
    now
  );

  return sortedCoupons[0] ?? null;
}

결과는 올바를 수 있다. 하지만 쿠폰 하나를 찾기 위해 전체 목록의 순서를 모두 결정한다.

가장 가까운 쿠폰 하나만 필요하다면 목록을 한 번 확인하면서 현재까지 가장 가까운 쿠폰만 기억할 수 있다.

function findNextExpiringCoupon(coupons, now) {
  let nextCoupon = null;

  for (const coupon of coupons) {
    if (!isCouponAvailable(coupon, now)) {
      continue;
    }

    if (!coupon.expiresAt) {
      continue;
    }

    if (
      nextCoupon === null ||
      coupon.expiresAt < nextCoupon.expiresAt
    ) {
      nextCoupon = coupon;
    }
  }

  return nextCoupon;
}

이 코드는 전체 쿠폰의 순서를 만들지 않는다.

각 쿠폰을 확인하면서 지금까지 발견한 가장 가까운 만료일만 유지한다. 모든 쿠폰을 확인한 뒤에는 가장 먼저 만료되는 쿠폰 하나가 남는다.

두 방법 중 하나가 언제나 더 좋은 것은 아니다.

필요한 결과적절한 방법
전체 쿠폰을 만료일순으로 표시전체 목록 정렬
가장 먼저 만료되는 쿠폰 하나 표시한 번 탐색하며 최솟값 유지
만료 임박 쿠폰 3개 표시정렬 후 일부 선택 또는 상위 후보만 유지
데이터베이스의 대량 쿠폰 조회조건 검색과 ORDER BY, LIMIT
만료 하루 전 사용자에게 알림예약 작업 또는 주기적인 대상 조회

문제의 출력이 달라지면 적절한 알고리즘도 달라진다.

코드를 잘 작성하는 것만으로는 이 선택을 할 수 없다. 무엇이 필요한 결과인지 먼저 알아야 한다.

느린 화면을 발견했다고 정렬 코드부터 최적화하면 안 된다

만료일순으로 정렬한 뒤 쿠폰 화면이 느려졌다고 생각해보자.

AI에게 다음과 같이 요청할 수 있다.

이 정렬 알고리즘을 더 빠르게 바꿔줘.

하지만 화면이 느린 원인이 정렬이라는 근거는 아직 없다.

실제 지연은 다음과 같은 곳에서 발생할 수 있다.

  • 서버가 모든 쿠폰 데이터를 반환하고 있다.
  • 데이터베이스가 사용자 쿠폰을 찾을 때 전체 테이블을 확인한다.
  • 쿠폰 이미지의 크기가 너무 크다.
  • 프런트엔드에서 같은 계산을 여러 번 실행한다.
  • 사용 완료 기록을 쿠폰마다 별도의 API로 조회한다.
  • 네트워크 응답 자체가 느리다.

쿠폰 20개를 정렬하는 시간은 매우 짧은데 데이터베이스 조회에 2초가 걸릴 수도 있다. 이 상황에서 정렬 함수를 개선해도 사용자가 느끼는 속도는 거의 달라지지 않는다.

문제 해결은 “느리다”라는 증상을 곧바로 특정 코드의 문제로 바꾸는 일이 아니다.

다음 흐름으로 확인해야 한다.

flowchart TD
    A["사용자가 느끼는 문제"]
    --> B["관찰 가능한 기준 정의"]
    --> C["데이터로 원인 측정"]
    --> D["해결 방법 후보 비교"]
    --> E["구현 후 결과 검증"]

예를 들어 목표를 다음과 같이 정의할 수 있다.

사용자가 쿠폰 화면을 열면 사용 가능한 쿠폰 20개가 1초 안에 표시되어야 한다.

그다음 구간별 시간을 측정한다.

구간측정할 내용
데이터베이스쿠폰 조회 시간과 조회한 행의 수
서버검증, 변환 및 정렬 시간
네트워크응답 크기와 전송 시간
프런트엔드렌더링과 이미지 로딩 시간
사용자 경험화면을 사용할 수 있게 되는 전체 시간

측정 결과 데이터베이스 조회가 병목이라면 인덱스나 쿼리를 개선해야 한다. 응답 데이터가 너무 많다면 페이지네이션이 필요하다. 이미지가 원인이라면 정렬 알고리즘을 수정할 이유가 없다.

적절한 해결 방법을 선택하려면 코드보다 먼저 문제의 위치를 찾아야 한다.

기능이 동작해도 원하는 결과를 만들지 못할 수 있다

쿠폰 목록을 만료일순으로 정렬하는 기능을 배포했다고 생각해보자.

테스트도 통과했고 화면에도 오류가 없다.

그렇다면 문제는 해결된 것일까?

기술적으로는 기능이 동작한다. 하지만 처음 해결하려던 문제는 “사용자가 중요한 쿠폰을 놓친다”는 것이었다.

이를 확인하려면 코드의 동작뿐 아니라 사용자 결과도 살펴봐야 한다.

  • 만료되기 전에 사용된 쿠폰의 비율이 증가했는가?
  • 만료 직전 쿠폰을 확인한 사용자가 늘었는가?
  • 사용하지 못하고 만료되는 쿠폰이 줄었는가?
  • 사용자들이 만료 예정 쿠폰을 더 쉽게 발견하는가?
  • 정렬 변경으로 다른 중요한 쿠폰을 찾기 어려워지지는 않았는가?

코드가 요구사항대로 실행되는지는 테스트로 확인할 수 있다. 그러나 기능이 사용자의 문제를 해결했는지는 운영 지표와 사용자 행동을 통해 확인해야 한다.

문제 해결은 배포에서 끝나지 않는다.

정의한 문제가 실제로 줄어들었는지 확인할 때 비로소 해결 방법을 평가할 수 있다.

AI는 구현 후보를 만들 수 있지만 선택 기준까지 자동으로 결정하지 않는다

AI는 쿠폰 목록을 정렬하는 함수를 빠르게 만들 수 있다.

다음과 같은 작업도 맡길 수 있다.

  • 만료일이 없는 값을 마지막에 배치한다.
  • 사용할 수 없는 쿠폰을 필터링한다.
  • 가장 가까운 만료일을 찾는다.
  • 데이터베이스 쿼리를 작성한다.
  • 테스트 사례를 만든다.
  • 여러 구현 방법의 복잡도를 비교한다.

하지만 어떤 기능이 실제로 필요한지는 서비스의 목적과 제약 조건에 따라 달라진다.

AI가 적절한 해결책을 제안하려면 개발자가 다음 정보를 제공해야 한다.

  • 사용자가 겪는 실제 상황
  • 원하는 최종 결과
  • 데이터가 저장된 위치
  • 데이터의 크기와 증가 가능성
  • 응답 시간 요구사항
  • 반드시 지켜야 하는 비즈니스 규칙
  • 허용할 수 있는 실패와 허용할 수 없는 실패
  • 구현 후 성공 여부를 판단할 기준

이 정보가 없다면 AI는 일반적인 가정을 바탕으로 코드를 생성한다. 코드는 자연스러워 보여도 실제 서비스와 맞지 않을 수 있다.

AI에게 일을 잘 맡기는 능력도 결국 문제를 명확히 정의하는 능력에서 시작한다.

코드를 작성하기 전에 구분해야 할 네 가지가 있다

서비스 개발에서는 문제, 목표, 방법과 코드가 쉽게 섞인다.

이를 다음과 같이 구분할 수 있다.

구분쿠폰 서비스의 예
문제사용자가 만료 예정 쿠폰을 발견하지 못한다
목표사용하지 못하고 만료되는 쿠폰을 줄인다
해결 방법만료 임박 표시, 정렬, 배너 또는 알림
구현필터링 함수, 정렬 알고리즘, 조회 쿼리, 알림 작업

코드는 마지막 단계에 있다.

문제를 해결 방법처럼 표현하면 선택지가 줄어든다.

문제: 쿠폰이 만료일순으로 정렬되지 않는다.

이렇게 정의하면 답은 정렬 기능으로 제한된다.

반면 사용자 결과를 중심으로 정의하면 여러 방법을 비교할 수 있다.

문제: 사용자가 곧 만료되는 쿠폰을 발견하지 못한다.

이제 정렬 외에도 배너, 표시, 알림과 목록 필터링을 고려할 수 있다.

좋은 문제 정의는 코드를 더 복잡하게 만드는 것이 아니라 불필요한 코드를 만들지 않게 한다.

구현을 시작하기 전에 물어봐야 할 질문

새로운 기능이나 버그를 맡았을 때 바로 코드를 생성하기 전에 다음 질문을 확인할 수 있다.

  1. 사용자가 실제로 겪는 문제는 무엇인가?
  2. 현재 보이는 현상은 문제인가, 원인인가, 해결 방법인가?
  3. 문제가 해결되면 사용자에게 무엇이 달라지는가?
  4. 성공 여부를 어떤 결과나 지표로 확인할 수 있는가?
  5. 필요한 출력은 전체 목록인가, 일부 결과인가, 하나의 값인가?
  6. 판단에 필요한 원본 데이터는 무엇이며 어디에 있는가?
  7. 외부에서 들어오는 데이터는 어떤 검증이 필요한가?
  8. 반드시 지켜야 하는 비즈니스 규칙은 무엇인가?
  9. 데이터의 크기와 변경 빈도는 어느 정도인가?
  10. 실제 병목이나 오류의 원인을 측정했는가?
  11. 가능한 해결 방법은 하나뿐인가?
  12. 각 방법은 속도, 메모리, 최신성, 복잡성 중 무엇을 교환하는가?
  13. 이 로직은 프런트엔드, 서버와 데이터베이스 중 어디에서 실행해야 하는가?
  14. 구현 후 원래 문제가 해결되었는지 어떻게 확인할 것인가?
  15. 선택한 방법이 적절한 이유를 코드 없이 설명할 수 있는가?

이 질문에 답한 뒤에는 AI가 만든 코드도 훨씬 구체적으로 평가할 수 있다.

코드를 만드는 사람보다 문제와 선택을 설명하는 사람이 필요하다

코드를 생성하는 능력은 중요하다.

명확하게 정의된 작업을 정확한 문법으로 구현하고, 읽기 쉬운 구조로 정리하며, 테스트 가능한 형태로 만드는 능력은 여전히 필요하다.

그러나 실제 서비스의 문제는 처음부터 함수 이름과 입력값을 갖춘 상태로 주어지지 않는다.

개발자는 사용자의 불편과 비즈니스 요구사항을 살펴보고 다음 내용을 결정해야 한다.

  1. 무엇이 실제 문제인가?
  2. 어떤 결과를 만들어야 하는가?
  3. 어떤 데이터와 규칙이 필요한가?
  4. 어떤 해결 방법을 선택할 수 있는가?
  5. 현재 조건에는 어떤 방법이 가장 적절한가?
  6. 구현한 결과가 문제를 해결했는가?

이 과정이 빠지면 올바른 코드로 불필요한 기능을 만들 수 있다. 성능이 좋은 알고리즘으로 잘못 정의된 문제를 더 빠르게 처리할 수도 있다.

코드를 생성하는 능력은 정해진 해결 방법을 구현하는 능력이고, 문제를 해결하는 능력은 무엇을 해결할지 정의하고 여러 방법의 비용을 비교하여 적절한 선택을 하는 능력이다.

AI가 코드를 더 빠르게 생성할수록 이 차이는 더욱 분명해진다.

앞으로 개발자의 가치는 얼마나 많은 코드를 직접 입력했는지만으로 결정되지 않는다. 무엇을 만들지, 왜 이 방법을 선택했는지, 실제 문제가 해결되었는지를 설명할 수 있어야 한다.

profile
Vision eXperience Developer

0개의 댓글