
프로그래밍을 처음 배울 때 변수는 보통 “값을 담는 상자”라고 설명한다.
const name = "JungWoo";
let age = 30;
입문 단계에서는 이해하기 좋은 설명이다.
하지만 실제 서비스를 개발하기 시작하면 이 정의만으로는 부족하다.
사용자 이름, 상품 가격, 쿠폰 만료일, 로그인 상태처럼 수많은 값이 어디에서 생기고, 어디에 저장되며, 언제 바뀌고, 언제 사라지는지를 판단해야 하기 때문이다.
실제 서비스에서 변수를 만든다는 것은 단순히 값을 메모리에 저장하는 일이 아니다.
현실 세계의 정보에 이름과 의미를 부여하고, 프로그램이 그 값을 검증하고 계산하고 전달할 수 있도록 표현하는 일이다.
다음 코드를 살펴보자.
const totalPrice = 100 + 5;
계산 결과는 알 수 있지만 100과 5가 무엇을 의미하는지는 알기 어렵다.
상품 가격인지, 배송비인지, 할인 금액인지 코드를 작성한 사람 외에는 알 수 없다.
값에 의미 있는 이름을 붙이면 코드가 달라진다.
const productPrice = 100;
const shippingFee = 5;
const totalPrice = productPrice + shippingFee;
이제 이 코드를 처음 보는 사람도 상품 가격에 배송비를 더해 최종 금액을 계산한다는 사실을 이해할 수 있다.
변수의 중요한 역할은 값을 저장하는 것보다 값의 의미를 표현하는 데 있다.
100은 단순한 숫자이지만 productPrice라는 이름이 붙는 순간 서비스에서 사용하는 상품 가격이 된다.
서비스에서 다루는 대부분의 개념은 이런 과정을 통해 코드로 표현된다.
const productName = "Wireless Keyboard";
const productPrice = 89;
const quantity = 2;
const discountRate = 0.1;
const isMember = true;
const deliveryAddress = "Sydney NSW";
현실에 존재하는 상품명, 가격, 수량, 할인율, 회원 여부와 배송 주소가 프로그램이 처리할 수 있는 변수로 바뀐 것이다.
변수라는 이름 때문에 “변수는 변하는 값”이라고 생각하기 쉽다. 하지만 실제 개발에서 변수를 만든다는 것은 값을 반드시 변경하겠다는 뜻이 아니다.
JavaScript에서는 일반적으로 const를 기본으로 사용하고, 다른 값을 다시 할당해야 할 때만 let을 사용한다.
const couponTitle = "Dinner Date";
let remainingUses = 3;
remainingUses = remainingUses - 1;
couponTitle은 쿠폰을 처리하는 동안 다른 값으로 바뀌지 않는다.
반면 remainingUses는 쿠폰이 사용될 때마다 줄어들 수 있다.
두 값 모두 변수이지만 변경 가능성은 다르다. 따라서 변수를 만든다는 것은 “변하게 만든다”기보다 “프로그램에서 다룰 수 있도록 이름을 붙인다”는 의미에 가깝다.
연습 코드에서는 개발자가 직접 값을 작성하는 경우가 많다.
const userName = "JungWoo";
하지만 실제 서비스에서는 대부분의 값이 외부에서 들어온다.
사용자가 회원가입 화면에 입력한 이메일일 수도 있고, 주소에 포함된 상품 ID일 수도 있다.
const email = formData.email;
const productId = params.productId;
데이터베이스에서 조회한 결과일 수도 있다.
const product = await productRepository.findById(productId);
const productPrice = product.price;
로그인한 사용자의 세션에서 가져올 수도 있다.
const currentUser = await getCurrentUser();
const userId = currentUser.id;
외부 API 응답이나 환경설정에서 가져올 수도 있다.
const temperature = weatherResponse.temperature;
const databaseUrl = process.env.DATABASE_URL;
다른 값을 계산해 새로운 변수를 만들기도 한다.
const subtotal = productPrice * quantity;
const discountAmount = subtotal * discountRate;
const finalPrice = subtotal - discountAmount;
실제 서비스에서 하나의 요청이 처리되는 동안 데이터는 계속 이동한다.
사용자가 값을 입력하면 프론트엔드가 그 값을 변수에 담고, 서버로 전송한다.
서버는 입력값을 검증한 뒤 비즈니스 규칙을 적용하고, 처리 결과를 데이터베이스에 저장하거나 다시 사용자에게 전달한다.
변수는 이 전체 흐름에서 데이터를 연결하는 역할을 한다.
쿠폰 금액이 100달러를 넘을 수 없는 서비스를 생각해보자.
if (couponAmount > 100) {
throw new Error("쿠폰 금액이 너무 큽니다.");
}
코드는 동작하지만 100의 의미가 명확하지 않다.
최대 쿠폰 금액인지, 사용 횟수인지, 달러인지 알 수 없다.
이렇게 의미가 드러나지 않는 값을 흔히 매직 넘버라고 부른다.
const MAX_COUPON_AMOUNT_AUD = 100;
if (couponAmount > MAX_COUPON_AMOUNT_AUD) {
throw new Error("허용된 쿠폰 금액을 초과했습니다.");
}
값에 이름을 붙이자 서비스 규칙이 명확해졌다.
정책이 변경되더라도 관련된 숫자를 코드 전체에서 찾아다닐 필요 없이 한 곳에서 수정할 수 있다.
변수는 단지 코드를 짧게 만드는 도구가 아니다.
서비스의 정책과 규칙을 코드 안에 표현하는 도구다.
어떤 값이 변경될 수 있다고 해서 모두 let으로 선언하면 되는 것은 아니다.
실제 서비스에서는 누가 값을 변경하는지, 얼마나 자주 변경하는지, 얼마나 오래 보관해야 하는지를 함께 생각해야 한다.
예를 들어 쿠폰의 최대 유효기간이 365일이라고 하자.
개발자가 정책을 변경하고 서비스를 다시 배포해야 한다면 코드 상수로 둘 수 있다.
const MAX_COUPON_VALIDITY_DAYS = 365;
국가나 배포 환경에 따라 값이 다르다면 환경변수로 관리할 수 있다.
const maxValidityDays = Number(
process.env.MAX_COUPON_VALIDITY_DAYS
);
관리자가 관리자 페이지에서 직접 변경해야 한다면 데이터베이스에 저장하는 편이 적절하다.
const settings = await settingsRepository.get();
const maxValidityDays = settings.maxCouponValidityDays;
모두 프로그램 안에서는 변수로 사용되지만 값의 출처와 관리 책임은 다르다.
사용자의 이름이나 쿠폰 내용처럼 사용자마다 다른 값은 데이터베이스에 저장해야 한다.
API 키와 데이터베이스 주소처럼 환경마다 다른 값은 환경변수로 관리해야 한다.
입력 중인 텍스트나 모달의 열림 상태처럼 현재 화면에서만 필요한 값은 UI 상태로 관리할 수 있다.
따라서 변수를 설계할 때는 “이 값을 무엇으로 선언할까?”보다 “이 값은 어디에서 관리되어야 할까?”를 먼저 질문해야 한다.
React에서 다음과 같이 변수를 선언했다고 가정해보자.
let title = "";
title의 값을 바꿀 수는 있지만 React는 그 사실을 알지 못한다. 따라서 화면이 자동으로 업데이트되지 않는다.
화면에 영향을 주는 값은 상태로 관리해야 한다.
const [title, setTitle] = useState("");
쿠폰 편집 화면이라면 제목, 만료일, 이모지, 배경색, 미리보기 창의 상태 등을 관리할 수 있다.
const [couponTitle, setCouponTitle] = useState("");
const [expiryDate, setExpiryDate] = useState(null);
const [selectedEmoji, setSelectedEmoji] = useState("❤️");
const [backgroundColor, setBackgroundColor] = useState("#FFB6C1");
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
이 값들이 바뀌면 화면도 함께 변경된다.
그러나 새로고침하면 상태는 일반적으로 사라진다.
사용자가 작성한 쿠폰을 나중에도 확인하거나 다른 기기에서 열어야 한다면 화면 상태만으로는 부족하다.
저장 버튼을 눌렀을 때 서버로 전송하고 데이터베이스에 저장해야 한다.
같은 쿠폰 제목도 입력 중에는 화면 상태이고, API를 처리할 때는 서버의 지역 변수이며, 저장이 완료된 후에는 데이터베이스의 영구 데이터가 된다.
데이터의 역할과 생명주기에 따라 존재하는 위치가 달라지는 것이다.
함수 안에서 만들어진 변수는 함수가 실행되는 동안만 필요하다.
function calculateCouponStatus(expiryDate) {
const now = new Date();
const isExpired = expiryDate < now;
return isExpired ? "expired" : "active";
}
now와 isExpired는 쿠폰 상태를 계산하는 동안만 사용된다.
데이터베이스에 저장할 필요가 없다.
반면 사용자가 만든 쿠폰은 앱을 종료한 뒤에도 남아 있어야 한다.
await database.coupon.create({
data: couponData,
});
변수를 설계할 때는 다음과 같은 질문이 필요하다.
화면을 닫은 뒤에도 필요하다면 단순한 변수만으로는 충분하지 않다.
브라우저 저장소나 데이터베이스처럼 값을 더 오래 보관할 수 있는 장소가 필요하다.
다음 코드는 동작하지만 의미를 파악하기 어렵다.
const x = a * b;
서비스의 개념을 이름에 반영하면 코드 자체가 설명이 된다.
const subtotal = productPrice * quantity;
조건을 표현하는 변수는 서비스 규칙을 더욱 명확하게 보여준다.
const isCouponExpired = coupon.expiresAt < new Date();
const hasRemainingUses = coupon.remainingUses > 0;
const isRecipient = coupon.recipientId === currentUser.id;
const canRedeemCoupon =
!isCouponExpired &&
hasRemainingUses &&
isRecipient;
이 코드는 “쿠폰이 만료되지 않았고, 사용 횟수가 남아 있으며, 현재 사용자가 쿠폰의 수신자일 때 사용할 수 있다”는 규칙을 표현한다.
좋은 변수명은 주석 없이도 코드가 무엇을 판단하는지 알려준다. 반대로 data, value, result, temp처럼 범위가 지나치게 넓은 이름을 반복해서 사용하면 코드가 실제로 무엇을 의미하는지 알기 어려워진다.
변수명은 단순한 개발 취향이 아니다.
개발자와 기획자, 그리고 서비스의 요구사항을 연결하는 언어다.
상품 가격과 수량은 원본 데이터이고, 두 값을 곱한 소계는 계산된 값이다.
const productPrice = 100;
const quantity = 2;
const subtotal = productPrice * quantity;
쿠폰의 만료일은 데이터베이스에 저장할 수 있지만, 현재 만료되었는지는 저장된 만료일과 현재 시간을 비교해 계산할 수 있다.
const isExpired = new Date(coupon.expiresAt) < new Date();
데이터베이스에 expiresAt과 isExpired를 모두 저장하면 두 값이 서로 맞지 않는 문제가 생길 수 있다.
{
"expiresAt": "2026-12-31",
"isExpired": false
}
2027년이 되어도 isExpired가 자동으로 true가 되지는 않는다. 별도의 업데이트 작업이 없다면 잘못된 상태로 남게 된다.
따라서 다른 원본 데이터를 통해 언제든 계산할 수 있는 값이라면 필요할 때 계산하는 편이 안전하다.
물론 검색 성능이나 통계 처리 때문에 계산 결과를 저장해야 하는 경우도 있지만, 그때는 원본 데이터와 계산 결과를 어떻게 동기화할지 함께 설계해야 한다.
사용자가 입력한 값을 변수에 담았다고 해서 바로 신뢰해서는 안 된다.
const couponAmount = request.body.amount;
악의적인 사용자는 음수나 문자열, 비정상적으로 큰 값을 보낼 수 있다. 화면에서 숫자 입력만 가능하게 만들었더라도 API 요청은 직접 조작할 수 있다.
const couponAmount = Number(request.body.amount);
if (!Number.isFinite(couponAmount)) {
throw new Error("쿠폰 금액은 숫자여야 합니다.");
}
if (couponAmount <= 0) {
throw new Error("쿠폰 금액은 0보다 커야 합니다.");
}
if (couponAmount > MAX_COUPON_AMOUNT_AUD) {
throw new Error("허용된 쿠폰 금액을 초과했습니다.");
}
외부에서 들어온 값은 검증하기 전까지 신뢰할 수 없는 데이터다. 검증을 통과한 뒤에야 서비스의 비즈니스 로직에서 안전하게 사용할 수 있다.
변수를 이해한다는 것은 값을 선언하는 문법만 아는 것이 아니다. 값이 어디에서 왔는지, 신뢰할 수 있는지, 어떤 형태로 변환해야 하는지까지 이해하는 것이다.
하나의 값이나 중간 계산 결과는 변수로 표현할 수 있다.
const isExpired = coupon.expiresAt < new Date();
하지만 동일한 규칙을 여러 곳에서 반복해서 사용한다면 함수로 분리하는 편이 좋다.
function canRedeemCoupon(coupon, currentUserId) {
const isActive = coupon.status === "active";
const hasRemainingUses = coupon.remainingUses > 0;
const isNotExpired = coupon.expiresAt > new Date();
const isRecipient = coupon.recipientId === currentUserId;
return (
isActive &&
hasRemainingUses &&
isNotExpired &&
isRecipient
);
}
하나의 값은 변수, 반복되는 계산은 함수, 관련된 값과 행동의 묶음은 객체나 클래스로 표현할 수 있다.
오랫동안 보존해야 하는 값은 데이터베이스에 저장하고, 화면을 변화시키는 값은 상태로 관리한다.
이러한 구분은 문법의 문제가 아니라 책임의 문제다.
실제 서비스를 개발할 때는 값을 발견하는 순간 바로 변수를 선언하기보다 먼저 그 값의 성격을 생각해야 한다.
이 질문에 대한 답에 따라 지역 변수, UI 상태, 환경변수, 설정 데이터, 데이터베이스 중 적절한 위치가 결정된다.
프로그래밍에서 변수는 가장 먼저 배우는 개념 중 하나다.
너무 기초적인 문법처럼 보여서 let과 const의 차이만 이해하면 끝났다고 생각하기 쉽다.
하지만 실제 서비스의 대부분은 결국 값을 어떻게 다루느냐의 문제다.
사용자의 입력을 어떤 변수로 받을지, 어떤 값을 검증할지, 무엇을 계산할지, 어디에 저장할지, 언제 상태를 변경할지에 따라 서비스의 동작과 안정성이 결정된다.
따라서 “변수로 만든다”는 것은 단순히 값을 상자에 넣는 일이 아니다.
현실 세계의 정보에 이름과 의미를 부여하고, 그 정보가 프로그램 안에서 안전하게 이동하고 계산되고 저장될 수 있도록 설계하는 일이다.
변수를 제대로 이해하기 시작하면 코드가 단순한 문법의 집합으로 보이지 않는다.
사용자와 상품, 쿠폰과 정책, 상태와 행동이 서로 연결되어 하나의 서비스를 이루는 구조로 보이기 시작한다.