
프로그래밍을 처음 배울 때 알고리즘은 보통 문제를 해결하기 위한 순서와 절차라고 배운다.
예를 들어 여러 쿠폰 중에서 특정 쿠폰을 찾는 문제는 다음과 같이 해결할 수 있다.
function findCoupon(coupons, couponId) {
return coupons.find((coupon) => coupon.id === couponId);
}
앞에서부터 쿠폰을 하나씩 확인하다가 ID가 같은 쿠폰을 반환한다. 알고리즘의 기본적인 개념을 이해하기에는 충분한 예제다.
그런데 지금은 이런 코드도 AI가 몇 초 만에 작성한다.
코드를 설명해달라고 요청할 수도 있고, 더 빠른 방법으로 개선해달라고 할 수도 있다. 테스트 코드와 예외 처리까지 함께 만들어달라고 요청하는 것도 가능하다.
그렇다면 한 가지 질문이 생긴다.
AI가 알고리즘 문제를 풀고 코드까지 작성해주는데, 우리는 왜 알고리즘을 다시 배워야 할까?
실제 서비스를 개발할 때는 무엇을 작성할 수 있는지만으로 문제가 해결되지 않기 때문이다.
다음과 같은 판단이 남아 있다.
AI는 해결책을 빠르게 제안할 수 있다. 하지만 그 해결책을 이해하고 선택하고 검증하는 기준은 개발자에게 필요하다.
AI 시대에 알고리즘을 배운다는 것은 정답 코드를 외우는 일이 아니라, AI가 만든 해결 절차가 우리 서비스에 적합한지 판단하는 기준을 만드는 일이다.
쿠폰 서비스에서 사용자가 쿠폰을 사용할 수 있는지 판단하는 기능을 만든다고 생각해보자.
AI에게 다음과 같이 요청할 수 있다.
쿠폰이 사용되었거나 만료되었는지 확인하는 함수를 만들어줘.
AI는 다음과 비슷한 코드를 만들어줄 수 있다.
function canRedeemCoupon(coupon) {
return !coupon.isUsed && new Date(coupon.expiresAt) > new Date();
}
이 코드는 두 가지를 확인한다.
주어진 요구사항만 보면 자연스러운 해결책이다. 코드도 정상적으로 실행된다.
하지만 실제 쿠폰 서비스의 사용 가능 여부를 판단하기에는 아직 부족하다.
expiresAt은 어느 지역의 시간을 기준으로 하는가?AI가 코드를 잘못 작성했다고 단정할 수는 없다. 처음 요청에 이러한 조건이 포함되지 않았기 때문이다.
문제가 명확하지 않으면 AI는 빈 부분을 일반적인 가정으로 채운다. 그리고 그 가정이 실제 서비스의 정책과 다르면, 문법적으로 올바른 코드가 잘못된 결과를 만든다.
따라서 알고리즘을 설계하기 전에 다음 요소부터 분명하게 해야 한다.
| 구분 | 쿠폰 사용에서 결정해야 할 내용 |
|---|---|
| 입력 | 쿠폰 상태, 요청한 사용자, 서버 시간 |
| 출력 | 사용 가능 여부, 실패 이유 |
| 규칙 | 수신자만 사용 가능, 활성 상태, 잔여 횟수 존재 |
| 제약 조건 | 동시 요청, 중복 요청, 만료 시각 |
| 반드시 지켜야 할 조건 | 사용 횟수가 0보다 작아지지 않아야 함 |
코드를 생성하는 것보다 먼저 해야 하는 일은 무엇을 올바른 결과로 볼 것인지 결정하는 것이다.
앞의 조건을 반영해 쿠폰 사용 가능 여부를 조금 더 구체적으로 표현해보자.
function evaluateCouponRedemption({ coupon, requesterId, now }) {
if (coupon.recipientId !== requesterId) {
return { allowed: false, reason: "NOT_RECIPIENT" };
}
if (coupon.status !== "ACTIVE") {
return { allowed: false, reason: "NOT_ACTIVE" };
}
if (coupon.remainingUses <= 0) {
return { allowed: false, reason: "NO_REMAINING_USES" };
}
if (now.getTime() >= coupon.expiresAt.getTime()) {
return { allowed: false, reason: "EXPIRED" };
}
return { allowed: true };
}
이제 함수가 확인하는 서비스 규칙이 코드에 명확하게 드러난다.
단순히 true나 false만 반환하지 않고, 사용할 수 없는 이유도 함께 반환한다. 현재 시간도 함수 내부에서 직접 생성하지 않고 외부에서 전달받는다. 서버 시간을 사용할 수 있고, 테스트에서는 원하는 시간을 넣어 만료 조건을 검증할 수 있다.
이전 코드보다 실제 서비스에 가까워졌다.
하지만 이 함수가 allowed: true를 반환했다고 해서 쿠폰 사용이 안전하게 완료된 것은 아니다.
쿠폰의 남은 사용 횟수가 1회라고 생각해보자.
remainingUses를 조회한다.각 요청의 알고리즘만 보면 올바르게 동작했다. 하지만 두 요청을 함께 보면 1회용 쿠폰이 두 번 사용되었다.
이 문제는 조건문 하나를 더 추가한다고 해결되지 않는다. 확인과 변경을 하나의 안전한 작업으로 처리해야 한다.
예를 들어 데이터베이스에서 다음과 같은 조건부 변경을 수행할 수 있다.
UPDATE coupons
SET remaining_uses = remaining_uses - 1
WHERE id = $1
AND recipient_id = $2
AND status = 'ACTIVE'
AND expires_at > $3
AND remaining_uses > 0
RETURNING id, remaining_uses;
이 쿼리는 사용 조건을 만족하는 쿠폰만 변경한다.
두 요청이 동시에 들어오더라도 remaining_uses > 0이라는 조건을 만족한 요청만 성공해야 한다. 반환된 행이 없다면 쿠폰을 찾지 못했거나, 만료되었거나, 이미 모두 사용된 것이다.
여기서 알고리즘은 더 이상 함수 안의 조건문만을 의미하지 않는다.
쿠폰을 조회하고, 조건을 확인하고, 상태를 변경하고, 성공 여부를 반환하는 전체 처리 순서가 알고리즘이 된다.
프런트엔드에서도 쿠폰의 사용 가능 여부를 계산할 수 있다.
const isRedeemButtonDisabled =
coupon.status !== "ACTIVE" ||
coupon.remainingUses <= 0 ||
Date.now() >= new Date(coupon.expiresAt).getTime();
이 값은 버튼을 비활성화하고 사용자에게 현재 상태를 빠르게 보여주는 데 유용하다.
그러나 브라우저에 있는 쿠폰 데이터는 이미 오래된 정보일 수 있다. 사용자는 요청 값을 변경할 수도 있고, 다른 기기에서 같은 쿠폰을 먼저 사용할 수도 있다.
따라서 화면의 계산 결과를 최종 판단으로 사용해서는 안 된다.
| 위치 | 담당하는 판단 |
|---|---|
| UI | 현재 데이터에 따른 예상 상태를 보여준다 |
| 서버 | 사용자와 입력을 검증하고 비즈니스 규칙을 적용한다 |
| 데이터베이스 | 최신 상태를 기준으로 변경 가능 여부를 결정한다 |
| 테스트 | 규칙과 상태 전이가 계속 유지되는지 확인한다 |
쿠폰의 실제 사용 가능 횟수에 대한 Source of Truth는 데이터베이스에 있다. UI의 isRedeemButtonDisabled는 원본 데이터가 아니라 화면 표시를 위해 계산한 값이다.
알고리즘을 이해한다는 것은 로직의 순서만 이해하는 것이 아니다. 각 판단이 어디에서 이루어져야 하는지 구분하는 것까지 포함한다.
AI에게 특정 쿠폰을 빠르게 찾는 방법을 요청하면 해시 테이블이나 Map을 사용하라는 답을 받을 수 있다.
기존 코드는 배열을 앞에서부터 탐색한다.
const coupon = coupons.find(
(coupon) => coupon.id === targetCouponId
);
쿠폰이 많아질수록 확인해야 하는 데이터도 늘어난다.
반복해서 쿠폰을 조회한다면 ID를 기준으로 Map을 만들 수 있다.
const couponsById = new Map(
coupons.map((coupon) => [coupon.id, coupon])
);
const coupon = couponsById.get(targetCouponId);
처음에 Map을 만드는 비용이 들지만, 이후에는 ID로 쿠폰을 반복해서 찾기 쉬워진다.
그렇다고 두 번째 코드가 항상 더 좋은 것은 아니다.
Map이 유리할 수 있다.Map을 만드는 것은 적절하지 않다.Map보다 적절한 인덱스가 중요하다.| 상황 | 고려할 수 있는 방법 |
|---|---|
| 작은 목록에서 한 번 조회 | 배열의 선형 탐색 |
| 메모리 목록에서 반복 조회 | Map으로 조회 구조 생성 |
| 데이터베이스의 대량 데이터 조회 | ID 인덱스를 사용한 쿼리 |
| 상태가 자주 변경되는 데이터 | 최신 원본 데이터와 일관성 우선 |
AI는 여러 구현 방법을 제안할 수 있다. 알고리즘 지식은 그중 가장 어려운 방법을 선택하게 만드는 지식이 아니다.
알고리즘 지식은 데이터의 크기, 조회 횟수, 변경 빈도와 저장 위치를 기준으로 충분히 적절한 방법을 선택하게 하는 지식이다.
AI는 구현 코드를 바탕으로 테스트 코드도 생성할 수 있다.
하지만 코드에 잘못된 가정이 들어 있다면, AI가 만든 테스트도 그 가정을 그대로 확인할 수 있다. 테스트가 통과한다는 사실은 작성된 코드와 테스트가 서로 일치한다는 뜻일 뿐, 서비스의 요구사항까지 올바르다는 뜻은 아니다.
쿠폰 사용 기능에서 먼저 정의해야 할 것은 테스트 코드가 아니라 다음과 같은 조건이다.
이처럼 처리 전후에 반드시 유지되어야 하는 조건을 불변 조건이라고 한다.
동시 요청에 관한 테스트는 다음과 같은 모습이 될 수 있다.
it("1회용 쿠폰에 두 요청이 동시에 들어와도 한 번만 성공한다", async () => {
const results = await Promise.all([
redeemCoupon(couponId, currentUser),
redeemCoupon(couponId, currentUser),
]);
const successCount = results.filter(
(result) => result.success
).length;
expect(successCount).toBe(1);
});
이 테스트는 함수가 어떤 조건문을 사용했는지 확인하지 않는다. 대신 1회용 쿠폰은 한 번만 사용되어야 한다는 서비스의 약속을 검증한다.
개발자가 불변 조건과 반례를 정의하면 AI는 테스트 구현을 빠르게 도울 수 있다. 그러나 무엇을 검증해야 하는지 정하지 못하면 많은 테스트가 있어도 중요한 오류를 놓칠 수 있다.
AI가 잘할 수 있는 일은 분명히 존재한다.
그러나 AI가 서비스의 모든 맥락을 자동으로 알고 있는 것은 아니다.
| 작업 | AI가 도울 수 있는 부분 | 개발자가 책임질 부분 |
|---|---|---|
| 문제 정의 | 질문과 누락 조건 제안 | 해결해야 할 실제 문제 결정 |
| 방법 탐색 | 여러 알고리즘 후보 생성 | 서비스 제약에 맞는 방법 선택 |
| 코드 작성 | 구현과 리팩터링 | 코드가 정책을 정확히 반영하는지 확인 |
| 테스트 | 테스트 사례와 코드 생성 | 불변 조건과 중요한 반례 정의 |
| 성능 개선 | 복잡도 분석과 대안 제안 | 실제 병목 측정과 비용 판단 |
| 운영 | 오류 원인 분석 지원 | 사용자와 데이터에 미치는 결과 책임 |
AI에게 코드를 맡긴다고 해서 개발자의 일이 사라지는 것은 아니다. 개발자의 역할이 코드 입력에서 판단과 검증으로 이동한다.
오히려 코드 생성 비용이 낮아질수록 더 많은 해결책이 빠르게 만들어진다. 그중 무엇을 사용할지 결정하는 능력이 이전보다 중요해진다.
AI가 생성한 코드를 바로 서비스에 적용하기 전에 다음 질문을 확인할 수 있다.
마지막 질문에 답하기 어렵다면 코드는 동작하더라도 아직 충분히 이해한 해결책이라고 보기 어렵다.
AI가 없던 시기에는 알고리즘을 직접 구현하는 능력이 중요했다. 지금은 알고리즘의 구현뿐 아니라 설명, 변환, 테스트까지 AI의 도움을 받을 수 있다.
그렇다고 알고리즘 학습의 가치가 사라진 것은 아니다.
알고리즘을 모르면 AI가 만든 코드가 그럴듯한지 확인할 수는 있어도, 그것이 정확한지 판단하기 어렵다. 더 빠르다는 설명을 들어도 어떤 비용과 조건에서 빨라지는지 알기 어렵다. 테스트가 통과해도 어떤 반례가 빠졌는지 발견하기 어렵다.
반대로 알고리즘의 원리를 이해하면 AI를 코드 자동완성 도구보다 더 넓게 활용할 수 있다.
결국 AI 시대에 필요한 것은 AI보다 빠르게 코드를 작성하는 능력이 아니다.
AI 시대에 알고리즘을 배운다는 것은 정답 코드를 손으로 재현하는 훈련이 아니라, 문제를 명확히 정의하고 생성된 해결책의 정확성과 비용을 판단하며 그 결과를 책임질 기준을 만드는 일이다.
AI는 답을 빠르게 만들 수 있다.
하지만 어떤 질문을 해야 하는지, 무엇을 올바른 답으로 볼 것인지, 그 답을 실제 서비스에 사용해도 되는지는 여전히 개발자가 결정해야 한다.